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

commita9d0fc18679bff81fa175b9b91b16b71c35ca7ec
parente4b5f9b99a
authorLucas Galante <[email protected]>
date2026-06-30 22:39
Implement dynamic rounded corners for MenuBar and StatusBar widgets

 src/backend/window_runner.rs           |  15 ++-
 src/color.rs                           |  52 ++++-----
 src/config.rs                          | 103 ++++++++++++------
 src/layout.rs                          | 190 +++++++++++++++++++++++++--------
 src/widget/container/breadcrumb.rs     |   4 +
 src/widget/container/menu.rs           |  39 ++++++-
 src/widget/container/scroll_box.rs     |   8 +-
 src/widget/container/scrolling_list.rs |   4 +
 src/widget/display/preview.rs          |   2 +-
 src/widget/display/status_bar.rs       |  60 ++++++++++-
 src/widget/input/button.rs             |   4 +-
 src/widget/input/keybinds_control.rs   |   4 +-
 12 files changed, 369 insertions(+), 116 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 3f74c2c..c231b11 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -1028,6 +1028,10 @@ pub trait Application: Sized + 'static {
     fn input_regions(&self) -> Option<Vec<(i32, i32, i32, i32)>> {
         None
     }
+
+    fn desired_size(&self) -> Option<(u32, u32)> {
+        None
+    }
     
     fn ui_context(&self) -> Option<&crate::context::UiContext> {
         None
@@ -1629,7 +1633,9 @@ impl<A: Application> CompositorHandler for EngineState<A> {
         _qh: &QueueHandle<Self>,
         _surface: &wl_surface::WlSurface,
         _output: &wl_output::WlOutput,
-    ) {}
+    ) {
+        self.redraw = true;
+    }
     
     fn surface_leave(
         &mut self,
@@ -2376,6 +2382,13 @@ pub fn run<A: Application>() {
             engine_state.redraw = true;
         }
 
+        if let Some((w, h)) = engine_state.inner.desired_size() {
+            if (engine_state.logical_width - w as f32).abs() > 0.001 || (engine_state.logical_height - h as f32).abs() > 0.001 {
+                engine_state.resize(w as f32, h as f32);
+                engine_state.redraw = true;
+            }
+        }
+
         if let Some(ref mut pk) = engine_state.pressed_key {
             let now = std::time::Instant::now();
             if now.duration_since(pk.first_pressed) >= KEY_REPEAT_DELAY {
diff --git a/src/color.rs b/src/color.rs
index ffbad7e..c2293db 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -37,9 +37,9 @@ static BACKPLATE_OPACITY: RwLock<Option<f32>> = RwLock::new(None);
 static TOGGLE_ON_COLOR: RwLock<[f32; 4]> = RwLock::new(TOGGLE_ON);
 static TOGGLE_OFF_COLOR: RwLock<[f32; 4]> = RwLock::new(TOGGLE_OFF);
 static TOGGLE_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.18, 0.18, 0.22, 1.0]);
-static SCROLLINGLIST_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 0.3]);
-static SCROLLINGLIST_ENTRY_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.04]);
-static SCROLLINGLIST_ENTRY_HIGHLIGHT_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.8]);
+static LIST_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 0.3]);
+static LIST_ENTRY_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.04]);
+static LIST_ENTRY_HIGHLIGHT_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.8]);
 static BREADCRUMB_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 1.0]);
 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]);
@@ -181,7 +181,7 @@ fn parse_and_set_colors(content: &str) {
     if let Some(c) = get_color("/style/highlight/primary") {
         if let Ok(mut lock) = HIGHLIGHT_PRIMARY_COLOR.write() { *lock = [c[0], c[1], c[2], 0.12]; }
     }
-    if let Some(c) = get_color("/style/button/background").or_else(|| get_color("/layout/button_background_color")) {
+    if let Some(c) = get_color("/style/control/button/background").or_else(|| get_color("/layout/button_background_color")) {
         if let Ok(mut lock) = BUTTON_BACKGROUND_COLOR.write() { *lock = c; }
     }
     if let Some(c) = get_color("/layout/menubar_tab_label_color").or_else(|| get_color("/layout/paginator_tab_label_color")) {
@@ -190,13 +190,13 @@ fn parse_and_set_colors(content: &str) {
     if let Some(c) = get_color("/layout/toggle_enabled_color") {
         if let Ok(mut lock) = TOGGLE_ON_COLOR.write() { *lock = c; }
     }
-    if let Some(c) = get_color("/style/toggle/disabled_color") {
+    if let Some(c) = get_color("/style/control/toggle/disabled_color") {
         if let Ok(mut lock) = TOGGLE_OFF_COLOR.write() { *lock = c; }
     }
     if let Some(c) = get_color("/layout/toggle_bg_color") {
         if let Ok(mut lock) = TOGGLE_BG_COLOR.write() { *lock = c; }
     }
-    let parsed_scrollinglist_bg = get_color("/layout/scrollinglist_bg_color");
+    let parsed_list_bg = get_color("/layout/list_bg_color");
     let parsed_breadcrumb_bg = get_color("/layout/breadcrumb_bg_color");
     let parsed_popover_bg = get_color("/layout/popover_bg_color");
 
@@ -207,18 +207,18 @@ fn parse_and_set_colors(content: &str) {
         if let Ok(mut lock) = LAYER_COLOR.write() { *lock = c; }
     }
 
-    if let Some(c) = parsed_scrollinglist_bg {
-        if let Ok(mut lock) = SCROLLINGLIST_BG_COLOR.write() {
+    if let Some(c) = parsed_list_bg {
+        if let Ok(mut lock) = LIST_BG_COLOR.write() {
             *lock = [c[0], c[1], c[2], 0.3];
         }
     }
-    if let Some(c) = get_color("/layout/scrollinglist_entry_bg_color") {
-        if let Ok(mut lock) = SCROLLINGLIST_ENTRY_BG_COLOR.write() {
+    if let Some(c) = get_color("/layout/list_entry_bg_color") {
+        if let Ok(mut lock) = LIST_ENTRY_BG_COLOR.write() {
             *lock = c;
         }
     }
-    if let Some(c) = get_color("/layout/scrollinglist_entry_highlight_color") {
-        if let Ok(mut lock) = SCROLLINGLIST_ENTRY_HIGHLIGHT_COLOR.write() {
+    if let Some(c) = get_color("/layout/list_entry_highlight_color") {
+        if let Ok(mut lock) = LIST_ENTRY_HIGHLIGHT_COLOR.write() {
             *lock = c;
         }
     }
@@ -226,7 +226,7 @@ fn parse_and_set_colors(content: &str) {
         if let Ok(mut lock) = BREADCRUMB_BG_COLOR.write() {
             *lock = [c[0], c[1], c[2], 1.0];
         }
-    } else if let Some(c) = parsed_scrollinglist_bg {
+    } else if let Some(c) = parsed_list_bg {
         if let Ok(mut lock) = BREADCRUMB_BG_COLOR.write() {
             *lock = [c[0], c[1], c[2], 1.0];
         }
@@ -235,7 +235,7 @@ fn parse_and_set_colors(content: &str) {
         if let Ok(mut lock) = POPOVER_BG_COLOR.write() {
             *lock = [c[0], c[1], c[2], 1.0];
         }
-    } else if let Some(c) = parsed_scrollinglist_bg {
+    } else if let Some(c) = parsed_list_bg {
         if let Ok(mut lock) = POPOVER_BG_COLOR.write() {
             *lock = [c[0], c[1], c[2], 1.0];
         }
@@ -452,35 +452,35 @@ pub fn set_toggle_bg_color(color: [f32; 4]) {
     }
 }
 
-pub fn scrollinglist_bg_color() -> [f32; 4] {
+pub fn list_bg_color() -> [f32; 4] {
     load_colors_once();
-    *SCROLLINGLIST_BG_COLOR.read().unwrap()
+    *LIST_BG_COLOR.read().unwrap()
 }
 
-pub fn set_scrollinglist_bg_color(color: [f32; 4]) {
-    if let Ok(mut lock) = SCROLLINGLIST_BG_COLOR.write() {
+pub fn set_list_bg_color(color: [f32; 4]) {
+    if let Ok(mut lock) = LIST_BG_COLOR.write() {
         *lock = [color[0], color[1], color[2], 0.3];
     }
 }
 
-pub fn scrollinglist_entry_bg_color() -> [f32; 4] {
+pub fn list_entry_bg_color() -> [f32; 4] {
     load_colors_once();
-    *SCROLLINGLIST_ENTRY_BG_COLOR.read().unwrap()
+    *LIST_ENTRY_BG_COLOR.read().unwrap()
 }
 
-pub fn set_scrollinglist_entry_bg_color(color: [f32; 4]) {
-    if let Ok(mut lock) = SCROLLINGLIST_ENTRY_BG_COLOR.write() {
+pub fn set_list_entry_bg_color(color: [f32; 4]) {
+    if let Ok(mut lock) = LIST_ENTRY_BG_COLOR.write() {
         *lock = color;
     }
 }
 
-pub fn scrollinglist_entry_highlight_color() -> [f32; 4] {
+pub fn list_entry_highlight_color() -> [f32; 4] {
     load_colors_once();
-    *SCROLLINGLIST_ENTRY_HIGHLIGHT_COLOR.read().unwrap()
+    *LIST_ENTRY_HIGHLIGHT_COLOR.read().unwrap()
 }
 
-pub fn set_scrollinglist_entry_highlight_color(color: [f32; 4]) {
-    if let Ok(mut lock) = SCROLLINGLIST_ENTRY_HIGHLIGHT_COLOR.write() {
+pub fn set_list_entry_highlight_color(color: [f32; 4]) {
+    if let Ok(mut lock) = LIST_ENTRY_HIGHLIGHT_COLOR.write() {
         *lock = color;
     }
 }
diff --git a/src/config.rs b/src/config.rs
index 825ef0d..4a55b4b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -69,7 +69,7 @@ fn kdl_to_json(doc: &kdl::KdlDocument) -> serde_json::Value {
                 }
             }
         } else {
-            let list_names = ["keybind", "pointer_bind", "gesture_bind", "mode_rule", "tag_layout", "startup", "device"];
+            let list_names = ["key_bindings", "pointer_bind", "gesture_bind", "mode_rule", "tag_layout", "startup", "device"];
             if list_names.contains(&name.as_str()) {
                 map.insert(name, serde_json::Value::Array(vec![val]));
             } else {
@@ -144,31 +144,64 @@ pub fn parse_config_path(key: &str, default_section: &str) -> (String, String, O
     }
 }
 
-pub fn update_kdl_in_memory(doc: &mut kdl::KdlDocument, key: &str, value: &str, default_section: &str) -> bool {
-    let (target_section, target_node, target_prop) = parse_config_path(key, default_section);
-    
-    let section_node = if let Some(node) = doc.nodes_mut().iter_mut().find(|n| n.name().value() == target_section) {
-        node
+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"
+];
+
+fn get_or_create_node_mut<'a>(doc: &'a mut kdl::KdlDocument, path: &[&str]) -> Option<&'a mut kdl::KdlNode> {
+    if path.is_empty() {
+        return None;
+    }
+    let segment = path[0];
+    let idx = if let Some(i) = doc.nodes().iter().position(|n| n.name().value() == segment) {
+        i
     } else {
-        if let Ok(new_node) = format!("{}\n", target_section).parse::<kdl::KdlNode>() {
-            doc.nodes_mut().push(new_node);
-            doc.nodes_mut().last_mut().unwrap()
-        } else {
-            return false;
-        }
+        let new_node = format!("{}\n", segment).parse::<kdl::KdlNode>().ok()?;
+        doc.nodes_mut().push(new_node);
+        doc.nodes().len() - 1
     };
+    if path.len() == 1 {
+        Some(&mut doc.nodes_mut()[idx])
+    } else {
+        let children = doc.nodes_mut()[idx].ensure_children();
+        get_or_create_node_mut(children, &path[1..])
+    }
+}
+
+fn get_node_ref<'a>(doc: &'a kdl::KdlDocument, path: &[&str]) -> Option<&'a kdl::KdlNode> {
+    if path.is_empty() {
+        return None;
+    }
+    let segment = path[0];
+    let node = doc.nodes().iter().find(|n| n.name().value() == segment)?;
+    if path.len() == 1 {
+        Some(node)
+    } else {
+        let children = node.children()?;
+        get_node_ref(children, &path[1..])
+    }
+}
+
+pub fn update_kdl_in_memory(doc: &mut kdl::KdlDocument, key: &str, value: &str, _default_section: &str) -> bool {
+    let parts: Vec<&str> = key.split('.').collect();
+    if parts.is_empty() {
+        return false;
+    }
 
-    let children = section_node.ensure_children();
+    let is_property = parts.len() >= 2 && PROP_NODES.contains(&parts[parts.len() - 2]);
 
-    let child_node = if let Some(child) = children.nodes_mut().iter_mut().find(|n| n.name().value() == target_node) {
-        child
+    let (node_path, target_prop) = if is_property {
+        (&parts[0..parts.len() - 1], Some(parts[parts.len() - 1].to_string()))
     } else {
-        if let Ok(new_child) = format!("{}\n", target_node).parse::<kdl::KdlNode>() {
-            children.nodes_mut().push(new_child);
-            children.nodes_mut().last_mut().unwrap()
-        } else {
-            return false;
-        }
+        (&parts[0..parts.len()], None)
+    };
+
+    let child_node = if let Some(node) = get_or_create_node_mut(doc, node_path) {
+        node
+    } else {
+        return false;
     };
 
     let existing_ty = if let Some(ref prop_name) = target_prop {
@@ -307,15 +340,21 @@ pub fn write_config_value(path: &str, key: &str, value: &str, default_section: &
 
 pub fn get_kdl_type_annotation(kdl_content: &str, key_path: &str) -> Option<String> {
     let doc: kdl::KdlDocument = kdl_content.parse().ok()?;
-    let (target_section, target_node, target_prop) = parse_config_path(key_path, "");
-    
-    // Find the section node
-    let section_node = doc.nodes().iter().find(|n| n.name().value() == target_section)?;
-    
-    // Find the child node
-    let children = section_node.children()?;
-    let child_node = children.nodes().iter().find(|n| n.name().value() == target_node)?;
-    
+    let parts: Vec<&str> = key_path.split('.').collect();
+    if parts.is_empty() {
+        return None;
+    }
+
+    let is_property = parts.len() >= 2 && PROP_NODES.contains(&parts[parts.len() - 2]);
+
+    let (node_path, target_prop) = if is_property {
+        (&parts[0..parts.len() - 1], Some(parts[parts.len() - 1].to_string()))
+    } else {
+        (&parts[0..parts.len()], None)
+    };
+
+    let child_node = get_node_ref(&doc, node_path)?;
+
     if let Some(prop_name) = target_prop {
         let entry = child_node.entries().iter().find(|e| e.name().map(|n| n.value()) == Some(&prop_name))?;
         entry.ty().map(|t| t.value().to_string())
@@ -348,11 +387,11 @@ mod tests {
 
     #[test]
     fn test_get_kdl_type_annotation() {
-        let content = "input {\n    accel_profile (\"menu:flat,adaptive,none,custom\")\"flat\"\n    gestures pinch=(bool)true\n}\n";
+        let content = "input {\n    accel_profile (\"menu:flat,adaptive,none,custom\")\"flat\"\n    touchpad {\n        gestures pinch=(bool)true\n    }\n}\n";
         let ty1 = get_kdl_type_annotation(content, "input.accel_profile");
         assert_eq!(ty1, Some("menu:flat,adaptive,none,custom".to_string()));
         
-        let ty2 = get_kdl_type_annotation(content, "input.gestures.pinch");
+        let ty2 = get_kdl_type_annotation(content, "input.touchpad.gestures.pinch");
         assert_eq!(ty2, Some("bool".to_string()));
     }
 }
diff --git a/src/layout.rs b/src/layout.rs
index 4a1412c..8968c92 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -16,22 +16,38 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
         }
         _ => {
             let flat_key = match prefix {
-                "style.button.font" => "button_font",
-                "style.button.padding" => "button_padding",
-                "style.button_strip.font" => "button_strip_font",
-                "style.button_strip.spacing" => "button_strip_spacing",
-                "style.dropdown.height" => "dropdown_height",
-                "style.font_selector.height" => "font_selector_height",
+                "style.list.font" => "list_font",
+                "style.control.breadcrumb.font" => "breadcrumb_font",
+                "style.control.breadcrumb.corner_radius" => "breadcrumb_corner_radius",
+                "style.control.button.font" => "button_font",
+                "style.control.button.padding" => "button_padding",
+                "style.control.button.corner_radius" => "button_corner_radius",
+                "style.list.corner_radius" => "list_corner_radius",
+                "style.textbox.corner_radius" => "textbox_corner_radius",
+                "style.control.dropdown.font" => "dropdown_font",
+                "style.control.font_selector.font" => "font_selector_font",
+                "style.control.slider.font" => "slider_font",
+                "style.control.spinbox.font" => "spinbox_font",
+                "style.section.font" => "section_label_font",
+                "style.textbox.font" => "textbox_font",
+                "style.control.button_strip.font" => "button_strip_font",
+                "style.control.button_strip.spacing" => "button_strip_spacing",
+                "style.control.dropdown.height" => "dropdown_height",
+                "style.control.dropdown.corner_radius" => "dropdown_corner_radius",
+                "style.control.font_selector.height" => "font_selector_height",
+                "style.control.font_selector.corner_radius" => "font_selector_corner_radius",
                 "style.label.font" => "label_font",
-                "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.control.slider.height" => "slider_height",
+                "style.control.slider.corner_radius" => "slider_corner_radius",
+                "style.control.spinbox.height" => "spinbox_height",
+                "style.control.spinbox.button_padding" => "spinbox_button_padding",
+                "style.control.spinbox.corner_radius" => "spinbox_corner_radius",
                 "style.textbox.height" => "textbox_height",
-                "style.toggle.font" => "toggle_font",
-                "style.toggle.height" => "toggle_height",
-                "style.toggle.border_width" => "toggle_border_width",
-                "style.toggle.disabled_color" => "toggle_disabled_color",
+                "style.control.toggle.font" => "toggle_font",
+                "style.control.toggle.height" => "toggle_height",
+                "style.control.toggle.border_width" => "toggle_border_width",
+                "style.control.toggle.disabled_color" => "toggle_disabled_color",
+                "style.control.toggle.corner_radius" => "toggle_corner_radius",
                 "style.status.normal_color" => "status_normal_color",
                 "style.status.background_color" => "status_background_color",
                 "style.status.background_blur" => "status_background_blur",
@@ -45,9 +61,13 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 "style.overlay.position" => "overlay_position",
                 "style.overlay.border_gap" => "overlay_border_gap",
                 "style.editor.last_page" => "last_page",
-                "style.surfaces.desktop.background_color" => "desktop_background_color",
-                "style.surfaces.desktop.grid_color" => "desktop_grid_color",
-                "style.surfaces.desktop.line_width" => "desktop_line_width",
+                "style.surfaces.desktop.gap_color" => "desktop_gap_color",
+                "style.surfaces.desktop.cell_color" => "desktop_cell_color",
+                "style.surfaces.desktop.gap_width" => "desktop_gap_width",
+                "style.surfaces.desktop.cell_corner_radius" => "desktop_cell_corner_radius",
+                "style.surfaces.desktop.cell_fade_inset" => "desktop_cell_fade_inset",
+                "style.surfaces.desktop.enable_solid_color" => "desktop_enable_solid_color",
+                "style.surfaces.desktop.solid_color" => "desktop_solid_color",
                 "style.surfaces.plate.padding" => "plate_padding",
                 "style.surfaces.backplate.color" => "backplate_color",
                 "style.surfaces.backplate.blur" => "backplate_blur",
@@ -140,6 +160,8 @@ static FONT_SELECTOR_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
 static DROPDOWN_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
 static TOGGLE_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
 static SLIDER_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
+static BREADCRUMB_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
+static LIST_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
 static TOGGLE_BORDER_WIDTH: RwLock<f32> = RwLock::new(1.0);
 static TOGGLE_FONT: RwLock<String> = RwLock::new(String::new());
 static TOGGLE_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
@@ -160,9 +182,9 @@ 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 SCROLLINGLIST_FONT: RwLock<String> = RwLock::new(String::new());
-static SCROLLINGLIST_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
-static SCROLLINGLIST_JUSTIFICATION: RwLock<u8> = RwLock::new(0);
+static LIST_FONT: RwLock<String> = RwLock::new(String::new());
+static LIST_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
+static LIST_JUSTIFICATION: RwLock<u8> = RwLock::new(0);
 static PLATE_OPACITY: RwLock<f32> = RwLock::new(1.0);
 static PAGE_OPACITY: RwLock<f32> = RwLock::new(1.0);
 static LAYER_OPACITY: RwLock<f32> = RwLock::new(1.0);
@@ -201,7 +223,7 @@ pub fn reload_config() {
         let mut textbox_font_changed = false;
         let mut spinbox_font_changed = false;
         let mut slider_font_changed = false;
-        let mut scrollinglist_font_changed = false;
+        let mut list_font_changed = false;
         for line in content.lines() {
             let trimmed = line.trim();
             if let Some(rest) = trimmed.strip_prefix("label_margin") {
@@ -312,6 +334,24 @@ pub fn reload_config() {
                     }
                 }
             }
+            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;
+                    }
+                }
+            }
+            if let Some(rest) = trimmed.strip_prefix("list_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) = LIST_CORNER_RADIUS.write() {
+                        *lock = val;
+                    }
+                }
+            }
             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();
@@ -713,26 +753,26 @@ pub fn reload_config() {
                     slider_font_changed = true;
                 }
             }
-            if let Some(rest) = trimmed.strip_prefix("scrollinglist_font") {
+            if let Some(rest) = trimmed.strip_prefix("list_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) = SCROLLINGLIST_FONT.write() {
+                if let Ok(mut lock) = LIST_FONT.write() {
                     if *lock != font {
                         *lock = font;
                         changed = true;
                     }
                 }
                 if changed {
-                    scrollinglist_font_changed = true;
+                    list_font_changed = true;
                 }
             }
-            if let Some(rest) = trimmed.strip_prefix("scrollinglist_justification") {
+            if let Some(rest) = trimmed.strip_prefix("list_justification") {
                 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::<u8>() {
-                    if let Ok(mut lock) = SCROLLINGLIST_JUSTIFICATION.write() {
+                    if let Ok(mut lock) = LIST_JUSTIFICATION.write() {
                         *lock = val;
                     }
                 }
@@ -788,8 +828,8 @@ pub fn reload_config() {
                 *lock = None;
             }
         }
-        if scrollinglist_font_changed {
-            if let Ok(mut lock) = SCROLLINGLIST_FONT_CACHED.write() {
+        if list_font_changed {
+            if let Ok(mut lock) = LIST_FONT_CACHED.write() {
                 *lock = None;
             }
         }
@@ -1761,8 +1801,8 @@ pub fn set_dropdown_font(font: &str) {
     }
 }
 
-// ScrollingList Font
-pub fn scrollinglist_font() -> String {
+// List Font
+pub fn list_font() -> String {
     use std::sync::Once;
     static INIT: Once = Once::new();
     INIT.call_once(|| {
@@ -1770,18 +1810,18 @@ pub fn scrollinglist_font() -> String {
         if let Some(content) = read_config() {
             for line in content.lines() {
                 let trimmed = line.trim();
-                if let Some(rest) = trimmed.strip_prefix("scrollinglist_font") {
+                if let Some(rest) = trimmed.strip_prefix("list_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) = SCROLLINGLIST_FONT.write() {
+        if let Ok(mut lock) = LIST_FONT.write() {
             *lock = font;
         }
     });
-    let lock = SCROLLINGLIST_FONT.read().unwrap();
+    let lock = LIST_FONT.read().unwrap();
     if lock.is_empty() {
         "Outfit".to_string()
     } else {
@@ -1789,44 +1829,44 @@ pub fn scrollinglist_font() -> String {
     }
 }
 
-pub fn scrollinglist_font_parsed() -> (String, f32) {
-    if let Ok(lock) = SCROLLINGLIST_FONT_CACHED.read() {
+pub fn list_font_parsed() -> (String, f32) {
+    if let Ok(lock) = LIST_FONT_CACHED.read() {
         if let Some(ref val) = *lock {
             return val.clone();
         }
     }
-    let font_str = scrollinglist_font();
+    let font_str = list_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) = SCROLLINGLIST_FONT_CACHED.write() {
+    if let Ok(mut lock) = LIST_FONT_CACHED.write() {
         *lock = Some(val.clone());
     }
     val
 }
 
-pub fn set_scrollinglist_font(font: &str) {
-    if let Ok(mut lock) = SCROLLINGLIST_FONT.write() {
+pub fn set_list_font(font: &str) {
+    if let Ok(mut lock) = LIST_FONT.write() {
         *lock = font.to_string();
     }
-    if let Ok(mut lock) = SCROLLINGLIST_FONT_CACHED.write() {
+    if let Ok(mut lock) = LIST_FONT_CACHED.write() {
         *lock = None;
     }
 }
 
-// ScrollingList Justification
-pub fn scrollinglist_justification() -> u8 {
+// List Justification
+pub fn list_justification() -> u8 {
     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("scrollinglist_justification") {
+                if let Some(rest) = trimmed.strip_prefix("list_justification") {
                     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::<u8>() {
-                        if let Ok(mut lock) = SCROLLINGLIST_JUSTIFICATION.write() {
+                        if let Ok(mut lock) = LIST_JUSTIFICATION.write() {
                             *lock = val;
                         }
                     }
@@ -1834,11 +1874,11 @@ pub fn scrollinglist_justification() -> u8 {
             }
         }
     });
-    *SCROLLINGLIST_JUSTIFICATION.read().unwrap()
+    *LIST_JUSTIFICATION.read().unwrap()
 }
 
-pub fn set_scrollinglist_justification(just: u8) {
-    if let Ok(mut lock) = SCROLLINGLIST_JUSTIFICATION.write() {
+pub fn set_list_justification(just: u8) {
+    if let Ok(mut lock) = LIST_JUSTIFICATION.write() {
         *lock = just;
     }
 }
@@ -2285,6 +2325,62 @@ pub fn set_textbox_corner_radius(radius: f32) {
     }
 }
 
+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()
+}
+
+pub fn set_breadcrumb_corner_radius(radius: f32) {
+    if let Ok(mut lock) = BREADCRUMB_CORNER_RADIUS.write() {
+        *lock = radius;
+    }
+}
+
+pub fn list_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("list_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) = LIST_CORNER_RADIUS.write() {
+                            *lock = val;
+                        }
+                    }
+                }
+            }
+        }
+    });
+    *LIST_CORNER_RADIUS.read().unwrap()
+}
+
+pub fn set_list_corner_radius(radius: f32) {
+    if let Ok(mut lock) = LIST_CORNER_RADIUS.write() {
+        *lock = radius;
+    }
+}
+
 pub fn font_selector_corner_radius() -> f32 {
     use std::sync::Once;
     static INIT: Once = Once::new();
diff --git a/src/widget/container/breadcrumb.rs b/src/widget/container/breadcrumb.rs
index d5c25b8..3476180 100644
--- a/src/widget/container/breadcrumb.rs
+++ b/src/widget/container/breadcrumb.rs
@@ -97,6 +97,10 @@ impl Element for Breadcrumb {
         }
     }
 
+    fn corner_radius(&self) -> f32 {
+        crate::layout::breadcrumb_corner_radius()
+    }
+
     fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
         let mut quads = Vec::new();
         quads.push((self.x, self.y, self.w, self.h, self.color()));
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 46a282b..9921d12 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -431,9 +431,41 @@ impl Element for MenuBar {
     }
 
     fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
+        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() };
+            if is_bp {
+                let (px, py, pw, ph) = unsafe { (*p_ptr).rect() };
+                let (x, y, w, h) = self.rect();
+                let is_at_top = (y - py).abs() < 0.1;
+                let is_at_bottom = (y + h - (py + ph)).abs() < 0.1;
+                
+                if is_at_top && is_at_bottom {
+                    let is_at_left = (x - px).abs() < 0.1;
+                    let is_at_right = (x + w - (px + pw)).abs() < 0.1;
+                    return (is_at_left, is_at_right, is_at_right, is_at_left);
+                } else if is_at_top {
+                    return (true, true, false, false);
+                } else if is_at_bottom {
+                    return (false, false, true, true);
+                }
+            }
+        }
+        (false, false, false, false)
+    }
+
+    fn corner_radius(&self) -> f32 {
+        if let Some(p_ptr) = self.parent {
+            unsafe { (*p_ptr).corner_radius() }
+        } else {
+            0.0
+        }
     }
 
+
     fn set_hovered(&mut self, v: bool) {
         self.base.hovered = v;
     }
@@ -906,7 +938,10 @@ impl Element for MenuBar {
         let mut quads = Vec::new();
 
         // Draw main background
-        quads.push((self.base.x, self.base.y, self.base.w, self.base.h, colors::sidebar_bg_color()));
+        let (r1, r2, r3, r4) = self.rounded_corners();
+        if !r1 && !r2 && !r3 && !r4 {
+            quads.push((self.base.x, self.base.y, self.base.w, self.base.h, self.color()));
+        }
 
         // 1. Highlight the title on hover or open
         if !self.context_options.is_empty() {
diff --git a/src/widget/container/scroll_box.rs b/src/widget/container/scroll_box.rs
index 8a38289..7ddcef4 100644
--- a/src/widget/container/scroll_box.rs
+++ b/src/widget/container/scroll_box.rs
@@ -63,7 +63,11 @@ impl Element for ScrollBox {
         self.viewport_y = y + self.viewport_offset_y;
         self.viewport_h = h + self.viewport_offset_h;
     }
-    fn color(&self) -> [f32; 4] { crate::color::scrollinglist_bg_color() }
+    fn color(&self) -> [f32; 4] { crate::color::list_bg_color() }
+
+    fn corner_radius(&self) -> f32 {
+        crate::layout::list_corner_radius()
+    }
 
     fn focus(&mut self) {
         focus::set_focused(self);
@@ -106,7 +110,7 @@ impl Element for ScrollBox {
         let mut quads = Vec::new();
         
         // Background
-        quads.push((self.base.x, self.base.y, self.base.w, self.base.h, crate::color::scrollinglist_bg_color()));
+        quads.push((self.base.x, self.base.y, self.base.w, self.base.h, crate::color::list_bg_color()));
 
         // Border lines
         let box_border_color = if focus::is_focused(self) {
diff --git a/src/widget/container/scrolling_list.rs b/src/widget/container/scrolling_list.rs
index 6f95c91..9e1be06 100644
--- a/src/widget/container/scrolling_list.rs
+++ b/src/widget/container/scrolling_list.rs
@@ -59,6 +59,10 @@ impl Element for ScrollingList {
     fn color(&self) -> [f32; 4] {
         self.scroll_box.color()
     }
+
+    fn corner_radius(&self) -> f32 {
+        crate::layout::list_corner_radius()
+    }
     fn as_ptr(&self) -> *mut (dyn Element + 'static) {
         self as *const Self as *mut Self as *mut (dyn Element + 'static)
     }
diff --git a/src/widget/display/preview.rs b/src/widget/display/preview.rs
index 76bae42..f8f5637 100644
--- a/src/widget/display/preview.rs
+++ b/src/widget/display/preview.rs
@@ -256,7 +256,7 @@ impl PreviewState {
         preview_sec.content_y = cy + half_h - pad - 12.0;
         preview_sec.finish();
 
-        let bg_color = color::scrollinglist_bg_color();
+        let bg_color = color::list_bg_color();
         canvas.rect(bg_color, cx + 12.0, rect_y, cw - 24.0, rect_h);
 
         if let Some(image_data) = &self.image_preview {
diff --git a/src/widget/display/status_bar.rs b/src/widget/display/status_bar.rs
index 4f1ad19..fcad77b 100644
--- a/src/widget/display/status_bar.rs
+++ b/src/widget/display/status_bar.rs
@@ -1,8 +1,10 @@
 use crate::colors;
 use crate::widget::*;
 use crate::widget::display::{TextLabel, make_widget_text_buffer};
+use crate::context::UiContext;
 
 pub struct StatusBar {
+    pub base: Widget,
     x: f32, y: f32, w: f32, h: f32,
     hovered: bool,
     pub text: String,
@@ -10,11 +12,13 @@ pub struct StatusBar {
     pub text_offset_x: Option<f32>,
     pub text_color: Option<[f32; 4]>,
     pub bg_color: Option<[f32; 4]>,
+    pub parent: Option<*mut (dyn Element + 'static)>,
 }
 
 impl StatusBar {
     pub fn new() -> Self {
         Self {
+            base: Widget::new_rect(0.0, 0.0, 0.0, 0.0),
             x: 0.0,
             y: 0.0,
             w: 0.0,
@@ -25,6 +29,7 @@ impl StatusBar {
             text_offset_x: None,
             text_color: None,
             bg_color: None,
+            parent: None,
         }
     }
     pub fn with_text(mut self, text: &str) -> Self {
@@ -55,8 +60,13 @@ impl StatusBar {
 }
 
 impl Element for StatusBar {
+    fn base(&self) -> Option<&Widget> { Some(&self.base) }
+    fn base_mut(&mut self) -> Option<&mut Widget> { Some(&mut self.base) }
     fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+        self.x = x; self.y = y; self.w = w; self.h = h;
+        self.base.x = x; self.base.y = y; self.base.w = w; self.base.h = h;
+    }
     fn as_ptr(&self) -> *mut (dyn Element + 'static) {
         self as *const Self as *mut Self as *mut (dyn Element + 'static)
     }
@@ -66,6 +76,54 @@ impl Element for StatusBar {
     fn color(&self) -> [f32; 4] { self.bg_color.unwrap_or(colors::STATUS_BG) }
     fn set_hovered(&mut self, v: bool) { self.hovered = v; }
     fn hovered(&self) -> bool { self.hovered }
+
+    fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
+        self.parent
+    }
+
+    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, _ctx: &mut UiContext) {
+        self.parent = parent;
+    }
+
+    fn rounded_corners(&self) -> (bool, bool, bool, bool) {
+        if let Some(p_ptr) = self.parent {
+            let is_bp = unsafe { (*p_ptr).is_backplate() };
+            if is_bp {
+                let (px, py, pw, ph) = unsafe { (*p_ptr).rect() };
+                let (x, y, w, h) = self.rect();
+                let is_at_top = (y - py).abs() < 0.1;
+                let is_at_bottom = (y + h - (py + ph)).abs() < 0.1;
+                
+                if is_at_top && is_at_bottom {
+                    let is_at_left = (x - px).abs() < 0.1;
+                    let is_at_right = (x + w - (px + pw)).abs() < 0.1;
+                    return (is_at_left, is_at_right, is_at_right, is_at_left);
+                } else if is_at_top {
+                    return (true, true, false, false);
+                } else if is_at_bottom {
+                    return (false, false, true, true);
+                }
+            }
+        }
+        (false, false, false, false)
+    }
+
+    fn corner_radius(&self) -> f32 {
+        if let Some(p_ptr) = self.parent {
+            unsafe { (*p_ptr).corner_radius() }
+        } else {
+            0.0
+        }
+    }
+
+    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+        let (r1, r2, r3, r4) = self.rounded_corners();
+        if !r1 && !r2 && !r3 && !r4 {
+            vec![(self.x, self.y, self.w, self.h, self.color())]
+        } else {
+            Vec::new()
+        }
+    }
     fn set_text(&mut self, text: &str) {
         if self.text != text {
             self.text = text.to_string();
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index bf7dfee..1da0ab8 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -255,7 +255,7 @@ impl Element for Button {
                 }
             };
             let justify = if self.kind == ButtonKind::ListRow {
-                match crate::layout::scrollinglist_justification() {
+                match crate::layout::list_justification() {
                     0 => Justification::Left,
                     2 => Justification::Right,
                     _ => Justification::Center,
@@ -307,7 +307,7 @@ impl Element for Button {
 
     fn widget_font(&self) -> Option<String> {
         if self.kind == ButtonKind::ListRow {
-            Some(crate::layout::scrollinglist_font())
+            Some(crate::layout::list_font())
         } else {
             Some(crate::layout::button_font())
         }
diff --git a/src/widget/input/keybinds_control.rs b/src/widget/input/keybinds_control.rs
index 219509e..aa631ca 100644
--- a/src/widget/input/keybinds_control.rs
+++ b/src/widget/input/keybinds_control.rs
@@ -57,7 +57,7 @@ impl KeybindsControl {
         let content = std::fs::read_to_string(&path).unwrap_or_default();
         let val: serde_json::Value = serde_json::from_str(&content).unwrap_or_default();
         let mut loaded = Vec::new();
-        if let Some(arr) = val.get("keybind").and_then(|k| k.as_array()) {
+        if let Some(arr) = val.get("key_bindings").and_then(|k| k.as_array()) {
             for v in arr {
                 let mods = v.get("mods").and_then(|m| m.as_str()).unwrap_or("").to_string();
                 let key = v.get("key").and_then(|k| k.as_str()).unwrap_or("").to_string();
@@ -123,7 +123,7 @@ impl KeybindsControl {
         }
         
         if let Some(obj) = val.as_object_mut() {
-            obj.insert("keybind".to_string(), serde_json::Value::Array(keybinds));
+            obj.insert("key_bindings".to_string(), serde_json::Value::Array(keybinds));
         }
         
         if let Ok(updated_str) = serde_json::to_string_pretty(&val) {