system settings
git clone https://git.lucas.co/cce-system-interface.git
Update System settings interface: fix statusbar dropdown gap alignment and update system page layout & watchers
src/input_handler.rs | 34 ++--
src/main.rs | 18 +-
src/pages/system_info.rs | 425 +++++++++++++++++++++++++++++++++++++++++------
src/renderer.rs | 17 +-
src/watchers.rs | 2 +-
5 files changed, 420 insertions(+), 76 deletions(-)
diff --git a/src/input_handler.rs b/src/input_handler.rs
index 1893279..c1c16cf 100644
--- a/src/input_handler.rs
+++ b/src/input_handler.rs
@@ -210,21 +210,27 @@ impl SystemInterface {
let event = cce_ui::widget::Event::MouseWheel { delta: delta.clone(), x: lx, y: ly, local_x: lx, local_y: ly };
let mut handled = false;
if let Some(root) = self.get_page_root_widget() {
- unsafe {
- for child in (*root).children(&self.ui_context).into_iter().rev() {
- let (cx, cy, _, _) = (*child).rect();
- let mut local_adjusted = event.clone();
- match &mut local_adjusted {
- cce_ui::widget::Event::MouseWheel { local_x, local_y, .. } => {
- *local_x -= cx;
- *local_y -= cy;
+ if self.app.current_page == Page::System {
+ if self.ui_context.propagate_event(&event, root) {
+ handled = true;
+ }
+ } else {
+ unsafe {
+ for child in (*root).children(&self.ui_context).into_iter().rev() {
+ let (cx, cy, _, _) = (*child).rect();
+ let mut local_adjusted = event.clone();
+ match &mut local_adjusted {
+ cce_ui::widget::Event::MouseWheel { local_x, local_y, .. } => {
+ *local_x -= cx;
+ *local_y -= cy;
+ }
+ _ => {}
+ }
+ let adjusted_event = (*root).transform_event_for_child(child, local_adjusted, &self.ui_context);
+ if self.ui_context.propagate_event(&adjusted_event, child) {
+ handled = true;
+ break;
}
- _ => {}
- }
- let adjusted_event = (*root).transform_event_for_child(child, local_adjusted, &self.ui_context);
- if self.ui_context.propagate_event(&adjusted_event, child) {
- handled = true;
- break;
}
}
}
diff --git a/src/main.rs b/src/main.rs
index 486ab86..7f7dbf8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -168,7 +168,7 @@ struct SystemInterface {
rx_audio: std::sync::mpsc::Receiver<pages::audio::AudioState>,
rx_network: std::sync::mpsc::Receiver<pages::network::NetworkState>,
pub rx_processes: std::sync::mpsc::Receiver<pages::processes::ProcessesState>,
- rx_system: std::sync::mpsc::Receiver<pages::system_info::SystemState>,
+ rx_system: std::sync::mpsc::Receiver<pages::system_info::SystemInfo>,
rx_storage: std::sync::mpsc::Receiver<pages::storage::StorageState>,
rx_notifications: std::sync::mpsc::Receiver<pages::notifications::NotificationsConfig>,
rx_services: std::sync::mpsc::Receiver<Vec<pages::processes::ServiceInfo>>,
@@ -237,8 +237,17 @@ impl cce_ui::engine::Application for SystemInterface {
let switcher = cce_ui::widget::Switcher::new(sidebar_width, 0.0, 820.0 - sidebar_width, 680.0);
let mut pages = Vec::new();
for page in Page::ALL.iter() {
- let page_widget = cce_ui::widget::Page::new(sidebar_width, 0.0, 820.0 - sidebar_width, 680.0)
+ let mut page_widget = cce_ui::widget::Page::new(sidebar_width, 0.0, 820.0 - sidebar_width, 680.0)
.with_label(page.label());
+ if *page == Page::System {
+ page_widget.layout = Box::new(cce_ui::widget::AdaptiveGridLayout {
+ min_col_width: 320.0,
+ gap: 20.0,
+ padding_x: 10.0,
+ padding_y: 10.0,
+ grid: None,
+ });
+ }
pages.push(page_widget);
}
@@ -317,6 +326,7 @@ impl cce_ui::engine::Application for SystemInterface {
.with_placeholder("Search sections & parameters...")
.with_draw_bg_border(false),
};
+ this.app.system_info.sender = Some(this.sender.clone());
for page in &mut this.pages {
this.switcher.add_child(page.as_ptr(), &mut this.ui_context);
@@ -534,7 +544,7 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
}
}
while let Ok(s) = self.rx_system.try_recv() {
- system_info::update(&mut self.app.system_info, system_info::SystemMessage::Refreshed(s));
+ system_info::update(&mut self.app.system_info, system_info::SystemMessage::Refreshed(s), &mut self.ui_context);
if self.app.current_page == Page::System {
self.needs_rebuild = true;
}
@@ -605,7 +615,7 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
AppAction::Exit => {}
AppAction::Audio(m) => audio::update(&mut self.app.audio, m.clone()),
AppAction::Radios(m) => network::update(&mut self.app.network, m.clone()),
- AppAction::SystemInfo(m) => system_info::update(&mut self.app.system_info, m.clone()),
+ AppAction::SystemInfo(m) => system_info::update(&mut self.app.system_info, m.clone(), &mut self.ui_context),
AppAction::Processes(m) => processes::update(&mut self.app.processes, m.clone()),
AppAction::Notifications(m) => notifications::update(&mut self.app.notifications, m.clone()),
AppAction::Storage(m) => match m {
diff --git a/src/pages/system_info.rs b/src/pages/system_info.rs
index 4f54a72..cba5d90 100644
--- a/src/pages/system_info.rs
+++ b/src/pages/system_info.rs
@@ -1,6 +1,6 @@
use crate::app::{AppAction, PageContent, SectionContextExt};
use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy};
-use cce_ui::widget::{Label, Dropdown, InfoBox, Element};
+use cce_ui::widget::{Label, Dropdown, InfoBox, Element, Button, SectionContainer, Container};
#[derive(Debug, Clone, Default)]
pub struct BatteryInfo {
@@ -22,7 +22,7 @@ pub struct NotificationsConfig {
pub duration: i32,
}
-#[derive(Debug, Clone)]
+#[derive(Clone)]
pub struct SystemState {
pub hostname: String,
pub kernel: String,
@@ -47,6 +47,47 @@ pub struct SystemState {
pub cpu_gov_menu: Dropdown,
pub gpu_gov_menu: Dropdown,
+ // Native layout tracking and widgets
+ pub initialized: bool,
+ pub sender: Option<calloop::channel::Sender<AppAction>>,
+ pub hostname_label: Label,
+ pub uptime_label: Label,
+
+ pub battery_label_pct: Label,
+ pub battery_label_state: Label,
+ pub battery_label_time: Label,
+ pub battery_label_details: Label,
+ pub battery_label_ac: Label,
+
+ pub cpu_info_box: InfoBox,
+ pub gpu_info_box: InfoBox,
+
+ pub suspend_btn: Button,
+ pub hibernate_btn: Button,
+ pub reboot_btn: Button,
+ pub poweroff_btn: Button,
+ pub force_shutdown_btn: Button,
+
+ pub sec_system: SectionContainer,
+ pub sec_actions: SectionContainer,
+ pub sec_cpu: SectionContainer,
+ pub sec_gpu: SectionContainer,
+ pub sec_cpu_gov: SectionContainer,
+ pub sec_gpu_gov: SectionContainer,
+ pub sec_battery: SectionContainer,
+
+ pub actions_row: Container,
+}
+
+impl std::fmt::Debug for SystemState {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("SystemState")
+ .field("hostname", &self.hostname)
+ .field("kernel", &self.kernel)
+ .field("uptime", &self.uptime)
+ .field("loaded", &self.loaded)
+ .finish()
+ }
}
impl Default for SystemState {
@@ -79,13 +120,108 @@ impl Default for SystemState {
0,
).with_label("GPU Power Limit"),
+ initialized: false,
+ sender: None,
+ hostname_label: Label::new(""),
+ uptime_label: Label::new(""),
+
+ battery_label_pct: Label::new("").with_font_size(24.0).with_color([92, 143, 97]),
+ battery_label_state: Label::new("").with_font_size(12.0).with_color([135, 135, 153]),
+ battery_label_time: Label::new("").with_font_size(12.0).with_color([135, 135, 153]),
+ battery_label_details: Label::new("").with_font_size(11.0).with_color([135, 135, 153]),
+ battery_label_ac: Label::new("").with_font_size(14.0).with_color([212, 212, 212]),
+
+ cpu_info_box: InfoBox::new("CPU Governor", vec![]),
+ gpu_info_box: InfoBox::new("GPU Power Limit", vec![]),
+
+ suspend_btn: Button::new(0.0, 0.0, 0.0, 32.0)
+ .with_label("Suspend")
+ .with_bg([0.20, 0.33, 0.22, 1.0])
+ .with_hover_bg([0.25, 0.30, 0.26, 1.0])
+ .with_label_color([1.0, 1.0, 1.0, 1.0]),
+ hibernate_btn: Button::new(0.0, 0.0, 0.0, 32.0)
+ .with_label("Hibernate")
+ .with_bg([0.20, 0.33, 0.22, 1.0])
+ .with_hover_bg([0.25, 0.30, 0.26, 1.0])
+ .with_label_color([1.0, 1.0, 1.0, 1.0]),
+ reboot_btn: Button::new(0.0, 0.0, 0.0, 32.0)
+ .with_label("Reboot")
+ .with_bg([0.67, 0.20, 0.20, 1.0])
+ .with_hover_bg([0.25, 0.30, 0.26, 1.0])
+ .with_label_color([1.0, 1.0, 1.0, 1.0]),
+ poweroff_btn: Button::new(0.0, 0.0, 0.0, 32.0)
+ .with_label("Power Off")
+ .with_bg([0.67, 0.20, 0.20, 1.0])
+ .with_hover_bg([0.25, 0.30, 0.26, 1.0])
+ .with_label_color([1.0, 1.0, 1.0, 1.0]),
+ force_shutdown_btn: Button::new(0.0, 0.0, 0.0, 32.0)
+ .with_label("Force Shutdown")
+ .with_bg([0.67, 0.20, 0.20, 1.0])
+ .with_hover_bg([0.25, 0.30, 0.26, 1.0])
+ .with_label_color([1.0, 1.0, 1.0, 1.0]),
+
+ sec_system: SectionContainer::new("System").with_layout({
+ let mut l = cce_ui::widget::VerticalLayout::default();
+ l.padding_x = 12.0;
+ l.padding_y = 8.0;
+ l.spacing = 8.0;
+ l
+ }),
+ sec_actions: SectionContainer::new("System Actions").with_layout({
+ let mut l = cce_ui::widget::VerticalLayout::default();
+ l.padding_x = 12.0;
+ l.padding_y = 8.0;
+ l.spacing = 8.0;
+ l
+ }),
+ sec_cpu: SectionContainer::new("CPU").with_layout({
+ let mut l = cce_ui::widget::VerticalLayout::default();
+ l.padding_x = 12.0;
+ l.padding_y = 8.0;
+ l.spacing = 8.0;
+ l
+ }),
+ sec_gpu: SectionContainer::new("GPU").with_layout({
+ let mut l = cce_ui::widget::VerticalLayout::default();
+ l.padding_x = 12.0;
+ l.padding_y = 8.0;
+ l.spacing = 8.0;
+ l
+ }),
+ sec_cpu_gov: SectionContainer::new("CPU Governor").with_layout({
+ let mut l = cce_ui::widget::VerticalLayout::default();
+ l.padding_x = 12.0;
+ l.padding_y = 8.0;
+ l.spacing = 8.0;
+ l
+ }),
+ sec_gpu_gov: SectionContainer::new("GPU Power").with_layout({
+ let mut l = cce_ui::widget::VerticalLayout::default();
+ l.padding_x = 12.0;
+ l.padding_y = 8.0;
+ l.spacing = 8.0;
+ l
+ }),
+ sec_battery: SectionContainer::new("Battery").with_layout({
+ let mut l = cce_ui::widget::VerticalLayout::default();
+ l.padding_x = 12.0;
+ l.padding_y = 8.0;
+ l.spacing = 8.0;
+ l
+ }),
+
+ actions_row: Container::new().with_layout(cce_ui::widget::ColumnsLayout {
+ padding_x: 0.0,
+ padding_y: 0.0,
+ spacing: 8.0,
+ }),
}
}
}
#[derive(Debug, Clone)]
pub enum SystemMessage {
- Refreshed(SystemState),
+ Refreshed(SystemInfo),
Suspend,
Hibernate,
Reboot,
@@ -276,7 +412,23 @@ fn spawn_systemctl_force(action: &str) {
let _ = cce_ui::process::spawn_detached(cmd);
}
-pub async fn fetch_system_state() -> SystemState {
+#[derive(Debug, Clone, Default)]
+pub struct SystemInfo {
+ pub hostname: String,
+ pub kernel: String,
+ pub uptime: String,
+ pub cpu_model: String,
+ pub cpu_cores: u32,
+ pub cpu_usage: f32,
+ pub gpus: Vec<String>,
+ pub gpu_strings: Vec<String>,
+ pub battery: BatteryInfo,
+ pub on_ac: bool,
+ pub cpu_powersave: bool,
+ pub gpu_powersave: bool,
+}
+
+pub async fn fetch_system_state() -> SystemInfo {
let hostname = tokio::process::Command::new("hostname")
.output().await.ok()
.and_then(|o| String::from_utf8_lossy(&o.stdout).trim().split('.').next().map(|s| s.to_string()))
@@ -351,57 +503,37 @@ pub async fn fetch_system_state() -> SystemState {
list
}).clone();
- let cpu_temp = read_cpu_temp();
+
let tp_gpu_temp = read_thinkpad_gpu_temp();
let nv_gpu_temp = read_nvidia_gpu_temp().await;
- let cpu_label_text = format!("CPU {} ({} cores)", cpu_model, cpu_cores);
- let cpu_usage_text = format!("Usage {:.0}%", cpu_usage);
- let cpu_temp_text = cpu_temp.map(|t| format!("Temp {:.0}°C", t)).unwrap_or_else(|| "Temp N/A".to_string());
-
- let gpu_labels = gpus.iter().map(|gpu_name| {
+ let gpu_strings = gpus.iter().map(|gpu_name| {
let temp = if gpu_name.to_lowercase().contains("nvidia") {
nv_gpu_temp.or(tp_gpu_temp)
} else {
tp_gpu_temp
};
let temp_str = temp.map(|t| format!(" — {:.0}°C", t)).unwrap_or_default();
- let text = format!("GPU {}{}", gpu_name, temp_str);
- Label::new(&text).with_font_size(12.0).with_color([212, 212, 212])
+ format!("GPU {}{}", gpu_name, temp_str)
}).collect();
let (battery, on_ac) = fetch_upower().await;
let cpu_powersave = current_cpu_governor() == "powersave";
let gpu_powersave = current_gpu_power_cap().await;
- SystemState {
+ SystemInfo {
hostname,
kernel,
uptime,
- loaded: true,
-
cpu_model,
cpu_usage,
cpu_cores,
gpus,
- cpu_label: Label::new(&cpu_label_text).with_font_size(12.0).with_color([212, 212, 212]),
- cpu_usage_label: Label::new(&cpu_usage_text).with_font_size(12.0).with_color([212, 212, 212]),
- cpu_temp_label: Label::new(&cpu_temp_text).with_font_size(12.0).with_color([212, 212, 212]),
- gpu_labels,
-
+ gpu_strings,
battery,
on_ac,
cpu_powersave,
gpu_powersave,
- cpu_gov_menu: Dropdown::new(
- vec!["Performance".to_string(), "Powersave".to_string()],
- if cpu_powersave { 1 } else { 0 },
- ).with_label("CPU Governor"),
- gpu_gov_menu: Dropdown::new(
- vec!["Default (80W)".to_string(), "Eco Cap (5W)".to_string()],
- if gpu_powersave { 1 } else { 0 },
- ).with_label("GPU Power Limit"),
-
}
}
@@ -604,29 +736,123 @@ pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
final_pc
}
-pub fn update(state: &mut SystemState, msg: SystemMessage) {
+pub fn update(state: &mut SystemState, msg: SystemMessage, ctx: &mut cce_ui::context::UiContext) {
match msg {
SystemMessage::Refreshed(new) => {
state.hostname = new.hostname;
state.kernel = new.kernel;
state.uptime = new.uptime;
- state.loaded = new.loaded;
+ state.loaded = true;
state.cpu_model = new.cpu_model;
state.cpu_usage = new.cpu_usage;
state.cpu_cores = new.cpu_cores;
state.gpus = new.gpus;
- state.cpu_label = new.cpu_label;
- state.cpu_usage_label = new.cpu_usage_label;
- state.cpu_temp_label = new.cpu_temp_label;
- state.gpu_labels = new.gpu_labels;
state.battery = new.battery;
state.on_ac = new.on_ac;
state.cpu_powersave = new.cpu_powersave;
state.gpu_powersave = new.gpu_powersave;
- state.cpu_gov_menu.selected = new.cpu_gov_menu.selected;
- state.gpu_gov_menu.selected = new.gpu_gov_menu.selected;
+ state.cpu_gov_menu.selected = if new.cpu_powersave { 1 } else { 0 };
+ state.gpu_gov_menu.selected = if new.gpu_powersave { 1 } else { 0 };
+
+ if state.loaded {
+ state.hostname_label.set_text(&format!("{} — Linux {}", state.hostname, state.kernel));
+ state.uptime_label.set_text(&format!("Uptime: {}", state.uptime));
+
+ let cpu_label_text = format!("CPU {} ({} cores)", state.cpu_model, state.cpu_cores);
+ let cpu_usage_text = format!("Usage {:.0}%", state.cpu_usage);
+ let cpu_temp_text = read_cpu_temp().map(|t| format!("Temp {:.0}°C", t)).unwrap_or_else(|| "Temp N/A".to_string());
+
+ state.cpu_label.set_text(&cpu_label_text);
+ state.cpu_usage_label.set_text(&cpu_usage_text);
+ state.cpu_temp_label.set_text(&cpu_temp_text);
+
+ // Re-populate GPU labels
+ state.sec_gpu.clear_children(ctx);
+ state.gpu_labels.clear();
+ for text in &new.gpu_strings {
+ let mut lbl = Label::new(text).with_font_size(12.0).with_color([212, 212, 212]);
+ state.sec_gpu.add_child(lbl.as_ptr_mut(), ctx);
+ state.gpu_labels.push(lbl);
+ }
+
+ // Update info boxes
+ let (cpu_title, cpu_lines) = if state.cpu_powersave {
+ (
+ "CPU Governor: Powersave",
+ vec![
+ "• Active: powersave".to_string(),
+ "• Governor set to powersave — lower power, slower burst".to_string(),
+ ],
+ )
+ } else {
+ (
+ "CPU Governor: Performance",
+ vec![
+ "• Active: performance".to_string(),
+ "• Governor set to performance".to_string(),
+ ],
+ )
+ };
+ state.cpu_info_box.title = cpu_title.to_string();
+ state.cpu_info_box.lines = cpu_lines;
+
+ let (gpu_title, gpu_lines) = if state.gpu_powersave {
+ (
+ "GPU Power Limit: Eco Cap",
+ vec![
+ "• Mode: 5W Cap".to_string(),
+ "• NVIDIA power limit capped at 5W — minimal draw".to_string(),
+ ],
+ )
+ } else {
+ (
+ "GPU Power Limit: Default",
+ vec![
+ "• Mode: 80W Default".to_string(),
+ "• NVIDIA running at default power limit".to_string(),
+ ],
+ )
+ };
+ state.gpu_info_box.title = gpu_title.to_string();
+ state.gpu_info_box.lines = gpu_lines;
+
+ // Update battery labels
+ let bat = &state.battery;
+ let bat_icon = match bat.state.as_str() {
+ "charging" => "+",
+ "fully-charged" => "=",
+ _ => "",
+ };
+
+ let pct_color = if bat.percentage < 20.0 { [255, 84, 84] }
+ else if bat.percentage < 50.0 { [255, 186, 51] }
+ else { [92, 143, 97] };
+
+ let pct_str = format!("{} {:.0}%", bat_icon, bat.percentage);
+ state.battery_label_pct.set_text(&pct_str);
+ state.battery_label_pct.set_color(pct_color);
+
+ let state_str = format!("{} • {:.1}W • {:.1}/{:.1} Wh",
+ bat.state, bat.energy_rate, bat.energy, bat.energy_full);
+ state.battery_label_state.set_text(&state_str);
+
+ let time_str = if bat.time_to_empty > 0 {
+ format!("Time remaining: {}", format_duration(bat.time_to_empty))
+ } else if bat.time_to_full > 0 {
+ format!("Time to full: {}", format_duration(bat.time_to_full))
+ } else { String::new() };
+ state.battery_label_time.set_text(&time_str);
+
+ let detail_str = format!("{} {}", bat.vendor, bat.model);
+ state.battery_label_details.set_text(&detail_str);
+
+ let ac_str = if state.on_ac { "On AC Power" } else { "On Battery" };
+ state.battery_label_ac.set_text(ac_str);
+
+ state.hostname_label.mark_dirty(ctx);
+ }
}
SystemMessage::Suspend => spawn_systemctl("suspend"),
SystemMessage::Hibernate => spawn_systemctl("hibernate"),
@@ -654,8 +880,6 @@ pub fn update(state: &mut SystemState, msg: SystemMessage) {
state.gpu_gov_menu.selected = 1;
spawn_gpu_power(true);
}
-
-
}
}
@@ -684,28 +908,123 @@ impl crate::pages::AppPage for SystemState {
fn link_children(
&mut self,
page_root: &mut dyn cce_ui::widget::Element,
- sec_containers: &mut [cce_ui::widget::SectionContainer],
+ _sec_containers: &mut [cce_ui::widget::SectionContainer],
ctx: &mut cce_ui::context::UiContext,
) {
- for sec in sec_containers.iter_mut() {
- cce_ui::widget::link_parent_child(page_root, sec, ctx);
+ if !self.initialized {
+ self.initialized = true;
+
+ // Clear page root children to prevent duplicates
+ page_root.clear_children(ctx);
+
+ // Bind click callbacks to actions
+ if let Some(ref tx) = self.sender {
+ let tx1 = tx.clone();
+ self.suspend_btn = self.suspend_btn.clone().on_click(move || {
+ let _ = tx1.send(AppAction::SystemInfo(SystemMessage::Suspend));
+ });
+
+ let tx2 = tx.clone();
+ self.hibernate_btn = self.hibernate_btn.clone().on_click(move || {
+ let _ = tx2.send(AppAction::SystemInfo(SystemMessage::Hibernate));
+ });
+
+ let tx3 = tx.clone();
+ self.reboot_btn = self.reboot_btn.clone().on_click(move || {
+ let _ = tx3.send(AppAction::SystemInfo(SystemMessage::Reboot));
+ });
+
+ let tx4 = tx.clone();
+ self.poweroff_btn = self.poweroff_btn.clone().on_click(move || {
+ let _ = tx4.send(AppAction::SystemInfo(SystemMessage::PowerOff));
+ });
+
+ let tx5 = tx.clone();
+ self.force_shutdown_btn = self.force_shutdown_btn.clone().on_click(move || {
+ let _ = tx5.send(AppAction::SystemInfo(SystemMessage::ForceShutdown));
+ });
+ }
+
+ // Set up initial rect sizes for info boxes
+ self.cpu_info_box.set_rect(0.0, 0.0, 0.0, 80.0);
+ self.gpu_info_box.set_rect(0.0, 0.0, 0.0, 80.0);
+
+ // Build layout tree:
+ // ── 1. System Section ──
+ self.sec_system.add_child(self.hostname_label.as_ptr_mut(), ctx);
+ self.sec_system.add_child(self.uptime_label.as_ptr_mut(), ctx);
+
+ // ── 2. Actions Section ──
+ self.actions_row.add_child(self.suspend_btn.as_ptr_mut(), ctx);
+ self.actions_row.add_child(self.hibernate_btn.as_ptr_mut(), ctx);
+ self.actions_row.add_child(self.reboot_btn.as_ptr_mut(), ctx);
+ self.actions_row.add_child(self.poweroff_btn.as_ptr_mut(), ctx);
+
+ self.sec_actions.add_child(self.actions_row.as_ptr_mut(), ctx);
+ self.sec_actions.add_child(self.force_shutdown_btn.as_ptr_mut(), ctx);
+
+ // ── 3. CPU Section ──
+ self.sec_cpu.add_child(self.cpu_label.as_ptr_mut(), ctx);
+ self.sec_cpu.add_child(self.cpu_usage_label.as_ptr_mut(), ctx);
+ self.sec_cpu.add_child(self.cpu_temp_label.as_ptr_mut(), ctx);
+
+ // ── 4. GPU Section ──
+ for gpu_lbl in &mut self.gpu_labels {
+ self.sec_gpu.add_child(gpu_lbl.as_ptr_mut(), ctx);
+ }
+
+ // ── 5. CPU Governor Section ──
+ self.sec_cpu_gov.add_child(self.cpu_gov_menu.as_ptr_mut(), ctx);
+ self.sec_cpu_gov.add_child(self.cpu_info_box.as_ptr_mut(), ctx);
+
+ // ── 6. GPU Power Section ──
+ self.sec_gpu_gov.add_child(self.gpu_gov_menu.as_ptr_mut(), ctx);
+ self.sec_gpu_gov.add_child(self.gpu_info_box.as_ptr_mut(), ctx);
+
+ // ── 7. Battery Section ──
+ self.sec_battery.add_child(self.battery_label_pct.as_ptr_mut(), ctx);
+ self.sec_battery.add_child(self.battery_label_state.as_ptr_mut(), ctx);
+ self.sec_battery.add_child(self.battery_label_time.as_ptr_mut(), ctx);
+ self.sec_battery.add_child(self.battery_label_details.as_ptr_mut(), ctx);
+ self.sec_battery.add_child(self.battery_label_ac.as_ptr_mut(), ctx);
+
+ // Mount SectionContainers onto the Page
+ page_root.add_child(self.sec_system.as_ptr_mut(), ctx);
+ page_root.add_child(self.sec_actions.as_ptr_mut(), ctx);
+ page_root.add_child(self.sec_cpu.as_ptr_mut(), ctx);
+ page_root.add_child(self.sec_gpu.as_ptr_mut(), ctx);
+ page_root.add_child(self.sec_cpu_gov.as_ptr_mut(), ctx);
+ page_root.add_child(self.sec_gpu_gov.as_ptr_mut(), ctx);
+ page_root.add_child(self.sec_battery.as_ptr_mut(), ctx);
}
- cce_ui::widget::link_parent_child(&mut sec_containers[4], &mut self.cpu_gov_menu, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[5], &mut self.gpu_gov_menu, ctx);
+
+ // Link parent-child for focus context
+ cce_ui::widget::link_parent_child(page_root, &mut self.sec_system, ctx);
+ cce_ui::widget::link_parent_child(page_root, &mut self.sec_actions, ctx);
+ cce_ui::widget::link_parent_child(page_root, &mut self.sec_cpu, ctx);
+ cce_ui::widget::link_parent_child(page_root, &mut self.sec_gpu, ctx);
+ cce_ui::widget::link_parent_child(page_root, &mut self.sec_cpu_gov, ctx);
+ cce_ui::widget::link_parent_child(page_root, &mut self.sec_gpu_gov, ctx);
+ cce_ui::widget::link_parent_child(page_root, &mut self.sec_battery, ctx);
+
+ cce_ui::widget::link_parent_child(&mut self.sec_cpu_gov, &mut self.cpu_gov_menu, ctx);
+ cce_ui::widget::link_parent_child(&mut self.sec_cpu_gov, &mut self.cpu_info_box, ctx);
+ cce_ui::widget::link_parent_child(&mut self.sec_gpu_gov, &mut self.gpu_gov_menu, ctx);
+ cce_ui::widget::link_parent_child(&mut self.sec_gpu_gov, &mut self.gpu_info_box, ctx);
}
fn view(
&mut self,
- cx: f32,
- cy: f32,
- cw: f32,
- ch: f32,
- root_focused: bool,
- sec_focused: &[bool],
- layout: &mut dyn LayoutStrategy,
- ctx: &mut cce_ui::context::UiContext,
+ _cx: f32,
+ _cy: f32,
+ _cw: f32,
+ _ch: f32,
+ _root_focused: bool,
+ _sec_focused: &[bool],
+ _layout: &mut dyn LayoutStrategy,
+ _ctx: &mut cce_ui::context::UiContext,
) -> crate::app::PageContent {
- view(self, cx, cy, cw, ch, root_focused, sec_focused, layout, ctx)
+ crate::app::PageContent::new()
}
fn propagate_widget_changes(&mut self, actions: &mut Vec<crate::app::AppAction>) {
diff --git a/src/renderer.rs b/src/renderer.rs
index 02248df..5a79d9a 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -91,8 +91,9 @@ impl SystemInterface {
let mut dummy_pc = PageContent::new();
let dropdown_w = 180.0f32;
let dropdown_h = 18.0f32;
- let dropdown_x = logical_sw - dropdown_w - 12.0;
- let dropdown_y = logical_sh - self.status_height + (self.status_height - dropdown_h) / 2.0;
+ let dropdown_gap = (self.status_height - dropdown_h) / 2.0;
+ let dropdown_x = logical_sw - dropdown_w - dropdown_gap;
+ let dropdown_y = logical_sh - self.status_height + dropdown_gap;
cce_ui::layout::render_widget(&mut dummy_pc, &mut self.page_dropdown, dropdown_x, dropdown_y, dropdown_w, dropdown_h, &mut self.ui_context);
let switcher_h = if self.search_open {
logical_sh - self.header_height - 42.0 - self.status_height
@@ -105,6 +106,13 @@ impl SystemInterface {
let mut window_pc = PageContent::new();
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 page_idx = Page::ALL.iter().position(|&p| p == self.app.current_page).unwrap_or(0);
+ let active_page_widget = &self.pages[page_idx];
+ if self.app.current_page == Page::System {
+ self.scroll_y = active_page_widget.scroll_y;
+ self.max_scroll_y = (active_page_widget.content_h - active_page_widget.base.base.h).max(0.0);
+ }
+
let mut search_pc = PageContent::new();
if self.search_open {
search_pc.rects.push((
@@ -378,8 +386,9 @@ impl SystemInterface {
}
}
- let logical_tw = tw / s;
- let logical_lh = lh / s;
+ let scale_factor = cce_ui::scale::scale_factor();
+ let logical_tw = tw / scale_factor;
+ let logical_lh = lh / scale_factor;
let text_x = if left_align {
base.x + 8.0
} else {
diff --git a/src/watchers.rs b/src/watchers.rs
index 317549f..a1cdf5d 100644
--- a/src/watchers.rs
+++ b/src/watchers.rs
@@ -7,7 +7,7 @@ pub struct Watchers {
pub rx_audio: Receiver<audio::AudioState>,
pub rx_network: Receiver<network::NetworkState>,
pub rx_processes: Receiver<processes::ProcessesState>,
- pub rx_system: Receiver<system_info::SystemState>,
+ pub rx_system: Receiver<system_info::SystemInfo>,
pub rx_storage: Receiver<storage::StorageState>,
pub rx_notifications: Receiver<notifications::NotificationsConfig>,
pub rx_services: Receiver<Vec<processes::ServiceInfo>>,