system settings
git clone https://git.lucas.co/cce-system-interface.git
feat: SectionContainer DISSOLVED (Phase 6w) — settings is embedded-base-FREE
The per-rebuild SectionContainer clones were pure event/focus plumbing (never
painted, zero rects): propagate roots whose container children were the pages'
widgets, plus the ctrl-nav focus targets. Both reduce to app state:
- AppPage::section_widgets() replaces get_section_containers + link_children +
clear_children: one widget-pointer group per section (old count/order/link
order). The widgets dispatch directly as propagate roots, flattened in the
legacy order (sections last-to-first, reverse link order within).
- Section-level keyboard focus is SystemInterface::focused_section (index),
single-slot with the global widget focus like the shared FOCUSED_WIDGET was:
entry focuses section 0; ctrl+j/k cycle sections; ctrl+i descends to the
section's first widget (legacy went via the header/container intermediates);
ctrl+u ascends from a widget back to its section; a widget click that takes
global focus drops the section highlight; page switch resets it.
- This also removes a latent use-after-free: the focused section clone was
dropped and reallocated every rebuild while focus::FOCUSED_WIDGET kept the
raw pointer (surviving only by same-size allocation reuse).
A/B render dumps: all nine pages byte-identical modulo live data (sections
never painted). Live-verified: notifications spinbox/menu open+select with
in-frame occlusion, audio spinbox round trip through pactl+watcher, processes
filter-box click-to-focus, services list wheel (6v regions intact).
Ctrl-nav not headlessly drivable — user spot-check pending.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/input_handler.rs | 125 ++++++++++++++++++++++++++++++++-------------
src/main.rs | 7 ++-
src/pages/accounts.rs | 70 +++++++------------------
src/pages/audio.rs | 72 ++++++--------------------
src/pages/fonts.rs | 68 +++---------------------
src/pages/mod.rs | 17 +++---
src/pages/network.rs | 40 ++-------------
src/pages/notifications.rs | 39 +++-----------
src/pages/packages.rs | 47 ++---------------
src/pages/processes.rs | 37 ++------------
src/pages/storage.rs | 42 ++-------------
src/pages/system_info.rs | 38 ++++----------
src/renderer.rs | 28 +++-------
13 files changed, 182 insertions(+), 448 deletions(-)
diff --git a/src/input_handler.rs b/src/input_handler.rs
index 9ec603b..83dc7a0 100644
--- a/src/input_handler.rs
+++ b/src/input_handler.rs
@@ -114,6 +114,7 @@ impl SystemInterface {
let idx = self.page_dropdown.selected;
if idx < Page::ALL.len() {
cce_ui::widget::focus::clear_focus();
+ self.focused_section = None;
let new_page = Page::ALL[idx];
self.app.current_page = new_page;
self.current_page_shared.store(idx as u8, std::sync::atomic::Ordering::SeqCst);
@@ -166,6 +167,12 @@ impl SystemInterface {
self.propagate_widget_changes(&mut actions);
+ // Single-slot focus (Phase 6w): if a widget click took the global focus, the
+ // section-level highlight yields — exactly as when both lived in FOCUSED_WIDGET.
+ if state == cce_ui::widget::ElementState::Pressed && cce_ui::widget::focus::has_focus() {
+ self.focused_section = None;
+ }
+
for a in &actions {
self.handle_action(a);
}
@@ -248,10 +255,18 @@ impl SystemInterface {
false
}
- /// The current page's event-dispatch roots (Phase 6u — the Page widget is dissolved):
- /// the app-held section-container clones every page links its widgets under.
+ /// The current page's event-dispatch roots (Phase 6w — SectionContainer dissolved):
+ /// the pages' widgets themselves, flattened in the legacy propagate order (sections
+ /// last-to-first, and within a section the container children were visited in
+ /// reverse link order).
pub(crate) fn page_dispatch_roots(&mut self) -> Vec<*mut (dyn cce_ui::widget::Element + 'static)> {
- self.page_sec_containers.iter_mut().map(|s| s.as_ptr_mut()).collect()
+ self.app
+ .get_current_page_mut()
+ .section_widgets()
+ .into_iter()
+ .rev()
+ .flat_map(|group| group.into_iter().rev())
+ .collect()
}
/// Replicates the dissolved Page's event routing: the out-of-bounds gate (events whose
@@ -322,7 +337,7 @@ impl SystemInterface {
}
let roots = self.page_dispatch_roots();
- for root in roots.into_iter().rev() {
+ for root in roots {
if self.ui_context.propagate_event(event, root) {
if !is_pointer_move {
return true;
@@ -383,54 +398,92 @@ impl SystemInterface {
}
if event.state == cce_ui::widget::ElementState::Pressed && !event.repeat {
- let is_nav_key = match (&event.logical_key, event.ctrl) {
- (cce_ui::widget::Key::Character(c), true) if c == "j" || c == "J" || c == "k" || c == "K" || c == "u" || c == "U" || c == "i" || c == "I" => true,
- _ => false,
+ let (forward, backward, ascend, descend) = match (&event.logical_key, event.ctrl) {
+ (cce_ui::widget::Key::Character(c), true) => (
+ c == "j" || c == "J",
+ c == "k" || c == "K",
+ c == "u" || c == "U",
+ c == "i" || c == "I",
+ ),
+ _ => (false, false, false, false),
};
- if is_nav_key {
+ if forward || backward || ascend || descend {
+ // SectionContainer dissolved (Phase 6w): section-level focus is the
+ // app-side index, widget-level focus stays in the global focus module,
+ // and the two are single-slot (as when sections and widgets shared the
+ // one FOCUSED_WIDGET). Nav within a section walks the page's widget
+ // group where the container's child list used to be walked.
if cce_ui::widget::focus::has_focus() {
+ // Widget-internal nav first (ctrl+i descend into a widget's own
+ // children still works through the pointer walk).
if cce_ui::widget::focus::navigate_focus(&event.logical_key, event.ctrl) {
self.needs_rebuild = true;
return true;
}
- // Sections are parentless with the Page dissolved (6u), so the
- // parent-pointer walk can't cycle BETWEEN them — do it app-side.
- if let cce_ui::widget::Key::Character(c) = &event.logical_key {
- let forward = c == "j" || c == "J";
- let backward = c == "k" || c == "K";
+ let groups = self.app.get_current_page_mut().section_widgets();
+ let focused_pos = groups.iter().enumerate().find_map(|(si, g)| {
+ g.iter()
+ .position(|&w| unsafe { cce_ui::widget::focus::is_focused(&*w) })
+ .map(|wi| (si, wi))
+ });
+ if let Some((si, wi)) = focused_pos {
+ if forward || backward {
+ let group = &groups[si];
+ let next = if forward {
+ (wi + 1) % group.len()
+ } else if wi == 0 {
+ group.len() - 1
+ } else {
+ wi - 1
+ };
+ let next_ptr = group[next];
+ unsafe {
+ let w = &mut *next_ptr;
+ cce_ui::widget::focus::set_focused(w);
+ w.focus();
+ }
+ self.needs_rebuild = true;
+ return true;
+ }
+ if ascend {
+ cce_ui::widget::focus::clear_focus();
+ self.focused_section = Some(si);
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
+ } else if let Some(idx) = self.focused_section {
+ let groups = self.app.get_current_page_mut().section_widgets();
+ if !groups.is_empty() {
+ let idx = idx.min(groups.len() - 1);
if forward || backward {
- let mut roots = self.page_dispatch_roots();
- let focused_idx = roots.iter().position(|&r| unsafe {
- cce_ui::widget::focus::is_focused(&*r)
+ self.focused_section = Some(if forward {
+ (idx + 1) % groups.len()
+ } else if idx == 0 {
+ groups.len() - 1
+ } else {
+ idx - 1
});
- if let (Some(idx), true) = (focused_idx, !roots.is_empty()) {
- let next = if forward {
- (idx + 1) % roots.len()
- } else if idx == 0 {
- roots.len() - 1
- } else {
- idx - 1
- };
+ self.needs_rebuild = true;
+ return true;
+ }
+ if descend {
+ if let Some(&first) = groups[idx].first() {
unsafe {
- let sec = &mut *roots[next];
- cce_ui::widget::focus::set_focused(sec);
- sec.focus();
+ let w = &mut *first;
+ cce_ui::widget::focus::set_focused(w);
+ w.focus();
}
+ self.focused_section = None;
self.needs_rebuild = true;
return true;
}
}
}
} else {
- // Entry point: the dissolved page root used to take focus here; focus
- // the first section instead.
- let roots = self.page_dispatch_roots();
- if let Some(&first) = roots.first() {
- unsafe {
- let sec = &mut *first;
- cce_ui::widget::focus::set_focused(sec);
- sec.focus();
- }
+ // Entry point: focus the first section.
+ if !self.app.get_current_page_mut().section_widgets().is_empty() {
+ self.focused_section = Some(0);
self.needs_rebuild = true;
return true;
}
diff --git a/src/main.rs b/src/main.rs
index 2b00342..cc1bc3e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -78,7 +78,10 @@ struct SystemInterface {
scrollable_text_items_start_idx: usize,
scrollable_buttons_start_idx: usize,
last_scroll_y: f32,
- page_sec_containers: Vec<cce_ui::widget::SectionContainer>,
+ // SectionContainer DISSOLVED (Phase 6w): section-level keyboard focus is this index
+ // (single-slot with the global widget focus — descending clears it); the per-section
+ // widget groups come from AppPage::section_widgets each time they're needed.
+ focused_section: Option<usize>,
page_dropdown: cce_ui::widget::Adapted<cce_ui::widget::input::Dropdown>,
// Switcher + Page DISSOLVED (Phase 6u): the current page is app.current_page, page
// scroll is scroll_y/max_scroll_y, and the page scrollbar is this app-owned widget
@@ -171,7 +174,7 @@ impl cce_ui::engine::Application for SystemInterface {
scrollable_text_items_start_idx: 0,
scrollable_buttons_start_idx: 0,
last_scroll_y: 0.0,
- page_sec_containers: Vec::new(),
+ focused_section: None,
page_dropdown,
page_scroll_bar: cce_ui::widget::ScrollBar::new(),
content_h: 0.0,
diff --git a/src/pages/accounts.rs b/src/pages/accounts.rs
index f9f2643..acf59aa 100644
--- a/src/pages/accounts.rs
+++ b/src/pages/accounts.rs
@@ -823,59 +823,25 @@ pub fn update(state: &mut AccountsState, msg: AccountsMessage) {
}
impl crate::pages::AppPage for AccountsState {
- fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
- self.email_box.clear_children(ctx);
- self.email_box.set_parent(None, ctx);
- self.password_box.clear_children(ctx);
- self.password_box.set_parent(None, ctx);
- self.imap_box.clear_children(ctx);
- self.imap_box.set_parent(None, ctx);
- self.smtp_box.clear_children(ctx);
- self.smtp_box.set_parent(None, ctx);
- self.oauth_client_id_box.clear_children(ctx);
- self.oauth_client_id_box.set_parent(None, ctx);
- self.oauth_client_secret_box.clear_children(ctx);
- self.oauth_client_secret_box.set_parent(None, ctx);
- }
-
- fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
- vec![
- 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,
- }),
- ]
- }
-
- fn link_children(
- &mut self,
- sec_containers: &mut [cce_ui::widget::SectionContainer],
- ctx: &mut cce_ui::context::UiContext,
- ) {
-
- if self.editing_oauth_creds {
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.oauth_client_id_box, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.oauth_client_secret_box, ctx);
+ // Sections: [Accounts, Modify Accounts] — the modify group depends on the mode.
+ fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+ use cce_ui::widget::Element;
+ let modify: Vec<*mut (dyn Element + 'static)> = if self.editing_oauth_creds {
+ vec![
+ self.oauth_client_id_box.as_ptr_mut(),
+ self.oauth_client_secret_box.as_ptr_mut(),
+ ]
} else if self.adding_new {
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.email_box, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.password_box, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.imap_box, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.smtp_box, ctx);
- }
+ vec![
+ self.email_box.as_ptr_mut(),
+ self.password_box.as_ptr_mut(),
+ self.imap_box.as_ptr_mut(),
+ self.smtp_box.as_ptr_mut(),
+ ]
+ } else {
+ Vec::new()
+ };
+ vec![Vec::new(), modify]
}
fn view(
diff --git a/src/pages/audio.rs b/src/pages/audio.rs
index 4ff7733..4c7fef6 100644
--- a/src/pages/audio.rs
+++ b/src/pages/audio.rs
@@ -407,75 +407,33 @@ pub fn update(state: &mut AudioState, msg: AudioMessage) {
}
impl crate::pages::AppPage for AudioState {
- fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
- for sb in &mut self.sink_spinboxes {
- sb.clear_children(ctx);
- sb.set_parent(None, ctx);
- }
- for sb in &mut self.source_spinboxes {
- sb.clear_children(ctx);
- sb.set_parent(None, ctx);
- }
- for slider in &mut self.sink_sliders {
- slider.clear_children(ctx);
- slider.set_parent(None, ctx);
- }
- for slider in &mut self.source_sliders {
- slider.clear_children(ctx);
- slider.set_parent(None, ctx);
- }
- }
-
- 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,
- })
- .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),
- ]
- }
-
- fn link_children(
- &mut self,
- sec_containers: &mut [cce_ui::widget::SectionContainer],
- ctx: &mut cce_ui::context::UiContext,
- ) {
-
+ // Sections: [Output, Input] — only active devices' controls, spinbox before slider
+ // per device (the old link order).
+ fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+ use cce_ui::widget::Element;
+ let mut output: Vec<*mut (dyn Element + 'static)> = Vec::new();
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 let Some(sb) = self.sink_spinboxes.get_mut(i) {
+ output.push(sb.as_ptr_mut());
}
- if i < self.sink_sliders.len() {
- cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut *self.sink_sliders[i], ctx);
+ if let Some(sl) = self.sink_sliders.get_mut(i) {
+ output.push(sl.as_ptr_mut());
}
}
}
-
+ let mut input: Vec<*mut (dyn Element + 'static)> = Vec::new();
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 let Some(sb) = self.source_spinboxes.get_mut(i) {
+ input.push(sb.as_ptr_mut());
}
- if i < self.source_sliders.len() {
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut *self.source_sliders[i], ctx);
+ if let Some(sl) = self.source_sliders.get_mut(i) {
+ input.push(sl.as_ptr_mut());
}
}
}
+ vec![output, input]
}
fn view(
diff --git a/src/pages/fonts.rs b/src/pages/fonts.rs
index 87a692f..de53a04 100644
--- a/src/pages/fonts.rs
+++ b/src/pages/fonts.rs
@@ -97,70 +97,18 @@ impl Default for FontsState {
}
impl AppPage for FontsState {
- fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
- self.sans_box.clear_children(ctx);
- self.sans_box.set_parent(None, ctx);
- self.serif_box.clear_children(ctx);
- self.serif_box.set_parent(None, ctx);
- self.mono_box.clear_children(ctx);
- self.mono_box.set_parent(None, ctx);
- self.borders_box.clear_children(ctx);
- self.borders_box.set_parent(None, ctx);
- self.status_box.clear_children(ctx);
- self.status_box.set_parent(None, ctx);
- self.fuzzel_box.clear_children(ctx);
- self.fuzzel_box.set_parent(None, ctx);
- self.terminal_box.clear_children(ctx);
- self.terminal_box.set_parent(None, ctx);
- self.borders_menu.clear_children(ctx);
- self.borders_menu.set_parent(None, ctx);
- self.status_menu.clear_children(ctx);
- self.status_menu.set_parent(None, ctx);
- self.fuzzel_menu.clear_children(ctx);
- self.fuzzel_menu.set_parent(None, ctx);
- self.terminal_menu.clear_children(ctx);
- self.terminal_menu.set_parent(None, ctx);
- self.fuzzel_size_box.clear_children(ctx);
- self.fuzzel_size_box.set_parent(None, ctx);
- self.terminal_size_box.clear_children(ctx);
- self.terminal_size_box.set_parent(None, ctx);
- }
-
- fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+ // Sections: [Preferred Fonts, Borders, Status Interface, Fuzzel, Terminal]
+ fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+ use cce_ui::widget::Element;
vec![
- 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),
+ vec![self.sans_box.as_ptr_mut(), self.serif_box.as_ptr_mut(), self.mono_box.as_ptr_mut()],
+ vec![self.borders_menu.as_ptr_mut(), self.borders_box.as_ptr_mut()],
+ vec![self.status_menu.as_ptr_mut(), self.status_box.as_ptr_mut()],
+ vec![self.fuzzel_menu.as_ptr_mut(), self.fuzzel_box.as_ptr_mut(), self.fuzzel_size_box.as_ptr_mut()],
+ vec![self.terminal_menu.as_ptr_mut(), self.terminal_box.as_ptr_mut(), self.terminal_size_box.as_ptr_mut()],
]
}
- fn link_children(
- &mut self,
- sec_containers: &mut [cce_ui::widget::SectionContainer],
- ctx: &mut cce_ui::context::UiContext,
- ) {
-
- cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.sans_box, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.serif_box, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.mono_box, ctx);
-
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.borders_menu, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.borders_box, ctx);
-
- cce_ui::widget::link_parent_child(&mut sec_containers[2], &mut self.status_menu, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[2], &mut self.status_box, ctx);
-
- cce_ui::widget::link_parent_child(&mut sec_containers[3], &mut self.fuzzel_menu, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[3], &mut self.fuzzel_box, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[3], &mut self.fuzzel_size_box, ctx);
-
- cce_ui::widget::link_parent_child(&mut sec_containers[4], &mut self.terminal_menu, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[4], &mut self.terminal_box, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[4], &mut self.terminal_size_box, ctx);
- }
-
fn view(
&mut self,
cx: f32,
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index 684c14f..31f8ef1 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -59,17 +59,12 @@ impl Page {
}
pub trait AppPage {
- fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext);
-
- fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer>;
-
- /// Wire the page's widgets under the section containers (Phase 6u: the Page widget is
- /// dissolved — the app-held section clones are the top-level dispatch/focus roots).
- fn link_children(
- &mut self,
- sec_containers: &mut [cce_ui::widget::SectionContainer],
- ctx: &mut cce_ui::context::UiContext,
- );
+ /// Per-section event/nav widget groups (Phase 6w: SectionContainer dissolved). One
+ /// inner Vec per section — same count and order as the old section containers (the
+ /// outer length drives the `sec_focused` flags) — holding the widgets that used to
+ /// hang under that section's container, in the old link order. The widgets dispatch
+ /// directly as propagate roots and the groups drive the app-side ctrl-nav.
+ fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>>;
fn view(
&mut self,
diff --git a/src/pages/network.rs b/src/pages/network.rs
index 2a6dbca..164f215 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -525,46 +525,14 @@ impl NetworkState {
}
impl crate::pages::AppPage for NetworkState {
- fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
- self.wifi_toggle.clear_children(ctx);
- self.wifi_toggle.set_parent(None, ctx);
- self.bt_toggle.clear_children(ctx);
- self.bt_toggle.set_parent(None, ctx);
- }
-
- fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+ // Sections: [WiFi, Bluetooth]
+ fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn Element + 'static)>> {
vec![
- 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,
- }),
+ vec![self.wifi_toggle.as_ptr_mut()],
+ vec![self.bt_toggle.as_ptr_mut()],
]
}
- fn link_children(
- &mut self,
- sec_containers: &mut [cce_ui::widget::SectionContainer],
- ctx: &mut cce_ui::context::UiContext,
- ) {
-
- cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.wifi_toggle, ctx);
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.bt_toggle, ctx);
- }
-
fn view(
&mut self,
cx: f32,
diff --git a/src/pages/notifications.rs b/src/pages/notifications.rs
index 8697698..3d83dcd 100644
--- a/src/pages/notifications.rs
+++ b/src/pages/notifications.rs
@@ -184,37 +184,14 @@ fn get_config_path() -> String {
}
impl AppPage for NotificationsState {
- fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
- self.enable_toggle.clear_children(ctx);
- self.enable_toggle.set_parent(None, ctx);
- self.bell_menu.clear_children(ctx);
- self.bell_menu.set_parent(None, ctx);
- self.duration_spinbox.clear_children(ctx);
- self.duration_spinbox.set_parent(None, ctx);
- }
-
- 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,
- padding_x: 0.0,
- padding_y: 0.0,
- grid: None,
- }),
- ]
- }
-
- fn link_children(
- &mut self,
- sec_containers: &mut [cce_ui::widget::SectionContainer],
- ctx: &mut cce_ui::context::UiContext,
- ) {
- 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);
+ // Sections: [Notifications Settings]
+ fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+ use cce_ui::widget::Element;
+ vec![vec![
+ self.enable_toggle.as_ptr_mut(),
+ self.bell_menu.as_ptr_mut(),
+ self.duration_spinbox.as_ptr_mut(),
+ ]]
}
fn view(
diff --git a/src/pages/packages.rs b/src/pages/packages.rs
index 9761917..13c6daa 100644
--- a/src/pages/packages.rs
+++ b/src/pages/packages.rs
@@ -718,52 +718,15 @@ impl PackagesState {
}
impl crate::pages::AppPage for PackagesState {
- fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
- self.search_box.clear_children(ctx);
- self.search_box.set_parent(None, ctx);
- }
-
- fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+ // Sections: [Packages, Package Info, System Update]
+ fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn Element + 'static)>> {
vec![
- 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,
- }),
+ vec![self.search_box.as_ptr_mut()],
+ Vec::new(),
+ Vec::new(),
]
}
- fn link_children(
- &mut self,
- sec_containers: &mut [cce_ui::widget::SectionContainer],
- ctx: &mut cce_ui::context::UiContext,
- ) {
-
- cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.search_box, ctx);
- }
-
fn view(
&mut self,
cx: f32,
diff --git a/src/pages/processes.rs b/src/pages/processes.rs
index 92f5456..352445f 100644
--- a/src/pages/processes.rs
+++ b/src/pages/processes.rs
@@ -481,43 +481,14 @@ fn service_action(name: &str, action: &str, is_system: bool) {
}
impl crate::pages::AppPage for ProcessesState {
- fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
- self.services_search_box.clear_children(ctx);
- self.services_search_box.set_parent(None, ctx);
- }
-
- fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+ // Sections: [Processes, Services]
+ fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn Element + 'static)>> {
vec![
- 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,
- }),
+ Vec::new(),
+ vec![self.services_search_box.as_ptr_mut()],
]
}
- fn link_children(
- &mut self,
- sec_containers: &mut [cce_ui::widget::SectionContainer],
- ctx: &mut cce_ui::context::UiContext,
- ) {
-
- cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.services_search_box, ctx);
- }
-
fn view(
&mut self,
cx: f32,
diff --git a/src/pages/storage.rs b/src/pages/storage.rs
index 96a8034..ef81d1d 100644
--- a/src/pages/storage.rs
+++ b/src/pages/storage.rs
@@ -309,45 +309,9 @@ pub fn update(state: &mut StorageState, msg: StorageMessage) {
}
impl crate::pages::AppPage for StorageState {
- fn clear_children(&mut self, _ctx: &mut cce_ui::context::UiContext) {}
-
- fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
- vec![
- 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,
- }),
- ]
- }
-
- fn link_children(
- &mut self,
- _sec_containers: &mut [cce_ui::widget::SectionContainer],
- _ctx: &mut cce_ui::context::UiContext,
- ) {
+ // Sections: [Local Storage, Memory, Full System Backup] — no evented widgets.
+ fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+ vec![Vec::new(), Vec::new(), Vec::new()]
}
fn view(
diff --git a/src/pages/system_info.rs b/src/pages/system_info.rs
index 98af427..75102f8 100644
--- a/src/pages/system_info.rs
+++ b/src/pages/system_info.rs
@@ -894,38 +894,20 @@ pub fn update(state: &mut SystemState, msg: SystemMessage, ctx: &mut cce_ui::con
impl crate::pages::AppPage for SystemState {
- fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
- self.cpu_gov_menu.clear_children(ctx);
- self.cpu_gov_menu.set_parent(None, ctx);
- self.gpu_gov_menu.clear_children(ctx);
- self.gpu_gov_menu.set_parent(None, ctx);
- }
-
- fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+ // Sections: [System, System Actions, CPU, GPU, CPU Governor, GPU Power, Battery]
+ fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+ use cce_ui::widget::Element;
vec![
- cce_ui::widget::SectionContainer::new("System").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 Actions").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("CPU").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("GPU").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("CPU Governor").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("GPU Power").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("Battery").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
+ Vec::new(),
+ Vec::new(),
+ Vec::new(),
+ Vec::new(),
+ vec![self.cpu_gov_menu.as_ptr_mut()],
+ vec![self.gpu_gov_menu.as_ptr_mut()],
+ Vec::new(),
]
}
- fn link_children(
- &mut self,
- sec_containers: &mut [cce_ui::widget::SectionContainer],
- ctx: &mut cce_ui::context::UiContext,
- ) {
- // Phase 6u: System renders through the immediate view like every other page;
- // only its two menus need event dispatch/focus, linked into the app-held clone
- // sections exactly as the other pages do (the old one-time widget tree — labels,
- // buttons, state-owned sections — is dead; the view emits text/buttons directly).
- 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);
- }
-
fn view(
&mut self,
cx: f32,
diff --git a/src/renderer.rs b/src/renderer.rs
index fbd8d28..c93dc06 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -84,26 +84,10 @@ fn collect_window_child(
impl SystemInterface {
pub(crate) fn rebuild_layout(&mut self, sw: f32, sh: f32) {
+ // SectionContainer dissolved (Phase 6w): no per-rebuild section clones to
+ // relink — the page's widgets dispatch directly (registration happens in
+ // render_widget during the view pass below).
self.ui_context.clear_hierarchy();
- // ── Rebuild Element Focus Hierarchy (Switcher + Page dissolved, Phase 6u:
- // sections are the top-level dispatch/focus roots) ──
- for c in &mut self.page_sec_containers {
- c.clear_children(&mut self.ui_context);
- c.set_parent(None, &mut self.ui_context);
- }
-
- // Clear all widgets' hierarchy links
- self.search_box.clear_children(&mut self.ui_context);
- self.search_box.set_parent(None, &mut self.ui_context);
-
- for p in Page::ALL {
- self.app.get_page_mut(p).clear_children(&mut self.ui_context);
- }
-
- let active_page = self.app.get_current_page_mut();
- self.page_sec_containers = active_page.get_section_containers();
-
- active_page.link_children(&mut self.page_sec_containers, &mut self.ui_context);
self.sidebar_width = 0.0;
self.header_height = 0.0; // No CSD Titlebar
@@ -664,8 +648,10 @@ impl SystemInterface {
let mut layout = AdaptiveGrid::new(260.0, 20.0);
// Page root dissolved (6u): the ctrl-nav entry focuses section 0, so root focus is
// permanently false; views that highlighted on it OR in their first section's bool.
- let sec_focused: Vec<bool> = self.page_sec_containers.iter()
- .map(|c| cce_ui::widget::focus::is_focused(c))
+ // Section focus is the app-side index now (Phase 6w).
+ let n_sections = self.app.get_current_page_mut().section_widgets().len();
+ let sec_focused: Vec<bool> = (0..n_sections)
+ .map(|i| self.focused_section == Some(i))
.collect();
self.app.get_current_page_mut().view(cx, cy, cw, ch, false, &sec_focused, &mut layout, &mut self.ui_context)
}