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

commit1938fa0f02d4f5e6c36e70c27919c034b0c143d9
parenta4f9c2527a
authorLucas Galante <[email protected]>
date2026-07-30 14:50
feat: optional style.control.toggle.button_color — recolors only the slide button

When set, the slide toggle's gliding button fills with this fixed color
(button_opacity still applies) instead of crossfading the on/off state
colors. Track, border, and state colors are untouched; unset falls back
to the existing crossfade.

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

 src/color.rs                 | 19 +++++++++++++++++++
 src/widget/input/checkbox.rs | 23 ++++++++++++++---------
 2 files changed, 33 insertions(+), 9 deletions(-)

diff --git a/src/color.rs b/src/color.rs
index 6cc0808..664716a 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -43,6 +43,7 @@ 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 TOGGLE_BUTTON_COLOR: RwLock<Option<[f32; 4]>> = RwLock::new(None);
 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]);
@@ -381,6 +382,9 @@ fn parse_and_set_colors(content: &str) {
     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; }
     }
+    if let Some(c) = get_color("/style/control/toggle/button_color") {
+        if let Ok(mut lock) = TOGGLE_BUTTON_COLOR.write() { *lock = Some(c); }
+    }
     if let Some(c) = get_color("/style/control/ramp/background") {
         if let Ok(mut lock) = RAMP_BACKGROUND_COLOR.write() { *lock = c; }
     }
@@ -1185,6 +1189,21 @@ pub fn set_toggle_bg_color(color: [f32; 4]) {
     }
 }
 
+/// Optional dedicated fill for the slide toggle's gliding button
+/// (`style.control.toggle.button_color`). `None` → the button falls back to
+/// crossfading the on/off state colors. Only the button reads this — the
+/// track, border, and state colors are untouched.
+pub fn toggle_button_color() -> Option<[f32; 4]> {
+    load_colors_once();
+    *TOGGLE_BUTTON_COLOR.read().unwrap()
+}
+
+pub fn set_toggle_button_color(color: Option<[f32; 4]>) {
+    if let Ok(mut lock) = TOGGLE_BUTTON_COLOR.write() {
+        *lock = color;
+    }
+}
+
 pub fn list_bg_color() -> [f32; 4] {
     load_colors_once();
     *LIST_BG_COLOR.read().unwrap()
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index f905453..1810949 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -295,16 +295,21 @@ impl Toggle {
     /// 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.
     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) * crate::layout::toggle_button_opacity(),
-        ]
+        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()]
     }
 
     /// The rocker's two halves over `rect` as FLAT relief steps: (half rect,