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

commitca4bab688a1882de98aedb8d27a8f078dd99e022
parentc0c531c875
authorLucas Galante <[email protected]>
date2026-06-16 14:40
Refactor parameters panel layout to use native widget labels and full-width widgets

 src/backend/window_runner.rs          | 45 +++++++++++++++----
 src/widget/container/paginator.rs     |  6 +++
 src/widget/container/parameters_bg.rs | 85 +++++++++--------------------------
 src/widget/json_layout.rs             |  6 +++
 src/widget/mod.rs                     | 16 +++++--
 5 files changed, 81 insertions(+), 77 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index c199ded..f691d3f 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -34,7 +34,7 @@ use glyphon::{
     Cache, FontSystem, Resolution, SwashCache, TextArea, TextAtlas,
     TextBounds, TextRenderer, Viewport, Buffer, Attrs, Metrics,
 };
-use crate::widget::{TextItem, MouseButton, ElementState, MouseScrollDelta, KeyEvent, Key, NamedKey};
+use crate::widget::{Element, TextItem, MouseButton, ElementState, MouseScrollDelta, KeyEvent, Key, NamedKey};
 use crate::wayland::{WaylandSurfaceHandle, detect_scale_factor};
 use crate::backend::WgpuAdapter;
 
@@ -570,6 +570,31 @@ pub fn extra_quad_vertices(
     verts
 }
 
+fn get_child_widget_for_quad<'a>(
+    w: &'a dyn crate::widget::Element,
+    qx: f32, qy: f32, qw: f32, qh: f32,
+) -> &'a dyn crate::widget::Element {
+    if let Some(pbg) = w.as_any().downcast_ref::<crate::widget::ParametersBg>() {
+        for sb_opt in &pbg.spinboxes {
+            if let Some(sb) = sb_opt {
+                let (sx, sy, sww, shh) = sb.rect();
+                if qx >= sx - 0.1 && qx + qw <= sx + sww + 0.1 && qy >= sy - 0.1 && qy + qh <= sy + shh + 0.1 {
+                    return sb;
+                }
+            }
+        }
+        for btn_opt in &pbg.buttons {
+            if let Some(btn) = btn_opt {
+                let (bx, by, bww, bhh) = btn.rect();
+                if qx >= bx - 0.1 && qx + qw <= bx + bww + 0.1 && qy >= by - 0.1 && qy + qh <= by + bhh + 0.1 {
+                    return btn;
+                }
+            }
+        }
+    }
+    w
+}
+
 pub fn push_extra_quad_vertices(
     w: &dyn crate::widget::Element,
     qx: f32, qy: f32, qw: f32, qh: f32,
@@ -578,14 +603,15 @@ pub fn push_extra_quad_vertices(
     clip_circle: [f32; 3],
     out: &mut Vec<Vertex>,
 ) {
-    let corners = w.rounded_corners();
+    let target_w = get_child_widget_for_quad(w, qx, qy, qw, qh);
+    let corners = target_w.rounded_corners();
     if corners == (false, false, false, false) {
         out.extend_from_slice(&quad_vertices_with_clip(qx, qy, qw, qh, sw, sh, qc, clip_circle));
         return;
     }
 
-    let (wx, mut wy, ww, mut wh) = w.rect();
-    let top_room = crate::widget::label_offset(w);
+    let (wx, mut wy, ww, mut wh) = target_w.rect();
+    let top_room = crate::widget::label_offset(target_w);
     wy += top_room;
     wh -= top_room;
     let extra_corners = (
@@ -595,7 +621,7 @@ pub fn push_extra_quad_vertices(
         corners.3 && qx <= wx + 0.1 && qy + qh >= wy + wh - 0.1,
     );
 
-    push_rounded_rect_vertices_corners(qx, qy, qw, qh, w.corner_radius(), sw, sh, qc, clip_circle, extra_corners, None, out);
+    push_rounded_rect_vertices_corners(qx, qy, qw, qh, target_w.corner_radius(), sw, sh, qc, clip_circle, extra_corners, None, out);
 }
 
 pub fn extra_quad_vertices_clipped(
@@ -620,7 +646,8 @@ pub fn push_extra_quad_vertices_clipped(
     clip_circle: [f32; 3],
     out: &mut Vec<Vertex>,
 ) {
-    let corners = w.rounded_corners();
+    let target_w = get_child_widget_for_quad(w, qx, qy, qw, qh);
+    let corners = target_w.rounded_corners();
     if corners == (false, false, false, false) {
         let (cx0, cy0, cx1, cy1) = clip;
         let ix0 = qx.max(cx0);
@@ -634,8 +661,8 @@ pub fn push_extra_quad_vertices_clipped(
         return;
     }
 
-    let (wx, mut wy, ww, mut wh) = w.rect();
-    let top_room = crate::widget::label_offset(w);
+    let (wx, mut wy, ww, mut wh) = target_w.rect();
+    let top_room = crate::widget::label_offset(target_w);
     wy += top_room;
     wh -= top_room;
     let extra_corners = (
@@ -645,7 +672,7 @@ pub fn push_extra_quad_vertices_clipped(
         corners.3 && qx <= wx + 0.1 && qy + qh >= wy + wh - 0.1,
     );
 
-    push_rounded_rect_vertices_corners(qx, qy, qw, qh, w.corner_radius(), sw, sh, qc, clip_circle, extra_corners, Some(clip), out);
+    push_rounded_rect_vertices_corners(qx, qy, qw, qh, target_w.corner_radius(), sw, sh, qc, clip_circle, extra_corners, Some(clip), out);
 }
 
 pub fn circle_vertices(
diff --git a/src/widget/container/paginator.rs b/src/widget/container/paginator.rs
index ef7a1b8..f1d48fc 100644
--- a/src/widget/container/paginator.rs
+++ b/src/widget/container/paginator.rs
@@ -473,6 +473,12 @@ impl Paginator {
 
 
 impl Element for Paginator {
+    fn as_any(&self) -> &dyn std::any::Any { self }
+    fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
+    fn as_ptr(&self) -> *mut (dyn Element + 'static) {
+        self as *const Self as *mut Self as *mut (dyn Element + 'static)
+    }
+
     fn rect(&self) -> (f32, f32, f32, f32) {
         (self.x, self.y, self.w, self.h)
     }
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index da05798..a3d3904 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -12,7 +12,7 @@ pub struct ParametersBg {
     mouse_pos: Option<(f32, f32)>,
     sliders: Vec<Option<Slider>>,
     float3s: Vec<Option<Float3>>,
-    spinboxes: Vec<Option<Spinbox>>,
+    pub spinboxes: Vec<Option<Spinbox>>,
     pub buttons: Vec<Option<Button>>,
     pub choices: Vec<Option<Dropdown>>,
     pub texts: Vec<Option<TextBox>>,
@@ -65,7 +65,11 @@ impl ParametersBg {
                 24.0
             } else if p.2.starts_with("float3") {
                 108.0
-            } else if p.2 == "text" || p.2.starts_with("spinbox") || p.2.starts_with("choice") || p.2 == "button" || p.2 == "toggle" || p.2 == "checkbox" {
+            } else if p.2.starts_with("slider") {
+                38.0
+            } else if p.2 == "text" || p.2.starts_with("spinbox") || p.2.starts_with("choice") {
+                42.0
+            } else if p.2 == "button" || p.2 == "toggle" || p.2 == "checkbox" {
                 24.0
             } else {
                 20.0
@@ -81,11 +85,7 @@ impl ParametersBg {
         for (i, s_opt) in self.sliders.iter_mut().enumerate() {
             if let Some(s) = s_opt {
                 let r = rects[i];
-                let track_x = self.base.x + 100.0;
-                let track_w = (self.base.w - 100.0 - 20.0).max(10.0);
-                let track_y = r.1 + 4.0;
-                let track_h = 12.0;
-                s.set_rect(track_x, track_y, track_w, track_h);
+                s.set_rect(r.0, r.1, r.2, r.3);
             }
         }
         for (i, f_opt) in self.float3s.iter_mut().enumerate() {
@@ -97,41 +97,31 @@ impl ParametersBg {
         for (i, sb_opt) in self.spinboxes.iter_mut().enumerate() {
             if let Some(sb) = sb_opt {
                 let r = rects[i];
-                let box_x = self.base.x + 100.0;
-                let box_w = (self.base.w - 100.0 - 16.0).max(10.0);
-                sb.set_rect(box_x, r.1, box_w, r.3);
+                sb.set_rect(r.0, r.1, r.2, r.3);
             }
         }
         for (i, b_opt) in self.buttons.iter_mut().enumerate() {
             if let Some(b) = b_opt {
                 let r = rects[i];
-                let box_x = self.base.x + 100.0;
-                let box_w = (self.base.w - 100.0 - 16.0).max(10.0);
-                b.set_rect(box_x, r.1, box_w, r.3);
+                b.set_rect(r.0, r.1, r.2, r.3);
             }
         }
         for (i, d_opt) in self.choices.iter_mut().enumerate() {
             if let Some(d) = d_opt {
                 let r = rects[i];
-                let box_x = self.base.x + 100.0;
-                let box_w = (self.base.w - 100.0 - 16.0).max(10.0);
-                d.set_rect(box_x, r.1, box_w, r.3);
+                d.set_rect(r.0, r.1, r.2, r.3);
             }
         }
         for (i, tb_opt) in self.texts.iter_mut().enumerate() {
             if let Some(tb) = tb_opt {
                 let r = rects[i];
-                let box_x = self.base.x + 100.0;
-                let box_w = (self.base.w - 100.0 - 16.0).max(10.0);
-                tb.set_rect(box_x, r.1, box_w, r.3);
+                tb.set_rect(r.0, r.1, r.2, r.3);
             }
         }
         for (i, cb_opt) in self.checkboxes.iter_mut().enumerate() {
             if let Some(cb) = cb_opt {
                 let r = rects[i];
-                let box_x = self.base.x + 100.0;
-                let box_w = (self.base.w - 100.0 - 16.0).max(10.0);
-                cb.set_rect(box_x, r.1, box_w, r.3);
+                cb.set_rect(r.0, r.1, r.2, r.3);
             }
         }
     }
@@ -142,13 +132,6 @@ impl ParametersBg {
         for (i, (name, value, ptype)) in self.display_params.iter().enumerate() {
             let r = rects[i];
             if ptype.starts_with("slider") {
-                labels.push(TextLabel {
-                    text: name.clone(),
-                    x: self.base.x + 8.0,
-                    y: r.1,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
                 if let Some(s) = &self.sliders[i] {
                     labels.extend(s.text_labels());
                 }
@@ -189,35 +172,14 @@ impl ParametersBg {
                     color: [0xee, 0xee, 0xf0],
                 });
             } else if ptype.starts_with("spinbox") {
-                labels.push(TextLabel {
-                    text: name.clone(),
-                    x: self.base.x + 8.0,
-                    y: r.1 + (r.3 - 12.0) / 2.0 - 2.0,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
                 if let Some(sb) = &self.spinboxes[i] {
                     labels.extend(sb.text_labels());
                 }
             } else if ptype == "text" {
-                labels.push(TextLabel {
-                    text: name.clone(),
-                    x: self.base.x + 8.0,
-                    y: r.1 + (r.3 - 12.0) / 2.0 - 2.0,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
                 if let Some(tb) = &self.texts[i] {
                     labels.extend(tb.text_labels());
                 }
             } else if ptype.starts_with("choice") {
-                labels.push(TextLabel {
-                    text: name.clone(),
-                    x: self.base.x + 8.0,
-                    y: r.1 + (r.3 - 12.0) / 2.0 - 2.0,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
                 if let Some(d) = &self.choices[i] {
                     labels.extend(d.text_labels());
                 }
@@ -226,13 +188,6 @@ impl ParametersBg {
                     labels.extend(b.text_labels());
                 }
             } else if ptype == "toggle" || ptype == "checkbox" {
-                labels.push(TextLabel {
-                    text: name.clone(),
-                    x: self.base.x + 8.0,
-                    y: r.1 + (r.3 - 12.0) / 2.0 - 2.0,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
                 if let Some(cb) = &self.checkboxes[i] {
                     labels.extend(cb.text_labels());
                 }
@@ -438,9 +393,9 @@ impl Element for ParametersBg {
         for (i, p) in self.display_params.iter().enumerate() {
             if p.2.starts_with("slider") {
                 let r = rects[i];
-                let row_y = r.1;
-                if py >= row_y - 2.0 && py <= row_y + 18.0 {
-                    if let Some(s) = &mut self.sliders[i] {
+                if let Some(s) = &mut self.sliders[i] {
+                    let top = crate::widget::label_offset(s);
+                    if py >= r.1 + top && py <= r.1 + r.3 {
                         s.drag_begin(px, py);
                         self.dragging_param = Some(i);
                         break;
@@ -1360,7 +1315,7 @@ impl ParamController for ParametersBg {
                     } else {
                         0.0
                     };
-                    Some(Slider::new().with_value(t).with_range(min, max).with_readout(true))
+                    Some(Slider::new().with_value(t).with_range(min, max).with_readout(true).with_label(&p.0))
                 } else {
                     None
                 }
@@ -1378,7 +1333,7 @@ impl ParamController for ParametersBg {
                 if p.2.starts_with("spinbox") {
                     let (min, max, step) = parse_spinbox_range(&p.2);
                     let val = p.1.parse::<i32>().unwrap_or(min);
-                    Some(Spinbox::new(val, min, max, step))
+                    Some(Spinbox::new(val, min, max, step).with_label(&p.0))
                 } else {
                     None
                 }
@@ -1395,14 +1350,14 @@ impl ParamController for ParametersBg {
                     let options_str = p.2.strip_prefix("choice:").unwrap_or("");
                     let options: Vec<String> = options_str.split(',').map(|s| s.to_string()).collect();
                     let selected = options.iter().position(|o| o == &p.1).unwrap_or(0);
-                    Some(Dropdown::new(options, selected))
+                    Some(Dropdown::new(options, selected).with_label(&p.0))
                 } else {
                     None
                 }
             }).collect();
             self.texts = self.display_params.iter().map(|p| {
                 if p.2 == "text" {
-                    Some(TextBox::new(p.1.clone()))
+                    Some(TextBox::new(p.1.clone()).with_label(&p.0))
                 } else {
                     None
                 }
@@ -1410,7 +1365,7 @@ impl ParamController for ParametersBg {
             self.checkboxes = 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();
+                    let mut cb = Checkbox::new().with_label(&p.0);
                     cb.set_checked(checked);
                     Some(cb)
                 } else {
diff --git a/src/widget/json_layout.rs b/src/widget/json_layout.rs
index 839ec7a..f30b49f 100644
--- a/src/widget/json_layout.rs
+++ b/src/widget/json_layout.rs
@@ -275,6 +275,12 @@ impl JsonLayoutWidget {
 }
 
 impl Element for JsonLayoutWidget {
+    fn as_any(&self) -> &dyn std::any::Any { self }
+    fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
+    fn as_ptr(&self) -> *mut (dyn Element + 'static) {
+        self as *const Self as *mut Self as *mut (dyn Element + 'static)
+    }
+
     fn base(&self) -> Option<&Widget> { Some(&self.base) }
     fn base_mut(&mut self) -> Option<&mut Widget> { Some(&mut self.base) }
     fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 596a81c..e1ddd06 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -127,13 +127,23 @@ pub trait Element {
     fn preferred_height(&self) -> Option<f32> { None }
 
     fn as_any(&self) -> &dyn std::any::Any {
-        panic!("as_any not implemented");
+        struct DummyAny;
+        static DUMMY: DummyAny = DummyAny;
+        &DUMMY
     }
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
-        panic!("as_any_mut not implemented");
+        struct DummyAny;
+        thread_local! {
+            static DUMMY_MUT: std::cell::UnsafeCell<DummyAny> = std::cell::UnsafeCell::new(DummyAny);
+        }
+        DUMMY_MUT.with(|d| unsafe { &mut *d.get() })
     }
     fn as_ptr(&self) -> *mut (dyn Element + 'static) {
-        panic!("as_ptr not implemented");
+        struct DummyElement;
+        impl Element for DummyElement {
+            fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
+        }
+        std::ptr::null_mut::<DummyElement>() as *mut (dyn Element + 'static)
     }
 
     fn handle_event(&mut self, event: &Event, ctx: &mut UiContext) -> bool {