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

commitc48d1f13db727ac8773d456a238304ea0031fb23
parentba5588ac17
authorLucas Galante <[email protected]>
date2026-06-29 20:12
Add support for style.spinbox.button_padding

 src/layout.rs               | 39 +++++++++++++++++++++
 src/widget/input/spinbox.rs | 85 +++++++++++++++++++++++++++++++--------------
 2 files changed, 97 insertions(+), 27 deletions(-)

diff --git a/src/layout.rs b/src/layout.rs
index 785567e..1678787 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -26,6 +26,7 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 "style.slider.height" => "slider_height",
                 "style.slider.corner_radius" => "slider_corner_radius",
                 "style.spinbox.height" => "spinbox_height",
+                "style.spinbox.button_padding" => "spinbox_button_padding",
                 "style.textbox.height" => "textbox_height",
                 "style.toggle.font" => "toggle_font",
                 "style.toggle.height" => "toggle_height",
@@ -109,6 +110,7 @@ pub fn parse_font_string(s: &str) -> (String, Option<f32>) {
 
 static SECTION_PADDING: RwLock<f32> = RwLock::new(8.0);
 static SPINBOX_HEIGHT: RwLock<f32> = RwLock::new(26.0);
+static SPINBOX_BUTTON_PADDING: RwLock<f32> = RwLock::new(0.0);
 static COLOR_SELECTOR_HEIGHT: RwLock<f32> = RwLock::new(22.0);
 static TEXTBOX_HEIGHT: RwLock<f32> = RwLock::new(44.0);
 static FONT_SELECTOR_HEIGHT: RwLock<f32> = RwLock::new(44.0);
@@ -285,6 +287,15 @@ pub fn reload_config() {
                     }
                 }
             }
+            if let Some(rest) = trimmed.strip_prefix("spinbox_button_padding") {
+                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_BUTTON_PADDING.write() {
+                        *lock = val;
+                    }
+                }
+            }
             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();
@@ -2220,6 +2231,34 @@ pub fn set_spinbox_corner_radius(radius: f32) {
     }
 }
 
+pub fn spinbox_button_padding() -> 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_button_padding") {
+                    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_BUTTON_PADDING.write() {
+                            *lock = val;
+                        }
+                    }
+                }
+            }
+        }
+    });
+    *SPINBOX_BUTTON_PADDING.read().unwrap()
+}
+
+pub fn set_spinbox_button_padding(padding: f32) {
+    if let Ok(mut lock) = SPINBOX_BUTTON_PADDING.write() {
+        *lock = padding;
+    }
+}
+
 pub fn textbox_corner_radius() -> f32 {
     use std::sync::Once;
     static INIT: Once = Once::new();
diff --git a/src/widget/input/spinbox.rs b/src/widget/input/spinbox.rs
index 625c5f1..107d90c 100644
--- a/src/widget/input/spinbox.rs
+++ b/src/widget/input/spinbox.rs
@@ -148,8 +148,16 @@ impl Element for Spinbox {
             self.hover_inc = false;
             return changed || was != self.base.hovered;
         }
-        let hd = px >= self.base.x + self.base.w * 0.55 && px < self.base.x + self.base.w * 0.775;
-        let hi = px >= self.base.x + self.base.w * 0.775;
+        let top = self.base.label_offset();
+        let visual_h = self.base.h - top;
+        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 split_dec = self.base.x + self.base.w * 0.55;
+        
+        let in_y = py >= btn_y && py < btn_y + btn_h;
+        let hd = in_y && px >= split_dec + p && px < self.base.x + self.base.w * 0.775;
+        let hi = in_y && px >= self.base.x + self.base.w * 0.775 && px < self.base.x + self.base.w - p;
         let changed = hd != self.hover_dec || hi != self.hover_inc;
         self.hover_dec = hd;
         self.hover_inc = hi;
@@ -167,25 +175,29 @@ impl Element for Spinbox {
         if !self.hit_test(px, py, ctx) { return false; }
         match state {
             ElementState::Pressed => {
-                let display_w = self.base.w * 0.55;
-                let btn_w = self.base.w * 0.225;
-                let split_dec = self.base.x + display_w;
-                let split_inc = self.base.x + display_w + btn_w;
-                if px >= split_dec && px < split_inc {
+                let top = self.base.label_offset();
+                let visual_h = self.base.h - top;
+                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 split_dec = self.base.x + self.base.w * 0.55;
+                
+                let in_y = py >= btn_y && py < btn_y + btn_h;
+                if in_y && px >= split_dec + p && px < self.base.x + self.base.w * 0.775 {
                     let old_val = self.value;
                     self.value = (self.value - self.step).max(self.min);
                     if self.value != old_val {
                         self.just_changed = true;
                     }
                     true
-                } else if px >= split_inc {
+                } else if in_y && px >= self.base.x + self.base.w * 0.775 && px < self.base.x + self.base.w - p {
                     let old_val = self.value;
                     self.value = (self.value + self.step).min(self.max);
                     if self.value != old_val {
                         self.just_changed = true;
                     }
                     true
-                } else {
+                } else if px < split_dec {
                     self.editing = true;
                     if self.decimals > 0 {
                         let divisor = 10.0f32.powi(self.decimals as i32);
@@ -200,6 +212,8 @@ impl Element for Spinbox {
                     self.cursor_idx = click_idx;
                     focus::set_focused(self);
                     true
+                } else {
+                    false
                 }
             }
             ElementState::Released => {
@@ -323,10 +337,9 @@ impl Element for Spinbox {
         let mut quads = Vec::new();
         let top = self.base.label_offset();
         let visual_h = self.base.h - top;
-        let btn_w = self.base.w * 0.225;
         let display_w = self.base.w * 0.55;
+        let p = crate::layout::spinbox_button_padding();
         let split_dec = self.base.x + display_w;
-        let split_inc = self.base.x + display_w + btn_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 };
         
@@ -337,8 +350,16 @@ impl Element for Spinbox {
         };
         
         quads.push((self.base.x, self.base.y + top, display_w, visual_h, display_bg));
-        quads.push((split_dec, self.base.y + top, btn_w, visual_h, dec_col));
-        quads.push((split_inc, self.base.y + top, btn_w, visual_h, inc_col));
+        
+        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;
+        
+        if btn_h > 0.0 && btn_w_padded > 0.0 {
+            quads.push((split_dec + p, btn_y, btn_w_padded, btn_h, dec_col));
+            quads.push((split_dec + p + btn_w_padded, btn_y, btn_w_padded, btn_h, inc_col));
+        }
         
         if self.editing {
             let border_color = [0.20, 0.50, 0.85, 1.0];
@@ -392,20 +413,30 @@ impl Element for Spinbox {
             });
         }
 
-        labels.push(TextLabel {
-            text: "-".to_string(),
-            x: self.base.x + self.base.w * 0.6625 - 4.0,
-            y: crate::layout::align_text_y(self.base.y, self.base.h, 12.0, top),
-            font_size: 12.0,
-            color: [0xcc, 0xcc, 0xd4],
-        });
-        labels.push(TextLabel {
-            text: "+".to_string(),
-            x: self.base.x + self.base.w * 0.8875 - 4.0,
-            y: crate::layout::align_text_y(self.base.y, self.base.h, 12.0, top),
-            font_size: 12.0,
-            color: [0xcc, 0xcc, 0xd4],
-        });
+        let p = crate::layout::spinbox_button_padding();
+        let split_dec = self.base.x + self.base.w * 0.55;
+        let pair_w = (self.base.w * 0.45 - 2.0 * p).max(0.0);
+        let btn_w_padded = pair_w / 2.0;
+
+        if btn_w_padded > 0.0 {
+            let dec_center_x = split_dec + p + btn_w_padded * 0.5;
+            let inc_center_x = split_dec + p + btn_w_padded * 1.5;
+
+            labels.push(TextLabel {
+                text: "-".to_string(),
+                x: dec_center_x - 4.0,
+                y: crate::layout::align_text_y(self.base.y, self.base.h, 12.0, top),
+                font_size: 12.0,
+                color: [0xcc, 0xcc, 0xd4],
+            });
+            labels.push(TextLabel {
+                text: "+".to_string(),
+                x: inc_center_x - 4.0,
+                y: crate::layout::align_text_y(self.base.y, self.base.h, 12.0, top),
+                font_size: 12.0,
+                color: [0xcc, 0xcc, 0xd4],
+            });
+        }
         labels
     }