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

commit0c6a36790da70a0bda79dde6613413f64a4d802a
parentb16c40bfda
authorLucas Galante <[email protected]>
date2026-07-17 10:00
feat: parameter panel bool rows are Toggles instead of Checkboxes

The toggle/checkbox param rows in ParametersBg now build the Toggle
widget (pill with half-border) rather than Checkbox; the field is
renamed checkboxes -> toggles. New arcs() getter serves the toggles'
border corner arcs, which rounded mode emits but the quad views can't
carry.

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

 src/backend/window_runner.rs          |  2 +-
 src/widget/container/parameters_bg.rs | 67 ++++++++++++++++++++++-------------
 2 files changed, 44 insertions(+), 25 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 85e8b76..021b953 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -1197,7 +1197,7 @@ fn get_child_widget_for_quad<'a>(
                 }
             }
         }
-        for cb_opt in &pbg.checkboxes {
+        for cb_opt in &pbg.toggles {
             if let Some(cb) = cb_opt {
                 let (cx, cy, cww, chh) = cb.rect();
                 if qx >= cx - 0.1 && qx + qw <= cx + cww + 0.1 && qy >= cy - 0.1 && qy + qh <= cy + chh + 0.1 {
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 0f4274c..c791367 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -1,5 +1,5 @@
 //! Narrow-trait `ParametersBg` (Phase 5s) — the designer's parameter panel: a scrollable column
-//! of param rows (sliders, spinboxes, dropdowns, text boxes, checkboxes, colors, float3s,
+//! of param rows (sliders, spinboxes, dropdowns, text boxes, toggles, colors, float3s,
 //! buttons, section borders, and an inline emacs-flavored code editor), each row's widget owned
 //! by value in parallel `Vec<Option<..>>` fields (most already `Adapted<W>` from earlier
 //! phases), plus a raw-pointer `children` container list. The designer stores it as
@@ -25,7 +25,7 @@ use crate::colors;
 use crate::scene::layout::Rect;
 use crate::scene::paint::PaintCtx;
 use crate::widget::display::{Float3, TextLabel};
-use crate::widget::input::{Button, Checkbox, ColorSelector, Dropdown, Slider, Spinbox, TextBox};
+use crate::widget::input::{Button, ColorSelector, Dropdown, Slider, Spinbox, TextBox, Toggle};
 use crate::widget::{
     Adapted, WidgetHost, ElementState, Event, EventCtx, Input, Key, Layout, MouseButton,
     MouseScrollDelta, NamedKey, Paint, ParamController, TextEditorState, UiContext,
@@ -44,7 +44,7 @@ pub struct ParametersBg {
     pub buttons: Vec<Option<Adapted<Button>>>,
     pub choices: Vec<Option<Adapted<Dropdown>>>,
     pub texts: Vec<Option<Adapted<TextBox>>>,
-    pub checkboxes: Vec<Option<Adapted<Checkbox>>>,
+    pub toggles: Vec<Option<Adapted<Toggle>>>,
     pub colors: Vec<Option<crate::widget::Adapted<ColorSelector>>>,
     visible: bool,
     pub scroll_y: f32,
@@ -81,7 +81,7 @@ impl ParametersBg {
             buttons: Vec::new(),
             choices: Vec::new(),
             texts: Vec::new(),
-            checkboxes: Vec::new(),
+            toggles: Vec::new(),
             colors: Vec::new(),
             visible: true,
             scroll_y: 0.0,
@@ -281,7 +281,7 @@ impl ParametersBg {
                 tb.set_rect(r.0, r.1, r.2, r.3);
             }
         }
-        for (i, cb_opt) in self.checkboxes.iter_mut().enumerate() {
+        for (i, cb_opt) in self.toggles.iter_mut().enumerate() {
             if let Some(cb) = cb_opt {
                 let r = rects[i];
                 cb.set_rect(r.0, r.1, r.2, r.3);
@@ -375,7 +375,7 @@ impl ParametersBg {
                     labels.extend(b.own_text_labels());
                 }
             } else if ptype == "toggle" || ptype == "checkbox" {
-                if let Some(cb) = &self.checkboxes[i] {
+                if let Some(cb) = &self.toggles[i] {
                     labels.extend(cb.own_text_labels());
                 }
             } else if ptype.starts_with("color") || ptype == "rgb" || ptype == "rgba" {
@@ -602,7 +602,7 @@ impl ParametersBg {
                     param_quads.extend(sb.extra_quads());
                 }
             } else if p.2 == "toggle" || p.2 == "checkbox" {
-                if let Some(cb) = &self.checkboxes[i] {
+                if let Some(cb) = &self.toggles[i] {
                     param_quads.extend(cb.extra_quads());
                 }
             } else if p.2.starts_with("color") || p.2 == "rgb" || p.2 == "rgba" {
@@ -626,7 +626,7 @@ impl ParametersBg {
     }
 
     /// The rounded companion to [`Self::plain_quads`]: the row controls whose boxes are
-    /// `Prim::RoundedRect` (textbox, dropdown, button, checkbox, color selector). Those
+    /// `Prim::RoundedRect` (textbox, dropdown, button, toggle, color selector). Those
     /// backgrounds never reach the plain view — `own_plain_quads` keeps `Prim::Quad` only —
     /// so a host that renders this panel through the legacy plain-quad hatch must read this
     /// getter too or the controls draw as bare text. Returned unclipped; the host clips to
@@ -654,7 +654,7 @@ impl ParametersBg {
                     out.extend(b.all_rounded_quads(ctx));
                 }
             } else if p.2 == "toggle" || p.2 == "checkbox" {
-                if let Some(cb) = &self.checkboxes[i] {
+                if let Some(cb) = &self.toggles[i] {
                     out.extend(cb.all_rounded_quads(ctx));
                 }
             } else if p.2.starts_with("color") || p.2 == "rgb" || p.2 == "rgba" {
@@ -665,6 +665,25 @@ impl ParametersBg {
         }
         out
     }
+
+    /// Arc companion to [`Self::rounded_quads`]: the toggle rows' border corner arcs
+    /// (`Prim::Arc`, emitted only in rounded mode). Returned unclipped, same as
+    /// `rounded_quads`; tuple layout matches `extra_arcs`:
+    /// (cx, cy, radius, thickness, start, end, color).
+    pub fn arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
+        if !self.visible {
+            return Vec::new();
+        }
+        let mut out = Vec::new();
+        for (i, p) in self.display_params.iter().enumerate() {
+            if p.2 == "toggle" || p.2 == "checkbox" {
+                if let Some(t) = &self.toggles[i] {
+                    out.extend(t.extra_arcs());
+                }
+            }
+        }
+        out
+    }
 }
 
 impl Layout for ParametersBg {
@@ -946,7 +965,7 @@ impl Input for ParametersBg {
         }
         let mut changed = false;
         let mut dummy = crate::context::UiContext::new();
-        for cb_opt in &mut self.checkboxes {
+        for cb_opt in &mut self.toggles {
             if let Some(cb) = cb_opt {
                 if cb.tick(dt, &mut dummy) {
                     changed = true;
@@ -1056,7 +1075,7 @@ impl Input for ParametersBg {
                         }
                     }
                 }
-                for cb_opt in &mut self.checkboxes {
+                for cb_opt in &mut self.toggles {
                     if let Some(cb) = cb_opt {
                         if cb.on_cursor_moved(px, py, ui) {
                             changed = true;
@@ -1200,7 +1219,7 @@ impl Input for ParametersBg {
                             }
                         }
                     } else if p.2 == "toggle" || p.2 == "checkbox" {
-                        if let Some(cb) = &mut self.checkboxes[i] {
+                        if let Some(cb) = &mut self.toggles[i] {
                             if cb.mouse_input(button, state, px, py, ui) {
                                 if cb.take_change() {
                                     if let Some(val) = cb.get_value_string() {
@@ -1752,12 +1771,12 @@ impl ParamController for ParametersBg {
                     None
                 }
             }).collect();
-            self.checkboxes = self.display_params.iter().map(|p| {
+            self.toggles = self.display_params.iter().map(|p| {
                 if p.2 == "toggle" || p.2 == "checkbox" {
-                    let checked = p.1.trim().to_lowercase() == "true";
-                    let mut cb = Checkbox::new().with_label(&p.0);
-                    cb.set_checked(checked);
-                    Some(cb)
+                    let on = p.1.trim().to_lowercase() == "true";
+                    let mut t = Toggle::new().with_label(&p.0);
+                    t.set_toggled(on);
+                    Some(t)
                 } else {
                     None
                 }
@@ -1809,9 +1828,9 @@ impl ParamController for ParametersBg {
                         if !tb.editing {
                             tb.set_value_string(&p_new.1);
                         }
-                    } else if let Some(ref mut cb) = self.checkboxes[i] {
-                        let checked = p_new.1.trim().to_lowercase() == "true";
-                        cb.set_checked(checked);
+                    } else if let Some(ref mut t) = self.toggles[i] {
+                        let on = p_new.1.trim().to_lowercase() == "true";
+                        t.set_toggled(on);
                     } else if let Some(ref mut c) = self.colors[i] {
                         if !c.editing {
                             c.set_value_string(&p_new.1);
@@ -1907,18 +1926,18 @@ mod tests {
             ("On", "true", "checkbox"),
         ]);
         assert_eq!(ParamController::node_params(&*p).len(), 3);
-        assert!(p.sliders[0].is_some() && p.choices[1].is_some() && p.checkboxes[2].is_some());
+        assert!(p.sliders[0].is_some() && p.choices[1].is_some() && p.toggles[2].is_some());
         // Rows were laid out from the cached rect.
         let (sx, _, sw, _) = p.sliders[0].as_ref().unwrap().rect();
         assert_eq!((sx, sw), (8.0, 284.0), "row rect derives from the assigned rect");
     }
 
     #[test]
-    fn checkbox_click_commits_value_and_unfocus_commits_editor() {
+    fn toggle_click_commits_value_and_unfocus_commits_editor() {
         let mut ctx = UiContext::new();
         let mut p = panel_with(&[("On", "false", "checkbox")]);
-        let (cx, cy, _, ch) = p.checkboxes[0].as_ref().unwrap().rect();
-        // Click the checkbox row (presses are ungated for this widget; the panel consumes
+        let (cx, cy, _, ch) = p.toggles[0].as_ref().unwrap().rect();
+        // Click the toggle row (presses are ungated for this widget; the panel consumes
         // every left press, so the return is true either way — assert the value flip).
         p.mouse_input(MouseButton::Left, ElementState::Pressed, cx + 6.0, cy + ch / 2.0, &mut ctx);
         p.mouse_input(MouseButton::Left, ElementState::Released, cx + 6.0, cy + ch / 2.0, &mut ctx);