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

commit8a7b6803f0ad58922977488d958d6ed9ec5d6b9d
parent212b568577
authorLucas Galante <[email protected]>
date2026-06-28 06:19
feat: implement independent widget fonts for Button, ButtonStrip, Labels, Dropdown, TextBox, Spinbox, and Slider

 src/layout.rs                         | 532 ++++++++++++++++++++++++++++++++++
 src/widget/container/parameters_bg.rs |   4 +
 src/widget/input/button.rs            |   4 +
 src/widget/input/button_strip.rs      |  10 +-
 src/widget/input/dropdown.rs          |   2 +-
 src/widget/input/slider.rs            |   4 +
 src/widget/input/spinbox.rs           |   2 +-
 src/widget/input/text_box.rs          |   2 +-
 8 files changed, 554 insertions(+), 6 deletions(-)

diff --git a/src/layout.rs b/src/layout.rs
index fffb09b..da005fa 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -108,6 +108,20 @@ static TOGGLE_FONT: RwLock<String> = RwLock::new(String::new());
 static TOGGLE_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
 static FONT_SELECTOR_FONT: RwLock<String> = RwLock::new(String::new());
 static FONT_SELECTOR_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
+static BUTTON_STRIP_FONT: RwLock<String> = RwLock::new(String::new());
+static BUTTON_STRIP_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
+static BUTTON_FONT: RwLock<String> = RwLock::new(String::new());
+static BUTTON_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
+static LABEL_FONT: RwLock<String> = RwLock::new(String::new());
+static LABEL_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
+static DROPDOWN_FONT: RwLock<String> = RwLock::new(String::new());
+static DROPDOWN_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
+static TEXTBOX_FONT: RwLock<String> = RwLock::new(String::new());
+static TEXTBOX_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
+static SPINBOX_FONT: RwLock<String> = RwLock::new(String::new());
+static SPINBOX_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
+static SLIDER_FONT: RwLock<String> = RwLock::new(String::new());
+static SLIDER_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
 static PLATE_CORNER_RADIUS: RwLock<f32> = RwLock::new(12.0);
 static PLATE_OPACITY: RwLock<f32> = RwLock::new(1.0);
 static PAGE_OPACITY: RwLock<f32> = RwLock::new(1.0);
@@ -140,6 +154,13 @@ pub fn reload_config() {
         let mut menubar_font_changed = false;
         let mut toggle_font_changed = false;
         let mut font_selector_font_changed = false;
+        let mut button_strip_font_changed = false;
+        let mut button_font_changed = false;
+        let mut label_font_changed = false;
+        let mut dropdown_font_changed = false;
+        let mut textbox_font_changed = false;
+        let mut spinbox_font_changed = false;
+        let mut slider_font_changed = false;
         for line in content.lines() {
             let trimmed = line.trim();
             if let Some(rest) = trimmed.strip_prefix("label_margin") {
@@ -537,6 +558,111 @@ pub fn reload_config() {
                     font_selector_font_changed = true;
                 }
             }
+            if let Some(rest) = trimmed.strip_prefix("button_strip_font") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                let rest = mod_rest(rest);
+                let font = rest.trim().to_string();
+                let mut changed = false;
+                if let Ok(mut lock) = BUTTON_STRIP_FONT.write() {
+                    if *lock != font {
+                        *lock = font;
+                        changed = true;
+                    }
+                }
+                if changed {
+                    button_strip_font_changed = true;
+                }
+            }
+            if let Some(rest) = trimmed.strip_prefix("button_font") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                let rest = mod_rest(rest);
+                let font = rest.trim().to_string();
+                let mut changed = false;
+                if let Ok(mut lock) = BUTTON_FONT.write() {
+                    if *lock != font {
+                        *lock = font;
+                        changed = true;
+                    }
+                }
+                if changed {
+                    button_font_changed = true;
+                }
+            }
+            if let Some(rest) = trimmed.strip_prefix("label_font") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                let rest = mod_rest(rest);
+                let font = rest.trim().to_string();
+                let mut changed = false;
+                if let Ok(mut lock) = LABEL_FONT.write() {
+                    if *lock != font {
+                        *lock = font;
+                        changed = true;
+                    }
+                }
+                if changed {
+                    label_font_changed = true;
+                }
+            }
+            if let Some(rest) = trimmed.strip_prefix("dropdown_font") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                let rest = mod_rest(rest);
+                let font = rest.trim().to_string();
+                let mut changed = false;
+                if let Ok(mut lock) = DROPDOWN_FONT.write() {
+                    if *lock != font {
+                        *lock = font;
+                        changed = true;
+                    }
+                }
+                if changed {
+                    dropdown_font_changed = true;
+                }
+            }
+            if let Some(rest) = trimmed.strip_prefix("textbox_font") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                let rest = mod_rest(rest);
+                let font = rest.trim().to_string();
+                let mut changed = false;
+                if let Ok(mut lock) = TEXTBOX_FONT.write() {
+                    if *lock != font {
+                        *lock = font;
+                        changed = true;
+                    }
+                }
+                if changed {
+                    textbox_font_changed = true;
+                }
+            }
+            if let Some(rest) = trimmed.strip_prefix("spinbox_font") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                let rest = mod_rest(rest);
+                let font = rest.trim().to_string();
+                let mut changed = false;
+                if let Ok(mut lock) = SPINBOX_FONT.write() {
+                    if *lock != font {
+                        *lock = font;
+                        changed = true;
+                    }
+                }
+                if changed {
+                    spinbox_font_changed = true;
+                }
+            }
+            if let Some(rest) = trimmed.strip_prefix("slider_font") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                let rest = mod_rest(rest);
+                let font = rest.trim().to_string();
+                let mut changed = false;
+                if let Ok(mut lock) = SLIDER_FONT.write() {
+                    if *lock != font {
+                        *lock = font;
+                        changed = true;
+                    }
+                }
+                if changed {
+                    slider_font_changed = true;
+                }
+            }
         }
         if menubar_font_changed {
             if let Ok(mut lock) = MENUBAR_FONT_CACHED.write() {
@@ -553,6 +679,41 @@ pub fn reload_config() {
                 *lock = None;
             }
         }
+        if button_strip_font_changed {
+            if let Ok(mut lock) = BUTTON_STRIP_FONT_CACHED.write() {
+                *lock = None;
+            }
+        }
+        if button_font_changed {
+            if let Ok(mut lock) = BUTTON_FONT_CACHED.write() {
+                *lock = None;
+            }
+        }
+        if label_font_changed {
+            if let Ok(mut lock) = LABEL_FONT_CACHED.write() {
+                *lock = None;
+            }
+        }
+        if dropdown_font_changed {
+            if let Ok(mut lock) = DROPDOWN_FONT_CACHED.write() {
+                *lock = None;
+            }
+        }
+        if textbox_font_changed {
+            if let Ok(mut lock) = TEXTBOX_FONT_CACHED.write() {
+                *lock = None;
+            }
+        }
+        if spinbox_font_changed {
+            if let Ok(mut lock) = SPINBOX_FONT_CACHED.write() {
+                *lock = None;
+            }
+        }
+        if slider_font_changed {
+            if let Ok(mut lock) = SLIDER_FONT_CACHED.write() {
+                *lock = None;
+            }
+        }
         crate::color::reload_colors(&content);
     }
 }
@@ -1309,6 +1470,377 @@ pub fn set_font_selector_font(font: &str) {
     }
 }
 
+// Button Strip Font
+pub fn button_strip_font() -> String {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        let mut font = "Outfit".to_string();
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("button_strip_font") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                    let rest = mod_rest(rest);
+                    font = rest.trim().to_string();
+                }
+            }
+        }
+        if let Ok(mut lock) = BUTTON_STRIP_FONT.write() {
+            *lock = font;
+        }
+    });
+    let lock = BUTTON_STRIP_FONT.read().unwrap();
+    if lock.is_empty() {
+        "Outfit".to_string()
+    } else {
+        lock.clone()
+    }
+}
+
+pub fn button_strip_font_parsed() -> (String, f32) {
+    if let Ok(lock) = BUTTON_STRIP_FONT_CACHED.read() {
+        if let Some(ref val) = *lock {
+            return val.clone();
+        }
+    }
+    let font_str = button_strip_font();
+    let parsed = parse_font_string(&font_str);
+    let size = parsed.1.unwrap_or(12.0);
+    let val = (parsed.0, size);
+    if let Ok(mut lock) = BUTTON_STRIP_FONT_CACHED.write() {
+        *lock = Some(val.clone());
+    }
+    val
+}
+
+pub fn set_button_strip_font(font: &str) {
+    if let Ok(mut lock) = BUTTON_STRIP_FONT.write() {
+        *lock = font.to_string();
+    }
+    if let Ok(mut lock) = BUTTON_STRIP_FONT_CACHED.write() {
+        *lock = None;
+    }
+}
+
+// Button Font
+pub fn button_font() -> String {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        let mut font = "Outfit".to_string();
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("button_font") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                    let rest = mod_rest(rest);
+                    font = rest.trim().to_string();
+                }
+            }
+        }
+        if let Ok(mut lock) = BUTTON_FONT.write() {
+            *lock = font;
+        }
+    });
+    let lock = BUTTON_FONT.read().unwrap();
+    if lock.is_empty() {
+        "Outfit".to_string()
+    } else {
+        lock.clone()
+    }
+}
+
+pub fn button_font_parsed() -> (String, f32) {
+    if let Ok(lock) = BUTTON_FONT_CACHED.read() {
+        if let Some(ref val) = *lock {
+            return val.clone();
+        }
+    }
+    let font_str = button_font();
+    let parsed = parse_font_string(&font_str);
+    let size = parsed.1.unwrap_or(12.0);
+    let val = (parsed.0, size);
+    if let Ok(mut lock) = BUTTON_FONT_CACHED.write() {
+        *lock = Some(val.clone());
+    }
+    val
+}
+
+pub fn set_button_font(font: &str) {
+    if let Ok(mut lock) = BUTTON_FONT.write() {
+        *lock = font.to_string();
+    }
+    if let Ok(mut lock) = BUTTON_FONT_CACHED.write() {
+        *lock = None;
+    }
+}
+
+// Label Font
+pub fn label_font() -> String {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        let mut font = "Outfit".to_string();
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("label_font") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                    let rest = mod_rest(rest);
+                    font = rest.trim().to_string();
+                }
+            }
+        }
+        if let Ok(mut lock) = LABEL_FONT.write() {
+            *lock = font;
+        }
+    });
+    let lock = LABEL_FONT.read().unwrap();
+    if lock.is_empty() {
+        "Outfit".to_string()
+    } else {
+        lock.clone()
+    }
+}
+
+pub fn label_font_parsed() -> (String, f32) {
+    if let Ok(lock) = LABEL_FONT_CACHED.read() {
+        if let Some(ref val) = *lock {
+            return val.clone();
+        }
+    }
+    let font_str = label_font();
+    let parsed = parse_font_string(&font_str);
+    let size = parsed.1.unwrap_or(12.0);
+    let val = (parsed.0, size);
+    if let Ok(mut lock) = LABEL_FONT_CACHED.write() {
+        *lock = Some(val.clone());
+    }
+    val
+}
+
+pub fn set_label_font(font: &str) {
+    if let Ok(mut lock) = LABEL_FONT.write() {
+        *lock = font.to_string();
+    }
+    if let Ok(mut lock) = LABEL_FONT_CACHED.write() {
+        *lock = None;
+    }
+}
+
+// Dropdown Font
+pub fn dropdown_font() -> String {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        let mut font = "Outfit".to_string();
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("dropdown_font") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                    let rest = mod_rest(rest);
+                    font = rest.trim().to_string();
+                }
+            }
+        }
+        if let Ok(mut lock) = DROPDOWN_FONT.write() {
+            *lock = font;
+        }
+    });
+    let lock = DROPDOWN_FONT.read().unwrap();
+    if lock.is_empty() {
+        "Outfit".to_string()
+    } else {
+        lock.clone()
+    }
+}
+
+pub fn dropdown_font_parsed() -> (String, f32) {
+    if let Ok(lock) = DROPDOWN_FONT_CACHED.read() {
+        if let Some(ref val) = *lock {
+            return val.clone();
+        }
+    }
+    let font_str = dropdown_font();
+    let parsed = parse_font_string(&font_str);
+    let size = parsed.1.unwrap_or(12.0);
+    let val = (parsed.0, size);
+    if let Ok(mut lock) = DROPDOWN_FONT_CACHED.write() {
+        *lock = Some(val.clone());
+    }
+    val
+}
+
+pub fn set_dropdown_font(font: &str) {
+    if let Ok(mut lock) = DROPDOWN_FONT.write() {
+        *lock = font.to_string();
+    }
+    if let Ok(mut lock) = DROPDOWN_FONT_CACHED.write() {
+        *lock = None;
+    }
+}
+
+// Textbox Font
+pub fn textbox_font() -> String {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        let mut font = "Outfit".to_string();
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("textbox_font") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                    let rest = mod_rest(rest);
+                    font = rest.trim().to_string();
+                }
+            }
+        }
+        if let Ok(mut lock) = TEXTBOX_FONT.write() {
+            *lock = font;
+        }
+    });
+    let lock = TEXTBOX_FONT.read().unwrap();
+    if lock.is_empty() {
+        "Outfit".to_string()
+    } else {
+        lock.clone()
+    }
+}
+
+pub fn textbox_font_parsed() -> (String, f32) {
+    if let Ok(lock) = TEXTBOX_FONT_CACHED.read() {
+        if let Some(ref val) = *lock {
+            return val.clone();
+        }
+    }
+    let font_str = textbox_font();
+    let parsed = parse_font_string(&font_str);
+    let size = parsed.1.unwrap_or(12.0);
+    let val = (parsed.0, size);
+    if let Ok(mut lock) = TEXTBOX_FONT_CACHED.write() {
+        *lock = Some(val.clone());
+    }
+    val
+}
+
+pub fn set_textbox_font(font: &str) {
+    if let Ok(mut lock) = TEXTBOX_FONT.write() {
+        *lock = font.to_string();
+    }
+    if let Ok(mut lock) = TEXTBOX_FONT_CACHED.write() {
+        *lock = None;
+    }
+}
+
+// Spinbox Font
+pub fn spinbox_font() -> String {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        let mut font = "monospace".to_string();
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("spinbox_font") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                    let rest = mod_rest(rest);
+                    font = rest.trim().to_string();
+                }
+            }
+        }
+        if let Ok(mut lock) = SPINBOX_FONT.write() {
+            *lock = font;
+        }
+    });
+    let lock = SPINBOX_FONT.read().unwrap();
+    if lock.is_empty() {
+        "monospace".to_string()
+    } else {
+        lock.clone()
+    }
+}
+
+pub fn spinbox_font_parsed() -> (String, f32) {
+    if let Ok(lock) = SPINBOX_FONT_CACHED.read() {
+        if let Some(ref val) = *lock {
+            return val.clone();
+        }
+    }
+    let font_str = spinbox_font();
+    let parsed = parse_font_string(&font_str);
+    let size = parsed.1.unwrap_or(12.0);
+    let val = (parsed.0, size);
+    if let Ok(mut lock) = SPINBOX_FONT_CACHED.write() {
+        *lock = Some(val.clone());
+    }
+    val
+}
+
+pub fn set_spinbox_font(font: &str) {
+    if let Ok(mut lock) = SPINBOX_FONT.write() {
+        *lock = font.to_string();
+    }
+    if let Ok(mut lock) = SPINBOX_FONT_CACHED.write() {
+        *lock = None;
+    }
+}
+
+// Slider Font
+pub fn slider_font() -> String {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        let mut font = "Outfit".to_string();
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("slider_font") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+                    let rest = mod_rest(rest);
+                    font = rest.trim().to_string();
+                }
+            }
+        }
+        if let Ok(mut lock) = SLIDER_FONT.write() {
+            *lock = font;
+        }
+    });
+    let lock = SLIDER_FONT.read().unwrap();
+    if lock.is_empty() {
+        "Outfit".to_string()
+    } else {
+        lock.clone()
+    }
+}
+
+pub fn slider_font_parsed() -> (String, f32) {
+    if let Ok(lock) = SLIDER_FONT_CACHED.read() {
+        if let Some(ref val) = *lock {
+            return val.clone();
+        }
+    }
+    let font_str = slider_font();
+    let parsed = parse_font_string(&font_str);
+    let size = parsed.1.unwrap_or(12.0);
+    let val = (parsed.0, size);
+    if let Ok(mut lock) = SLIDER_FONT_CACHED.write() {
+        *lock = Some(val.clone());
+    }
+    val
+}
+
+pub fn set_slider_font(font: &str) {
+    if let Ok(mut lock) = SLIDER_FONT.write() {
+        *lock = font.to_string();
+    }
+    if let Ok(mut lock) = SLIDER_FONT_CACHED.write() {
+        *lock = None;
+    }
+}
+
 
 pub fn section_label_font() -> String {
     use std::sync::Once;
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 265df53..9198417 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -284,6 +284,10 @@ fn parse_float3_value(val_str: &str, min: f32, max: f32) -> [f32; 3] {
 impl Element for ParametersBg {
     crate::impl_widget_base!(ParametersBg);
 
+    fn widget_font(&self) -> Option<String> {
+        Some(crate::layout::label_font())
+    }
+
     fn rounded_corners(&self) -> (bool, bool, bool, bool) { (true, true, true, true) }
 
     fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index 16e32ed..dae2fd6 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -272,6 +272,10 @@ impl Element for Button {
         }
     }
 
+    fn widget_font(&self) -> Option<String> {
+        Some(crate::layout::button_font())
+    }
+
     fn corner_radius(&self) -> f32 {
         crate::layout::button_corner_radius()
     }
diff --git a/src/widget/input/button_strip.rs b/src/widget/input/button_strip.rs
index 2c87f1a..3f6e5cf 100644
--- a/src/widget/input/button_strip.rs
+++ b/src/widget/input/button_strip.rs
@@ -88,7 +88,7 @@ impl ButtonStrip {
         let inactive_g = (active_g as f32 * 0.78) as u8;
         let inactive_b = (active_b as f32 * 0.78) as u8;
 
-        let (font_fam, font_size) = crate::layout::menubar_font_parsed();
+        let (font_fam, font_size) = crate::layout::button_strip_font_parsed();
         let scale = crate::scale::scale_factor().max(1.0);
 
         for (i, page_name) in self.buttons.iter().enumerate() {
@@ -187,7 +187,7 @@ impl ButtonStrip {
         let get_button_weight = |i: usize| -> f32 {
             let label = &self.buttons[i];
             let trimmed = label.trim();
-            let font_info = crate::layout::menubar_font_parsed();
+            let font_info = crate::layout::button_strip_font_parsed();
             let font_fam = font_info.0;
             let font_size = font_info.1;
             let padding = crate::layout::button_padding();
@@ -373,7 +373,7 @@ impl Element for ButtonStrip {
 
     fn text_labels(&self) -> Vec<TextLabel> {
         let mut labels = Vec::new();
-        let font_info = crate::layout::menubar_font_parsed();
+        let font_info = crate::layout::button_strip_font_parsed();
         let font_fam = font_info.0;
         let font_size = font_info.1;
         for (i, btn_label) in self.buttons.iter().enumerate() {
@@ -452,6 +452,10 @@ impl Element for ButtonStrip {
         }
         false
     }
+
+    fn widget_font(&self) -> Option<String> {
+        Some(crate::layout::button_strip_font())
+    }
 }
 
 impl Control for ButtonStrip {}
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index ffdbcbd..6cda685 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -186,7 +186,7 @@ impl Element for Dropdown {
     }
 
     fn widget_font(&self) -> Option<String> {
-        Some(self.font_family.clone())
+        Some(crate::layout::dropdown_font())
     }
 
     fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index e7b06dc..08c4f9e 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -113,6 +113,10 @@ impl Slider {
 impl Element for Slider {
     crate::impl_widget_base!(Slider);
 
+    fn widget_font(&self) -> Option<String> {
+        Some(crate::layout::slider_font())
+    }
+
     fn get_value_string(&self) -> Option<String> {
         let scaled_val = self.min + self.value * (self.max - self.min);
         Some(format!("{:.2}", scaled_val))
diff --git a/src/widget/input/spinbox.rs b/src/widget/input/spinbox.rs
index 3a4751c..3d4c9a5 100644
--- a/src/widget/input/spinbox.rs
+++ b/src/widget/input/spinbox.rs
@@ -133,7 +133,7 @@ impl Element for Spinbox {
 
     fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
     fn value(&self) -> i32 { self.value }
-    fn widget_font(&self) -> Option<String> { Some("monospace".to_string()) }
+    fn widget_font(&self) -> Option<String> { Some(crate::layout::spinbox_font()) }
 
     fn on_cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
         let was = self.base.hovered;
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 98c6799..0ee9be8 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -467,7 +467,7 @@ impl Element for TextBox {
 
     fn draggable(&self) -> bool { !self.disabled }
     fn is_dragging(&self) -> bool { self.dragging }
-    fn widget_font(&self) -> Option<String> { Some(self.font_family.clone()) }
+    fn widget_font(&self) -> Option<String> { Some(crate::layout::textbox_font()) }
 
     fn drag_begin(&mut self, _px: f32, _py: f32) {
         if self.disabled || !self.editing { return; }