git.lucas.co / cce-system-interface
system settings
git clone https://git.lucas.co/cce-system-interface.git

commita79b23e2160edb3db733ec6762146d36ec69da67
parentd7604d5b67
authorLucas Galante <[email protected]>
date2026-06-02 13:26
Update system configuration and interface modules

 Cargo.lock            |   1 +
 src/main.rs           | 367 +++++++++++++++--------
 src/pages/colors.rs   |  27 +-
 src/pages/input.rs    |  73 ++++-
 src/pages/layout.rs   | 812 +++++++++++++++++++++++++++++++++++++++++++++++++-
 src/pages/typeface.rs |   5 +-
 6 files changed, 1144 insertions(+), 141 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index ce5956a..addd3cb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -352,6 +352,7 @@ dependencies = [
  "calloop",
  "calloop-wayland-source",
  "glyphon",
+ "libc",
  "pollster",
  "raw-window-handle",
  "resvg",
diff --git a/src/main.rs b/src/main.rs
index 6c3fc8f..3de63e3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -194,6 +194,7 @@ struct SystemInterface {
     rx_display: std::sync::mpsc::Receiver<pages::display::DisplayState>,
     rx_network: std::sync::mpsc::Receiver<pages::network::NetworkState>,
     rx_layout: std::sync::mpsc::Receiver<pages::layout::LayoutState>,
+    rx_wm_events: std::sync::mpsc::Receiver<()>,
     rx_input: std::sync::mpsc::Receiver<pages::input::InputState>,
     rx_fingers: std::sync::mpsc::Receiver<Vec<Finger>>,
     rx_hardware: std::sync::mpsc::Receiver<pages::hardware::HardwareState>,
@@ -267,6 +268,38 @@ impl clear_ui::engine::Application for SystemInterface {
             });
             rx
         };
+        let rx_wm_events = {
+            let (tx, rx) = std::sync::mpsc::channel::<()>();
+            tokio::spawn(async move {
+                let display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".to_string());
+                let windows_path = format!("/tmp/ccec-windows-{}", display);
+                let tags_path = format!("/tmp/ccec-tags-{}", display);
+                let title_path = format!("/tmp/ccec-title-{}", display);
+                
+                let mut last_mod = std::time::SystemTime::UNIX_EPOCH;
+                
+                let check_mtime = |path: &str| -> Option<std::time::SystemTime> {
+                    std::fs::metadata(path).and_then(|m| m.modified()).ok()
+                };
+
+                loop {
+                    let mut changed = false;
+                    for p in &[&windows_path, &tags_path, &title_path] {
+                        if let Some(mtime) = check_mtime(p) {
+                            if mtime > last_mod {
+                                last_mod = mtime;
+                                changed = true;
+                            }
+                        }
+                    }
+                    if changed {
+                        if tx.send(()).is_err() { break; }
+                    }
+                    tokio::time::sleep(std::time::Duration::from_millis(500)).await;
+                }
+            });
+            rx
+        };
         let rx_input = {
             let (tx, rx) = std::sync::mpsc::channel::<pages::input::InputState>();
             tokio::spawn(async move {
@@ -358,6 +391,7 @@ impl clear_ui::engine::Application for SystemInterface {
             rx_display,
             rx_network,
             rx_layout,
+            rx_wm_events,
             rx_input,
             rx_fingers,
             rx_hardware,
@@ -495,6 +529,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
         let mut page_buttons = Vec::new();
 
         clear_ui::widget::hover_animation::reset_frame_registration();
+        clear_ui::widget::popovers::clear();
         clear_ui::widget::hover_animation::set_scroll_offset(self.scroll_y);
         clear_ui::widget::hover_animation::set_cursor_pos(self.cursor_x / s, self.cursor_y / s);
 
@@ -536,120 +571,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
 
         // Page content in LOGICAL coordinates, then scale to physical
         let mut pc = self.render_page_content(lcx, lcy, lcw, lch);
-
-        let mut popovers = Vec::new();
-        Self::collect_popover_rects(&self.page_root_container, &mut popovers);
-
-        if !popovers.is_empty() {
-            pc.texts.retain(|(text, size, tx, ty, _, _)| {
-                let text_w = text.chars().count() as f32 * *size * 0.65;
-                for &(px, py, pw, ph) in &popovers {
-                    let x_overlap = *tx <= px + pw && (*tx + text_w) >= px;
-                    let y_overlap = *ty <= py + ph && (*ty + *size) >= py;
-                    if x_overlap && y_overlap {
-                        return false;
-                    }
-                }
-                true
-            });
-        }
-
-        let mut max_y = 0.0f32;
-        for (_, _, y, _, h) in &pc.rects {
-            max_y = max_y.max(y + h);
-        }
-        for (_, size, _, y, _, _) in &pc.texts {
-            max_y = max_y.max(y + size);
-        }
-        for btn in &pc.buttons {
-            max_y = max_y.max(btn.y + btn.h);
-        }
-        self.max_scroll_y = (max_y - lch).max(0.0);
-        self.scroll_y = self.scroll_y.min(self.max_scroll_y);
-
-        let scroll_offset_y = self.scroll_y;
-
-        for (c, x, y, w, h) in &pc.rects {
-            widgets.push(AppWidget {
-                x: *x * s, y: (*y - scroll_offset_y) * s, w: *w * s, h: *h * s,
-                color: *c, hover_color: *c,
-                hovering: false, kind: WidgetKind::Static,
-            });
-        }
-        for (t, size, x, y, tc, font_opt) in &pc.texts {
-            text_items.push(TextItem {
-                buffer: make_text_buffer_with_font(
-                    &mut self.font_system,
-                    t,
-                    *size * s,
-                    font_opt.as_deref(),
-                    &self.sans_serif_family,
-                    &self.serif_family,
-                    &self.monospace_family,
-                ),
-                x: *x * s, y: (*y - scroll_offset_y) * s,
-                color: glyphon::Color::rgb(
-                    (tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
-                ),
-            });
-        }
-        for btn in &pc.buttons {
-            widgets.push(AppWidget {
-                x: btn.x * s, y: (btn.y - scroll_offset_y) * s, w: btn.w * s, h: btn.h * s,
-                color: btn.bg, hover_color: btn.hover_bg,
-                hovering: false,
-                kind: WidgetKind::ActionButton(btn.action.clone()),
-            });
-            let buf = make_text_buffer(&mut self.font_system, &btn.label, btn.label_size * s);
-            let tw = buf.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0);
-            let lh = btn.label_size * s * 1.4;
-            let mut left_align = btn.left_align;
-
-            // Auto-detect if inside a ScrollBox to apply left alignment by default
-            if !left_align && btn.w >= 60.0 {
-                if self.app.current_page == Page::Typefaces {
-                    let sb = &self.app.typeface.list_box;
-                    let (sb_x, sb_y, sb_w, sb_h) = sb.rect();
-                    if btn.x >= sb_x - 1.0 && btn.x + btn.w <= sb_x + sb_w + 1.0
-                       && btn.y >= sb_y - 1.0 && btn.y + btn.h <= sb_y + sb_h + 1.0 {
-                        left_align = true;
-                    }
-                } else if self.app.current_page == Page::Hardware {
-                    let sb = &self.app.hardware.cpu_list_box;
-                    let (sb_x, sb_y, sb_w, sb_h) = sb.rect();
-                    if btn.x >= sb_x - 1.0 && btn.x + btn.w <= sb_x + sb_w + 1.0
-                       && btn.y >= sb_y - 1.0 && btn.y + btn.h <= sb_y + sb_h + 1.0 {
-                        left_align = true;
-                    }
-                } else if self.app.current_page == Page::Services {
-                    let sb = &self.app.services.list_box;
-                    let (sb_x, sb_y, sb_w, sb_h) = sb.rect();
-                    if btn.x >= sb_x - 1.0 && btn.x + btn.w <= sb_x + sb_w + 1.0
-                       && btn.y >= sb_y - 1.0 && btn.y + btn.h <= sb_y + sb_h + 1.0 {
-                        left_align = true;
-                    }
-                }
-            }
-
-            let text_x = if left_align {
-                btn.x * s + 8.0 * s
-            } else {
-                btn.x * s + (btn.w * s - tw) / 2.0
-            };
-
-            text_items.push(TextItem {
-                buffer: buf,
-                x: text_x, y: (btn.y - scroll_offset_y) * s + (btn.h * s - lh) / 2.0,
-                color: glyphon::Color::rgb(
-                    (btn.label_color[0] * 255.0) as u8,
-                    (btn.label_color[1] * 255.0) as u8,
-                    (btn.label_color[2] * 255.0) as u8,
-                ),
-            });
-            let mut cb = btn.clone();
-            cb.x *= s; cb.y = (cb.y - scroll_offset_y) * s; cb.w *= s; cb.h *= s;
-            page_buttons.push(cb);
-        }
+        clear_ui::layout::render_popovers(&mut pc);
 
         // ── Rebuild Widget Focus Hierarchy ──
         self.page_root_container.clear_children();
@@ -686,6 +608,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
         self.app.layout.cascade_offset_spinbox.clear_children(); self.app.layout.cascade_offset_spinbox.set_parent(None);
         self.app.layout.edge_gap_spinbox.clear_children(); self.app.layout.edge_gap_spinbox.set_parent(None);
         self.app.layout.top_gap_spinbox.clear_children(); self.app.layout.top_gap_spinbox.set_parent(None);
+        self.app.layout.grid_gap_spinbox.clear_children(); self.app.layout.grid_gap_spinbox.set_parent(None);
+        self.app.layout.transition_duration_spinbox.clear_children(); self.app.layout.transition_duration_spinbox.set_parent(None);
+        self.app.layout.status_height_spinbox.clear_children(); self.app.layout.status_height_spinbox.set_parent(None);
 
         for cs in &mut self.app.colors.color_selectors {
             cs.clear_children();
@@ -697,11 +622,19 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
 
         self.app.input.rate_spinbox.clear_children(); self.app.input.rate_spinbox.set_parent(None);
         self.app.input.delay_spinbox.clear_children(); self.app.input.delay_spinbox.set_parent(None);
+        self.app.input.scroll_toggle.clear_children(); self.app.input.scroll_toggle.set_parent(None);
         self.app.input.scroll_friction_spinbox.clear_children(); self.app.input.scroll_friction_spinbox.set_parent(None);
+        self.app.input.natural_toggle.clear_children(); self.app.input.natural_toggle.set_parent(None);
+        self.app.input.scroll_speed_spinbox.clear_children(); self.app.input.scroll_speed_spinbox.set_parent(None);
+        self.app.input.pointer_toggle.clear_children(); self.app.input.pointer_toggle.set_parent(None);
         self.app.input.pointer_friction_spinbox.clear_children(); self.app.input.pointer_friction_spinbox.set_parent(None);
+        self.app.input.trackpad_toggle.clear_children(); self.app.input.trackpad_toggle.set_parent(None);
         self.app.input.trackpad_friction_spinbox.clear_children(); self.app.input.trackpad_friction_spinbox.set_parent(None);
+        self.app.input.dwtp_toggle.clear_children(); self.app.input.dwtp_toggle.set_parent(None);
         self.app.input.trackpoint_accel_speed_spinbox.clear_children(); self.app.input.trackpoint_accel_speed_spinbox.set_parent(None);
         self.app.input.trackpoint_accel_profile_menu.clear_children(); self.app.input.trackpoint_accel_profile_menu.set_parent(None);
+        self.app.input.cursor_theme_menu.clear_children(); self.app.input.cursor_theme_menu.set_parent(None);
+        self.app.input.cursor_size_spinbox.clear_children(); self.app.input.cursor_size_spinbox.set_parent(None);
 
         for sb in &mut self.app.audio.sink_spinboxes {
             sb.clear_children();
@@ -756,9 +689,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                 link_parent_child(&mut self.page_root_container, &mut self.app.network.wifi_list_box.scroll_box);
             }
             Page::Layout => {
-                self.page_sec_containers.resize_with(4, clear_ui::widget::Container::new);
+                self.page_sec_containers.resize_with(5, clear_ui::widget::Container::new);
                 
-                for i in 0..4 {
+                for i in 0..5 {
                     link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[i]);
                 }
                 
@@ -769,11 +702,14 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                 link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.cascade_offset_spinbox);
                 link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.edge_gap_spinbox);
                 link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.top_gap_spinbox);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.grid_gap_spinbox);
                 
                 link_parent_child(&mut self.page_sec_containers[2], &mut self.app.layout.transition_duration_spinbox);
+                
+                link_parent_child(&mut self.page_sec_containers[3], &mut self.app.layout.status_height_spinbox);
 
                 for menu in &mut self.app.layout.tag_layout_menus {
-                    link_parent_child(&mut self.page_sec_containers[3], menu);
+                    link_parent_child(&mut self.page_sec_containers[4], menu);
                 }
             }
             Page::Colors => {
@@ -786,12 +722,14 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                 link_parent_child(&mut self.page_root_container, &mut self.app.notifications.opacity_slider);
             }
             Page::Input => {
-                self.page_sec_containers.resize_with(4, clear_ui::widget::Container::new);
+                self.page_sec_containers.resize_with(5, clear_ui::widget::Container::new);
                 link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[0]);
                 link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[1]);
                 link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[2]);
                 link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[3]);
+                link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[4]);
                 
+                link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.dwtp_toggle);
                 link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.trackpoint_accel_speed_spinbox);
                 link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.trackpoint_accel_profile_menu);
                 
@@ -801,9 +739,15 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                 link_parent_child(&mut self.page_sec_containers[2], &mut self.app.input.cursor_theme_menu);
                 link_parent_child(&mut self.page_sec_containers[2], &mut self.app.input.cursor_size_spinbox);
                 
+                link_parent_child(&mut self.page_sec_containers[3], &mut self.app.input.scroll_toggle);
                 link_parent_child(&mut self.page_sec_containers[3], &mut self.app.input.scroll_friction_spinbox);
-                link_parent_child(&mut self.page_sec_containers[3], &mut self.app.input.pointer_friction_spinbox);
-                link_parent_child(&mut self.page_sec_containers[3], &mut self.app.input.trackpad_friction_spinbox);
+                link_parent_child(&mut self.page_sec_containers[3], &mut self.app.input.natural_toggle);
+                link_parent_child(&mut self.page_sec_containers[3], &mut self.app.input.scroll_speed_spinbox);
+                
+                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.input.pointer_toggle);
+                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.input.pointer_friction_spinbox);
+                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.input.trackpad_toggle);
+                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.input.trackpad_friction_spinbox);
             }
             Page::Audio => {
                 self.page_sec_containers.resize_with(2, clear_ui::widget::Container::new);
@@ -826,6 +770,120 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
             _ => {}
         }
 
+        let mut popovers = Vec::new();
+        Self::collect_popover_rects(&self.page_root_container, &mut popovers);
+
+        if !popovers.is_empty() {
+            pc.texts.retain(|(text, size, tx, ty, _, _)| {
+                let text_w = text.chars().count() as f32 * *size * 0.65;
+                for &(px, py, pw, ph) in &popovers {
+                    let x_overlap = *tx <= px + pw && (*tx + text_w) >= px;
+                    let y_overlap = *ty <= py + ph && (*ty + *size) >= py;
+                    if x_overlap && y_overlap {
+                        return false;
+                    }
+                }
+                true
+            });
+        }
+
+        let mut max_y = 0.0f32;
+        for (_, _, y, _, h) in &pc.rects {
+            max_y = max_y.max(y + h);
+        }
+        for (_, size, _, y, _, _) in &pc.texts {
+            max_y = max_y.max(y + size);
+        }
+        for btn in &pc.buttons {
+            max_y = max_y.max(btn.y + btn.h);
+        }
+        self.max_scroll_y = (max_y - lch).max(0.0);
+        self.scroll_y = self.scroll_y.min(self.max_scroll_y);
+
+        let scroll_offset_y = self.scroll_y;
+
+        for (c, x, y, w, h) in &pc.rects {
+            widgets.push(AppWidget {
+                x: *x * s, y: (*y - scroll_offset_y) * s, w: *w * s, h: *h * s,
+                color: *c, hover_color: *c,
+                hovering: false, kind: WidgetKind::Static,
+            });
+        }
+        for (t, size, x, y, tc, font_opt) in &pc.texts {
+            text_items.push(TextItem {
+                buffer: make_text_buffer_with_font(
+                    &mut self.font_system,
+                    t,
+                    *size * s,
+                    font_opt.as_deref(),
+                    &self.sans_serif_family,
+                    &self.serif_family,
+                    &self.monospace_family,
+                ),
+                x: *x * s, y: (*y - scroll_offset_y) * s,
+                color: glyphon::Color::rgb(
+                    (tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
+                ),
+            });
+        }
+        for btn in &pc.buttons {
+            widgets.push(AppWidget {
+                x: btn.x * s, y: (btn.y - scroll_offset_y) * s, w: btn.w * s, h: btn.h * s,
+                color: btn.bg, hover_color: btn.hover_bg,
+                hovering: false,
+                kind: WidgetKind::ActionButton(btn.action.clone()),
+            });
+            let buf = make_text_buffer(&mut self.font_system, &btn.label, btn.label_size * s);
+            let tw = buf.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0);
+            let lh = btn.label_size * s * 1.4;
+            let mut left_align = btn.left_align;
+
+            // Auto-detect if inside a ScrollBox to apply left alignment by default
+            if !left_align && btn.w >= 60.0 {
+                if self.app.current_page == Page::Typefaces {
+                    let sb = &self.app.typeface.list_box;
+                    let (sb_x, sb_y, sb_w, sb_h) = sb.rect();
+                    if btn.x >= sb_x - 1.0 && btn.x + btn.w <= sb_x + sb_w + 1.0
+                       && btn.y >= sb_y - 1.0 && btn.y + btn.h <= sb_y + sb_h + 1.0 {
+                        left_align = true;
+                    }
+                } else if self.app.current_page == Page::Hardware {
+                    let sb = &self.app.hardware.cpu_list_box;
+                    let (sb_x, sb_y, sb_w, sb_h) = sb.rect();
+                    if btn.x >= sb_x - 1.0 && btn.x + btn.w <= sb_x + sb_w + 1.0
+                       && btn.y >= sb_y - 1.0 && btn.y + btn.h <= sb_y + sb_h + 1.0 {
+                        left_align = true;
+                    }
+                } else if self.app.current_page == Page::Services {
+                    let sb = &self.app.services.list_box;
+                    let (sb_x, sb_y, sb_w, sb_h) = sb.rect();
+                    if btn.x >= sb_x - 1.0 && btn.x + btn.w <= sb_x + sb_w + 1.0
+                       && btn.y >= sb_y - 1.0 && btn.y + btn.h <= sb_y + sb_h + 1.0 {
+                        left_align = true;
+                    }
+                }
+            }
+
+            let text_x = if left_align {
+                btn.x * s + 8.0 * s
+            } else {
+                btn.x * s + (btn.w * s - tw) / 2.0
+            };
+
+            text_items.push(TextItem {
+                buffer: buf,
+                x: text_x, y: (btn.y - scroll_offset_y) * s + (btn.h * s - lh) / 2.0,
+                color: glyphon::Color::rgb(
+                    (btn.label_color[0] * 255.0) as u8,
+                    (btn.label_color[1] * 255.0) as u8,
+                    (btn.label_color[2] * 255.0) as u8,
+                ),
+            });
+            let mut cb = btn.clone();
+            cb.x *= s; cb.y = (cb.y - scroll_offset_y) * s; cb.w *= s; cb.h *= s;
+            page_buttons.push(cb);
+        }
+
         // Draw global hover highlight if active
         clear_ui::widget::hover_animation::post_render_check();
         if let Some((qx, qy, qw, qh, qc)) = clear_ui::widget::hover_animation::get_quad() {
@@ -959,6 +1017,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
             layout::update(&mut self.app.layout, layout::LayoutMessage::Refreshed(s));
             self.needs_rebuild = true;
         }
+        while let Ok(_) = self.rx_wm_events.try_recv() {
+            self.needs_rebuild = true;
+        }
         while let Ok(s) = self.rx_input.try_recv() {
             input::update(&mut self.app.input, input::InputMessage::Refreshed(s));
             self.needs_rebuild = true;
@@ -1108,9 +1169,15 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
             if self.app.layout.top_gap_spinbox.cursor_moved(lx, ly) {
                 changed = true;
             }
+            if self.app.layout.grid_gap_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
             if self.app.layout.transition_duration_spinbox.cursor_moved(lx, ly) {
                 changed = true;
             }
+            if self.app.layout.status_height_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
             for menu in &mut self.app.layout.tag_layout_menus {
                 if menu.cursor_moved(lx, ly) {
                     changed = true;
@@ -1370,7 +1437,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                     if self.app.layout.cascade_offset_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
                     if self.app.layout.edge_gap_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
                     if self.app.layout.top_gap_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.layout.grid_gap_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
                     if self.app.layout.transition_duration_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.layout.status_height_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
                     for menu in &mut self.app.layout.tag_layout_menus {
                         if menu.hit_test(lx, ly) { clicked_any_focusable = true; }
                     }
@@ -1491,6 +1560,14 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                     pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
                 ));
             }
+            let sb = &mut self.app.layout.grid_gap_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Layout(
+                    pages::layout::LayoutMessage::SetGridGap(sb.value as u16)
+                ));
+            }
             let sb = &mut self.app.layout.transition_duration_spinbox;
             if !sb.hit_test(lx, ly) { sb.unfocus(); }
             let old = sb.value;
@@ -1499,6 +1576,14 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                     pages::layout::LayoutMessage::SetTransitionDuration(sb.value as u16)
                 ));
             }
+            let sb = &mut self.app.layout.status_height_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Layout(
+                    pages::layout::LayoutMessage::SetStatusHeight(sb.value as u16)
+                ));
+            }
         }
         if self.app.current_page == Page::Layout {
             for (idx, menu) in self.app.layout.tag_layout_menus.iter_mut().enumerate() {
@@ -1525,6 +1610,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                         4 => pages::colors::ColorsMessage::PickSeparatorColor,
                         5 => pages::colors::ColorsMessage::PickSliderTrackColor,
                         6 => pages::colors::ColorsMessage::PickColorBordersColor,
+                        7 => pages::colors::ColorsMessage::PickLowColor,
+                        8 => pages::colors::ColorsMessage::PickNormalColor,
                         _ => pages::colors::ColorsMessage::PickLowColor,
                     }));
                 }
@@ -1537,6 +1624,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                         4 => pages::colors::ColorsMessage::SetSeparatorColor(cp.color),
                         5 => pages::colors::ColorsMessage::SetSliderTrackColor(cp.color),
                         6 => pages::colors::ColorsMessage::SetColorBordersColor(cp.color),
+                        7 => pages::colors::ColorsMessage::SetLowColor(cp.color),
+                        8 => pages::colors::ColorsMessage::SetNormalColor(cp.color),
                         _ => pages::colors::ColorsMessage::SetLowColor(cp.color),
                     }));
                 }
@@ -1561,6 +1650,12 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
             if sb.mouse_input(button, state, lx, ly) && sb.value != old {
                 actions.push(AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
             }
+            let sb = &mut self.app.input.scroll_speed_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Input(pages::input::InputMessage::ApplyScrollSpeed));
+            }
             let sb = &mut self.app.input.pointer_friction_spinbox;
             if !sb.hit_test(lx, ly) { sb.unfocus(); }
             let old = sb.value;
@@ -1605,6 +1700,11 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
             if toggle.take_click() {
                 actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialScroll));
             }
+            let toggle = &mut self.app.input.natural_toggle;
+            toggle.mouse_input(button, state, lx, ly);
+            if toggle.take_click() {
+                actions.push(AppAction::Input(pages::input::InputMessage::ToggleNaturalScroll));
+            }
             let toggle = &mut self.app.input.pointer_toggle;
             toggle.mouse_input(button, state, lx, ly);
             if toggle.take_click() {
@@ -2112,6 +2212,16 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                 }
                 changed = true;
             }
+            let sb = &mut self.app.layout.grid_gap_spinbox;
+            let old = sb.value;
+            if sb.keyboard_input(event) {
+                if sb.value != old {
+                    actions.push(AppAction::Layout(
+                        pages::layout::LayoutMessage::SetGridGap(sb.value as u16)
+                    ));
+                }
+                changed = true;
+            }
             let sb = &mut self.app.layout.transition_duration_spinbox;
             let old = sb.value;
             if sb.keyboard_input(event) {
@@ -2122,6 +2232,16 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                 }
                 changed = true;
             }
+            let sb = &mut self.app.layout.status_height_spinbox;
+            let old = sb.value;
+            if sb.keyboard_input(event) {
+                if sb.value != old {
+                    actions.push(AppAction::Layout(
+                        pages::layout::LayoutMessage::SetStatusHeight(sb.value as u16)
+                    ));
+                }
+                changed = true;
+            }
             for a in &actions {
                 self.handle_action(a);
             }
@@ -2145,6 +2265,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                             4 => pages::colors::ColorsMessage::SetSeparatorColor(cp.color),
                             5 => pages::colors::ColorsMessage::SetSliderTrackColor(cp.color),
                             6 => pages::colors::ColorsMessage::SetColorBordersColor(cp.color),
+                            7 => pages::colors::ColorsMessage::SetLowColor(cp.color),
+                            8 => pages::colors::ColorsMessage::SetNormalColor(cp.color),
                             _ => pages::colors::ColorsMessage::SetLowColor(cp.color),
                         }));
                     }
@@ -2188,6 +2310,11 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
                 self.needs_rebuild = true;
                 return true;
             }
+            if self.app.input.scroll_speed_spinbox.keyboard_input(event) {
+                self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyScrollSpeed));
+                self.needs_rebuild = true;
+                return true;
+            }
             if self.app.input.pointer_friction_spinbox.keyboard_input(event) {
                 self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
                 self.needs_rebuild = true;
diff --git a/src/pages/colors.rs b/src/pages/colors.rs
index 169547b..c8c05dd 100644
--- a/src/pages/colors.rs
+++ b/src/pages/colors.rs
@@ -23,6 +23,7 @@ pub struct ColorsState {
     pub slider_track_color: [u8; 3],
     pub page_low_color: [u8; 3],
     pub color_borders_color: [u8; 3],
+    pub normal_color: [u8; 3],
     pub color_selectors: Vec<ColorSelector>,
 }
 
@@ -37,6 +38,7 @@ impl Default for ColorsState {
             slider_track_color: [116, 116, 128],
             page_low_color: [71, 71, 81],
             color_borders_color: [124, 124, 137],
+            normal_color: [0xcc, 0xcc, 0xd8],
             color_selectors: vec![
                 ColorSelector::new([71, 71, 81]).with_label("Low Color"), // 0: Pages - Low Color
                 ColorSelector::new([0x3e, 0x3e, 0x3e]).with_label("High Color"), // 1: Layout - High Color
@@ -46,6 +48,7 @@ impl Default for ColorsState {
                 ColorSelector::new([116, 116, 128]).with_label("Slider Track"), // 5: Controls - Slider Track
                 ColorSelector::new([124, 124, 137]).with_label("Borders"), // 6: Controls - Borders
                 ColorSelector::new([0x0a, 0x1a, 0x0e]).with_label("Low Color"), // 7: Layout - Low Color
+                ColorSelector::new([0xcc, 0xcc, 0xd8]).with_label("Normal"), // 8: Status - Normal
             ],
         }
     }
@@ -61,6 +64,7 @@ pub enum ColorsMessage {
     SetSliderTrackColor([u8; 3]),
     SetPageLowColor([u8; 3]),
     SetColorBordersColor([u8; 3]),
+    SetNormalColor([u8; 3]),
     PickLowColor,
     PickHighColor,
     PickDisabledColor,
@@ -69,6 +73,7 @@ pub enum ColorsMessage {
     PickSliderTrackColor,
     PickPageLowColor,
     PickColorBordersColor,
+    PickNormalColor,
     Refreshed(ColorsState),
 }
 
@@ -101,6 +106,8 @@ pub fn read_colors_config() -> ColorsState {
     let page_low = parse_color_from_key(&content, "page_low_color", [71, 71, 81]);
 
     let color_borders = parse_color_from_key(&content, "color_borders_color", [124, 124, 137]);
+
+    let normal = parse_color_from_key(&content, "status_normal_color", [0xcc, 0xcc, 0xd8]);
     
     ColorsState {
         low_color: bg,
@@ -111,6 +118,7 @@ pub fn read_colors_config() -> ColorsState {
         slider_track_color: slider_track,
         page_low_color: page_low,
         color_borders_color: color_borders,
+        normal_color: normal,
         color_selectors: vec![
             ColorSelector::new(page_low).with_label("Low Color"), // 0: Pages - Low Color
             ColorSelector::new(border).with_label("High Color"), // 1: Layout - High Color
@@ -120,6 +128,7 @@ pub fn read_colors_config() -> ColorsState {
             ColorSelector::new(slider_track).with_label("Slider Track"), // 5: Controls - Slider Track
             ColorSelector::new(color_borders).with_label("Borders"), // 6: Controls - Borders
             ColorSelector::new(bg).with_label("Low Color"), // 7: Layout - Low Color
+            ColorSelector::new(normal).with_label("Normal"), // 8: Status - Normal
         ],
     }
 }
@@ -263,6 +272,12 @@ fn apply_color_borders_color(rgb: [u8; 3]) {
     clear_ui::color::set_color_borders_color([r, g, b, 1.0]);
 }
 
+fn apply_normal_color(rgb: [u8; 3]) {
+    let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
+    write_config_value("status_normal_color", &hex);
+    status_interface_reload();
+}
+
 pub fn view(state: &mut ColorsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
     let mut pc = PageContent::new();
     let mut y = cy + 12.0;
@@ -292,6 +307,9 @@ pub fn view(state: &mut ColorsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Pag
     // 3. Status Section
     let mut sec = Section::new(&mut pc, cx, y, cw, "Status");
     sec.spacing(8.0);
+    state.color_selectors[8].color = state.normal_color;
+    sec.widget(&mut pc, &mut state.color_selectors[8], 12.0, 220.0, 22.0);
+    sec.spacing(8.0);
     state.color_selectors[3].color = state.disabled_color;
     sec.widget(&mut pc, &mut state.color_selectors[3], 12.0, 220.0, 22.0);
     sec.spacing(8.0);
@@ -348,7 +366,11 @@ pub fn update(state: &mut ColorsState, msg: ColorsMessage) {
             state.color_borders_color = rgb;
             apply_color_borders_color(rgb);
         }
-        ColorsMessage::PickLowColor | ColorsMessage::PickHighColor | ColorsMessage::PickDisabledColor | ColorsMessage::PickSeparatorColor | ColorsMessage::PickVisualGuides | ColorsMessage::PickSliderTrackColor | ColorsMessage::PickPageLowColor | ColorsMessage::PickColorBordersColor => {}
+        ColorsMessage::SetNormalColor(rgb) => {
+            state.normal_color = rgb;
+            apply_normal_color(rgb);
+        }
+        ColorsMessage::PickLowColor | ColorsMessage::PickHighColor | ColorsMessage::PickDisabledColor | ColorsMessage::PickSeparatorColor | ColorsMessage::PickVisualGuides | ColorsMessage::PickSliderTrackColor | ColorsMessage::PickPageLowColor | ColorsMessage::PickColorBordersColor | ColorsMessage::PickNormalColor => {}
         ColorsMessage::Refreshed(new) => {
             *state = new;
         }
@@ -369,7 +391,7 @@ mod tests {
 
     #[test]
     fn test_parse_color_from_key() {
-        let content = "\n[layout]\nlow_color = \"#112233\"\nhigh_color = \"#445566\"\ndisabled_color = \"#778899\"\nstatus_separator_color = \"#aabbcc\"\nvisual_guides_color = \"#ddeeff\"\nslider_track_color = \"#123456\"\npage_low_color = \"#474751\"\ncolor_borders_color = \"#abcdef\"\n";
+        let content = "\n[layout]\nlow_color = \"#112233\"\nhigh_color = \"#445566\"\ndisabled_color = \"#778899\"\nstatus_separator_color = \"#aabbcc\"\nvisual_guides_color = \"#ddeeff\"\nslider_track_color = \"#123456\"\npage_low_color = \"#474751\"\ncolor_borders_color = \"#abcdef\"\nstatus_normal_color = \"#ccccd8\"\n";
         assert_eq!(parse_color_from_key(content, "low_color", [0, 0, 0]), [17, 34, 51]);
         assert_eq!(parse_color_from_key(content, "high_color", [0, 0, 0]), [68, 85, 102]);
         assert_eq!(parse_color_from_key(content, "disabled_color", [0, 0, 0]), [119, 136, 153]);
@@ -378,6 +400,7 @@ mod tests {
         assert_eq!(parse_color_from_key(content, "slider_track_color", [0, 0, 0]), [18, 52, 86]);
         assert_eq!(parse_color_from_key(content, "page_low_color", [0, 0, 0]), [71, 71, 81]);
         assert_eq!(parse_color_from_key(content, "color_borders_color", [0, 0, 0]), [171, 205, 239]);
+        assert_eq!(parse_color_from_key(content, "status_normal_color", [0, 0, 0]), [204, 204, 216]);
         assert_eq!(parse_color_from_key(content, "non_existent", [1, 2, 3]), [1, 2, 3]);
     }
 
diff --git a/src/pages/input.rs b/src/pages/input.rs
index 0a69bef..4cb1159 100644
--- a/src/pages/input.rs
+++ b/src/pages/input.rs
@@ -49,6 +49,12 @@ pub struct InputState {
     pub trackpad_toggle: Toggle,
     pub trackpad_friction_spinbox: Spinbox,
 
+    // Scrolling settings
+    pub natural_scroll: bool,
+    pub scroll_speed: f32,
+    pub natural_toggle: Toggle,
+    pub scroll_speed_spinbox: Spinbox,
+
     // Trackpoint settings
     pub dwtp: bool,
     pub trackpoint_accel_speed: f32,
@@ -124,6 +130,11 @@ impl Default for InputState {
             trackpad_toggle: Toggle::new().with_label("Inertial Pointer (Trackpad)"),
             trackpad_friction_spinbox: Spinbox::new(95, 50, 99, 1).with_label("Trackpad Friction").with_unit("%"),
 
+            natural_scroll: false,
+            scroll_speed: 1.0,
+            natural_toggle: Toggle::new().with_label("Natural Scroll"),
+            scroll_speed_spinbox: Spinbox::new(10, 1, 100, 1).with_label("Scroll Speed").with_unit("x").with_decimals(1),
+
             dwtp: true,
             trackpoint_accel_speed: 0.5,
             trackpoint_accel_profile: "flat".to_string(),
@@ -161,6 +172,8 @@ pub enum InputMessage {
     ApplyPointerFriction,
     ToggleInertialTrackpad,
     ApplyTrackpadFriction,
+    ToggleNaturalScroll,
+    ApplyScrollSpeed,
 
     ToggleDwtp,
     ApplyTrackpointAccelSpeed,
@@ -182,7 +195,11 @@ pub fn read_input_config() -> InputState {
     let pointer_friction = parse_u16_key(&content, "pointer_friction", 95);
     let inertial_trackpad = parse_bool_from_default(&content, "inertial_trackpad", false);
     let trackpad_friction = parse_u16_key(&content, "trackpad_friction", 95);
+    let natural_scroll = parse_bool_from_default(&content, "natural_scroll", false);
+    let scroll_speed = parse_f32_key(&content, "scroll_speed", 1.0);
+    let scroll_speed_val = (scroll_speed * 10.0).round() as i32;
 
+    let _dwt = parse_bool_from_default(&content, "dwt", true);
     let dwtp = parse_bool_from_default(&content, "dwtp", true);
     let trackpoint_accel_speed = parse_f32_key(&content, "trackpoint_accel_speed", 0.5);
     let trackpoint_accel_profile = parse_string_key(&content, "trackpoint_accel_profile", "flat");
@@ -220,6 +237,11 @@ pub fn read_input_config() -> InputState {
         trackpad_toggle: Toggle::new().with_label("Inertial Pointer (Trackpad)"),
         trackpad_friction_spinbox: Spinbox::new(trackpad_friction as i32, 50, 99, 1).with_label("Trackpad Friction").with_unit("%"),
 
+        natural_scroll,
+        scroll_speed,
+        natural_toggle: Toggle::new().with_label("Natural Scroll"),
+        scroll_speed_spinbox: Spinbox::new(scroll_speed_val, 1, 100, 1).with_label("Scroll Speed").with_unit("x").with_decimals(1),
+
         dwtp,
         trackpoint_accel_speed,
         trackpoint_accel_profile,
@@ -320,11 +342,11 @@ fn write_config_value(key: &str, value: &str) {
     if !found {
         let section = if key == "tap_to_click" || key == "dwtp"
                 || key == "trackpoint_accel_speed" || key == "trackpoint_accel_profile"
-                || key == "cursor_theme" || key == "cursor_size" {
+                || key == "cursor_theme" || key == "cursor_size" || key == "natural_scroll" {
             "[input]"
         } else if key == "inertial_scroll" || key == "scroll_friction"
                || key == "inertial_pointer" || key == "pointer_friction"
-               || key == "inertial_trackpad" || key == "trackpad_friction" {
+               || key == "inertial_trackpad" || key == "trackpad_friction" || key == "scroll_speed" {
             "[inertial]"
         } else {
             "[repeat]"
@@ -423,15 +445,27 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
 
     y = sec.finish_focused(&mut pc, sec_focused.get(2).copied().unwrap_or(false));
 
-    // ── Inertial Input ──
-    let mut sec = Section::new(&mut pc, cx, y, cw, "Inertial Input");
+    // ── Scrolling ──
+    let mut sec = Section::new(&mut pc, cx, y, cw, "Scrolling");
 
     state.scroll_toggle.set_toggled(state.inertial_scroll);
     sec.widget(&mut pc, &mut state.scroll_toggle, 14.0, toggle_w, toggle_h);
     sec.spacing(12.0);
 
     sec.widget(&mut pc, &mut state.scroll_friction_spinbox, 14.0, 200.0, 26.0);
-    sec.spacing(16.0);
+    sec.spacing(12.0);
+
+    state.natural_toggle.set_toggled(state.natural_scroll);
+    sec.widget(&mut pc, &mut state.natural_toggle, 14.0, toggle_w, toggle_h);
+    sec.spacing(12.0);
+
+    sec.widget(&mut pc, &mut state.scroll_speed_spinbox, 14.0, 200.0, 26.0);
+    sec.spacing(8.0);
+
+    y = sec.finish_focused(&mut pc, sec_focused.get(3).copied().unwrap_or(false));
+
+    // ── Inertial Input ──
+    let mut sec = Section::new(&mut pc, cx, y, cw, "Inertial Input");
 
     state.pointer_toggle.set_toggled(state.inertial_pointer);
     sec.widget(&mut pc, &mut state.pointer_toggle, 14.0, toggle_w, toggle_h);
@@ -447,7 +481,7 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
     sec.widget(&mut pc, &mut state.trackpad_friction_spinbox, 14.0, 200.0, 26.0);
     sec.spacing(8.0);
 
-    y = sec.finish_focused(&mut pc, sec_focused.get(3).copied().unwrap_or(false));
+    y = sec.finish_focused(&mut pc, sec_focused.get(4).copied().unwrap_or(false));
 
     // ── Keybindings ──
     let mut sec = Section::new(&mut pc, cx, y, cw, "Keyboard Bindings");
@@ -470,8 +504,7 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
     }
     sec.finish(&mut pc);
 
-    state.trackpoint_accel_profile_menu.render_popover(&mut pc);
-    state.cursor_theme_menu.render_popover(&mut pc);
+
 
     pc
 }
@@ -492,11 +525,24 @@ pub fn update(state: &mut InputState, msg: InputMessage) {
         InputMessage::ToggleInertialScroll => {
             state.inertial_scroll = !state.inertial_scroll;
             write_config_value("inertial_scroll", &state.inertial_scroll.to_string());
+            send_ipc_command("reload");
         }
         InputMessage::ApplyScrollFriction => {
             let friction = state.scroll_friction_spinbox.value.max(50).min(99) as u16;
             state.scroll_friction = friction;
             write_config_value("scroll_friction", &friction.to_string());
+            send_ipc_command("reload");
+        }
+        InputMessage::ToggleNaturalScroll => {
+            state.natural_scroll = !state.natural_scroll;
+            write_config_value("natural_scroll", &state.natural_scroll.to_string());
+            send_ipc_command(&format!("input natural-scroll {}", state.natural_scroll));
+        }
+        InputMessage::ApplyScrollSpeed => {
+            let val = state.scroll_speed_spinbox.value as f32 / 10.0;
+            state.scroll_speed = val;
+            write_config_value("scroll_speed", &val.to_string());
+            send_ipc_command("reload");
         }
         InputMessage::ToggleInertialPointer => {
             state.inertial_pointer = !state.inertial_pointer;
@@ -582,5 +628,16 @@ mod tests {
         assert!(!state.is_over_trackpad(150.0, 199.0));
         assert!(!state.is_over_trackpad(150.0, 351.0));
     }
+
+    #[test]
+    fn test_parse_scrolling_params() {
+        let content = "[input]\nnatural_scroll = true\n[inertial]\nscroll_speed = 2.5\n";
+        assert_eq!(parse_bool_from_default(content, "natural_scroll", false), true);
+        assert_eq!(parse_f32_key(content, "scroll_speed", 1.0), 2.5);
+
+        let empty_content = "";
+        assert_eq!(parse_bool_from_default(empty_content, "natural_scroll", false), false);
+        assert_eq!(parse_f32_key(empty_content, "scroll_speed", 1.0), 1.0);
+    }
 }
 
diff --git a/src/pages/layout.rs b/src/pages/layout.rs
index e2ab962..ff12bdd 100644
--- a/src/pages/layout.rs
+++ b/src/pages/layout.rs
@@ -60,11 +60,15 @@ pub struct LayoutState {
     pub cascade_offset: u16,
     pub edge_gap: u16,
     pub top_gap: u16,
+    pub grid_gap: u16,
+    pub status_height: u16,
     pub transition_duration: u16,
     pub spinboxes: Vec<Spinbox>,
     pub cascade_offset_spinbox: Spinbox,
     pub edge_gap_spinbox: Spinbox,
     pub top_gap_spinbox: Spinbox,
+    pub grid_gap_spinbox: Spinbox,
+    pub status_height_spinbox: Spinbox,
     pub transition_duration_spinbox: Spinbox,
     pub tag_layout_menus: Vec<Dropdown>,
 }
@@ -79,11 +83,15 @@ impl Default for LayoutState {
             cascade_offset: 20,
             edge_gap: 48,
             top_gap: 48,
+            grid_gap: 6,
+            status_height: 24,
             transition_duration: 300,
             spinboxes: make_spinboxes(0, 6, 6, 6),
             cascade_offset_spinbox: Spinbox::new(20, 0, 200, 1),
             edge_gap_spinbox: Spinbox::new(48, 0, 200, 1),
             top_gap_spinbox: Spinbox::new(48, 0, 200, 1),
+            grid_gap_spinbox: Spinbox::new(6, 0, 200, 1),
+            status_height_spinbox: Spinbox::new(24, 0, 100, 1),
             transition_duration_spinbox: Spinbox::new(300, 0, 2000, 50),
             tag_layout_menus: (1..=4).map(|i| {
                 Dropdown::new(
@@ -107,7 +115,9 @@ pub enum LayoutMessage {
     SetCascadeOffset(u16),
     SetEdgeGap(u16),
     SetTopGap(u16),
+    SetGridGap(u16),
     SetTransitionDuration(u16),
+    SetStatusHeight(u16),
     SetTagLayout(usize, usize),
     Refreshed(LayoutState),
 }
@@ -121,6 +131,8 @@ pub fn read_layout_config() -> LayoutState {
     let co = parse_u16_from(&content, "cascade_offset", 20);
     let gl = parse_u16_from(&content, "gap_left", 48);
     let gt = parse_u16_from(&content, "gap_top", 48);
+    let gg = parse_u16_from(&content, "grid_gap", 6);
+    let sh = parse_u16_from(&content, "bar_height", 24);
     let td = parse_u16_from(&content, "transition_duration", 300);
 
     let tag_modes = parse_tag_layouts_from_config(&content);
@@ -145,11 +157,15 @@ pub fn read_layout_config() -> LayoutState {
         cascade_offset: co,
         edge_gap: gl,
         top_gap: gt,
+        grid_gap: gg,
+        status_height: sh,
         transition_duration: td,
         spinboxes: make_spinboxes(fs, ca, g, fl),
         cascade_offset_spinbox: Spinbox::new(co as i32, 0, 200, 1),
         edge_gap_spinbox: Spinbox::new(gl as i32, 0, 200, 1),
         top_gap_spinbox: Spinbox::new(gt as i32, 0, 200, 1),
+        grid_gap_spinbox: Spinbox::new(gg as i32, 0, 200, 1),
+        status_height_spinbox: Spinbox::new(sh as i32, 0, 100, 1),
         transition_duration_spinbox: Spinbox::new(td as i32, 0, 2000, 50),
         tag_layout_menus,
     }
@@ -305,10 +321,782 @@ fn apply_all_widths(s: &LayoutState) {
     w("transition_duration", s.transition_duration);
 }
 
+fn apply_single_layout_param(key: &str, val: u16) {
+    write_config_value(key, &val.to_string());
+    send_ipc_command(&format!("layout {} {}", key, val));
+}
+
+fn apply_edge_gap(val: u16) {
+    let val_str = val.to_string();
+    write_config_value("gap_left", &val_str);
+    write_config_value("gap_right", &val_str);
+    write_config_value("gap_bottom", &val_str);
+    send_ipc_command(&format!("layout gap_left {}", val));
+    send_ipc_command(&format!("layout gap_right {}", val));
+    send_ipc_command(&format!("layout gap_bottom {}", val));
+}
+
+#[derive(Debug, Clone)]
+struct PreviewWindow {
+    app_id: String,
+    title: String,
+    x: f32,
+    y: f32,
+    w: f32,
+    h: f32,
+    tags: u32,
+    _minimized: bool,
+    has_parent: bool,
+    layout_mode: String,
+}
+
+struct LayoutStatusInfo {
+    active_tags: u32,
+    focused_tags: u32,
+    _num_tags: u32,
+    windows: Vec<PreviewWindow>,
+    focused_title: String,
+    focused_layout_mode: String,
+}
+
+fn read_current_layout_status() -> LayoutStatusInfo {
+    let mut active_tags = 1;
+    let mut focused_tags = 1;
+    let mut _num_tags = 4;
+
+    let display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".to_string());
+    
+    let tags_path = format!("/tmp/ccec-tags-{}", display);
+    let tags_fallback = "/tmp/ccec-tags".to_string();
+    let tags_content = fs::read_to_string(&tags_path)
+        .or_else(|_| fs::read_to_string(&tags_fallback))
+        .unwrap_or_default();
+
+    if let Some(line) = tags_content.lines().next() {
+        let parts: Vec<&str> = line.split_whitespace().collect();
+        if parts.len() >= 3 {
+            active_tags = parts[0].parse().unwrap_or(1);
+            focused_tags = parts[1].parse().unwrap_or(1);
+            _num_tags = parts[2].parse().unwrap_or(4);
+        }
+    }
+
+    let title_path = format!("/tmp/ccec-title-{}", display);
+    let title_fallback = "/tmp/ccec-title".to_string();
+    let focused_title = fs::read_to_string(&title_path)
+        .or_else(|_| fs::read_to_string(&title_fallback))
+        .unwrap_or_default()
+        .trim()
+        .to_string();
+
+    let layout_path = format!("/tmp/ccec-layout-{}", display);
+    let layout_fallback = "/tmp/ccec-layout".to_string();
+    let focused_layout_mode = fs::read_to_string(&layout_path)
+        .or_else(|_| fs::read_to_string(&layout_fallback))
+        .unwrap_or_else(|_| "Cascade".to_string())
+        .trim()
+        .to_string();
+
+    let windows_path = format!("/tmp/ccec-windows-{}", display);
+    let windows_fallback = "/tmp/ccec-windows".to_string();
+    let windows_content = fs::read_to_string(&windows_path)
+        .or_else(|_| fs::read_to_string(&windows_fallback))
+        .unwrap_or_default();
+
+    let mut windows = Vec::new();
+    for line in windows_content.lines() {
+        if !line.starts_with("window ") { continue; }
+        
+        let mut app_id = String::new();
+        let mut title = String::new();
+        let mut x = 0.0;
+        let mut y = 0.0;
+        let mut w = 0.0;
+        let mut h = 0.0;
+        let mut tags = 0;
+        let mut minimized = false;
+        let mut has_parent = false;
+        let mut layout_mode = "Cascade".to_string();
+        
+        let parts = line.strip_prefix("window ").unwrap_or(line);
+        
+        let get_val = |p: &str, k: &str| -> Option<String> {
+            if let Some(idx) = p.find(k) {
+                let start = idx + k.len();
+                let mut end = p.len();
+                let next_keys = [
+                    " app_id=", " title=", " mode=", " decoration=", " presentation=",
+                    " tags=", " x=", " y=", " w=", " h=", " has_parent=", " minimized="
+                ];
+                for nk in next_keys {
+                    if nk != k {
+                        if let Some(nidx) = p[start..].find(nk) {
+                            end = end.min(start + nidx);
+                        }
+                    }
+                }
+                Some(p[start..end].trim().to_string())
+            } else {
+                None
+            }
+        };
+
+        if let Some(val) = get_val(parts, "app_id=") { app_id = val; }
+        if let Some(val) = get_val(parts, "title=") { title = val; }
+        if let Some(val) = get_val(parts, "x=") { x = val.parse().unwrap_or(0.0); }
+        if let Some(val) = get_val(parts, "y=") { y = val.parse().unwrap_or(0.0); }
+        if let Some(val) = get_val(parts, "w=") { w = val.parse().unwrap_or(0.0); }
+        if let Some(val) = get_val(parts, "h=") { h = val.parse().unwrap_or(0.0); }
+        if let Some(val) = get_val(parts, "tags=") { tags = val.parse().unwrap_or(0); }
+        if let Some(val) = get_val(parts, "minimized=") { minimized = val == "true"; }
+        if let Some(val) = get_val(parts, "has_parent=") { has_parent = val == "true"; }
+        if let Some(val) = get_val(parts, "mode=") { layout_mode = val; }
+
+        windows.push(PreviewWindow {
+            app_id,
+            title,
+            x,
+            y,
+            w,
+            h,
+            tags,
+            _minimized: minimized,
+            has_parent,
+            layout_mode,
+        });
+    }
+
+    LayoutStatusInfo {
+        active_tags,
+        focused_tags,
+        _num_tags,
+        windows,
+        focused_title,
+        focused_layout_mode,
+    }
+}
+
+fn get_short_app_name(app_id: &str) -> String {
+    let lower = app_id.to_lowercase();
+    if lower.contains("foot") || lower.contains("terminal") || lower.contains("kitty") || lower.contains("alacritty") {
+        "Term".to_string()
+    } else if lower.contains("firefox") || lower.contains("chrome") || lower.contains("qutebrowser") || lower.contains("browser") {
+        "Web".to_string()
+    } else if lower.contains("code") || lower.contains("vscodium") || lower.contains("neovim") || lower.contains("nvim") {
+        "Code".to_string()
+    } else if lower.contains("spotify") || lower.contains("music") {
+        "Musc".to_string()
+    } else if lower.contains("discord") {
+        "Disc".to_string()
+    } else if lower.contains("interface") {
+        "Intf".to_string()
+    } else if lower.is_empty() {
+        "Win".to_string()
+    } else {
+        let mut s = lower;
+        s.truncate(4);
+        if let Some(first) = s.chars().next() {
+            let first_upper = first.to_uppercase().to_string();
+            format!("{}{}", first_upper, &s[first.len_utf8()..])
+        } else {
+            "Win".to_string()
+        }
+    }
+}
+
 pub fn view(state: &mut LayoutState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_focused: &[bool]) -> PageContent {
     let mut pc = PageContent::new();
     let mut y = cy + 12.0;
 
+    // Current Layout Section (Read-only visual preview)
+    let mut sec_cl = Section::new(&mut pc, cx, y, cw, "Current Layout");
+    sec_cl.spacing(8.0);
+    
+    let info = read_current_layout_status();
+    
+    let card_w = (cw - 24.0) / 2.0;
+    let card_h = 135.0;
+    
+    for tag_idx in 0..4 {
+        let col = tag_idx % 2;
+        let row = tag_idx / 2;
+        let tx = cx + 8.0 + col as f32 * (card_w + 8.0);
+        let ty = sec_cl.ay() + row as f32 * (card_h + 8.0);
+        
+        let is_active = (info.active_tags & (1 << tag_idx)) != 0;
+        let is_focused = (info.focused_tags & (1 << tag_idx)) != 0;
+        
+        // Draw card border and background
+        let border_color = if is_focused {
+            [0.2, 0.6, 1.0, 1.0]
+        } else if is_active {
+            [0.28, 0.28, 0.32, 1.0]
+        } else {
+            [0.16, 0.16, 0.18, 1.0]
+        };
+        
+        let bg_color = if is_active {
+            [0.08, 0.08, 0.11, 0.9]
+        } else {
+            [0.05, 0.05, 0.07, 0.9]
+        };
+        
+        pc.rect(border_color, tx, ty, card_w, card_h);
+        pc.rect(bg_color, tx + 1.0, ty + 1.0, card_w - 2.0, card_h - 2.0);
+        
+        // Render tag label in top-left of the card
+        let tag_label = format!("T{}", tag_idx + 1);
+        let tag_label_color = if is_focused {
+            [1.0, 1.0, 1.0, 1.0]
+        } else if is_active {
+            [0.8, 0.8, 0.85, 1.0]
+        } else {
+            [0.4, 0.4, 0.45, 1.0]
+        };
+        pc.text(&tag_label, tx + 8.0, ty + 6.0, 9.5, tag_label_color);
+        
+        // Render miniature screen preview inside the box (on the left side)
+        let px = tx + 8.0;
+        let py = ty + 24.0;
+        let p_w = 70.0;
+        let p_h = 44.0;
+        
+        // Screen background
+        pc.rect([0.04, 0.04, 0.06, 1.0], px, py, p_w, p_h);
+        pc.rect([0.16, 0.16, 0.18, 1.0], px, py, p_w, 1.0); // Top border
+        pc.rect([0.16, 0.16, 0.18, 1.0], px, py + p_h - 1.0, p_w, 1.0); // Bottom border
+        pc.rect([0.16, 0.16, 0.18, 1.0], px, py, 1.0, p_h); // Left border
+        pc.rect([0.16, 0.16, 0.18, 1.0], px + p_w - 1.0, py, 1.0, p_h); // Right border
+        
+        // Find windows for this tag
+        let tag_windows: Vec<&PreviewWindow> = info.windows.iter()
+            .filter(|w| w.app_id != "clear-status-interface" && ((w.tags & (1 << tag_idx)) != 0 || w.tags == u32::MAX))
+            .collect();
+            
+        // Reference screen size
+        let screen_w = 1920.0;
+        let screen_h = 1200.0;
+        let scale_x = p_w / screen_w;
+        let scale_y = p_h / screen_h;
+        
+        for win in &tag_windows {
+            let wx = (px + win.x * scale_x).max(px).min(px + p_w);
+            let wy = (py + win.y * scale_y).max(py).min(py + p_h);
+            let ww = (win.w * scale_x).min(p_w - (wx - px));
+            let wh = (win.h * scale_y).min(p_h - (wy - py));
+            
+            let is_win_focused = !win.title.is_empty() && win.title == info.focused_title;
+            let color = if is_win_focused {
+                [0.2, 0.6, 1.0, 1.0]
+            } else {
+                [0.4, 0.4, 0.45, 1.0]
+            };
+            let fill = if is_win_focused {
+                [0.10, 0.32, 0.55, 0.4]
+            } else {
+                [0.12, 0.12, 0.15, 0.4]
+            };
+            
+            pc.rect(color, wx, wy, ww, wh);
+            pc.rect(fill, wx + 0.5, wy + 0.5, ww - 1.0, wh - 1.0);
+        }
+        
+        // Determine Tag layout mode
+        let layout_name = if is_focused {
+            info.focused_layout_mode.clone()
+        } else if let Some(win) = tag_windows.first() {
+            win.layout_mode.clone()
+        } else {
+            let layout_idx = state.tag_layout_menus.get(tag_idx).map(|m| m.selected).unwrap_or(0);
+            let layout_modes = ["Cascade", "Grid", "Fullscreen", "Floating", "Popup"];
+            layout_modes.get(layout_idx).copied().unwrap_or("Cascade").to_string()
+        };
+
+        // Render Visual Focus Hierarchy Tree on the right side of the card
+        let tx_tree = tx + 84.0;
+        let ty_tree = ty + 18.0;
+        let t_w = card_w - 92.0;
+        let t_h = card_h - 26.0;
+        
+        struct TreeNode {
+            label: String,
+            is_focused: bool,
+            is_layout: bool,
+            is_role: bool,
+            x: f32,
+            y: f32,
+            w: f32,
+            h: f32,
+        }
+        
+        struct TreeLine {
+            x0: f32,
+            y0: f32,
+            x1: f32,
+            y1: f32,
+            is_dotted: bool,
+        }
+        
+        let mut nodes = Vec::new();
+        let mut lines = Vec::new();
+        
+        // 1. Root Node: Layout mode
+        let layout_label = match layout_name.as_str() {
+            "Grid" => "GRID",
+            "Cascade" => "CASC",
+            "Fullscreen" => "FULL",
+            "Floating" => "FLOT",
+            "Popup" => "POP",
+            _ => "CASC",
+        };
+        nodes.push(TreeNode {
+            label: layout_label.to_string(),
+            is_focused: false,
+            is_layout: true,
+            is_role: false,
+            x: tx_tree + 2.0,
+            y: ty_tree + (t_h - 14.0) / 2.0,
+            w: 28.0,
+            h: 14.0,
+        });
+        
+        // 2. Window hierarchy
+        if tag_windows.is_empty() {
+            nodes.push(TreeNode {
+                label: "(empty)".to_string(),
+                is_focused: false,
+                is_layout: false,
+                is_role: true,
+                x: tx_tree + 46.0,
+                y: ty_tree + (t_h - 12.0) / 2.0,
+                w: 42.0,
+                h: 12.0,
+            });
+            lines.push(TreeLine {
+                x0: tx_tree + 30.0,
+                y0: ty_tree + t_h / 2.0,
+                x1: tx_tree + 46.0,
+                y1: ty_tree + t_h / 2.0,
+                is_dotted: true,
+            });
+        } else {
+            // Group transient/child windows under their parent
+            struct WinNode {
+                win: PreviewWindow,
+                children: Vec<PreviewWindow>,
+            }
+            let mut groups: Vec<WinNode> = Vec::new();
+            for w in &tag_windows {
+                if w.has_parent && !groups.is_empty() {
+                    groups.last_mut().unwrap().children.push((*w).clone());
+                } else {
+                    groups.push(WinNode {
+                        win: (*w).clone(),
+                        children: Vec::new(),
+                    });
+                }
+            }
+            
+            let num_roots = groups.len();
+            if num_roots == 1 {
+                // One root group: Layout -> Root -> Children
+                let root_g = &groups[0];
+                let rx = tx_tree + 44.0;
+                let ry = ty_tree + (t_h - 14.0) / 2.0;
+                let is_root_focused = !root_g.win.title.is_empty() && root_g.win.title == info.focused_title;
+                nodes.push(TreeNode {
+                    label: get_short_app_name(&root_g.win.app_id),
+                    is_focused: is_root_focused,
+                    is_layout: false,
+                    is_role: false,
+                    x: rx,
+                    y: ry,
+                    w: 42.0,
+                    h: 14.0,
+                });
+                lines.push(TreeLine {
+                    x0: tx_tree + 30.0,
+                    y0: ty_tree + t_h / 2.0,
+                    x1: rx,
+                    y1: ty_tree + t_h / 2.0,
+                    is_dotted: false,
+                });
+                
+                let child_count = root_g.children.len();
+                for (ci, child) in root_g.children.iter().enumerate() {
+                    let cx = tx_tree + 104.0;
+                    let cy = if child_count == 1 {
+                        ty_tree + (t_h - 12.0) / 2.0
+                    } else {
+                        ty_tree + 6.0 + (ci as f32 / (child_count - 1) as f32) * (t_h - 24.0)
+                    };
+                    let is_child_focused = !child.title.is_empty() && child.title == info.focused_title;
+                    nodes.push(TreeNode {
+                        label: get_short_app_name(&child.app_id),
+                        is_focused: is_child_focused,
+                        is_layout: false,
+                        is_role: false,
+                        x: cx,
+                        y: cy,
+                        w: 32.0,
+                        h: 12.0,
+                    });
+                    lines.push(TreeLine {
+                        x0: rx + 42.0,
+                        y0: ry + 7.0,
+                        x1: cx,
+                        y1: cy + 6.0,
+                        is_dotted: true,
+                    });
+                }
+            } else if num_roots == 2 {
+                // Two root groups (Master & Stack): C -> M & S -> Windows -> Children
+                let m_y = ty_tree + (t_h / 2.0) - 20.0;
+                let s_y = ty_tree + (t_h / 2.0) + 20.0;
+                
+                // M indicator
+                nodes.push(TreeNode {
+                    label: "M".to_string(),
+                    is_focused: false,
+                    is_layout: false,
+                    is_role: true,
+                    x: tx_tree + 44.0,
+                    y: m_y - 6.0,
+                    w: 12.0,
+                    h: 12.0,
+                });
+                lines.push(TreeLine {
+                    x0: tx_tree + 30.0,
+                    y0: ty_tree + t_h / 2.0,
+                    x1: tx_tree + 44.0,
+                    y1: m_y,
+                    is_dotted: false,
+                });
+                
+                // Master Window
+                let m_win = &groups[0];
+                let is_m_focused = !m_win.win.title.is_empty() && m_win.win.title == info.focused_title;
+                nodes.push(TreeNode {
+                    label: get_short_app_name(&m_win.win.app_id),
+                    is_focused: is_m_focused,
+                    is_layout: false,
+                    is_role: false,
+                    x: tx_tree + 68.0,
+                    y: m_y - 7.0,
+                    w: 36.0,
+                    h: 14.0,
+                });
+                lines.push(TreeLine {
+                    x0: tx_tree + 56.0,
+                    y0: m_y,
+                    x1: tx_tree + 68.0,
+                    y1: m_y,
+                    is_dotted: false,
+                });
+                
+                // S indicator
+                nodes.push(TreeNode {
+                    label: "S".to_string(),
+                    is_focused: false,
+                    is_layout: false,
+                    is_role: true,
+                    x: tx_tree + 44.0,
+                    y: s_y - 6.0,
+                    w: 12.0,
+                    h: 12.0,
+                });
+                lines.push(TreeLine {
+                    x0: tx_tree + 30.0,
+                    y0: ty_tree + t_h / 2.0,
+                    x1: tx_tree + 44.0,
+                    y1: s_y,
+                    is_dotted: false,
+                });
+                
+                // Stack Window
+                let s_win = &groups[1];
+                let is_s_focused = !s_win.win.title.is_empty() && s_win.win.title == info.focused_title;
+                nodes.push(TreeNode {
+                    label: get_short_app_name(&s_win.win.app_id),
+                    is_focused: is_s_focused,
+                    is_layout: false,
+                    is_role: false,
+                    x: tx_tree + 68.0,
+                    y: s_y - 7.0,
+                    w: 36.0,
+                    h: 14.0,
+                });
+                lines.push(TreeLine {
+                    x0: tx_tree + 56.0,
+                    y0: s_y,
+                    x1: tx_tree + 68.0,
+                    y1: s_y,
+                    is_dotted: false,
+                });
+                
+                // Master children
+                let m_child_count = m_win.children.len();
+                for (ci, child) in m_win.children.iter().enumerate() {
+                    let cx = tx_tree + 114.0;
+                    let cy = if m_child_count == 1 {
+                        m_y - 6.0
+                    } else {
+                        m_y - 20.0 + (ci as f32 / (m_child_count - 1) as f32) * 30.0
+                    };
+                    let is_child_focused = !child.title.is_empty() && child.title == info.focused_title;
+                    nodes.push(TreeNode {
+                        label: get_short_app_name(&child.app_id),
+                        is_focused: is_child_focused,
+                        is_layout: false,
+                        is_role: false,
+                        x: cx,
+                        y: cy,
+                        w: 28.0,
+                        h: 11.0,
+                    });
+                    lines.push(TreeLine {
+                        x0: tx_tree + 104.0,
+                        y0: m_y,
+                        x1: cx,
+                        y1: cy + 5.5,
+                        is_dotted: true,
+                    });
+                }
+                
+                // Stack children
+                let s_child_count = s_win.children.len();
+                for (ci, child) in s_win.children.iter().enumerate() {
+                    let cx = tx_tree + 114.0;
+                    let cy = if s_child_count == 1 {
+                        s_y - 6.0
+                    } else {
+                        s_y - 20.0 + (ci as f32 / (s_child_count - 1) as f32) * 30.0
+                    };
+                    let is_child_focused = !child.title.is_empty() && child.title == info.focused_title;
+                    nodes.push(TreeNode {
+                        label: get_short_app_name(&child.app_id),
+                        is_focused: is_child_focused,
+                        is_layout: false,
+                        is_role: false,
+                        x: cx,
+                        y: cy,
+                        w: 28.0,
+                        h: 11.0,
+                    });
+                    lines.push(TreeLine {
+                        x0: tx_tree + 104.0,
+                        y0: s_y,
+                        x1: cx,
+                        y1: cy + 5.5,
+                        is_dotted: true,
+                    });
+                }
+            } else {
+                // More than 2 roots: Master & Stacks (list of stack items)
+                let m_y = ty_tree + (t_h / 2.0) - 24.0;
+                let s_y = ty_tree + (t_h / 2.0) + 20.0;
+                
+                // M indicator
+                nodes.push(TreeNode {
+                    label: "M".to_string(),
+                    is_focused: false,
+                    is_layout: false,
+                    is_role: true,
+                    x: tx_tree + 44.0,
+                    y: m_y - 6.0,
+                    w: 12.0,
+                    h: 12.0,
+                });
+                lines.push(TreeLine {
+                    x0: tx_tree + 30.0,
+                    y0: ty_tree + t_h / 2.0,
+                    x1: tx_tree + 44.0,
+                    y1: m_y,
+                    is_dotted: false,
+                });
+                
+                // Master Window
+                let m_win = &groups[0];
+                let is_m_focused = !m_win.win.title.is_empty() && m_win.win.title == info.focused_title;
+                nodes.push(TreeNode {
+                    label: get_short_app_name(&m_win.win.app_id),
+                    is_focused: is_m_focused,
+                    is_layout: false,
+                    is_role: false,
+                    x: tx_tree + 68.0,
+                    y: m_y - 7.0,
+                    w: 36.0,
+                    h: 14.0,
+                });
+                lines.push(TreeLine {
+                    x0: tx_tree + 56.0,
+                    y0: m_y,
+                    x1: tx_tree + 68.0,
+                    y1: m_y,
+                    is_dotted: false,
+                });
+                
+                // S indicator
+                nodes.push(TreeNode {
+                    label: "S".to_string(),
+                    is_focused: false,
+                    is_layout: false,
+                    is_role: true,
+                    x: tx_tree + 44.0,
+                    y: s_y - 6.0,
+                    w: 12.0,
+                    h: 12.0,
+                });
+                lines.push(TreeLine {
+                    x0: tx_tree + 30.0,
+                    y0: ty_tree + t_h / 2.0,
+                    x1: tx_tree + 44.0,
+                    y1: s_y,
+                    is_dotted: false,
+                });
+                
+                // Render first 2 Stack items vertically spaced
+                let s1_win = &groups[1];
+                let is_s1_focused = !s1_win.win.title.is_empty() && s1_win.win.title == info.focused_title;
+                let s1_y = s_y - 14.0;
+                nodes.push(TreeNode {
+                    label: get_short_app_name(&s1_win.win.app_id),
+                    is_focused: is_s1_focused,
+                    is_layout: false,
+                    is_role: false,
+                    x: tx_tree + 68.0,
+                    y: s1_y - 7.0,
+                    w: 36.0,
+                    h: 14.0,
+                });
+                lines.push(TreeLine {
+                    x0: tx_tree + 56.0,
+                    y0: s_y,
+                    x1: tx_tree + 68.0,
+                    y1: s1_y,
+                    is_dotted: false,
+                });
+                
+                let s2_win = &groups[2];
+                let is_s2_focused = !s2_win.win.title.is_empty() && s2_win.win.title == info.focused_title;
+                let s2_y = s_y + 14.0;
+                nodes.push(TreeNode {
+                    label: get_short_app_name(&s2_win.win.app_id),
+                    is_focused: is_s2_focused,
+                    is_layout: false,
+                    is_role: false,
+                    x: tx_tree + 68.0,
+                    y: s2_y - 7.0,
+                    w: 36.0,
+                    h: 14.0,
+                });
+                lines.push(TreeLine {
+                    x0: tx_tree + 56.0,
+                    y0: s_y,
+                    x1: tx_tree + 68.0,
+                    y1: s2_y,
+                    is_dotted: false,
+                });
+            }
+        }
+        
+        let line_color = if is_active {
+            [0.30, 0.30, 0.35, 0.8]
+        } else {
+            [0.18, 0.18, 0.20, 0.6]
+        };
+        let draw_line = |pc: &mut PageContent, x0: f32, y0: f32, x1: f32, y1: f32, is_dotted: bool, color: [f32; 4]| {
+            if is_dotted {
+                if (x0 - x1).abs() < 0.1 {
+                    let sy = y0.min(y1);
+                    let ey = y0.max(y1);
+                    let mut curr_y = sy;
+                    while curr_y <= ey {
+                        pc.rect(color, x0 - 0.5, curr_y, 1.0, 1.0);
+                        curr_y += 3.0;
+                    }
+                } else if (y0 - y1).abs() < 0.1 {
+                    let sx = x0.min(x1);
+                    let ex = x0.max(x1);
+                    let mut curr_x = sx;
+                    while curr_x <= ex {
+                        pc.rect(color, curr_x, y0 - 0.5, 1.0, 1.0);
+                        curr_x += 3.0;
+                    }
+                } else {
+                    pc.rect(color, x0.min(x1), y0.min(y1), (x0 - x1).abs().max(1.0), (y0 - y1).abs().max(1.0));
+                }
+            } else {
+                if (x0 - x1).abs() < 0.1 {
+                    pc.rect(color, x0 - 0.5, y0.min(y1), 1.0, (y0 - y1).abs());
+                } else if (y0 - y1).abs() < 0.1 {
+                    pc.rect(color, x0.min(x1), y0 - 0.5, (x0 - x1).abs(), 1.0);
+                } else {
+                    pc.rect(color, x0.min(x1), y0.min(y1), (x0 - x1).abs().max(1.0), (y0 - y1).abs().max(1.0));
+                }
+            }
+        };
+        
+        for line in &lines {
+            let mid_x = (line.x0 + line.x1) / 2.0;
+            draw_line(&mut pc, line.x0, line.y0, mid_x, line.y0, line.is_dotted, line_color);
+            draw_line(&mut pc, mid_x, line.y0, mid_x, line.y1, line.is_dotted, line_color);
+            draw_line(&mut pc, mid_x, line.y1, line.x1, line.y1, line.is_dotted, line_color);
+        }
+        
+        // Draw nodes
+        for node in nodes {
+            let border = if node.is_layout {
+                [0.32, 0.32, 0.38, 1.0]
+            } else if node.is_role {
+                [0.20, 0.20, 0.24, 0.8]
+            } else if node.is_focused {
+                [0.2, 0.6, 1.0, 1.0]
+            } else {
+                [0.22, 0.22, 0.26, 1.0]
+            };
+            
+            let bg = if node.is_layout {
+                [0.14, 0.14, 0.18, 1.0]
+            } else if node.is_role {
+                [0.09, 0.09, 0.11, 0.9]
+            } else if node.is_focused {
+                [0.10, 0.32, 0.55, 1.0]
+            } else {
+                [0.11, 0.11, 0.15, 1.0]
+            };
+            
+            pc.rect(border, node.x, node.y, node.w, node.h);
+            pc.rect(bg, node.x + 1.0, node.y + 1.0, node.w - 2.0, node.h - 2.0);
+            
+            let text_color = if node.is_focused {
+                [1.0, 1.0, 1.0, 1.0]
+            } else if node.is_role {
+                [0.48, 0.48, 0.52, 1.0]
+            } else {
+                [0.78, 0.78, 0.82, 1.0]
+            };
+            
+            let text_sz = if node.is_layout {
+                7.5
+            } else if node.is_role {
+                7.0
+            } else {
+                7.0
+            };
+            
+            let char_width = text_sz * 0.52;
+            let text_w = node.label.len() as f32 * char_width;
+            let tx_offset = ((node.w - text_w) / 2.0).max(1.0);
+            let ty_offset = ((node.h - text_sz) / 2.0).max(1.0);
+            
+            pc.text(&node.label, node.x + tx_offset, node.y + ty_offset, text_sz, text_color);
+        }
+    }
+    
+    sec_cl.content_y += 2.0 * (card_h + 8.0) + 4.0;
+    y = sec_cl.finish(&mut pc);
+
     // 1. Border Width Section
     let mut sec_bw = Section::new(&mut pc, cx, y, cw, "Border Width");
     sec_bw.spacing(8.0);
@@ -350,9 +1138,7 @@ pub fn view(state: &mut LayoutState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_fo
     }
     default_layouts_sec.finish_focused(&mut pc, sec_focused.get(3).copied().unwrap_or(false));
 
-    for menu in &mut state.tag_layout_menus {
-        menu.render_popover(&mut pc);
-    }
+
 
     pc
 }
@@ -385,25 +1171,37 @@ pub fn update(state: &mut LayoutState, msg: LayoutMessage) {
             let val = v.min(200);
             state.cascade_offset = val;
             state.cascade_offset_spinbox.value = val as i32;
-            apply_all_widths(state);
+            apply_single_layout_param("cascade_offset", val);
         }
         LayoutMessage::SetEdgeGap(v) => {
             let val = v.min(200);
             state.edge_gap = val;
             state.edge_gap_spinbox.value = val as i32;
-            apply_all_widths(state);
+            apply_edge_gap(val);
         }
         LayoutMessage::SetTopGap(v) => {
             let val = v.min(200);
             state.top_gap = val;
             state.top_gap_spinbox.value = val as i32;
-            apply_all_widths(state);
+            apply_single_layout_param("gap_top", val);
+        }
+        LayoutMessage::SetGridGap(v) => {
+            let val = v.min(200);
+            state.grid_gap = val;
+            state.grid_gap_spinbox.value = val as i32;
+            apply_single_layout_param("grid_gap", val);
         }
         LayoutMessage::SetTransitionDuration(v) => {
             let val = v.min(2000);
             state.transition_duration = val;
             state.transition_duration_spinbox.value = val as i32;
-            apply_all_widths(state);
+            apply_single_layout_param("transition_duration", val);
+        }
+        LayoutMessage::SetStatusHeight(v) => {
+            let val = v.min(100);
+            state.status_height = val;
+            state.status_height_spinbox.value = val as i32;
+            apply_single_layout_param("bar_height", val);
         }
         LayoutMessage::SetTagLayout(tag, idx) => {
             if tag >= 1 && tag <= 4 && idx < 5 {
diff --git a/src/pages/typeface.rs b/src/pages/typeface.rs
index 9e799f0..e53f283 100644
--- a/src/pages/typeface.rs
+++ b/src/pages/typeface.rs
@@ -794,10 +794,7 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_
 
 
     // Render dropdown popovers on top of all other widgets
-    state.borders_menu.render_popover(&mut pc);
-    state.status_menu.render_popover(&mut pc);
-    state.fuzzel_menu.render_popover(&mut pc);
-    state.terminal_menu.render_popover(&mut pc);
+
 
     pc
 }