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

commit119c726170c755b850dd6ebacbd60c91e45f35f0
parentcd6bb0031b
authorLucas Galante <[email protected]>
date2026-06-28 05:33
Customize Toggle widget background and border style

 src/color.rs                 |  15 ++++++
 src/layout.rs                | 113 +++++++++++++++++++++++++++++++++++++++++++
 src/widget/input/checkbox.rs |  25 +++++++---
 3 files changed, 147 insertions(+), 6 deletions(-)

diff --git a/src/color.rs b/src/color.rs
index 306d99f..9ca5b43 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -36,6 +36,7 @@ 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 SCROLLINGLIST_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 0.3]);
 static SCROLLINGLIST_ENTRY_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.04]);
 static SCROLLINGLIST_ENTRY_HIGHLIGHT_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.8]);
@@ -175,6 +176,9 @@ fn parse_and_set_colors(content: &str) {
     if let Some(c) = get_color("/layout/toggle_disabled_color") {
         if let Ok(mut lock) = TOGGLE_OFF_COLOR.write() { *lock = c; }
     }
+    if let Some(c) = get_color("/layout/toggle_bg_color") {
+        if let Ok(mut lock) = TOGGLE_BG_COLOR.write() { *lock = c; }
+    }
     let parsed_scrollinglist_bg = get_color("/layout/scrollinglist_bg_color");
     let parsed_breadcrumb_bg = get_color("/layout/breadcrumb_bg_color");
     let parsed_popover_bg = get_color("/layout/popover_bg_color");
@@ -424,6 +428,17 @@ pub fn set_toggle_off_color(color: [f32; 4]) {
     }
 }
 
+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;
+    }
+}
+
 pub fn scrollinglist_bg_color() -> [f32; 4] {
     load_colors_once();
     *SCROLLINGLIST_BG_COLOR.read().unwrap()
diff --git a/src/layout.rs b/src/layout.rs
index 7171c7b..ea48d39 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -103,6 +103,9 @@ static FONT_SELECTOR_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
 static DROPDOWN_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
 static TOGGLE_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
 static SLIDER_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
+static TOGGLE_BORDER_WIDTH: RwLock<f32> = RwLock::new(1.0);
+static TOGGLE_FONT: RwLock<String> = RwLock::new(String::new());
+static TOGGLE_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
 static PLATE_CORNER_RADIUS: RwLock<f32> = RwLock::new(12.0);
 static PLATE_OPACITY: RwLock<f32> = RwLock::new(1.0);
 static PAGE_OPACITY: RwLock<f32> = RwLock::new(1.0);
@@ -133,6 +136,7 @@ pub fn align_text_y(y: f32, height: f32, font_size: f32, top_offset: f32) -> f32
 pub fn reload_config() {
     if let Some(content) = read_config() {
         let mut menubar_font_changed = false;
+        let mut toggle_font_changed = false;
         for line in content.lines() {
             let trimmed = line.trim();
             if let Some(rest) = trimmed.strip_prefix("label_margin") {
@@ -491,12 +495,41 @@ pub fn reload_config() {
                     }
                 }
             }
+            if let Some(rest) = trimmed.strip_prefix("toggle_border_width") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+                let val_str = rest.trim_end_matches('"').trim();
+                if let Ok(val) = val_str.parse::<f32>() {
+                    if let Ok(mut lock) = TOGGLE_BORDER_WIDTH.write() {
+                        *lock = val;
+                    }
+                }
+            }
+            if let Some(rest) = trimmed.strip_prefix("toggle_font") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                let rest = mod_rest(rest);
+                let font = rest.trim().to_string();
+                let mut changed = false;
+                if let Ok(mut lock) = TOGGLE_FONT.write() {
+                    if *lock != font {
+                        *lock = font;
+                        changed = true;
+                    }
+                }
+                if changed {
+                    toggle_font_changed = true;
+                }
+            }
         }
         if menubar_font_changed {
             if let Ok(mut lock) = MENUBAR_FONT_CACHED.write() {
                 *lock = None;
             }
         }
+        if toggle_font_changed {
+            if let Ok(mut lock) = TOGGLE_FONT_CACHED.write() {
+                *lock = None;
+            }
+        }
         crate::color::reload_colors(&content);
     }
 }
@@ -827,6 +860,34 @@ pub fn set_toggle_corner_radius(radius: f32) {
     }
 }
 
+pub fn toggle_border_width() -> f32 {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("toggle_border_width") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+                    let val_str = rest.trim_end_matches('"').trim();
+                    if let Ok(val) = val_str.parse::<f32>() {
+                        if let Ok(mut lock) = TOGGLE_BORDER_WIDTH.write() {
+                            *lock = val;
+                        }
+                    }
+                }
+            }
+        }
+    });
+    *TOGGLE_BORDER_WIDTH.read().unwrap()
+}
+
+pub fn set_toggle_border_width(width: f32) {
+    if let Ok(mut lock) = TOGGLE_BORDER_WIDTH.write() {
+        *lock = width;
+    }
+}
+
 pub fn slider_corner_radius() -> f32 {
     use std::sync::Once;
     static INIT: Once = Once::new();
@@ -1121,6 +1182,58 @@ pub fn set_menubar_font(font: &str) {
     }
 }
 
+pub fn toggle_font() -> String {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        let mut font = "Outfit".to_string();
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("toggle_font") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                    let rest = mod_rest(rest);
+                    font = rest.trim().to_string();
+                }
+            }
+        }
+        if let Ok(mut lock) = TOGGLE_FONT.write() {
+            *lock = font;
+        }
+    });
+    let lock = TOGGLE_FONT.read().unwrap();
+    if lock.is_empty() {
+        "Outfit".to_string()
+    } else {
+        lock.clone()
+    }
+}
+
+pub fn toggle_font_parsed() -> (String, f32) {
+    if let Ok(lock) = TOGGLE_FONT_CACHED.read() {
+        if let Some(ref val) = *lock {
+            return val.clone();
+        }
+    }
+    let font_str = toggle_font();
+    let parsed = parse_font_string(&font_str);
+    let size = parsed.1.unwrap_or(12.0);
+    let val = (parsed.0, size);
+    if let Ok(mut lock) = TOGGLE_FONT_CACHED.write() {
+        *lock = Some(val.clone());
+    }
+    val
+}
+
+pub fn set_toggle_font(font: &str) {
+    if let Ok(mut lock) = TOGGLE_FONT.write() {
+        *lock = font.to_string();
+    }
+    if let Ok(mut lock) = TOGGLE_FONT_CACHED.write() {
+        *lock = None;
+    }
+}
+
 pub fn section_label_font() -> String {
     use std::sync::Once;
     static INIT: Once = Once::new();
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index b79c926..301e60f 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -256,15 +256,27 @@ impl Element for Toggle {
     }
 
     fn color(&self) -> [f32; 4] {
-        if self.toggled {
-            colors::TOGGLE_ON
-        } else if self.base.hovered {
-            colors::TOGGLE_HOVER
+        colors::toggle_bg_color()
+    }
+
+    fn solid_border(&self) -> Option<([f32; 4], f32)> {
+        let border_color = if self.toggled {
+            colors::toggle_on_color()
+        } else {
+            colors::toggle_off_color()
+        };
+        let border_w = crate::layout::toggle_border_width();
+        if border_w > 0.0 {
+            Some((border_color, border_w))
         } else {
-            colors::TOGGLE_OFF
+            None
         }
     }
 
+    fn widget_font(&self) -> Option<String> {
+        Some(crate::layout::toggle_font())
+    }
+
     fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, ctx: &mut UiContext) -> bool {
         if button != MouseButton::Left { return false; }
         match state {
@@ -288,7 +300,8 @@ impl Element for Toggle {
         let mut labels = Vec::new();
         if let Some(ref label) = self.base.label {
             let font_size = 12.0;
-            let est_w = crate::widget::display::measure_text(label, font_size);
+            let font_fam = crate::layout::toggle_font_parsed().0;
+            let est_w = crate::widget::display::measure_text_width(label, &font_fam, font_size);
             labels.push(TextLabel {
                 text: label.clone(),
                 x: self.base.x + (self.base.w - est_w) / 2.0,