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

commitf850b8a9cb4de28de630468da1c60b97bb6c3f04
parentc472cea9de
authorLucas Galante <[email protected]>
date2026-07-03 01:48
Added style.surface.backplate.menubar and style.surface.backplate.statusbar styling domains

 src/color.rs                     | 80 ++++++++++++++++++++++++++++++++++++++++
 src/config.rs                    | 33 ++++++++++++++++-
 src/layout.rs                    |  6 +++
 src/widget/container/menu.rs     | 51 +++++++++++++++++++++++--
 src/widget/display/status_bar.rs | 37 ++++++++++++++++---
 src/widget/input/text_box.rs     |  1 +
 6 files changed, 198 insertions(+), 10 deletions(-)

diff --git a/src/color.rs b/src/color.rs
index 42389b0..b7873ee 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -47,6 +47,14 @@ static POPOVER_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 1.0])
 static PAGE_COLOR: RwLock<[f32; 4]> = RwLock::new([0.0, 0.0, 0.0, 0.0]);
 static LAYER_COLOR: RwLock<[f32; 4]> = RwLock::new([0.0, 0.0, 0.0, 0.0]);
 static BACKPLATE_CORNER_RADIUS: RwLock<f32> = RwLock::new(12.0);
+
+static BACKPLATE_MENUBAR_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 1.0]);
+static BACKPLATE_MENUBAR_TEXT_COLOR: RwLock<[f32; 4]> = RwLock::new([0.90196, 0.90196, 0.94902, 1.0]);
+static BACKPLATE_MENUBAR_BLUR: RwLock<bool> = RwLock::new(false);
+
+static BACKPLATE_STATUSBAR_COLOR: RwLock<[f32; 4]> = RwLock::new([0.06, 0.06, 0.10, 1.0]);
+static BACKPLATE_STATUSBAR_TEXT_COLOR: RwLock<[f32; 4]> = RwLock::new([0.6666, 0.6666, 0.7333, 1.0]);
+static BACKPLATE_STATUSBAR_BLUR: RwLock<bool> = RwLock::new(false);
 static BUTTON_BACKGROUND_COLOR: RwLock<[f32; 4]> = RwLock::new(BUTTON_IDLE);
 
 static TREE_BACKGROUND_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 0.3]);
@@ -219,6 +227,30 @@ fn parse_and_set_colors(content: &str) {
         if let Ok(mut lock) = PAGE_LOW_COLOR.write() { *lock = c; }
         if let Ok(mut lock) = BACKPLATE_OPACITY.write() { *lock = Some(c[3]); }
     }
+
+    if let Some(c) = get_color("/style/surface/backplate/menubar/color") {
+        if let Ok(mut lock) = BACKPLATE_MENUBAR_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/backplate/menubar/text_color") {
+        if let Ok(mut lock) = BACKPLATE_MENUBAR_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(blur) = val.pointer("/style/surface/backplate/menubar/blur").and_then(|v| v.as_bool()) {
+        if let Ok(mut lock) = BACKPLATE_MENUBAR_BLUR.write() { *lock = blur; }
+    } else if let Some(blur_val) = val.pointer("/style/surface/backplate/menubar/blur").and_then(|v| v.as_f64()) {
+        if let Ok(mut lock) = BACKPLATE_MENUBAR_BLUR.write() { *lock = blur_val > 0.001; }
+    }
+
+    if let Some(c) = get_color("/style/surface/backplate/statusbar/color") {
+        if let Ok(mut lock) = BACKPLATE_STATUSBAR_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/backplate/statusbar/text_color") {
+        if let Ok(mut lock) = BACKPLATE_STATUSBAR_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(blur) = val.pointer("/style/surface/backplate/statusbar/blur").and_then(|v| v.as_bool()) {
+        if let Ok(mut lock) = BACKPLATE_STATUSBAR_BLUR.write() { *lock = blur; }
+    } else if let Some(blur_val) = val.pointer("/style/surface/backplate/statusbar/blur").and_then(|v| v.as_f64()) {
+        if let Ok(mut lock) = BACKPLATE_STATUSBAR_BLUR.write() { *lock = blur_val > 0.001; }
+    }
     if let Some(c) = get_color("/layout/color_borders_color") {
         if let Ok(mut lock) = COLOR_BORDERS_COLOR.write() { *lock = c; }
     }
@@ -955,3 +987,51 @@ pub fn set_tree_open_search_key(k: String) {
     }
 }
 
+pub fn backplate_menubar_color() -> [f32; 4] {
+    load_colors_once();
+    *BACKPLATE_MENUBAR_COLOR.read().unwrap()
+}
+pub fn set_backplate_menubar_color(c: [f32; 4]) {
+    if let Ok(mut lock) = BACKPLATE_MENUBAR_COLOR.write() { *lock = c; }
+}
+
+pub fn backplate_menubar_text_color() -> [f32; 4] {
+    load_colors_once();
+    *BACKPLATE_MENUBAR_TEXT_COLOR.read().unwrap()
+}
+pub fn set_backplate_menubar_text_color(c: [f32; 4]) {
+    if let Ok(mut lock) = BACKPLATE_MENUBAR_TEXT_COLOR.write() { *lock = c; }
+}
+
+pub fn backplate_menubar_blur() -> bool {
+    load_colors_once();
+    *BACKPLATE_MENUBAR_BLUR.read().unwrap()
+}
+pub fn set_backplate_menubar_blur(b: bool) {
+    if let Ok(mut lock) = BACKPLATE_MENUBAR_BLUR.write() { *lock = b; }
+}
+
+pub fn backplate_statusbar_color() -> [f32; 4] {
+    load_colors_once();
+    *BACKPLATE_STATUSBAR_COLOR.read().unwrap()
+}
+pub fn set_backplate_statusbar_color(c: [f32; 4]) {
+    if let Ok(mut lock) = BACKPLATE_STATUSBAR_COLOR.write() { *lock = c; }
+}
+
+pub fn backplate_statusbar_text_color() -> [f32; 4] {
+    load_colors_once();
+    *BACKPLATE_STATUSBAR_TEXT_COLOR.read().unwrap()
+}
+pub fn set_backplate_statusbar_text_color(c: [f32; 4]) {
+    if let Ok(mut lock) = BACKPLATE_STATUSBAR_TEXT_COLOR.write() { *lock = c; }
+}
+
+pub fn backplate_statusbar_blur() -> bool {
+    load_colors_once();
+    *BACKPLATE_STATUSBAR_BLUR.read().unwrap()
+}
+pub fn set_backplate_statusbar_blur(b: bool) {
+    if let Ok(mut lock) = BACKPLATE_STATUSBAR_BLUR.write() { *lock = b; }
+}
+
diff --git a/src/config.rs b/src/config.rs
index 83487b1..58f8f23 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -252,7 +252,8 @@ pub fn parse_config_path(key: &str, default_section: &str) -> (String, String, O
 const PROP_NODES: &[&str] = &[
     "gestures", "key_bindings", "pointer_bind", "gesture_bind",
     "button", "button_strip", "dropdown", "toggle", "spinbox", "slider", "font_selector",
-    "status", "overlay", "backplate", "desktop", "list", "section", "textbox", "multiline", "editor", "tree"
+    "status", "overlay", "backplate", "desktop", "list", "section", "textbox", "multiline", "editor", "tree",
+    "menubar", "statusbar"
 ];
 
 fn get_or_create_node_mut<'a>(doc: &'a mut kdl::KdlDocument, path: &[&str]) -> Option<&'a mut kdl::KdlNode> {
@@ -583,6 +584,36 @@ mod tests {
         let doc_parsed = kdl_str.parse::<kdl::KdlDocument>();
         assert!(doc_parsed.is_ok(), "Failed to parse KDL: {:?}", doc_parsed.err());
     }
+
+    #[test]
+    fn test_backplate_menubar_statusbar_styling() {
+        // Trigger load_colors_once first to initialize the Once block from the real config file
+        let _ = crate::color::backplate_statusbar_blur();
+
+        let content = r##"
+            style {
+                surface {
+                    backplate blur=(f64)0.1 color=(rgba)"#5e657acf" corner_radius=(i64)12 {
+                        menubar blur=(bool)true color=(rgba)"#1a1d26d0" text_color=(rgba)"#e2e4f0ff"
+                        statusbar blur=(bool)false color=(rgba)"#12141cd0" text_color=(rgba)"#b5b9c8ff"
+                    }
+                }
+            }
+        "##;
+        
+        // Parse into json and set colors
+        crate::color::reload_colors(content);
+
+        // Verify values are parsed correctly
+        assert_eq!(crate::color::backplate_menubar_blur(), true);
+        assert_eq!(crate::color::backplate_statusbar_blur(), false);
+
+        // Colors are in sRGB converted to linear, let's verify text colors
+        let menubar_txt = crate::color::backplate_menubar_text_color();
+        assert!(menubar_txt[0] > 0.0);
+        let statusbar_txt = crate::color::backplate_statusbar_text_color();
+        assert!(statusbar_txt[0] > 0.0);
+    }
 }
 
 fn format_kdl_type(ty: &str) -> String {
diff --git a/src/layout.rs b/src/layout.rs
index 932d835..4c1c355 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -126,6 +126,12 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 "style.surface.backplate.color" => "backplate_color",
                 "style.surface.backplate.blur" => "backplate_blur",
                 "style.surface.backplate.corner_radius" => "backplate_corner_radius",
+                "style.surface.backplate.menubar.color" => "backplate_menubar_color",
+                "style.surface.backplate.menubar.text_color" => "backplate_menubar_text_color",
+                "style.surface.backplate.menubar.blur" => "backplate_menubar_blur",
+                "style.surface.backplate.statusbar.color" => "backplate_statusbar_color",
+                "style.surface.backplate.statusbar.text_color" => "backplate_statusbar_text_color",
+                "style.surface.backplate.statusbar.blur" => "backplate_statusbar_blur",
                 "style.surface.page.opacity" => "page_opacity",
                 "style.surface.page.margin" => "page_margin",
                 "style.surface.graph.cell_color" => "graph_cell_color",
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index f133c42..4a0ef26 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -301,6 +301,24 @@ impl MenuBar {
         };
         Some((dx, dy, dw, dh))
     }
+
+    pub fn text_color(&self) -> [f32; 4] {
+        if let Some(p_ptr) = self.parent {
+            if unsafe { (*p_ptr).is_backplate() } {
+                return crate::colors::backplate_menubar_text_color();
+            }
+        }
+        crate::colors::menubar_tab_label_color()
+    }
+
+    pub fn is_blur_enabled(&self) -> bool {
+        if let Some(p_ptr) = self.parent {
+            if unsafe { (*p_ptr).is_backplate() } {
+                return crate::colors::backplate_menubar_blur();
+            }
+        }
+        self.blur
+    }
 }
 
 impl Element for MenuBar {
@@ -463,9 +481,16 @@ impl Element for MenuBar {
     }
 
     fn color(&self) -> [f32; 4] {
+        if let Some(p_ptr) = self.parent {
+            if unsafe { (*p_ptr).is_backplate() } {
+                return crate::colors::backplate_menubar_color();
+            }
+        }
         self.color.unwrap_or_else(|| colors::sidebar_bg_color())
     }
 
+
+
     fn rounded_corners(&self) -> (bool, bool, bool, bool) {
         if let Some(p_ptr) = self.parent {
             let is_bp = unsafe { (*p_ptr).is_backplate() };
@@ -714,7 +739,7 @@ impl Element for MenuBar {
                     pc.rect(colors::PANEL_MENU_HOVER, dx + 2.0, iy + 2.0, dw - 4.0, DROPDOWN_ITEM_H - 4.0);
                 }
                 
-                let label_color = crate::colors::menubar_tab_label_color();
+                let label_color = self.text_color();
                 let srgb = crate::colors::to_srgb(label_color);
                 let color_f32 = [srgb[0], srgb[1], srgb[2], 1.0];
                 let bounds = Some([dx, dy, dx + dw, dy + dh]);
@@ -741,7 +766,7 @@ impl Element for MenuBar {
                 pc.rect(colors::PANEL_MENU_HOVER, dx + 2.0, iy + 2.0, dw - 4.0, DROPDOWN_ITEM_H - 4.0);
             }
 
-            let label_color = crate::colors::menubar_tab_label_color();
+            let label_color = self.text_color();
             let srgb = crate::colors::to_srgb(label_color);
             let color_f32 = [srgb[0], srgb[1], srgb[2], 1.0];
             let bounds = Some([dx, dy, dx + dw, dy + dh]);
@@ -917,7 +942,7 @@ impl Element for MenuBar {
         if !self.visible {
             return Vec::new();
         }
-        let label_color = crate::colors::menubar_tab_label_color();
+        let label_color = self.text_color();
         let srgb = crate::colors::to_srgb(label_color);
         let text_color = [
             (srgb[0] * 255.0) as u8,
@@ -1178,6 +1203,24 @@ impl Menu {
         };
         (dx, dy, dw, dh)
     }
+
+    pub fn text_color(&self) -> [f32; 4] {
+        if let Some(p_ptr) = self.parent {
+            let mut curr = p_ptr;
+            loop {
+                if unsafe { (*curr).is_backplate() } {
+                    return crate::colors::backplate_menubar_text_color();
+                }
+                let dummy = crate::context::UiContext::new();
+                if let Some(next_p) = unsafe { (*curr).parent(&dummy) } {
+                    curr = next_p;
+                } else {
+                    break;
+                }
+            }
+        }
+        crate::colors::menubar_tab_label_color()
+    }
 }
 impl Element for Menu {
     crate::impl_widget_base!(Menu);
@@ -1351,7 +1394,7 @@ impl Element for Menu {
     }
 
     fn text_labels(&self) -> Vec<TextLabel> {
-        let label_color = crate::colors::menubar_tab_label_color();
+        let label_color = self.text_color();
         let srgb = crate::colors::to_srgb(label_color);
         let text_color = [
             (srgb[0] * 255.0) as u8,
diff --git a/src/widget/display/status_bar.rs b/src/widget/display/status_bar.rs
index fcad77b..dd9a9bb 100644
--- a/src/widget/display/status_bar.rs
+++ b/src/widget/display/status_bar.rs
@@ -57,6 +57,24 @@ impl StatusBar {
     pub fn set_bg_color(&mut self, color: [f32; 4]) {
         self.bg_color = Some(color);
     }
+
+    pub fn get_actual_text_color(&self) -> [f32; 4] {
+        if let Some(p_ptr) = self.parent {
+            if unsafe { (*p_ptr).is_backplate() } {
+                return crate::colors::backplate_statusbar_text_color();
+            }
+        }
+        self.text_color.unwrap_or([0.6666, 0.6666, 0.7333, 1.0])
+    }
+
+    pub fn is_blur_enabled(&self) -> bool {
+        if let Some(p_ptr) = self.parent {
+            if unsafe { (*p_ptr).is_backplate() } {
+                return crate::colors::backplate_statusbar_blur();
+            }
+        }
+        false
+    }
 }
 
 impl Element for StatusBar {
@@ -73,7 +91,14 @@ impl Element for StatusBar {
     fn as_ptr_mut(&mut self) -> *mut (dyn Element + 'static) {
         self as *mut Self as *mut (dyn Element + 'static)
     }
-    fn color(&self) -> [f32; 4] { self.bg_color.unwrap_or(colors::STATUS_BG) }
+    fn color(&self) -> [f32; 4] {
+        if let Some(p_ptr) = self.parent {
+            if unsafe { (*p_ptr).is_backplate() } {
+                return crate::colors::backplate_statusbar_color();
+            }
+        }
+        self.bg_color.unwrap_or(colors::STATUS_BG)
+    }
     fn set_hovered(&mut self, v: bool) { self.hovered = v; }
     fn hovered(&self) -> bool { self.hovered }
 
@@ -138,11 +163,12 @@ impl Element for StatusBar {
     fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
         if let Some(ref text_buf) = self.text_buf {
             let offset_x = self.text_offset_x.unwrap_or(12.0);
-            let color = self.text_color.map(|c| glyphon::Color::rgb(
+            let c = self.get_actual_text_color();
+            let color = glyphon::Color::rgb(
                 (c[0] * 255.0) as u8,
                 (c[1] * 255.0) as u8,
                 (c[2] * 255.0) as u8,
-            )).unwrap_or_else(|| glyphon::Color::rgb(0xaa, 0xaa, 0xbb));
+            );
             vec![(text_buf, self.x + offset_x, self.y + 4.0, color)]
         } else {
             Vec::new()
@@ -151,11 +177,12 @@ impl Element for StatusBar {
     fn text_labels(&self) -> Vec<TextLabel> {
         if !self.text.is_empty() {
             let offset_x = self.text_offset_x.unwrap_or(12.0);
-            let color = self.text_color.map(|c| [
+            let c = self.get_actual_text_color();
+            let color = [
                 (c[0] * 255.0) as u8,
                 (c[1] * 255.0) as u8,
                 (c[2] * 255.0) as u8,
-            ]).unwrap_or([0xaa, 0xaa, 0xbb]);
+            ];
             vec![TextLabel {
                 text: self.text.clone(),
                 x: self.x + offset_x,
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 5c2cd62..f911e64 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -1699,6 +1699,7 @@ mod tests {
     #[test]
     fn test_multiline_textbox_border_width() {
         let _dummy = crate::context::UiContext::new();
+        crate::layout::set_textbox_multiline_border_width(1.0);
         let tb_single = TextBox::new("Singleline".to_string()).with_multiline(false);
         let tb_multi = TextBox::new("Multiline".to_string()).with_multiline(true);