git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commitc8e9c74f56c149d02a30bb474fe54359b4ff8834
parent0f08c0d04f
authorLucas Galante <[email protected]>
date2026-08-22 22:56
toggle: no color of its own — the control inherits its plate

A toggle painted its own palette (background_color plus the
enabled/disabled state tints) so it read as a colored chip sitting ON a
plate rather than something worked out of it: in the designer's slide
style that was an olive pad for on and a lavender one for off, cutting
across the plate's tint, blur and light.

The widget now paints no fill in any style, so the plate's material
shows through and state reads from light and relief alone — the DE's
transparent-face convention (closed dropdowns, inset troughs):

- slide: the glider is a bare raised pad of the plate; its beveled rim
  and its position ARE the read.
- rocker: unchanged but for the dropped fill — the halves already read
  through `face_light` plus their beveled step.
- flat (control_relief off): the hue gradient is replaced by the same
  neutral `face_light` overlay the rocker uses, so the two styles now
  differ only by the relief they are named for, and neither introduces
  a color.

The toggle color statics/getters and their config loading go with it
rather than lingering as keys that silently do nothing; the TOGGLE_*
consts stay for the graph's node geometry switch, a different control.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/color.rs                 |  60 +++---------------
 src/widget/input/checkbox.rs | 141 ++++++++++---------------------------------
 2 files changed, 39 insertions(+), 162 deletions(-)

diff --git a/src/color.rs b/src/color.rs
index 4897ac4..345862a 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -40,9 +40,6 @@ static CONTROL_LABEL_HOVER_COLOR: RwLock<Option<[f32; 4]>> = RwLock::new(None);
 static CONTROL_LABEL_FOCUS_COLOR: RwLock<Option<[f32; 4]>> = RwLock::new(None);
 static OPACITY: RwLock<Option<f32>> = RwLock::new(None);
 static BACKPLATE_OPACITY: RwLock<Option<f32>> = RwLock::new(None);
-static TOGGLE_ON_COLOR: RwLock<[f32; 4]> = RwLock::new(TOGGLE_ON);
-static TOGGLE_OFF_COLOR: RwLock<[f32; 4]> = RwLock::new(TOGGLE_OFF);
-static TOGGLE_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.18, 0.18, 0.22, 1.0]);
 static LIST_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 0.3]);
 static LIST_ENTRY_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.04]);
 static LIST_ENTRY_HIGHLIGHT_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.8]);
@@ -367,24 +364,9 @@ fn parse_and_set_colors(content: &str) {
     if let Some(c) = get_color("/style/control/label/focus_color") {
         if let Ok(mut lock) = CONTROL_LABEL_FOCUS_COLOR.write() { *lock = Some(c); }
     }
-    // State colors: explicit enabled/disabled keys win; the gradient/border
-    // color is only a fallback for whichever state key is absent.
-    let toggle_gradient_color = get_color("/style/control/toggle/gradient_color")
-        .or_else(|| get_color("/style/control/toggle/border_color"))
-        .or_else(|| get_color("/layout/toggle_border_color"));
-
-    if let Some(c) = get_color("/style/control/toggle/enabled_color")
-        .or_else(|| get_color("/layout/toggle_enabled_color"))
-        .or(toggle_gradient_color) {
-        if let Ok(mut lock) = TOGGLE_ON_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/toggle/disabled_color")
-        .or(toggle_gradient_color) {
-        if let Ok(mut lock) = TOGGLE_OFF_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/toggle/background_color").or_else(|| get_color("/layout/toggle_bg_color")) {
-        if let Ok(mut lock) = TOGGLE_BG_COLOR.write() { *lock = c; }
-    }
+    // The toggle state colors (enabled/disabled/gradient/background) are not
+    // read: the control inherits its plate — see the note by the retired
+    // getters below.
     if let Some(c) = get_color("/style/control/ramp/background") {
         if let Ok(mut lock) = RAMP_BACKGROUND_COLOR.write() { *lock = c; }
     }
@@ -1156,38 +1138,10 @@ pub fn read_backplate_opacity_if_configured() -> Option<f32> {
     *BACKPLATE_OPACITY.read().unwrap()
 }
 
-pub fn toggle_on_color() -> [f32; 4] {
-    load_colors_once();
-    *TOGGLE_ON_COLOR.read().unwrap()
-}
-
-pub fn set_toggle_on_color(color: [f32; 4]) {
-    if let Ok(mut lock) = TOGGLE_ON_COLOR.write() {
-        *lock = color;
-    }
-}
-
-pub fn toggle_off_color() -> [f32; 4] {
-    load_colors_once();
-    *TOGGLE_OFF_COLOR.read().unwrap()
-}
-
-pub fn set_toggle_off_color(color: [f32; 4]) {
-    if let Ok(mut lock) = TOGGLE_OFF_COLOR.write() {
-        *lock = color;
-    }
-}
-
-pub fn toggle_bg_color() -> [f32; 4] {
-    load_colors_once();
-    *TOGGLE_BG_COLOR.read().unwrap()
-}
-
-pub fn set_toggle_bg_color(color: [f32; 4]) {
-    if let Ok(mut lock) = TOGGLE_BG_COLOR.write() {
-        *lock = color;
-    }
-}
+// The toggle's own palette (enabled/disabled/background) is RETIRED: a toggle
+// inherits the plate it sits on and marks state with light and relief, so
+// there is nothing left to tint. The `TOGGLE_*` consts above survive for the
+// graph's node geometry switch, which is a different control.
 
 pub fn list_bg_color() -> [f32; 4] {
     load_colors_once();
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index 60c77c2..238420f 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -265,14 +265,6 @@ impl Toggle {
         self.toggled
     }
 
-    fn gradient_color(&self) -> [f32; 4] {
-        if self.toggled {
-            colors::toggle_on_color()
-        } else {
-            colors::toggle_off_color()
-        }
-    }
-
     /// The slide style's button (config `style.control.toggle.style =
     /// "slide"`): half the widget wide, gliding between the left (off) and
     /// right (on) ends by the animated `slide_t`. `None` when the style is
@@ -290,23 +282,6 @@ impl Toggle {
         })
     }
 
-    /// The slide button's face color: the disabled/enabled state colors
-    /// (rgba) crossfaded by the animated position, so the fill morphs while
-    /// the button glides. The colors are used verbatim — their alpha channel
-    /// IS the button's opacity, so a glass tint is a low-alpha state color
-    /// (the beveled edges carry the button's read either way).
-    pub fn slide_button_color(&self) -> [f32; 4] {
-        let off = colors::toggle_off_color();
-        let on = colors::toggle_on_color();
-        let t = self.slide_t;
-        [
-            off[0] + (on[0] - off[0]) * t,
-            off[1] + (on[1] - off[1]) * t,
-            off[2] + (on[2] - off[2]) * t,
-            off[3] + (on[3] - off[3]) * t,
-        ]
-    }
-
     /// The rocker's two halves over `rect` as FLAT relief steps: (half rect,
     /// per-corner radii, (top, right, bottom, left) walls, raised). The state
     /// half (top when on, bottom when off) is a plateau raised out of the
@@ -396,8 +371,11 @@ impl Layout for Toggle {
 }
 
 impl Paint for Toggle {
+    /// No fill of its own: a toggle is worked out of the plate it sits on, so
+    /// the plate's material (tint, blur, whatever it is) shows through and the
+    /// state reads from light and relief alone — see [`Paint::paint`].
     fn color(&self) -> [f32; 4] {
-        colors::toggle_bg_color()
+        [0.0, 0.0, 0.0, 0.0]
     }
 
     fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
@@ -420,51 +398,36 @@ impl Paint for Toggle {
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
         let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
         let radius = crate::layout::toggle_corner_radius();
-        let bg = colors::toggle_bg_color();
         let slide = crate::layout::toggle_slide();
 
+        // A toggle paints NO fill of its own, in any style: it is worked out
+        // of the plate it sits on, so the plate's own material shows through
+        // and the state reads from light and relief — the DE's transparent-face
+        // convention (closed dropdowns, inset troughs). The state colors this
+        // used to tint with (enabled/disabled/background) are retired with the
+        // rest of the toggle's palette.
         if slide {
             // The slide style: the widget is the track; a half-width button
             // glides between its ends with the state (animated in `tick`).
-            // Under control_relief the button is a raised plateau riding the
-            // track (its beveled edges also reach legacy-view hosts through
-            // `slide_button` — see `ParametersBg::reliefs`).
-            if bg[3] > 0.001 {
-                if radius > 0.0 {
-                    ctx.rounded_rect(rect, radius, (true, true, true, true), bg);
-                } else {
-                    ctx.quad(rect, bg);
-                }
-            }
+            // With no fill the button is a bare raised pad of the plate — its
+            // beveled rim and its position ARE the read (left off, right on).
+            // The rim also reaches legacy-view hosts through `slide_button`
+            // (see `ParametersBg::reliefs`).
             if let Some(btn) = self.slide_button(rect) {
-                let fill = self.slide_button_color();
-                if fill[3] > 0.003 {
-                    if radius > 0.0 {
-                        ctx.rounded_rect(btn, radius, (true, true, true, true), fill);
-                    } else {
-                        ctx.quad(btn, fill);
-                    }
-                }
-                // The beveled rim is the style's defining edge — the glass
-                // fill alone wouldn't read — so it draws in both DE styles.
                 let depth = crate::layout::bevel_width().min(h * 0.2);
                 ctx.boss_edges(btn, (radius, radius, radius, radius), depth, (true, true, true, true));
             }
-        } else if self.raised {
+        } else {
             // The rocker: two FLAT half faces (see `rocker_reliefs`) — the
-            // state half a raised plateau, the other recessed, hinge wall
-            // open so they meet in a single step. Each face carries its
-            // uniform `face_light` overlay (lit plateau, shaded floor) under
-            // the beveled edges, so faces reflect the same light the walls
-            // do. This is the physical read of the flat style's state
-            // gradient, which this style drops entirely.
-            if bg[3] > 0.001 {
-                if radius > 0.0 {
-                    ctx.rounded_rect(rect, radius, (true, true, true, true), bg);
-                } else {
-                    ctx.quad(rect, bg);
-                }
-            }
+            // state half tipped out toward the light, the other away — each
+            // carrying its uniform `face_light` overlay, a neutral light/shade
+            // over the plate rather than a color. Under `control_relief` the
+            // halves additionally wear their beveled step (state half a raised
+            // plateau, the other recessed, hinge wall open so they meet in a
+            // single step); without it that same lighting stands alone. The
+            // flat style's hue gradient is gone with the rest of the palette,
+            // so the two styles now differ only by the relief they were named
+            // for.
             for (half, radii, _, _) in self.rocker_reliefs(rect) {
                 let light = self.face_light(radii.0 > 0.0);
                 if light[3] > 0.001 {
@@ -472,55 +435,15 @@ impl Paint for Toggle {
                     ctx.rounded_rect(half, radius, corners, light);
                 }
             }
-            let depth = crate::layout::bevel_width().min(h * 0.2);
-            for (half, radii, walls, raised) in self.rocker_reliefs(rect) {
-                if raised {
-                    ctx.boss_edges(half, radii, depth, walls);
-                } else {
-                    ctx.recess_edges(half, radii, depth, walls);
-                }
-            }
-        } else if radius > 0.0 {
-            ctx.rounded_rect(rect, radius, (true, true, true, true), bg);
-        } else {
-            ctx.quad(rect, bg);
-        }
-
-        // The state half carries a vertical gradient of the state color: full opacity at
-        // the widget's outer edge (top when on, bottom when off), fading to transparent at
-        // the vertical middle. Banded quads (the content_bg gradient pattern) so it flows
-        // through the plain-quad views; bands inside the corner radius inset to follow the
-        // rounded corners. The band alphas follow a perceptual curve, not a straight ramp —
-        // see `colors::perceptual_fade_alpha`. The bands composite onto the bg quad above,
-        // so that is the backdrop the curve is solved against.
-        let grad = if self.raised || slide { [0.0; 4] } else { self.gradient_color() };
-        let half = h / 2.0;
-        if half > 0.0 && grad[3] > 0.0 {
-            let steps = (half.ceil() as usize).clamp(4, 32);
-            let band_h = half / steps as f32;
-            for i in 0..steps {
-                let t0 = i as f32 * band_h; // band start, as distance from the outer edge
-                let t_mid = t0 + band_h / 2.0;
-                let alpha = colors::perceptual_fade_alpha(
-                    t_mid / half,
-                    [grad[0], grad[1], grad[2]],
-                    [bg[0], bg[1], bg[2]],
-                    grad[3],
-                );
-                if alpha <= 0.003 {
-                    continue;
+            if self.raised {
+                let depth = crate::layout::bevel_width().min(h * 0.2);
+                for (half, radii, walls, raised) in self.rocker_reliefs(rect) {
+                    if raised {
+                        ctx.boss_edges(half, radii, depth, walls);
+                    } else {
+                        ctx.recess_edges(half, radii, depth, walls);
+                    }
                 }
-                let by = if self.toggled { y + t0 } else { y + h - t0 - band_h };
-                let inset = if radius > 0.0 && t_mid < radius {
-                    let dr = radius - t_mid;
-                    radius - (radius * radius - dr * dr).max(0.0).sqrt()
-                } else {
-                    0.0
-                };
-                ctx.quad(
-                    Rect { x: x + inset, y: by, width: w - 2.0 * inset, height: band_h },
-                    [grad[0], grad[1], grad[2], alpha],
-                );
             }
         }