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

commit74061b7da322622c70e139b3382e204da60c466f
parent014305b13f
authorLucas Galante <[email protected]>
date2026-07-01 13:09
Fix tree container and spinbox corner radius config pick-up and styling

 src/layout.rs                          | 284 +++++++--------------------------
 src/widget/container/scrolling_list.rs |  40 ++++-
 src/widget/container/treelist.rs       |  42 ++++-
 src/widget/input/spinbox.rs            |  67 ++++++++
 4 files changed, 196 insertions(+), 237 deletions(-)

diff --git a/src/layout.rs b/src/layout.rs
index d40dfd2..a215a25 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -1220,30 +1220,14 @@ pub fn set_toggle_height(height: f32) {
 }
 
 pub fn toggle_corner_radius() -> 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_corner_radius") {
-                    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_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *TOGGLE_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("toggle_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_toggle_corner_radius(radius: f32) {
-    if let Ok(mut lock) = TOGGLE_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("toggle_corner_radius", radius);
     }
 }
 
@@ -1276,58 +1260,29 @@ pub fn set_toggle_border_width(width: f32) {
 }
 
 pub fn slider_corner_radius() -> 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("slider_corner_radius") {
-                    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) = SLIDER_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *SLIDER_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("slider_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_slider_corner_radius(radius: f32) {
-    if let Ok(mut lock) = SLIDER_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("slider_corner_radius", radius);
     }
 }
 
 pub fn plate_corner_radius() -> 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("plate_corner_radius") {
-                    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) = PLATE_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *PLATE_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    let r = get_style_registry().read().unwrap();
+    r.get_float("plate_corner_radius")
+        .or_else(|| r.get_float("backplate_corner_radius"))
+        .unwrap_or(12.0)
 }
 
 pub fn set_plate_corner_radius(radius: f32) {
-    if let Ok(mut lock) = PLATE_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("plate_corner_radius", radius);
     }
 }
 
@@ -2242,114 +2197,50 @@ pub fn set_breadcrumb_font(font: &str) {
 }
 
 pub fn color_selector_preview_corner_radius() -> 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("color_selector_preview_corner_radius") {
-                    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) = COLOR_SELECTOR_PREVIEW_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *COLOR_SELECTOR_PREVIEW_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("color_selector_preview_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_color_selector_preview_corner_radius(radius: f32) {
-    if let Ok(mut lock) = COLOR_SELECTOR_PREVIEW_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("color_selector_preview_corner_radius", radius);
     }
 }
 
 pub fn color_selector_corner_radius() -> 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("color_selector_corner_radius") {
-                    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) = COLOR_SELECTOR_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *COLOR_SELECTOR_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("color_selector_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_color_selector_corner_radius(radius: f32) {
-    if let Ok(mut lock) = COLOR_SELECTOR_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("color_selector_corner_radius", radius);
     }
 }
 
 pub fn button_corner_radius() -> 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("button_corner_radius") {
-                    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) = BUTTON_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *BUTTON_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("button_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_button_corner_radius(radius: f32) {
-    if let Ok(mut lock) = BUTTON_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("button_corner_radius", radius);
     }
 }
 
 pub fn spinbox_corner_radius() -> 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("spinbox_corner_radius") {
-                    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) = SPINBOX_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *SPINBOX_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("spinbox_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_spinbox_corner_radius(radius: f32) {
-    if let Ok(mut lock) = SPINBOX_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("spinbox_corner_radius", radius);
     }
 }
 
@@ -2382,53 +2273,20 @@ pub fn set_spinbox_button_padding(padding: f32) {
 }
 
 pub fn textbox_corner_radius() -> 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("textbox_corner_radius") {
-                    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) = TEXTBOX_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *TEXTBOX_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("textbox_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_textbox_corner_radius(radius: f32) {
-    if let Ok(mut lock) = TEXTBOX_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("textbox_corner_radius", radius);
     }
 }
 
 pub fn breadcrumb_corner_radius() -> 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("breadcrumb_corner_radius") {
-                    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) = BREADCRUMB_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *BREADCRUMB_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("breadcrumb_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_breadcrumb_corner_radius(radius: f32) {
@@ -2463,58 +2321,26 @@ pub fn set_tree_corner_radius(radius: f32) {
 }
 
 pub fn font_selector_corner_radius() -> 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("font_selector_corner_radius") {
-                    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) = FONT_SELECTOR_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *FONT_SELECTOR_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("font_selector_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_font_selector_corner_radius(radius: f32) {
-    if let Ok(mut lock) = FONT_SELECTOR_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("font_selector_corner_radius", radius);
     }
 }
 
 pub fn dropdown_corner_radius() -> 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("dropdown_corner_radius") {
-                    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) = DROPDOWN_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *DROPDOWN_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("dropdown_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_dropdown_corner_radius(radius: f32) {
-    if let Ok(mut lock) = DROPDOWN_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("dropdown_corner_radius", radius);
     }
 }
 
diff --git a/src/widget/container/scrolling_list.rs b/src/widget/container/scrolling_list.rs
index 45778b9..942486f 100644
--- a/src/widget/container/scrolling_list.rs
+++ b/src/widget/container/scrolling_list.rs
@@ -124,7 +124,45 @@ impl Element for ScrollingList {
     }
 
     fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        self.scroll_box.extra_quads()
+        let mut quads = self.scroll_box.extra_quads();
+        let (r1, r2, r3, r4) = self.rounded_corners();
+        let has_rounded = r1 || r2 || r3 || r4;
+        if has_rounded {
+            if !quads.is_empty() {
+                quads.remove(0);
+            }
+        }
+        quads
+    }
+
+    fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
+        let mut quads = Vec::new();
+        let (r1, r2, r3, r4) = self.rounded_corners();
+        let has_rounded = r1 || r2 || r3 || r4;
+        if !has_rounded {
+            for &child_ptr in &self.children(ctx) {
+                let widget = unsafe { &*child_ptr };
+                quads.extend(widget.all_rounded_quads(ctx));
+            }
+            return quads;
+        }
+
+        let radius = self.corner_radius();
+        let (x, y, w, h) = self.rect();
+        
+        // Solid border if active
+        if let Some((border_color, thickness)) = self.solid_border() {
+            quads.push((x, y, w, h, radius, border_color, (r1, r2, r3, r4)));
+            quads.push((x + thickness, y + thickness, w - 2.0 * thickness, h - 2.0 * thickness, radius - thickness, crate::color::list_bg_color(), (r1, r2, r3, r4)));
+        } else {
+            quads.push((x, y, w, h, radius, crate::color::list_bg_color(), (r1, r2, r3, r4)));
+        }
+
+        for &child_ptr in &self.children(ctx) {
+            let widget = unsafe { &*child_ptr };
+            quads.extend(widget.all_rounded_quads(ctx));
+        }
+        quads
     }
 
     fn keyboard_input(&mut self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
diff --git a/src/widget/container/treelist.rs b/src/widget/container/treelist.rs
index c3baede..1b722e6 100644
--- a/src/widget/container/treelist.rs
+++ b/src/widget/container/treelist.rs
@@ -366,13 +366,11 @@ impl Element for TreeList {
 
     fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
         let mut quads = self.scroll_box.extra_quads();
-        
-        if focus::is_focused(self) {
-            let focus_color = [0.30, 0.50, 0.32, 1.0];
-            for i in 1..=4 {
-                if i < quads.len() {
-                    quads[i].4 = focus_color;
-                }
+        let (r1, r2, r3, r4) = self.rounded_corners();
+        let has_rounded = r1 || r2 || r3 || r4;
+        if has_rounded {
+            if !quads.is_empty() {
+                quads.remove(0);
             }
         }
 
@@ -579,6 +577,36 @@ impl Element for TreeList {
         self.children.clone()
     }
 
+    fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
+        let mut quads = Vec::new();
+        let (r1, r2, r3, r4) = self.rounded_corners();
+        let has_rounded = r1 || r2 || r3 || r4;
+        if !has_rounded {
+            for &child_ptr in &self.children(ctx) {
+                let widget = unsafe { &*child_ptr };
+                quads.extend(widget.all_rounded_quads(ctx));
+            }
+            return quads;
+        }
+
+        let radius = self.corner_radius();
+        let (x, y, w, h) = self.rect();
+        
+        // Solid border if active
+        if let Some((border_color, thickness)) = self.solid_border() {
+            quads.push((x, y, w, h, radius, border_color, (r1, r2, r3, r4)));
+            quads.push((x + thickness, y + thickness, w - 2.0 * thickness, h - 2.0 * thickness, radius - thickness, crate::color::list_bg_color(), (r1, r2, r3, r4)));
+        } else {
+            quads.push((x, y, w, h, radius, crate::color::list_bg_color(), (r1, r2, r3, r4)));
+        }
+
+        for &child_ptr in &self.children(ctx) {
+            let widget = unsafe { &*child_ptr };
+            quads.extend(widget.all_rounded_quads(ctx));
+        }
+        quads
+    }
+
     fn add_child(&mut self, child: *mut (dyn Element + 'static), _ctx: &mut UiContext) {
         self.children.push(child);
     }
diff --git a/src/widget/input/spinbox.rs b/src/widget/input/spinbox.rs
index 0c4e30e..523af68 100644
--- a/src/widget/input/spinbox.rs
+++ b/src/widget/input/spinbox.rs
@@ -335,9 +335,25 @@ impl Element for Spinbox {
 
     fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
         let mut quads = Vec::new();
+        let (r1, r2, r3, r4) = self.rounded_corners();
+        let has_rounded = r1 || r2 || r3 || r4;
+
         let top = self.base.label_offset();
         let visual_h = self.base.h - top;
         let display_w = self.base.w * 0.55;
+
+        if has_rounded {
+            if self.editing {
+                let char_width = 8.4;
+                let cursor_x = self.base.x + 4.0 + (self.cursor_idx as f32 * char_width);
+                let max_cursor_x = self.base.x + display_w - 4.0;
+                let final_cursor_x = cursor_x.min(max_cursor_x);
+                let cursor_y = self.base.y + top + (visual_h - 14.0) / 2.0;
+                quads.push((final_cursor_x, cursor_y, 1.5, 14.0, [0.80, 0.80, 0.85, 1.0]));
+            }
+            return quads;
+        }
+
         let p = crate::layout::spinbox_button_padding();
         let split_dec = self.base.x + display_w;
         let inc_col = if self.hover_inc { colors::SPINBOX_BUTTON_HOVER } else { colors::SPINBOX_BUTTON };
@@ -379,6 +395,57 @@ impl Element for Spinbox {
         quads
     }
 
+    fn all_rounded_quads(&self, _ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
+        let mut quads = Vec::new();
+        let (r1, r2, r3, r4) = self.rounded_corners();
+        let has_rounded = r1 || r2 || r3 || r4;
+        if !has_rounded {
+            return quads;
+        }
+
+        let top = self.base.label_offset();
+        let visual_h = self.base.h - top;
+        let radius = self.corner_radius();
+        
+        let display_bg = if self.editing {
+            [0.06, 0.10, 0.18, 1.0]
+        } else {
+            colors::SPINBOX_DISPLAY
+        };
+
+        let display_w = self.base.w * 0.55;
+        let border_color = if self.editing {
+            [0.20, 0.50, 0.85, 1.0]
+        } else if self.base.hovered {
+            [0.25, 0.25, 0.35, 1.0]
+        } else {
+            [0.18, 0.18, 0.24, 1.0]
+        };
+
+        // Draw display border (outer) and background (inner)
+        quads.push((self.base.x, self.base.y + top, display_w, visual_h, radius, border_color, (r1, false, false, r4)));
+        quads.push((self.base.x + 1.0, self.base.y + top + 1.0, display_w - 1.0, visual_h - 2.0, radius - 1.0, display_bg, (r1, false, false, r4)));
+
+        // Increment/decrement buttons
+        let p = crate::layout::spinbox_button_padding();
+        let btn_y = self.base.y + top + p;
+        let btn_h = (visual_h - 2.0 * p).max(0.0);
+        let pair_w = (self.base.w * 0.45 - 2.0 * p).max(0.0);
+        let btn_w_padded = pair_w / 2.0;
+        let split_dec = self.base.x + display_w;
+        let inc_col = if self.hover_inc { colors::SPINBOX_BUTTON_HOVER } else { colors::SPINBOX_BUTTON };
+        let dec_col = if self.hover_dec { colors::SPINBOX_BUTTON_HOVER } else { colors::SPINBOX_BUTTON };
+
+        if btn_h > 0.0 && btn_w_padded > 0.0 {
+            // Decrement button (middle, no rounded corners)
+            quads.push((split_dec + p, btn_y, btn_w_padded, btn_h, 0.0, dec_col, (false, false, false, false)));
+            // Increment button (right, rounded top-right and bottom-right)
+            quads.push((split_dec + p + btn_w_padded, btn_y, btn_w_padded, btn_h, radius, inc_col, (false, r2, r3, false)));
+        }
+
+        quads
+    }
+
     fn text_labels(&self) -> Vec<TextLabel> {
         let mut labels = Vec::new();
         if let Some(lbl) = self.control_label() {