GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
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() {