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

commite4e47bf69ae49f7ee5254c17e413e08fc51b2057
parentb24db72b5c
authorLucas Galante <[email protected]>
date2026-07-04 21:47
Implement half-borders for Toggle widget and support style.control.toggle.border_color in KDL

 src/backend/window_runner.rs          |  4 ++
 src/color.rs                          | 14 ++++--
 src/layout.rs                         |  1 +
 src/widget/container/control_panel.rs | 22 +++++++-
 src/widget/input/checkbox.rs          | 94 ++++++++++++++++++++++++++++++++++-
 5 files changed, 130 insertions(+), 5 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 1177784..d4cc7bc 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -796,6 +796,10 @@ pub fn push_widget_vertices(w: &dyn crate::widget::Element, sw: f32, sh: f32, cl
     if let Some((color, thickness)) = w.solid_border() {
         push_plate_solid_border_vertices(x, y, ww, h, radii, thickness, sw, sh, color, clip_circle, out);
     }
+
+    for (cx, cy, r, t, start, end, qc) in w.extra_arcs() {
+        push_arc_background_vertices(cx, cy, r, t, start, end, sw, sh, qc, 16, clip_circle, out);
+    }
 }
 
 pub fn extra_quad_vertices(
diff --git a/src/color.rs b/src/color.rs
index 62cff78..77caf85 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -310,11 +310,19 @@ fn parse_and_set_colors(content: &str) {
     if let Some(c) = get_color("/layout/menubar_tab_label_color").or_else(|| get_color("/layout/paginator_tab_label_color")) {
         if let Ok(mut lock) = MENUBAR_TAB_LABEL_COLOR.write() { *lock = c; }
     }
-    if let Some(c) = get_color("/style/control/toggle/enabled_color").or_else(|| get_color("/layout/toggle_enabled_color")) {
+    let toggle_border_color = get_color("/style/control/toggle/border_color")
+        .or_else(|| get_color("/layout/toggle_border_color"));
+
+    if let Some(c) = toggle_border_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; }
+    } 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("/layout/toggle_bg_color") {
         if let Ok(mut lock) = TOGGLE_BG_COLOR.write() { *lock = c; }
diff --git a/src/layout.rs b/src/layout.rs
index f0f92b1..e60403d 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -103,6 +103,7 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 "style.control.toggle.height" => "toggle_height",
                 "style.control.toggle.border_width" => "toggle_border_width",
                 "style.control.toggle.disabled_color" => "toggle_disabled_color",
+                "style.control.toggle.border_color" => "toggle_border_color",
                 "style.control.toggle.corner_radius" => "toggle_corner_radius",
                 "style.control.ramp.height" => "ramp_height",
                 "style.status.normal_color" => "status_normal_color",
diff --git a/src/widget/container/control_panel.rs b/src/widget/container/control_panel.rs
index af0f51c..10d78d2 100644
--- a/src/widget/container/control_panel.rs
+++ b/src/widget/container/control_panel.rs
@@ -261,7 +261,7 @@ impl Element for ControlPanel {
         unsafe {
             for child_ptr in &self.children {
                 let child = &**child_ptr;
-                let solid_border_opt = child.solid_border();
+                let solid_border_opt = if child.type_name() == "Toggle" { None } else { child.solid_border() };
                 let (cx, cy, cw, ch) = child.rect();
 
                 if let Some((b_color, _thickness)) = solid_border_opt {
@@ -383,6 +383,26 @@ impl Element for ControlPanel {
         quads
     }
 
+    fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
+        let mut arcs = Vec::new();
+        let scroll_y = self.scroll_box.scroll_y;
+        let y_start = self.base.y;
+        let y_end = self.base.y + self.base.h;
+
+        unsafe {
+            for child_ptr in &self.children {
+                let child = &**child_ptr;
+                for (cx, cy, r, t, start, end, color) in child.extra_arcs() {
+                    let cy_shifted = cy - scroll_y;
+                    if cy_shifted + r > y_start && cy_shifted - r < y_end {
+                        arcs.push((cx, cy_shifted, r, t, start, end, color));
+                    }
+                }
+            }
+        }
+        arcs
+    }
+
     fn popover_rect(&self) -> Option<(f32, f32, f32, f32)> {
         let scroll_y = self.scroll_box.scroll_y;
         unsafe {
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index 301e60f..aaa68c9 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -293,7 +293,99 @@ impl Element for Toggle {
     }
 
     fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        vec![(self.base.x, self.base.y, self.base.w, self.base.h, self.color())]
+        let mut quads = vec![(self.base.x, self.base.y, self.base.w, self.base.h, self.color())];
+        
+        let border_w = crate::layout::toggle_border_width();
+        if border_w > 0.0 {
+            let x = self.base.x;
+            let y = self.base.y;
+            let w = self.base.w;
+            let h = self.base.h;
+            let r = crate::layout::toggle_corner_radius();
+            let t = border_w;
+            
+            let border_color = if self.toggled {
+                colors::toggle_on_color()
+            } else {
+                colors::toggle_off_color()
+            };
+            
+            let edge_h = ((h / 2.0) - r).max(0.0);
+            
+            if self.toggled {
+                // Top edge
+                quads.push((x + r, y, w - 2.0 * r, t, border_color));
+                // Top half of left edge
+                if edge_h > 0.0 {
+                    quads.push((x, y + r, t, edge_h, border_color));
+                }
+                // Top half of right edge
+                if edge_h > 0.0 {
+                    quads.push((x + w - t, y + r, t, edge_h, border_color));
+                }
+            } else {
+                // Bottom edge
+                quads.push((x + r, y + h - t, w - 2.0 * r, t, border_color));
+                // Bottom half of left edge
+                if edge_h > 0.0 {
+                    quads.push((x, y + h / 2.0, t, edge_h, border_color));
+                }
+                // Bottom half of right edge
+                if edge_h > 0.0 {
+                    quads.push((x + w - t, y + h / 2.0, t, edge_h, border_color));
+                }
+            }
+        }
+        quads
+    }
+
+    fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
+        let mut arcs = Vec::new();
+        let border_w = crate::layout::toggle_border_width();
+        let r = crate::layout::toggle_corner_radius();
+        
+        if border_w > 0.0 && r > 0.1 {
+            let x = self.base.x;
+            let y = self.base.y;
+            let w = self.base.w;
+            let h = self.base.h;
+            let t = border_w;
+            
+            let border_color = if self.toggled {
+                colors::toggle_on_color()
+            } else {
+                colors::toggle_off_color()
+            };
+            
+            if self.toggled {
+                // Top-Left corner arc
+                arcs.push((
+                    x + r, y + r, r, t,
+                    std::f32::consts::PI, 1.5 * std::f32::consts::PI,
+                    border_color
+                ));
+                // Top-Right corner arc
+                arcs.push((
+                    x + w - r, y + r, r, t,
+                    1.5 * std::f32::consts::PI, 2.0 * std::f32::consts::PI,
+                    border_color
+                ));
+            } else {
+                // Bottom-Left corner arc
+                arcs.push((
+                    x + r, y + h - r, r, t,
+                    0.5 * std::f32::consts::PI, std::f32::consts::PI,
+                    border_color
+                ));
+                // Bottom-Right corner arc
+                arcs.push((
+                    x + w - r, y + h - r, r, t,
+                    0.0, 0.5 * std::f32::consts::PI,
+                    border_color
+                ));
+            }
+        }
+        arcs
     }
 
     fn text_labels(&self) -> Vec<TextLabel> {