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

commitbe3a7c0ac19d50e0b80d66f023a53074271b9ab1
parent1938fa0f02
authorLucas Galante <[email protected]>
date2026-07-30 14:56
feat: toggle button_color alpha is its opacity; state color keys win over border fallback

- style.control.toggle.button_color is now used verbatim (rgba): its
  alpha channel dictates the button's opacity, button_opacity no longer
  applies on the override path (it still governs the state-crossfade
  fallback).
- enabled_color / disabled_color now take precedence over the
  gradient_color/border_color fallback instead of being ignored whenever
  a border color is configured.

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

 src/color.rs                 | 16 ++++++++--------
 src/widget/input/checkbox.rs | 37 ++++++++++++++++++-------------------
 2 files changed, 26 insertions(+), 27 deletions(-)

diff --git a/src/color.rs b/src/color.rs
index 664716a..0349f57 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -364,20 +364,20 @@ 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) = toggle_gradient_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; }
-    } else {
-        if let Some(c) = get_color("/style/control/toggle/enabled_color").or_else(|| get_color("/layout/toggle_enabled_color")) {
-            if let Ok(mut lock) = TOGGLE_ON_COLOR.write() { *lock = c; }
-        }
-        if let Some(c) = get_color("/style/control/toggle/disabled_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; }
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index 1810949..299b634 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -290,26 +290,25 @@ impl Toggle {
         })
     }
 
-    /// The slide button's face color: the off/on state colors crossfaded by
-    /// the animated position, so the fill morphs while the button glides —
-    /// then knocked down to a mostly-transparent glass tint
-    /// (`style.control.toggle.button_opacity`): the beveled edges carry the
-    /// button's read, the fill is a whisper over the track.
-    /// `style.control.toggle.button_color` replaces the crossfaded base with
-    /// a fixed color (opacity still applies) — it recolors ONLY the button.
+    /// The slide button's face color. `style.control.toggle.button_color`
+    /// (rgba) is used verbatim when set — its alpha channel IS the button's
+    /// opacity, and it recolors ONLY the button. Unset, the off/on state
+    /// colors crossfade by the animated position, knocked down to a glass
+    /// tint by `style.control.toggle.button_opacity`: the beveled edges
+    /// carry the button's read, the fill is a whisper over the track.
     pub fn slide_button_color(&self) -> [f32; 4] {
-        let base = colors::toggle_button_color().unwrap_or_else(|| {
-            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,
-            ]
-        });
-        [base[0], base[1], base[2], base[3] * crate::layout::toggle_button_opacity()]
+        if let Some(c) = colors::toggle_button_color() {
+            return c;
+        }
+        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) * crate::layout::toggle_button_opacity(),
+        ]
     }
 
     /// The rocker's two halves over `rect` as FLAT relief steps: (half rect,