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

commit8f8f1ac77f14b266b531a14f2f0b3da57d95062c
parentee9093cbfe
authorLucas Galante <[email protected]>
date2026-07-05 19:06
Fix dynamic page button click and hover highlights on AppWidget

 src/input_handler.rs |  38 +++++++++++++
 src/main.rs          |  29 ++++++++--
 src/renderer.rs      | 153 +++++++++++++++++++++++++++++++++------------------
 3 files changed, 161 insertions(+), 59 deletions(-)

diff --git a/src/input_handler.rs b/src/input_handler.rs
index 9b6d3dd..8ea64b8 100644
--- a/src/input_handler.rs
+++ b/src/input_handler.rs
@@ -60,6 +60,19 @@ impl SystemInterface {
             }
         }
 
+        let scale = self.scale_factor as f32;
+        let phys_x = x * scale;
+        let phys_y = y * scale;
+
+        // Check if cursor hover state changed on any widget
+        for w in &self.widgets {
+            let is_hovered = phys_x >= w.x && phys_x <= w.x + w.w && phys_y >= w.y && phys_y <= w.y + w.h;
+            if w.hovering != is_hovered {
+                changed = true;
+                break;
+            }
+        }
+
         if changed {
             self.needs_rebuild = true;
         }
@@ -114,6 +127,7 @@ impl SystemInterface {
                             self.app.current_page = new_page;
                             self.current_page_shared.store(idx as u8, std::sync::atomic::Ordering::SeqCst);
                             self.scroll_y = 0.0;
+                            self.update_status_text();
                         }
                     }
                     self.needs_rebuild = true;
@@ -135,6 +149,7 @@ impl SystemInterface {
                         self.app.current_page = new_page;
                         self.current_page_shared.store(idx as u8, std::sync::atomic::Ordering::SeqCst);
                         self.scroll_y = 0.0;
+                        self.update_status_text();
                     }
                 }
                 self.needs_rebuild = true;
@@ -150,6 +165,29 @@ impl SystemInterface {
         }
 
         if button != cce_ui::widget::MouseButton::Left && button != cce_ui::widget::MouseButton::Right { return false; }
+
+        let scale = self.scale_factor as f32;
+        let phys_x = self.cursor_x * scale;
+        let phys_y = self.cursor_y * scale;
+
+        let mut button_handled = false;
+        let mut clicked_action = None;
+        for (btn, action) in &mut self.page_buttons[self.scrollable_buttons_start_idx..] {
+            if btn.mouse_input(button, state, phys_x, phys_y, &mut self.ui_context) {
+                button_handled = true;
+            }
+            if btn.take_click() {
+                clicked_action = Some(action.clone());
+                button_handled = true;
+                self.needs_rebuild = true;
+            }
+        }
+        if let Some(action) = clicked_action {
+            self.handle_action(&action);
+        }
+        if button_handled {
+            return true;
+        }
         let mut actions = Vec::new();
         if button == cce_ui::widget::MouseButton::Left && state == cce_ui::widget::ElementState::Released {
             if self.app.get_current_page_mut().handle_pointer_up(&mut self.ui_context) {
diff --git a/src/main.rs b/src/main.rs
index 49eef65..2420744 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,9 +20,6 @@ std::thread_local! {
     static BUFFER_CACHE: std::cell::RefCell<std::collections::HashMap<BufferCacheKey, Buffer>> = std::cell::RefCell::new(std::collections::HashMap::new());
 }
 
-fn make_text_buffer(fs: &mut FontSystem, text: &str, size: f32) -> Buffer {
-    make_text_buffer_with_font(fs, text, size, None, "", "", "")
-}
 
 fn find_cased_family(fs: &FontSystem, name: &str) -> Option<String> {
     let lower_name = name.to_lowercase();
@@ -47,7 +44,7 @@ fn make_text_buffer_with_font(
 ) -> Buffer {
     let scale = cce_ui::scale::scale_factor();
     let mut font_size = size;
-    let mut family_name = None;
+    let mut family_name = Some("sans-serif".to_string());
 
     if let Some(font_str) = font {
         let (parsed_family, parsed_size) = cce_ui::layout::parse_font_string(font_str);
@@ -199,6 +196,7 @@ struct SystemInterface {
     menubar: cce_ui::widget::Paginator,
     switcher: cce_ui::widget::Switcher,
     pages: Vec<cce_ui::widget::Page>,
+    statusbar: cce_ui::widget::StatusBar,
     sans_serif_family: String,
     serif_family: String,
     monospace_family: String,
@@ -261,7 +259,7 @@ impl cce_ui::engine::Application for SystemInterface {
             page_buttons: Vec::new(),
             sidebar_width,
             header_height: 0.0,
-            status_height: 0.0,
+            status_height: 24.0,
             cursor_x: 0.0,
             cursor_y: 0.0,
             rx_audio: watchers.rx_audio,
@@ -304,6 +302,7 @@ impl cce_ui::engine::Application for SystemInterface {
             menubar,
             switcher,
             pages,
+            statusbar: cce_ui::widget::StatusBar::new(),
             sans_serif_family: sans_family,
             serif_family,
             monospace_family,
@@ -324,6 +323,9 @@ impl cce_ui::engine::Application for SystemInterface {
 
         this.root_window.add_child(this.menubar.as_ptr(), &mut this.ui_context);
         this.root_window.add_child(this.switcher.as_ptr(), &mut this.ui_context);
+        this.root_window.add_child(this.statusbar.as_ptr(), &mut this.ui_context);
+
+        this.update_status_text();
 
         this.rebuild_layout(820.0, 680.0);
         this.needs_rebuild = true;
@@ -391,7 +393,8 @@ impl cce_ui::engine::Application for SystemInterface {
             self.rebuild_layout(width, height);
         }
         for w in &self.widgets {
-            quads.push((w.x, w.y, w.w, w.h, w.radius, w.color, w.corners));
+            let color = if w.hovering { w.hover_color } else { w.color };
+            quads.push((w.x, w.y, w.w, w.h, w.radius, color, w.corners));
         }
 
         // Draw global hover highlight if active
@@ -696,6 +699,20 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
         }
     }
 
+    fn update_status_text(&mut self) {
+        let msg = match self.app.current_page {
+            Page::Accounts => "Accounts: Manage your online identities, keys, and credentials.",
+            Page::Audio => "Audio Settings: Configure volume levels, inputs, and sound options.",
+            Page::Fonts => "Fonts Settings: Adjust font family preferences, typography, and scaling.",
+            Page::Packages => "Package Manager: Search, install, and update system packages.",
+            Page::Processes => "System Monitor: Inspect running tasks, system resources, and services.",
+            Page::Radios => "Network Settings: Configure wireless networks, radios, and connections.",
+            Page::Storage => "Storage Settings: Manage local disks, partition structures, and backup runs.",
+            Page::System => "System Settings: System properties, update checks, and notification parameters.",
+        };
+        self.statusbar.set_text(msg);
+    }
+
 }
 
 fn main() {
diff --git a/src/renderer.rs b/src/renderer.rs
index 8fc46e3..f477f47 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -1,4 +1,4 @@
-use crate::{SystemInterface, AppWidget, make_text_buffer, make_text_buffer_with_font};
+use crate::{SystemInterface, AppWidget, make_text_buffer_with_font};
 use cce_settings::app::PageContent;
 use cce_settings::pages::Page;
 use cce_ui::widget::{Element, TextItem, PageSelector};
@@ -39,7 +39,6 @@ impl SystemInterface {
 
         self.sidebar_width = self.menubar.sidebar_w();
         self.header_height = 0.0; // No CSD Titlebar
-        let s = 1.0f32;
         let mut widgets = Vec::new();
         let mut text_items = Vec::new();
         let mut page_buttons = Vec::new();
@@ -47,12 +46,21 @@ impl SystemInterface {
         cce_ui::widget::hover_animation::reset_frame_registration();
         cce_ui::widget::popovers::clear();
         cce_ui::widget::hover_animation::set_scroll_offset(self.scroll_y);
-        cce_ui::widget::hover_animation::set_cursor_pos(self.cursor_x / s, self.cursor_y / s);
+        cce_ui::widget::hover_animation::set_cursor_pos(self.cursor_x, self.cursor_y);
+
+        let s = self.scale_factor as f32;
+        let cursor_phys_x = self.cursor_x * s;
+        let cursor_phys_y = self.cursor_y * s;
+        let check_hover = |wx: f32, wy: f32, ww: f32, wh: f32| -> bool {
+            cursor_phys_x >= wx && cursor_phys_x <= wx + ww && cursor_phys_y >= wy && cursor_phys_y <= wy + wh
+        };
+        let logical_sw = sw;
+        let logical_sh = sh;
 
         let lcx = self.sidebar_width;
         let lcy = self.header_height;
-        let lcw = sw / s - self.sidebar_width;
-        let mut lch = sh / s - self.header_height - self.status_height;
+        let lcw = logical_sw - self.sidebar_width;
+        let mut lch = logical_sh - self.header_height - self.status_height;
         if self.search_open {
             lch -= 42.0;
         }
@@ -62,7 +70,7 @@ impl SystemInterface {
         self.switcher.set_active_index(Some(page_idx));
 
         // Update root window size, background color, opacity, corner radius, and children
-        self.root_window.set_rect(0.0, 0.0, sw / s, sh / s);
+        self.root_window.set_rect(0.0, 0.0, logical_sw, logical_sh);
         let win_r = 0x0a as f32 / 255.0;
         let win_g = 0x1a as f32 / 255.0;
         let win_b = 0x0e as f32 / 255.0;
@@ -72,6 +80,7 @@ impl SystemInterface {
         self.root_window.clear_children(&mut self.ui_context);
         self.root_window.add_child(self.menubar.as_ptr(), &mut self.ui_context);
         self.root_window.add_child(self.switcher.as_ptr(), &mut self.ui_context);
+        self.root_window.add_child(self.statusbar.as_ptr(), &mut self.ui_context);
         if self.search_open {
             use cce_ui::widget::focus::link_parent_child;
             link_parent_child(&mut self.root_window, &mut self.search_box, &mut self.ui_context);
@@ -79,25 +88,25 @@ impl SystemInterface {
 
         // Position sidebar and switcher below the titlebar
         let mut dummy_pc = PageContent::new();
-        cce_ui::layout::render_widget(&mut dummy_pc, &mut self.menubar, 0.0, self.header_height, self.sidebar_width, sh / s - self.header_height, &mut self.ui_context);
+        cce_ui::layout::render_widget(&mut dummy_pc, &mut self.menubar, 0.0, self.header_height, self.sidebar_width, logical_sh - self.header_height - self.status_height, &mut self.ui_context);
         let switcher_h = if self.search_open {
-            sh / s - self.header_height - 42.0
+            logical_sh - self.header_height - 42.0 - self.status_height
         } else {
-            sh / s - self.header_height
+            logical_sh - self.header_height - self.status_height
         };
-        cce_ui::layout::render_widget(&mut dummy_pc, &mut self.switcher, self.sidebar_width, self.header_height, sw / s - self.sidebar_width, switcher_h, &mut self.ui_context);
-
+        cce_ui::layout::render_widget(&mut dummy_pc, &mut self.switcher, self.sidebar_width, self.header_height, logical_sw - self.sidebar_width, switcher_h, &mut self.ui_context);
+        cce_ui::layout::render_widget(&mut dummy_pc, &mut self.statusbar, 0.0, logical_sh - self.status_height, logical_sw, self.status_height, &mut self.ui_context);
         // Render root window recursively
         let mut window_pc = PageContent::new();
-        cce_ui::layout::render_widget(&mut window_pc, &mut self.root_window, 0.0, 0.0, sw / s, sh / s, &mut self.ui_context);
+        cce_ui::layout::render_widget(&mut window_pc, &mut self.root_window, 0.0, 0.0, logical_sw, logical_sh, &mut self.ui_context);
 
         let mut search_pc = PageContent::new();
         if self.search_open {
             search_pc.rects.push((
                 [0.08, 0.08, 0.12, 1.0],
                 self.sidebar_width,
-                sh / s - 42.0,
-                sw / s - self.sidebar_width,
+                sh - 42.0,
+                sw - self.sidebar_width,
                 42.0,
                 0.0,
                 (false, false, false, false),
@@ -105,8 +114,8 @@ impl SystemInterface {
             search_pc.rects.push((
                 [0.18, 0.18, 0.24, 1.0],
                 self.sidebar_width,
-                sh / s - 42.0,
-                sw / s - self.sidebar_width,
+                sh - 42.0,
+                sw - self.sidebar_width,
                 1.0,
                 0.0,
                 (false, false, false, false),
@@ -115,8 +124,8 @@ impl SystemInterface {
                 &mut search_pc,
                 &mut self.search_box,
                 self.sidebar_width + 12.0,
-                sh / s - 36.0,
-                sw / s - self.sidebar_width - 24.0,
+                sh - 36.0,
+                sw - self.sidebar_width - 24.0,
                 30.0,
                 &mut self.ui_context,
             );
@@ -124,28 +133,35 @@ impl SystemInterface {
 
         // CSD Titlebar removed
 
+        eprintln!("WINDOW_PC_RECTS_LEN: {}", window_pc.rects.len());
         for pc_part in &[window_pc] {
-            for (c, x, y, w, h, r, corners) in &pc_part.rects {
+            for (idx, (c, x, y, w, h, r, corners)) in pc_part.rects.iter().enumerate() {
+                eprintln!("WINDOW_PC_RECT idx={}: color={:?}, x={}, y={}, w={}, h={}", idx, c, x, y, w, h);
+                let wx = *x * s;
+                let wy = *y * s;
+                let ww = *w * s;
+                let wh = *h * s;
                 widgets.push(AppWidget {
-                    x: *x * s, y: *y * s, w: *w * s, h: *h * s,
+                    x: wx, y: wy, w: ww, h: wh,
                     color: *c, hover_color: *c,
-                    hovering: false,
+                    hovering: check_hover(wx, wy, ww, wh),
                     radius: *r * s,
                     corners: *corners,
                 });
             }
-            for (t, size, x, y, tc, font_opt, bounds) in &pc_part.texts {
+            for (idx, (t, size, x, y, tc, font_opt, bounds)) in pc_part.texts.iter().enumerate() {
+                eprintln!("WINDOW_PC_TEXT idx={}: text='{}', size={}, x={}, y={}, bounds={:?}", idx, t, size, x, y, bounds);
                 text_items.push(TextItem {
                     buffer: make_text_buffer_with_font(
                         &mut self.font_system,
                         t,
-                        *size * s,
+                        *size,
                         font_opt.as_deref(),
                         &self.sans_serif_family,
                         &self.serif_family,
                         &self.monospace_family,
                     ),
-                    x: *x * s, y: *y * s,
+                    x: *x, y: *y,
                     color: glyphon::Color::rgb(
                         (tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
                     ),
@@ -223,16 +239,22 @@ impl SystemInterface {
 
         let scroll_offset_y = self.scroll_y;
 
-        for (c, x, y, w, h, r, corners) in &pc.rects {
+        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 ww = *w * s;
+            let wh = *h * s;
             widgets.push(AppWidget {
-                x: *x * s, y: (*y - scroll_offset_y) * s, w: *w * s, h: *h * s,
+                x: wx, y: wy, w: ww, h: wh,
                 color: *c, hover_color: *c,
-                hovering: false,
+                hovering: check_hover(wx, wy, ww, wh),
                 radius: *r * s,
                 corners: *corners,
             });
         }
-        for (t, size, x, y, tc, font_opt, bounds) in &pc.texts {
+        for (idx, (t, size, x, y, tc, font_opt, bounds)) in pc.texts.iter().enumerate() {
+            eprintln!("PAGE_PC_TEXT idx={}: text='{}', size={}, x={}, y={}, bounds={:?}", idx, t, size, x, y, bounds);
             let shifted_bounds = bounds.map(|[bl, bt, br, bb]| {
                 [bl, bt - scroll_offset_y, br, bb - scroll_offset_y]
             });
@@ -243,7 +265,7 @@ impl SystemInterface {
                 let text_buf = make_text_buffer_with_font(
                     &mut self.font_system,
                     t,
-                    *size * s,
+                    *size,
                     font_opt.as_deref(),
                     &self.sans_serif_family,
                     &self.serif_family,
@@ -259,14 +281,18 @@ impl SystemInterface {
                 let rect_w = text_w + 2.0 * pad_x;
                 let rect_h = *size + 2.0 * pad_y;
 
+                let wx = rect_x * s;
+                let wy = (rect_y - scroll_offset_y) * s;
+                let ww = rect_w * s;
+                let wh = rect_h * s;
                 widgets.push(AppWidget {
-                    x: rect_x * s,
-                    y: (rect_y - scroll_offset_y) * s,
-                    w: rect_w * s,
-                    h: rect_h * s,
+                    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: false,
+                    hovering: check_hover(wx, wy, ww, wh),
                     radius: 3.0 * s,
                     corners: (true, true, true, true),
                 });
@@ -286,13 +312,13 @@ impl SystemInterface {
                 buffer: make_text_buffer_with_font(
                     &mut self.font_system,
                     t,
-                    *size * s,
+                    *size,
                     font_opt.as_deref(),
                     &self.sans_serif_family,
                     &self.serif_family,
                     &self.monospace_family,
                 ),
-                x: *x * s, y: (*y - scroll_offset_y) * s,
+                x: *x, y: *y - scroll_offset_y,
                 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,
                 ),
@@ -303,19 +329,30 @@ impl SystemInterface {
             let base = btn.base().unwrap();
             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 ww = base.w * s;
+            let wh = base.h * s;
             widgets.push(AppWidget {
-                x: base.x * s, y: (base.y - scroll_offset_y) * s, w: base.w * s, h: base.h * s,
+                x: wx, y: wy, w: ww, h: wh,
                 color: bg, hover_color: hover_bg,
-                hovering: false,
+                hovering: check_hover(wx, wy, ww, wh),
                 radius: cce_ui::layout::button_corner_radius() * s,
                 corners: (true, true, true, true),
             });
             let label = base.label.as_deref().unwrap_or("");
             let label_size = 12.0;
-            let buf = make_text_buffer(&mut self.font_system, label, label_size * s);
-            let scale = cce_ui::scale::scale_factor();
-            let tw = buf.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0) / scale;
-            let lh = label_size * s * 1.4;
+            let buf = make_text_buffer_with_font(
+                &mut self.font_system,
+                label,
+                label_size,
+                None,
+                &self.sans_serif_family,
+                &self.serif_family,
+                &self.monospace_family,
+            );
+            let tw = buf.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0);
+            let lh = buf.metrics().line_height;
             let mut left_align = btn.justify == cce_ui::widget::Justification::Left;
 
             // Auto-detect if inside a ScrollBox to apply left alignment by default
@@ -336,16 +373,18 @@ impl SystemInterface {
                 }
             }
 
+            let logical_tw = tw / s;
+            let logical_lh = lh / s;
             let text_x = if left_align {
-                base.x * s + 8.0 * s
+                base.x + 8.0
             } else {
-                base.x * s + (base.w * s - tw) / 2.0
+                base.x + (base.w - logical_tw) / 2.0
             };
 
             let label_color = btn.label_color.unwrap_or([0.83, 0.83, 0.83, 1.0]);
             text_items.push(TextItem {
                 buffer: buf,
-                x: text_x, y: (base.y - scroll_offset_y) * s + (base.h * s - lh) / 2.0,
+                x: text_x, y: (base.y - scroll_offset_y) + (base.h - logical_lh) / 2.0,
                 color: glyphon::Color::rgb(
                     (label_color[0] * 255.0) as u8,
                     (label_color[1] * 255.0) as u8,
@@ -368,10 +407,14 @@ impl SystemInterface {
         cce_ui::layout::render_popovers(&mut popover_pc, &mut self.ui_context);
 
         for (c, x, y, w, h, r, corners) in &popover_pc.rects {
+            let wx = *x * s;
+            let wy = (*y - scroll_offset_y) * s;
+            let ww = *w * s;
+            let wh = *h * s;
             widgets.push(AppWidget {
-                x: *x * s, y: (*y - scroll_offset_y) * s, w: *w * s, h: *h * s,
+                x: wx, y: wy, w: ww, h: wh,
                 color: *c, hover_color: *c,
-                hovering: false,
+                hovering: check_hover(wx, wy, ww, wh),
                 radius: *r * s,
                 corners: *corners,
             });
@@ -384,13 +427,13 @@ impl SystemInterface {
                 buffer: make_text_buffer_with_font(
                     &mut self.font_system,
                     t,
-                    *size * s,
+                    *size,
                     font_opt.as_deref(),
                     &self.sans_serif_family,
                     &self.serif_family,
                     &self.monospace_family,
                 ),
-                x: *x * s, y: (*y - scroll_offset_y) * s,
+                x: *x, y: *y - scroll_offset_y,
                 color: glyphon::Color::rgb(
                     (tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
                 ),
@@ -405,10 +448,14 @@ impl SystemInterface {
 
 
         for (c, x, y, w, h, r, corners) in &search_pc.rects {
+            let wx = *x * s;
+            let wy = *y * s;
+            let ww = *w * s;
+            let wh = *h * s;
             widgets.push(AppWidget {
-                x: *x * s, y: *y * s, w: *w * s, h: *h * s,
+                x: wx, y: wy, w: ww, h: wh,
                 color: *c, hover_color: *c,
-                hovering: false,
+                hovering: check_hover(wx, wy, ww, wh),
                 radius: *r * s,
                 corners: *corners,
             });
@@ -418,13 +465,13 @@ impl SystemInterface {
                 buffer: make_text_buffer_with_font(
                     &mut self.font_system,
                     t,
-                    *size * s,
+                    *size,
                     font_opt.as_deref(),
                     &self.sans_serif_family,
                     &self.serif_family,
                     &self.monospace_family,
                 ),
-                x: *x * s, y: *y * s,
+                x: *x, y: *y,
                 color: glyphon::Color::rgb(
                     (tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
                 ),