system settings
git clone https://git.lucas.co/cce-system-interface.git
refactor: dispatch roots go by WidgetId (cce-ui plumbing retype)
propagate_event takes a WidgetId resolved through the registry; dispatch
sites name roots by id instead of minting raw pointers, and roots the
render path never registered are registered explicitly.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01N4ajhvVZtyEEEus9bodsj3
src/input_handler.rs | 47 +++++++++++++++++++++++-----------------------
src/pages/accounts.rs | 17 ++++++++---------
src/pages/audio.rs | 15 +++++++--------
src/pages/fonts.rs | 47 ++++++++++++++++++++++++++++++++++++++--------
src/pages/mod.rs | 10 ++++++++--
src/pages/network.rs | 8 ++++----
src/pages/notifications.rs | 9 ++++-----
src/pages/packages.rs | 21 ++++++++++++++++-----
src/pages/processes.rs | 15 +++++++++++----
src/pages/storage.rs | 2 +-
src/pages/system_info.rs | 17 +++++++++++++----
src/renderer.rs | 21 +++++++++++++++++++++
12 files changed, 155 insertions(+), 74 deletions(-)
diff --git a/src/input_handler.rs b/src/input_handler.rs
index 357694c..4b260f5 100644
--- a/src/input_handler.rs
+++ b/src/input_handler.rs
@@ -16,7 +16,7 @@ impl SystemInterface {
if self.search_open && ly_no_scroll >= (sh_logical - 42.0) {
// Routed (6bd): chrome coords, no scroll offset — same as the direct call.
let ev = cce_ui::widget::Event::PointerMove { x: lx_no_scroll, y: ly_no_scroll, local_x: lx_no_scroll, local_y: ly_no_scroll };
- let sb = self.search_box.as_ptr_mut();
+ let sb = self.search_box.id();
if self.ui_context.propagate_event(&ev, sb) {
self.needs_rebuild = true;
}
@@ -37,7 +37,7 @@ impl SystemInterface {
let mut changed = false;
{
let ev = cce_ui::widget::Event::PointerMove { x: lx_no_scroll, y: ly_no_scroll, local_x: lx_no_scroll, local_y: ly_no_scroll };
- let dd = self.page_dropdown.as_ptr_mut();
+ let dd = self.page_dropdown.id();
if self.ui_context.propagate_event(&ev, dd) {
changed = true;
}
@@ -97,7 +97,7 @@ impl SystemInterface {
let sh_logical = self.height as f32 / s;
if self.search_open && ly_no_scroll >= (sh_logical - 42.0) {
let ev = cce_ui::widget::Event::MouseButton { button, state, x: lx_no_scroll, y: ly_no_scroll, local_x: lx_no_scroll, local_y: ly_no_scroll };
- let sb = self.search_box.as_ptr_mut();
+ let sb = self.search_box.id();
if self.ui_context.propagate_event(&ev, sb) {
self.needs_rebuild = true;
}
@@ -127,8 +127,8 @@ impl SystemInterface {
}
let dd_ev = cce_ui::widget::Event::MouseButton { button, state, x: lx_no_scroll, y: ly_no_scroll, local_x: lx_no_scroll, local_y: ly_no_scroll };
- let dd_ptr = self.page_dropdown.as_ptr_mut();
- if self.ui_context.propagate_event(&dd_ev, dd_ptr) {
+ let dd_root = self.page_dropdown.id();
+ if self.ui_context.propagate_event(&dd_ev, dd_root) {
if self.page_dropdown.take_change() {
let idx = self.page_dropdown.selected;
if idx < Page::ALL.len() {
@@ -153,9 +153,9 @@ impl SystemInterface {
let mut button_handled = false;
let mut clicked_action = None;
let btn_ev = cce_ui::widget::Event::MouseButton { button, state, x: phys_x, y: phys_y, local_x: phys_x, local_y: phys_y };
- let btn_ptrs: Vec<_> = self.page_buttons[self.scrollable_buttons_start_idx..].iter_mut().map(|(b, _)| b.as_ptr_mut()).collect();
- for ptr in btn_ptrs {
- if self.ui_context.propagate_event(&btn_ev, ptr) {
+ let btn_roots: Vec<_> = self.page_buttons[self.scrollable_buttons_start_idx..].iter().map(|(b, _)| b.id()).collect();
+ for root in btn_roots {
+ if self.ui_context.propagate_event(&btn_ev, root) {
button_handled = true;
}
}
@@ -280,7 +280,7 @@ impl SystemInterface {
/// 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::WidgetHost + 'static)> {
+ pub(crate) fn page_dispatch_roots(&mut self) -> Vec<cce_ui::widget::WidgetId> {
self.app
.get_current_page_mut()
.section_widgets()
@@ -333,8 +333,8 @@ impl SystemInterface {
*y -= self.scroll_y;
*local_y -= self.scroll_y;
}
- let sb_ptr = self.page_scroll_bar.as_ptr_mut();
- if self.ui_context.propagate_event(&sb_event, sb_ptr) {
+ let sb_root = self.page_scroll_bar.id();
+ if self.ui_context.propagate_event(&sb_event, sb_root) {
if !is_pointer_move {
return true;
}
@@ -347,7 +347,11 @@ impl SystemInterface {
// hit-gate keeps missed presses falling through, so order vs the sections only
// matters for overlap — and the rows sit inside list frames the sections never
// claim. Collected fresh per event: the item Vecs get rebuilt across frames.
- let extra_roots = self.app.get_current_page_mut().extra_dispatch_roots();
+ let extra_roots = {
+ let page = self.app.get_current_page_mut();
+ page.register_extra_dispatch_roots(&mut self.ui_context);
+ page.extra_dispatch_roots()
+ };
for root in extra_roots {
if self.ui_context.propagate_event(event, root) {
if !is_pointer_move {
@@ -408,10 +412,7 @@ impl SystemInterface {
self.search_open = true;
self.search_box.set_value_string("");
self.search_query.clear();
- let search_box_ptr = self.search_box.as_ptr_mut();
- unsafe {
- (*search_box_ptr).focus();
- }
+ cce_ui::widget::WidgetHost::focus(&mut self.search_box);
self.ui_context.set_focused(&mut self.search_box);
self.needs_rebuild = true;
return true;
@@ -446,7 +447,7 @@ impl SystemInterface {
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) })
+ .position(|&id| cce_ui::widget::focus::is_focused_id(id))
.map(|wi| (si, wi))
});
if let Some((si, wi)) = focused_pos {
@@ -459,10 +460,9 @@ impl SystemInterface {
} else {
wi - 1
};
- let next_ptr = group[next];
- unsafe {
- let w = &mut *next_ptr;
- cce_ui::widget::focus::set_focused(w, Some(&mut self.ui_context));
+ let next_id = group[next];
+ cce_ui::widget::focus::set_focused_id(next_id, Some(&mut self.ui_context));
+ if let Some(w) = self.ui_context.get_widget_mut(next_id) {
w.focus();
}
self.needs_rebuild = true;
@@ -492,9 +492,8 @@ impl SystemInterface {
}
if descend {
if let Some(&first) = groups[idx].first() {
- unsafe {
- let w = &mut *first;
- cce_ui::widget::focus::set_focused(w, Some(&mut self.ui_context));
+ cce_ui::widget::focus::set_focused_id(first, Some(&mut self.ui_context));
+ if let Some(w) = self.ui_context.get_widget_mut(first) {
w.focus();
}
self.focused_section = None;
diff --git a/src/pages/accounts.rs b/src/pages/accounts.rs
index 08aa4a3..d5cb6ec 100644
--- a/src/pages/accounts.rs
+++ b/src/pages/accounts.rs
@@ -824,19 +824,18 @@ pub fn update(state: &mut AccountsState, msg: AccountsMessage) {
impl crate::pages::AppPage for AccountsState {
// Sections: [Accounts, Modify Accounts] — the modify group depends on the mode.
- fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::WidgetHost + 'static)>> {
- use cce_ui::widget::WidgetHost;
- let modify: Vec<*mut (dyn WidgetHost + 'static)> = if self.editing_oauth_creds {
+ fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
+ let modify: Vec<cce_ui::widget::WidgetId> = if self.editing_oauth_creds {
vec![
- self.oauth_client_id_box.as_ptr_mut(),
- self.oauth_client_secret_box.as_ptr_mut(),
+ self.oauth_client_id_box.id(),
+ self.oauth_client_secret_box.id(),
]
} else if self.adding_new {
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(),
+ self.email_box.id(),
+ self.password_box.id(),
+ self.imap_box.id(),
+ self.smtp_box.id(),
]
} else {
Vec::new()
diff --git a/src/pages/audio.rs b/src/pages/audio.rs
index 1795cd2..25c2645 100644
--- a/src/pages/audio.rs
+++ b/src/pages/audio.rs
@@ -405,27 +405,26 @@ pub fn update(state: &mut AudioState, msg: AudioMessage) {
impl crate::pages::AppPage for AudioState {
// 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::WidgetHost + 'static)>> {
- use cce_ui::widget::WidgetHost;
- let mut output: Vec<*mut (dyn WidgetHost + 'static)> = Vec::new();
+ fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
+ let mut output: Vec<cce_ui::widget::WidgetId> = Vec::new();
for (i, sink) in self.sinks.iter().enumerate() {
if sink.active {
if let Some(sb) = self.sink_spinboxes.get_mut(i) {
- output.push(sb.as_ptr_mut());
+ output.push(sb.id());
}
if let Some(sl) = self.sink_sliders.get_mut(i) {
- output.push(sl.as_ptr_mut());
+ output.push(sl.id());
}
}
}
- let mut input: Vec<*mut (dyn WidgetHost + 'static)> = Vec::new();
+ let mut input: Vec<cce_ui::widget::WidgetId> = Vec::new();
for (i, src) in self.sources.iter().enumerate() {
if src.active {
if let Some(sb) = self.source_spinboxes.get_mut(i) {
- input.push(sb.as_ptr_mut());
+ input.push(sb.id());
}
if let Some(sl) = self.source_sliders.get_mut(i) {
- input.push(sl.as_ptr_mut());
+ input.push(sl.id());
}
}
}
diff --git a/src/pages/fonts.rs b/src/pages/fonts.rs
index f51effa..99c6424 100644
--- a/src/pages/fonts.rs
+++ b/src/pages/fonts.rs
@@ -1,6 +1,6 @@
use std::fs;
use cce_ui::layout::LayoutStrategy;
-use cce_ui::widget::{TextBox, Dropdown, Spinbox, WidgetHost};
+use cce_ui::widget::{TextBox, Dropdown, Spinbox};
use crate::app::{PageContent, AppAction};
use crate::pages::AppPage;
@@ -98,17 +98,48 @@ impl Default for FontsState {
impl AppPage for FontsState {
// Sections: [Preferred Fonts, Borders, Status Interface, Fuzzel, Terminal]
- fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::WidgetHost + 'static)>> {
- use cce_ui::widget::WidgetHost;
+ fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
vec![
- 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()],
+ vec![self.sans_box.id(), self.serif_box.id(), self.mono_box.id()],
+ vec![self.borders_menu.id(), self.borders_box.id()],
+ vec![self.status_menu.id(), self.status_box.id()],
+ vec![self.fuzzel_menu.id(), self.fuzzel_box.id(), self.fuzzel_size_box.id()],
+ vec![self.terminal_menu.id(), self.terminal_box.id(), self.terminal_size_box.id()],
]
}
+ // Not every section widget passes through `render_widget`'s registration side
+ // effect (the menus draw custom); the id-rooted router needs them all resolvable.
+ fn register_extra_dispatch_roots(&mut self, ctx: &mut cce_ui::context::UiContext) {
+ use cce_ui::widget::WidgetHost;
+ let (id, ptr) = (self.sans_box.id(), self.sans_box.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.serif_box.id(), self.serif_box.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.mono_box.id(), self.mono_box.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.borders_menu.id(), self.borders_menu.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.borders_box.id(), self.borders_box.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.status_menu.id(), self.status_menu.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.status_box.id(), self.status_box.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.fuzzel_menu.id(), self.fuzzel_menu.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.fuzzel_box.id(), self.fuzzel_box.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.fuzzel_size_box.id(), self.fuzzel_size_box.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.terminal_menu.id(), self.terminal_menu.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.terminal_box.id(), self.terminal_box.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.terminal_size_box.id(), self.terminal_size_box.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ }
+
fn view(
&mut self,
cx: f32,
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index fd12931..58f7f01 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -64,7 +64,7 @@ pub trait AppPage {
/// 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::WidgetHost + 'static)>>;
+ fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>>;
fn view(
&mut self,
@@ -101,10 +101,16 @@ pub trait AppPage {
/// Extra top-level event-dispatch roots beyond the section containers: the per-row
/// widgets that used to hang under a `List`'s ScrollBox (dissolved — the rows now
/// dispatch directly; `Adapted` hit-gates presses/wheel so misses fall through).
- fn extra_dispatch_roots(&mut self) -> Vec<*mut (dyn cce_ui::widget::WidgetHost + 'static)> {
+ fn extra_dispatch_roots(&mut self) -> Vec<cce_ui::widget::WidgetId> {
Vec::new()
}
+ /// Refresh the extra dispatch roots' registrations (id-rooted router: roots resolve
+ /// through the registry). The row Vecs are rebuilt on data refresh, so pages
+ /// re-register the current allocations right before each dispatch — the same
+ /// liveness contract the pointer-rooted dispatch had. Default no-op.
+ fn register_extra_dispatch_roots(&mut self, _ctx: &mut cce_ui::context::UiContext) {}
+
/// The dissolved inner lists' wheel (`ScrollBox::mouse_wheel`, hit-scoped). Runs after
/// the widget dispatch and before the manual whole-page scroll fallback — the legacy
/// "inner ScrollBoxes take the wheel first" order.
diff --git a/src/pages/network.rs b/src/pages/network.rs
index 6b3f3c2..89ff7b1 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -1,7 +1,7 @@
use crate::app::{AppAction, PageContent, SectionContextExt};
use crate::scroll_region::ScrollRegion;
use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy, RenderTarget};
-use cce_ui::widget::{Adapted, Toggle, WidgetHost};
+use cce_ui::widget::{Adapted, Toggle};
#[derive(Debug, Clone)]
pub struct WifiNetwork {
@@ -526,10 +526,10 @@ impl NetworkState {
impl crate::pages::AppPage for NetworkState {
// Sections: [WiFi, Bluetooth]
- fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn WidgetHost + 'static)>> {
+ fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
vec![
- vec![self.wifi_toggle.as_ptr_mut()],
- vec![self.bt_toggle.as_ptr_mut()],
+ vec![self.wifi_toggle.id()],
+ vec![self.bt_toggle.id()],
]
}
diff --git a/src/pages/notifications.rs b/src/pages/notifications.rs
index 8e8c9a4..a7c0a33 100644
--- a/src/pages/notifications.rs
+++ b/src/pages/notifications.rs
@@ -185,12 +185,11 @@ fn get_config_path() -> String {
impl AppPage for NotificationsState {
// Sections: [Notifications Settings]
- fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::WidgetHost + 'static)>> {
- use cce_ui::widget::WidgetHost;
+ fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
vec![vec![
- self.enable_toggle.as_ptr_mut(),
- self.bell_menu.as_ptr_mut(),
- self.duration_spinbox.as_ptr_mut(),
+ self.enable_toggle.id(),
+ self.bell_menu.id(),
+ self.duration_spinbox.id(),
]]
}
diff --git a/src/pages/packages.rs b/src/pages/packages.rs
index 41cfd22..9e39f90 100644
--- a/src/pages/packages.rs
+++ b/src/pages/packages.rs
@@ -719,9 +719,9 @@ impl PackagesState {
impl crate::pages::AppPage for PackagesState {
// Sections: [Packages, Package Info, System Update]
- fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn WidgetHost + 'static)>> {
+ fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
vec![
- vec![self.search_box.as_ptr_mut()],
+ vec![self.search_box.id()],
Vec::new(),
Vec::new(),
]
@@ -780,10 +780,21 @@ impl crate::pages::AppPage for PackagesState {
}
}
- fn extra_dispatch_roots(&mut self) -> Vec<*mut (dyn WidgetHost + 'static)> {
+ fn extra_dispatch_roots(&mut self) -> Vec<cce_ui::widget::WidgetId> {
match self.active_tab {
- PackageTab::Installed => self.installed_items.iter_mut().map(|i| i.as_ptr_mut()).collect(),
- PackageTab::Updates => self.updates_items.iter_mut().map(|i| i.as_ptr_mut()).collect(),
+ PackageTab::Installed => self.installed_items.iter().map(|i| i.id()).collect(),
+ PackageTab::Updates => self.updates_items.iter().map(|i| i.id()).collect(),
+ }
+ }
+
+ fn register_extra_dispatch_roots(&mut self, ctx: &mut cce_ui::context::UiContext) {
+ let items = match self.active_tab {
+ PackageTab::Installed => &mut self.installed_items,
+ PackageTab::Updates => &mut self.updates_items,
+ };
+ for i in items.iter_mut() {
+ let (id, ptr) = (i.id(), i.as_ptr_mut());
+ ctx.register_widget(id, ptr);
}
}
diff --git a/src/pages/processes.rs b/src/pages/processes.rs
index 0a0b37f..0d51f1f 100644
--- a/src/pages/processes.rs
+++ b/src/pages/processes.rs
@@ -482,10 +482,10 @@ fn service_action(name: &str, action: &str, is_system: bool) {
impl crate::pages::AppPage for ProcessesState {
// Sections: [Processes, Services]
- fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn WidgetHost + 'static)>> {
+ fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
vec![
Vec::new(),
- vec![self.services_search_box.as_ptr_mut()],
+ vec![self.services_search_box.id()],
]
}
@@ -505,8 +505,15 @@ impl crate::pages::AppPage for ProcessesState {
fn propagate_widget_changes(&mut self, _actions: &mut Vec<crate::app::AppAction>) {}
- fn extra_dispatch_roots(&mut self) -> Vec<*mut (dyn WidgetHost + 'static)> {
- self.service_items.iter_mut().map(|i| i.as_ptr_mut()).collect()
+ fn extra_dispatch_roots(&mut self) -> Vec<cce_ui::widget::WidgetId> {
+ self.service_items.iter().map(|i| i.id()).collect()
+ }
+
+ fn register_extra_dispatch_roots(&mut self, ctx: &mut cce_ui::context::UiContext) {
+ for i in self.service_items.iter_mut() {
+ let (id, ptr) = (i.id(), i.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ }
}
fn handle_pointer_move(
diff --git a/src/pages/storage.rs b/src/pages/storage.rs
index 16beb8a..0c00000 100644
--- a/src/pages/storage.rs
+++ b/src/pages/storage.rs
@@ -310,7 +310,7 @@ pub fn update(state: &mut StorageState, msg: StorageMessage) {
impl crate::pages::AppPage for StorageState {
// Sections: [Local Storage, Memory, Full System Backup] — no evented widgets.
- fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::WidgetHost + 'static)>> {
+ fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
vec![Vec::new(), Vec::new(), Vec::new()]
}
diff --git a/src/pages/system_info.rs b/src/pages/system_info.rs
index 90d85e2..21140dd 100644
--- a/src/pages/system_info.rs
+++ b/src/pages/system_info.rs
@@ -820,19 +820,28 @@ pub fn update(state: &mut SystemState, msg: SystemMessage, ctx: &mut cce_ui::con
impl crate::pages::AppPage for SystemState {
// Sections: [System, System Actions, CPU, GPU, CPU Governor, GPU Power, Battery]
- fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::WidgetHost + 'static)>> {
- use cce_ui::widget::WidgetHost;
+ fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
vec![
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![self.cpu_gov_menu.id()],
+ vec![self.gpu_gov_menu.id()],
Vec::new(),
]
}
+ // The governor menus draw custom (no `render_widget` registration side effect);
+ // the id-rooted router needs them resolvable.
+ fn register_extra_dispatch_roots(&mut self, ctx: &mut cce_ui::context::UiContext) {
+ use cce_ui::widget::WidgetHost;
+ let (id, ptr) = (self.cpu_gov_menu.id(), self.cpu_gov_menu.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ let (id, ptr) = (self.gpu_gov_menu.id(), self.gpu_gov_menu.as_ptr_mut());
+ ctx.register_widget(id, ptr);
+ }
+
fn view(
&mut self,
cx: f32,
diff --git a/src/renderer.rs b/src/renderer.rs
index 78c0946..26ad154 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -641,6 +641,27 @@ impl SystemInterface {
self.widgets = widgets;
self.texts = texts;
self.page_buttons = page_buttons;
+
+ // The id-rooted router (`propagate_event(event, WidgetId)`) resolves roots
+ // through the registry, and `clear_hierarchy` above wiped it. The view pass
+ // re-registers page widgets through `render_widget`; the chrome dispatch roots
+ // never go through it, so re-register them here. The page buttons are
+ // per-rebuild clones — registration follows the fresh allocations.
+ {
+ let id = self.search_box.id();
+ let ptr = self.search_box.as_ptr_mut();
+ self.ui_context.register_widget(id, ptr);
+ let id = self.page_dropdown.id();
+ let ptr = self.page_dropdown.as_ptr_mut();
+ self.ui_context.register_widget(id, ptr);
+ let id = self.page_scroll_bar.id();
+ let ptr = self.page_scroll_bar.as_ptr_mut();
+ self.ui_context.register_widget(id, ptr);
+ for (btn, _) in self.page_buttons.iter_mut() {
+ let (id, ptr) = (btn.id(), btn.as_ptr_mut());
+ self.ui_context.register_widget(id, ptr);
+ }
+ }
self.needs_rebuild = false;
self.last_scroll_y = self.scroll_y;
self.ui_context.clear_dirty();