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

commit083142f05b4b41971bed3b217e42c87bfe45c961
parentfeac1d2d83
authorLucas Galante <[email protected]>
date2026-07-06 09:54
style: prevent double rendering, clean up fonts and pages, overhaul packages master-detail layout, and implement global viewport clipping

 src/main.rs                |   5 +-
 src/pages/accounts.rs      |  32 +++---
 src/pages/audio.rs         |  95 +++++++++++-----
 src/pages/fonts.rs         |  46 +++++---
 src/pages/network.rs       |  36 +++---
 src/pages/notifications.rs |   4 +
 src/pages/packages.rs      | 266 ++++++++++++++++++++++++++-------------------
 src/pages/processes.rs     |  34 +++---
 src/pages/storage.rs       |  48 ++++----
 src/renderer.rs            | 101 +++++++++++++----
 10 files changed, 427 insertions(+), 240 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index b872e0e..4ff1e4a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -138,8 +138,7 @@ impl cce_ui::engine::Application for SystemInterface {
         let win_opacity = 1.0f32;
         let win_radius = 12;
 
-        let mut font_system = FontSystem::new();
-        font_system.db_mut().load_fonts_dir("/home/lsgalante/Dropbox/Fonts");
+        let font_system = cce_ui::create_font_system();
 
         let mut this = Self {
             app: app_state,
@@ -348,7 +347,7 @@ impl cce_ui::engine::Application for SystemInterface {
     }
 
     fn clear_color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
+        [0.039, 0.102, 0.055, 1.0]
     }
 
     fn handle_pointer_move(&mut self, pos: cce_ui::engine::LogicalPosition, needs_rebuild: &mut bool) {
diff --git a/src/pages/accounts.rs b/src/pages/accounts.rs
index 7c47011..21cf72d 100644
--- a/src/pages/accounts.rs
+++ b/src/pages/accounts.rs
@@ -840,20 +840,24 @@ impl crate::pages::AppPage for AccountsState {
 
     fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
         vec![
-            cce_ui::widget::SectionContainer::new("Accounts").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
-            cce_ui::widget::SectionContainer::new("Modify Accounts").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
+            cce_ui::widget::SectionContainer::new("Accounts")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
+            cce_ui::widget::SectionContainer::new("Modify Accounts")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
         ]
     }
 
diff --git a/src/pages/audio.rs b/src/pages/audio.rs
index 039f1e3..a41f04f 100644
--- a/src/pages/audio.rs
+++ b/src/pages/audio.rs
@@ -428,20 +428,24 @@ impl crate::pages::AppPage for AudioState {
 
     fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
         vec![
-            cce_ui::widget::SectionContainer::new("Output").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
-            cce_ui::widget::SectionContainer::new("Input").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
+            cce_ui::widget::SectionContainer::new("Output")
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                })
+                .with_draw_children(false),
+            cce_ui::widget::SectionContainer::new("Input")
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                })
+                .with_draw_children(false),
         ]
     }
 
@@ -454,17 +458,26 @@ impl crate::pages::AppPage for AudioState {
         cce_ui::widget::link_parent_child(page_root, &mut sec_containers[0], ctx);
         cce_ui::widget::link_parent_child(page_root, &mut sec_containers[1], ctx);
 
-        for sb in &mut self.sink_spinboxes {
-            cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut **sb, ctx);
-        }
-        for sb in &mut self.source_spinboxes {
-            cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut **sb, ctx);
-        }
-        for slider in &mut self.sink_sliders {
-            cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut **slider, ctx);
+        for (i, sink) in self.sinks.iter().enumerate() {
+            if sink.active {
+                if i < self.sink_spinboxes.len() {
+                    cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut *self.sink_spinboxes[i], ctx);
+                }
+                if i < self.sink_sliders.len() {
+                    cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut *self.sink_sliders[i], ctx);
+                }
+            }
         }
-        for slider in &mut self.source_sliders {
-            cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut **slider, ctx);
+
+        for (i, src) in self.sources.iter().enumerate() {
+            if src.active {
+                if i < self.source_spinboxes.len() {
+                    cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut *self.source_spinboxes[i], ctx);
+                }
+                if i < self.source_sliders.len() {
+                    cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut *self.source_sliders[i], ctx);
+                }
+            }
         }
     }
 
@@ -583,9 +596,41 @@ mod tests {
 
     #[test]
     fn test_view_layout_grid() {
-        let mut state = AudioState::default();
+        use cce_ui::widget::{Spinbox, Slider};
+        let mut state = AudioState {
+            loaded: true,
+            sinks: vec![
+                AudioSink { id: 71, name: "Speaker".to_string(), volume: 0.57, muted: false, active: true },
+                AudioSink { id: 70, name: "HDMI1".to_string(), volume: 0.5, muted: false, active: false },
+                AudioSink { id: 69, name: "HDMI2".to_string(), volume: 0.5, muted: false, active: false },
+                AudioSink { id: 68, name: "HDMI3".to_string(), volume: 0.5, muted: false, active: false },
+            ],
+            sources: vec![],
+            sink_spinboxes: vec![
+                Box::new(Spinbox::new(57, 0, 100, 1)),
+                Box::new(Spinbox::new(50, 0, 100, 1)),
+                Box::new(Spinbox::new(50, 0, 100, 1)),
+                Box::new(Spinbox::new(50, 0, 100, 1)),
+            ],
+            source_spinboxes: vec![],
+            sink_sliders: vec![
+                Box::new(Slider::new()),
+                Box::new(Slider::new()),
+                Box::new(Slider::new()),
+                Box::new(Slider::new()),
+            ],
+            source_sliders: vec![],
+            sink_dragging: None,
+            source_dragging: None,
+        };
         let mut layout = AdaptiveGrid::new(260.0, 20.0);
         let pc = view(&mut state, 10.0, 20.0, 800.0, 600.0, &[false, false], &mut layout, &mut cce_ui::context::UiContext::new());
+        for (i, (c, x, y, w, h, r, _)) in pc.rects.iter().enumerate() {
+            println!("TEST_PC_RECT {}: color={:?}, x={}, y={}, w={}, h={}, r={}", i, c, x, y, w, h, r);
+        }
+        for (i, (t, sz, x, y, c, _, _)) in pc.texts.iter().enumerate() {
+            println!("TEST_PC_TEXT {}: text='{}', size={}, x={}, y={}, color={:?}", i, t, sz, x, y, c);
+        }
         assert!(!pc.rects.is_empty() || !pc.texts.is_empty());
     }
 
diff --git a/src/pages/fonts.rs b/src/pages/fonts.rs
index 1d3c590..5330159 100644
--- a/src/pages/fonts.rs
+++ b/src/pages/fonts.rs
@@ -120,11 +120,11 @@ impl AppPage for FontsState {
 
     fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
         vec![
-            cce_ui::widget::SectionContainer::new("Preferred Fonts"),
-            cce_ui::widget::SectionContainer::new("Borders"),
-            cce_ui::widget::SectionContainer::new("Status Interface"),
-            cce_ui::widget::SectionContainer::new("Fuzzel"),
-            cce_ui::widget::SectionContainer::new("Terminal"),
+            cce_ui::widget::SectionContainer::new("Preferred Fonts").with_draw_children(false),
+            cce_ui::widget::SectionContainer::new("Borders").with_draw_children(false),
+            cce_ui::widget::SectionContainer::new("Status Interface").with_draw_children(false),
+            cce_ui::widget::SectionContainer::new("Fuzzel").with_draw_children(false),
+            cce_ui::widget::SectionContainer::new("Terminal").with_draw_children(false),
         ]
     }
 
@@ -173,39 +173,49 @@ impl AppPage for FontsState {
         let mut builder = cce_ui::layout::PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(5);
 
         builder.add_section(&mut final_pc, "Preferred Fonts", sec_focused.get(0).copied().unwrap_or(false), |sec| {
-            sec.widget_full(&mut self.sans_box, 44.0, ctx);
-            sec.widget_full(&mut self.serif_box, 44.0, ctx);
-            sec.widget_full(&mut self.mono_box, 44.0, ctx);
+            let mut stack = sec.vstack(8.0);
+            let sec_w = stack.context.cw;
+            stack.add_widget(&mut self.sans_box, sec_w - 28.0, 44.0, ctx);
+            stack.add_widget(&mut self.serif_box, sec_w - 28.0, 44.0, ctx);
+            stack.add_widget(&mut self.mono_box, sec_w - 28.0, 44.0, ctx);
         });
 
         builder.add_section(&mut final_pc, "Borders", sec_focused.get(1).copied().unwrap_or(false), |sec| {
-            sec.widget_full(&mut self.borders_menu, 44.0, ctx);
+            let mut stack = sec.vstack(8.0);
+            let sec_w = stack.context.cw;
+            stack.add_widget(&mut self.borders_menu, sec_w - 28.0, 44.0, ctx);
             if self.borders_menu.selected == 3 {
-                sec.widget_full(&mut self.borders_box, 44.0, ctx);
+                stack.add_widget(&mut self.borders_box, sec_w - 28.0, 44.0, ctx);
             }
         });
 
         builder.add_section(&mut final_pc, "Status Interface", sec_focused.get(2).copied().unwrap_or(false), |sec| {
-            sec.widget_full(&mut self.status_menu, 44.0, ctx);
+            let mut stack = sec.vstack(8.0);
+            let sec_w = stack.context.cw;
+            stack.add_widget(&mut self.status_menu, sec_w - 28.0, 44.0, ctx);
             if self.status_menu.selected == 3 {
-                sec.widget_full(&mut self.status_box, 44.0, ctx);
+                stack.add_widget(&mut self.status_box, sec_w - 28.0, 44.0, ctx);
             }
         });
 
         builder.add_section(&mut final_pc, "Fuzzel", sec_focused.get(3).copied().unwrap_or(false), |sec| {
-            sec.widget_full(&mut self.fuzzel_menu, 44.0, ctx);
+            let mut stack = sec.vstack(8.0);
+            let sec_w = stack.context.cw;
+            stack.add_widget(&mut self.fuzzel_menu, sec_w - 28.0, 44.0, ctx);
             if self.fuzzel_menu.selected == 3 {
-                sec.widget_full(&mut self.fuzzel_box, 44.0, ctx);
+                stack.add_widget(&mut self.fuzzel_box, sec_w - 28.0, 44.0, ctx);
             }
-            sec.widget_full(&mut self.fuzzel_size_box, 44.0, ctx);
+            stack.add_widget(&mut self.fuzzel_size_box, sec_w - 28.0, 44.0, ctx);
         });
 
         builder.add_section(&mut final_pc, "Terminal", sec_focused.get(4).copied().unwrap_or(false), |sec| {
-            sec.widget_full(&mut self.terminal_menu, 44.0, ctx);
+            let mut stack = sec.vstack(8.0);
+            let sec_w = stack.context.cw;
+            stack.add_widget(&mut self.terminal_menu, sec_w - 28.0, 44.0, ctx);
             if self.terminal_menu.selected == 3 {
-                sec.widget_full(&mut self.terminal_box, 44.0, ctx);
+                stack.add_widget(&mut self.terminal_box, sec_w - 28.0, 44.0, ctx);
             }
-            sec.widget_full(&mut self.terminal_size_box, 44.0, ctx);
+            stack.add_widget(&mut self.terminal_size_box, sec_w - 28.0, 44.0, ctx);
         });
 
         final_pc
diff --git a/src/pages/network.rs b/src/pages/network.rs
index 57e10f5..d420ee3 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -526,20 +526,24 @@ impl crate::pages::AppPage for NetworkState {
 
     fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
         vec![
-            cce_ui::widget::SectionContainer::new("WiFi").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
-            cce_ui::widget::SectionContainer::new("Bluetooth").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
+            cce_ui::widget::SectionContainer::new("WiFi")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
+            cce_ui::widget::SectionContainer::new("Bluetooth")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
         ]
     }
 
@@ -553,9 +557,7 @@ impl crate::pages::AppPage for NetworkState {
         cce_ui::widget::link_parent_child(page_root, &mut sec_containers[1], ctx);
 
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.wifi_toggle, ctx);
-        if self.wifi_enabled && !self.available.is_empty() {
-            cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.wifi_list_box.scroll_box, ctx);
-        }
+        cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.wifi_list_box.scroll_box, ctx);
         cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.bt_toggle, ctx);
     }
 
diff --git a/src/pages/notifications.rs b/src/pages/notifications.rs
index f7c32ee..ec87eee 100644
--- a/src/pages/notifications.rs
+++ b/src/pages/notifications.rs
@@ -195,6 +195,7 @@ impl AppPage for NotificationsState {
     fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
         vec![
             cce_ui::widget::SectionContainer::new("Notifications Settings")
+                .with_draw_children(false)
                 .with_layout(cce_ui::widget::AdaptiveGridLayout {
                     min_col_width: 140.0,
                     gap: 8.0,
@@ -214,6 +215,9 @@ impl AppPage for NotificationsState {
         for sec in sec_containers.iter_mut() {
             cce_ui::widget::link_parent_child(page_root, sec, ctx);
         }
+        cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.enable_toggle, ctx);
+        cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.bell_menu, ctx);
+        cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.duration_spinbox, ctx);
     }
 
     fn view(
diff --git a/src/pages/packages.rs b/src/pages/packages.rs
index 3ef981b..ecaf534 100644
--- a/src/pages/packages.rs
+++ b/src/pages/packages.rs
@@ -327,8 +327,8 @@ pub fn view(
     ctx: &mut cce_ui::context::UiContext,
 ) -> PageContent {
     let mut final_pc = PageContent::new();
-    let sec_w = 320.0f32;
-    let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(2);
+    let sec_w = 260.0f32;
+    let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(3);
 
     // Section 1: Packages List
     builder.add_section(&mut final_pc, "Packages", sec_focused.first().copied().unwrap_or(false), |sec| {
@@ -398,6 +398,7 @@ pub fn view(
 
             match state.active_tab {
                 PackageTab::Installed => {
+                    state.installed_list_box.clear_children(ctx);
                     render_widget(sec.pc, &mut state.installed_list_box, list_box_x, list_box_y, list_box_w, list_box_h, ctx);
 
                     let filtered: Vec<&PackageInfo> = state.installed.iter()
@@ -421,6 +422,7 @@ pub fn view(
                             item.title = pkg.name.clone();
                             item.subtitle = Some(format!("Version: {}", pkg.version));
                             item.selected = Some(&pkg.name) == state.selected_package.as_ref();
+                            cce_ui::widget::link_parent_child(&mut state.installed_list_box.scroll_box, item, ctx);
                             render_widget(sec.pc, item, list_box_x + 24.0, draw_y, list_box_w - 44.0, item_h, ctx);
                         }
                     }
@@ -431,6 +433,7 @@ pub fn view(
                     }
                 }
                 PackageTab::Updates => {
+                    state.updates_list_box.clear_children(ctx);
                     render_widget(sec.pc, &mut state.updates_list_box, list_box_x, list_box_y, list_box_w, list_box_h, ctx);
 
                     let filtered: Vec<&UpdateInfo> = state.updates.iter()
@@ -454,6 +457,7 @@ pub fn view(
                             item.title = pkg.name.clone();
                             item.subtitle = Some(format!("{}  ->  {}", pkg.old_version, pkg.new_version));
                             item.selected = Some(&pkg.name) == state.selected_package.as_ref();
+                            cce_ui::widget::link_parent_child(&mut state.updates_list_box.scroll_box, item, ctx);
                             render_widget(sec.pc, item, list_box_x + 24.0, draw_y, list_box_w - 44.0, item_h, ctx);
                         }
                     }
@@ -466,103 +470,102 @@ pub fn view(
             }
 
             sec.content_y += list_box_h;
+        }
+    });
 
-            sec.add_section("Package Info", false, |subsec| {
-                if state.loading_info {
-                    subsec.text("Loading package details...", 12.0, 0.0, 12.0, TEXT_DIM);
-                } else if let Some(ref pkg_name) = state.selected_package {
-                    if let Some(ref info_raw) = state.selected_package_info {
-                        let mut parsed = parse_package_info(info_raw);
-                        if parsed.name.is_empty() {
-                            parsed.name = pkg_name.clone();
-                        }
+    // Section 2: Package Details Info
+    builder.add_section(&mut final_pc, "Package Info", sec_focused.get(1).copied().unwrap_or(false), |sec1| {
+        if state.loading_info {
+            sec1.text("Loading package details...", 12.0, 0.0, 12.0, TEXT_DIM);
+        } else if let Some(ref pkg_name) = state.selected_package {
+            if let Some(ref info_raw) = state.selected_package_info {
+                let mut parsed = parse_package_info(info_raw);
+                if parsed.name.is_empty() {
+                    parsed.name = pkg_name.clone();
+                }
 
-                        subsec.text(&parsed.name, 12.0, 0.0, 13.0, WHITE);
+                sec1.text(&parsed.name, 12.0, 0.0, 13.0, WHITE);
 
-                        let render_detail = |sub: &mut SectionContext<'_, PageContent>, key: &str, val: &str| {
-                            sub.text(key, 12.0, 0.0, 11.0, TEXT_DIM);
-                            let val_start_x = 90.0f32;
-                            let usable_w = sub.cw - val_start_x - 12.0;
-                            let char_w = 6.0f32;
-                            let max_chars = (usable_w / char_w).max(15.0) as usize;
+                let render_detail = |sub: &mut SectionContext<'_, PageContent>, key: &str, val: &str| {
+                    sub.text(key, 12.0, 0.0, 11.0, TEXT_DIM);
+                    let val_start_x = 90.0f32;
+                    let usable_w = sub.cw - val_start_x - 12.0;
+                    let char_w = 6.0f32;
+                    let max_chars = (usable_w / char_w).max(15.0) as usize;
 
-                            let lines = wrap_text(val, max_chars);
-                            for line in &lines {
-                                sub.text(line, val_start_x, 0.0, 11.0, TEXT_FG);
-                            }
-                            if lines.is_empty() {
-                            }
-                        };
+                    let lines = wrap_text(val, max_chars);
+                    for line in &lines {
+                        sub.text(line, val_start_x, 0.0, 11.0, TEXT_FG);
+                    }
+                };
 
-                        render_detail(subsec, "Version:", &parsed.version);
-                        if !parsed.size.is_empty() {
-                            render_detail(subsec, "Size:", &parsed.size);
-                        }
-                        if !parsed.licenses.is_empty() {
-                            render_detail(subsec, "Licenses:", &parsed.licenses);
-                        }
-                        if !parsed.website.is_empty() {
-                            render_detail(subsec, "Website:", &parsed.website);
-                        }
-                        if !parsed.packager.is_empty() {
-                            render_detail(subsec, "Packager:", &parsed.packager);
-                        }
-                        if !parsed.build_date.is_empty() {
-                            render_detail(subsec, "Build Date:", &parsed.build_date);
-                        }
-                        if !parsed.description.is_empty() {
-                            subsec.separator();
-                            render_detail(subsec, "Description:", &parsed.description);
-                        }
-                        if !parsed.required_by.is_empty() && parsed.required_by != "None" {
-                            subsec.separator();
-                            subsec.text("Required By:", 12.0, 0.0, 11.0, TEXT_DIM);
-
-                            let reqs: Vec<&str> = parsed.required_by.split_whitespace().collect();
-                            let cols_count = 3;
-                            let gap = 6.0;
-                            let btn_h = 24.0;
-
-                            for chunk in reqs.chunks(cols_count) {
-                                let btn_y = subsec.ay();
-                                let cols = subsec.row_layout(cols_count, gap);
-                                for (i, &pkg) in chunk.iter().enumerate() {
-                                    if let Some(&(x, w)) = cols.get(i) {
-                                        let action = AppAction::Packages(PackagesMessage::SelectAndScrollPackage(pkg.to_string()));
-                                        subsec.button(pkg, x, btn_y, w, btn_h, TOGGLE_OFF, BTN_HOVER, TEXT_FG, action);
-                                    }
-                                }
+                render_detail(sec1, "Version:", &parsed.version);
+                if !parsed.size.is_empty() {
+                    render_detail(sec1, "Size:", &parsed.size);
+                }
+                if !parsed.licenses.is_empty() {
+                    render_detail(sec1, "Licenses:", &parsed.licenses);
+                }
+                if !parsed.website.is_empty() {
+                    render_detail(sec1, "Website:", &parsed.website);
+                }
+                if !parsed.packager.is_empty() {
+                    render_detail(sec1, "Packager:", &parsed.packager);
+                }
+                if !parsed.build_date.is_empty() {
+                    render_detail(sec1, "Build Date:", &parsed.build_date);
+                }
+                if !parsed.description.is_empty() {
+                    sec1.separator();
+                    render_detail(sec1, "Description:", &parsed.description);
+                }
+                if !parsed.required_by.is_empty() && parsed.required_by != "None" {
+                    sec1.separator();
+                    sec1.text("Required By:", 12.0, 0.0, 11.0, TEXT_DIM);
+
+                    let reqs: Vec<&str> = parsed.required_by.split_whitespace().collect();
+                    let cols_count = 3;
+                    let gap = 6.0;
+                    let btn_h = 24.0;
+
+                    for chunk in reqs.chunks(cols_count) {
+                        let btn_y = sec1.ay();
+                        let cols = sec1.row_layout(cols_count, gap);
+                        for (i, &pkg) in chunk.iter().enumerate() {
+                            if let Some(&(x, w)) = cols.get(i) {
+                                let action = AppAction::Packages(PackagesMessage::SelectAndScrollPackage(pkg.to_string()));
+                                sec1.button(pkg, x, btn_y, w, btn_h, TOGGLE_OFF, BTN_HOVER, TEXT_FG, action);
                             }
                         }
-                        if !parsed.commands.is_empty() {
-                            subsec.separator();
-                            render_detail(subsec, "Commands:", &parsed.commands);
-                        }
+                    }
+                }
+                if !parsed.commands.is_empty() {
+                    sec1.separator();
+                    render_detail(sec1, "Commands:", &parsed.commands);
+                }
 
-                        if state.active_tab == PackageTab::Installed {
-                            let mut stack = subsec.vstack(8.0);
-                            let btn_h = 32.0;
-                            let (btn_lbl, bg, hover, action) = if state.uninstalling {
-                                ("Uninstalling...", TOGGLE_OFF, TOGGLE_OFF, AppAction::Packages(PackagesMessage::StartUninstall(pkg_name.clone())))
-                            } else {
-                                ("Uninstall Package", RED, BTN_HOVER, AppAction::Packages(PackagesMessage::StartUninstall(pkg_name.clone())))
-                            };
-                            stack.add_row(1, 0.0, btn_h, |ctx, _, x, w| {
-                                ctx.button(btn_lbl, x, ctx.ay(), w, btn_h, bg, hover, WHITE, action.clone());
-                            });
-                        }
+                if state.active_tab == PackageTab::Installed {
+                    let mut stack = sec1.vstack(8.0);
+                    let btn_h = 32.0;
+                    let (btn_lbl, bg, hover, action) = if state.uninstalling {
+                        ("Uninstalling...", TOGGLE_OFF, TOGGLE_OFF, AppAction::Packages(PackagesMessage::StartUninstall(pkg_name.clone())))
                     } else {
-                        subsec.text("No details available.", 12.0, 0.0, 12.0, TEXT_DIM);
-                    }
-                } else {
-                    subsec.text("Select a package to view details.", 12.0, 0.0, 12.0, TEXT_DIM);
+                        ("Uninstall Package", RED, BTN_HOVER, AppAction::Packages(PackagesMessage::StartUninstall(pkg_name.clone())))
+                    };
+                    stack.add_row(1, 0.0, btn_h, |ctx, _, x, w| {
+                        ctx.button(btn_lbl, x, ctx.ay(), w, btn_h, bg, hover, WHITE, action.clone());
+                    });
                 }
-            });
+            } else {
+                sec1.text("No details available.", 12.0, 0.0, 12.0, TEXT_DIM);
+            }
+        } else {
+            sec1.text("Select a package to view details.", 12.0, 0.0, 12.0, TEXT_DIM);
         }
     });
 
-    // Section 2: Update Actions / Status
-    builder.add_section(&mut final_pc, "System Update", sec_focused.get(1).copied().unwrap_or(false), |sec2| {
+    // Section 3: Update Actions / Status
+    builder.add_section(&mut final_pc, "System Update", sec_focused.get(2).copied().unwrap_or(false), |sec2| {
         if !state.loaded {
             sec2.text("Loading update status...", 12.0, 0.0, 12.0, TEXT_DIM);
         } else {
@@ -715,20 +718,33 @@ impl crate::pages::AppPage for PackagesState {
 
     fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
         vec![
-            cce_ui::widget::SectionContainer::new("Packages").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
-            cce_ui::widget::SectionContainer::new("System Update").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
+            cce_ui::widget::SectionContainer::new("Packages")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
+            cce_ui::widget::SectionContainer::new("Package Info")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
+            cce_ui::widget::SectionContainer::new("System Update")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
         ]
     }
 
@@ -740,16 +756,11 @@ impl crate::pages::AppPage for PackagesState {
     ) {
         cce_ui::widget::link_parent_child(page_root, &mut sec_containers[0], ctx);
         cce_ui::widget::link_parent_child(page_root, &mut sec_containers[1], ctx);
+        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[2], ctx);
 
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.search_box, ctx);
-        match self.active_tab {
-            PackageTab::Installed => {
-                cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.installed_list_box.scroll_box, ctx);
-            }
-            PackageTab::Updates => {
-                cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.updates_list_box.scroll_box, ctx);
-            }
-        }
+        cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.installed_list_box.scroll_box, ctx);
+        cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.updates_list_box.scroll_box, ctx);
     }
 
     fn view(
@@ -766,7 +777,44 @@ impl crate::pages::AppPage for PackagesState {
         view(self, cx, cy, cw, ch, sec_focused, layout, ctx)
     }
 
-    fn propagate_widget_changes(&mut self, _actions: &mut Vec<crate::app::AppAction>) {}
+    fn propagate_widget_changes(&mut self, actions: &mut Vec<crate::app::AppAction>) {
+        let query = if self.search_box.editing {
+            self.search_box.edit_buffer.to_lowercase()
+        } else {
+            self.search_box.text.to_lowercase()
+        };
+
+        match self.active_tab {
+            PackageTab::Installed => {
+                let filtered: Vec<&PackageInfo> = self.installed.iter()
+                    .filter(|p| p.name.to_lowercase().contains(&query) || p.version.to_lowercase().contains(&query))
+                    .collect();
+                for (idx, item) in self.installed_items.iter_mut().enumerate() {
+                    if item.just_clicked {
+                        item.just_clicked = false;
+                        if idx < filtered.len() {
+                            let pkg = filtered[idx];
+                            actions.push(AppAction::Packages(PackagesMessage::SelectPackage(Some(pkg.name.clone()))));
+                        }
+                    }
+                }
+            }
+            PackageTab::Updates => {
+                let filtered: Vec<&UpdateInfo> = self.updates.iter()
+                    .filter(|p| p.name.to_lowercase().contains(&query))
+                    .collect();
+                for (idx, item) in self.updates_items.iter_mut().enumerate() {
+                    if item.just_clicked {
+                        item.just_clicked = false;
+                        if idx < filtered.len() {
+                            let pkg = filtered[idx];
+                            actions.push(AppAction::Packages(PackagesMessage::SelectPackage(Some(pkg.name.clone()))));
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
 
 #[cfg(test)]
diff --git a/src/pages/processes.rs b/src/pages/processes.rs
index 588bed5..befa3f1 100644
--- a/src/pages/processes.rs
+++ b/src/pages/processes.rs
@@ -230,6 +230,7 @@ pub fn view(state: &mut ProcessesState, cx: f32, cy: f32, cw: f32, ch: f32, root
             let list_box_w = sec_w - 24.0;
             let list_box_h = 360.0;
             
+            state.services_list_box.clear_children(ctx);
             render_widget(sec.pc, &mut state.services_list_box, list_box_x, list_box_y, list_box_w, list_box_h, ctx);
 
             // Filter services
@@ -288,6 +289,7 @@ pub fn view(state: &mut ProcessesState, cx: f32, cy: f32, cw: f32, ch: f32, root
                     let item_btn = &mut state.service_items[idx];
                     item_btn.title = service.name.clone();
                     item_btn.subtitle = Some(desc_truncated);
+                    cce_ui::widget::link_parent_child(&mut state.services_list_box.scroll_box, item_btn, ctx);
                     render_widget(sec.pc, item_btn, list_box_x + 24.0, draw_y, list_box_w - 44.0, item_h, ctx);
 
                     // Render StatusDot
@@ -489,20 +491,24 @@ impl crate::pages::AppPage for ProcessesState {
 
     fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
         vec![
-            cce_ui::widget::SectionContainer::new("Processes").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
-            cce_ui::widget::SectionContainer::new("Services").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
+            cce_ui::widget::SectionContainer::new("Processes")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
+            cce_ui::widget::SectionContainer::new("Services")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
         ]
     }
 
diff --git a/src/pages/storage.rs b/src/pages/storage.rs
index 87a9bb2..429f8f6 100644
--- a/src/pages/storage.rs
+++ b/src/pages/storage.rs
@@ -309,27 +309,33 @@ impl crate::pages::AppPage for StorageState {
 
     fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
         vec![
-            cce_ui::widget::SectionContainer::new("Local Storage").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
-            cce_ui::widget::SectionContainer::new("Memory").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
-            cce_ui::widget::SectionContainer::new("Full System Backup").with_layout(cce_ui::widget::AdaptiveGridLayout {
-                min_col_width: 140.0,
-                gap: 8.0,
-                padding_x: 0.0,
-                padding_y: 0.0,
-                grid: None,
-            }),
+            cce_ui::widget::SectionContainer::new("Local Storage")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
+            cce_ui::widget::SectionContainer::new("Memory")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
+            cce_ui::widget::SectionContainer::new("Full System Backup")
+                .with_draw_children(false)
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
         ]
     }
 
diff --git a/src/renderer.rs b/src/renderer.rs
index abf8542..81c8922 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -194,6 +194,8 @@ impl SystemInterface {
         // Page content in LOGICAL coordinates, then scale to physical
         let pc = self.render_page_content(lcx, lcy, lcw, lch);
 
+
+
         if self.search_open && !self.search_query.is_empty() && !self.pages[page_idx].scroll_bar.dragging {
             let query_lower = self.search_query.to_lowercase();
             let mut first_match_y = None;
@@ -259,9 +261,23 @@ impl SystemInterface {
         for (idx, (c, x, y, w, h, r, corners)) in pc.rects.iter().enumerate() {
             eprintln!("PAGE_PC_RECT idx={}: color={:?}, x={}, y={}, w={}, h={}", idx, c, x, y, w, h);
             let wx = *x * s;
-            let wy = (*y - scroll_offset_y) * s;
+            let mut wy = (*y - scroll_offset_y) * s;
             let ww = *w * s;
-            let wh = *h * s;
+            let mut wh = *h * s;
+
+            let viewport_bottom = logical_sh - self.status_height;
+            if wy >= viewport_bottom || wy + wh <= 0.0 {
+                continue;
+            }
+            if wy < 0.0 {
+                let diff = 0.0 - wy;
+                wy = 0.0;
+                wh = (wh - diff).max(0.0);
+            }
+            if wy + wh > viewport_bottom {
+                wh = (viewport_bottom - wy).max(0.0);
+            }
+
             widgets.push(AppWidget {
                 x: wx, y: wy, w: ww, h: wh,
                 color: *c, hover_color: *c,
@@ -276,6 +292,22 @@ impl SystemInterface {
                 [bl, bt - scroll_offset_y, br, bb - scroll_offset_y]
             });
 
+            let viewport_bottom = logical_sh - self.status_height;
+            let final_bounds = match shifted_bounds {
+                Some(b) => Some([
+                    b[0],
+                    b[1].max(0.0),
+                    b[2],
+                    b[3].min(viewport_bottom),
+                ]),
+                None => Some([
+                    0.0,
+                    0.0,
+                    logical_sw,
+                    viewport_bottom,
+                ]),
+            };
+
             let matched = self.search_open && !self.search_query.is_empty() && t.to_lowercase().contains(&self.search_query.to_lowercase());
 
             if matched {
@@ -299,20 +331,31 @@ impl SystemInterface {
                 let rect_h = *size + 2.0 * pad_y;
 
                 let wx = rect_x * s;
-                let wy = (rect_y - scroll_offset_y) * s;
+                let mut wy = (rect_y - scroll_offset_y) * s;
                 let ww = rect_w * s;
-                let wh = rect_h * s;
-                widgets.push(AppWidget {
-                    x: wx,
-                    y: wy,
-                    w: ww,
-                    h: wh,
-                    color: [0.65, 0.45, 0.05, 0.4],
-                    hover_color: [0.65, 0.45, 0.05, 0.4],
-                    hovering: check_hover(wx, wy, ww, wh),
-                    radius: 3.0 * s,
-                    corners: (true, true, true, true),
-                });
+                let mut wh = rect_h * s;
+
+                if wy < viewport_bottom && wy + wh > 0.0 {
+                    if wy < 0.0 {
+                        let diff = 0.0 - wy;
+                        wy = 0.0;
+                        wh = (wh - diff).max(0.0);
+                    }
+                    if wy + wh > viewport_bottom {
+                        wh = (viewport_bottom - wy).max(0.0);
+                    }
+                    widgets.push(AppWidget {
+                        x: wx,
+                        y: wy,
+                        w: ww,
+                        h: wh,
+                        color: [0.65, 0.45, 0.05, 0.4],
+                        hover_color: [0.65, 0.45, 0.05, 0.4],
+                        hovering: check_hover(wx, wy, ww, wh),
+                        radius: 3.0 * s,
+                        corners: (true, true, true, true),
+                    });
+                }
             }
 
             let text_color = if self.search_open && !self.search_query.is_empty() {
@@ -339,7 +382,7 @@ impl SystemInterface {
                 color: glyphon::Color::rgb(
                     (text_color[0] * 255.0) as u8, (text_color[1] * 255.0) as u8, (text_color[2] * 255.0) as u8,
                 ),
-                bounds: shifted_bounds,
+                bounds: final_bounds,
             });
         }
         for (btn, action) in &pc.buttons {
@@ -347,9 +390,23 @@ impl SystemInterface {
             let bg = btn.bg.unwrap_or([0.16, 0.16, 0.24, 1.0]);
             let hover_bg = btn.hover_bg.unwrap_or([0.25, 0.30, 0.26, 1.0]);
             let wx = base.x * s;
-            let wy = (base.y - scroll_offset_y) * s;
+            let mut wy = (base.y - scroll_offset_y) * s;
             let ww = base.w * s;
-            let wh = base.h * s;
+            let mut wh = base.h * s;
+
+            let viewport_bottom = logical_sh - self.status_height;
+            if wy >= viewport_bottom || wy + wh <= 0.0 {
+                continue;
+            }
+            if wy < 0.0 {
+                let diff = 0.0 - wy;
+                wy = 0.0;
+                wh = (wh - diff).max(0.0);
+            }
+            if wy + wh > viewport_bottom {
+                wh = (viewport_bottom - wy).max(0.0);
+            }
+
             widgets.push(AppWidget {
                 x: wx, y: wy, w: ww, h: wh,
                 color: bg, hover_color: hover_bg,
@@ -400,6 +457,12 @@ impl SystemInterface {
             };
 
             let label_color = btn.label_color.unwrap_or([0.83, 0.83, 0.83, 1.0]);
+            let button_bounds = Some([
+                0.0,
+                0.0,
+                logical_sw,
+                viewport_bottom,
+            ]);
             text_items.push(TextItem {
                 buffer: buf,
                 x: text_x, y: (base.y - scroll_offset_y) + (base.h - logical_lh) / 2.0,
@@ -408,7 +471,7 @@ impl SystemInterface {
                     (label_color[1] * 255.0) as u8,
                     (label_color[2] * 255.0) as u8,
                 ),
-                bounds: None,
+                bounds: button_bounds,
             });
             let mut btn_clone = btn.clone();
             if let Some(base_mut) = btn_clone.base_mut() {