git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commit160bdf8b3e5a5e49ff53ca612d165934950caac9
parent32debd53d1
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-07 15:30
refactor: register widgets by reference (UiContext::register_host)

register_widget(id, ptr) took a raw pointer in a safe function; cce-ui is
making it an unsafe fn now that every app registers through
register_host(&mut w), which takes the widget's own id and a live borrow.
Registration loops that existed only to hand the registry raw pointers
(and the unsafe blocks and self_ptr casts around them) are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/app.rs    |  5 +----
 src/main.rs   |  6 ++----
 src/render.rs | 12 ++++++------
 3 files changed, 9 insertions(+), 14 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index 0f2b992..7cc7bb6 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -8598,10 +8598,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
         state.sync_cursor_and_selection();
         state.sync_parameters_pane();
         for i in 0..WIDGET_COUNT {
-            let w = state.slots.get_dyn_mut(i);
-            let id = w.base().id();
-            let ptr = w as *mut (dyn WidgetHost + 'static);
-            state.ui_context.register_widget(id, ptr);
+            state.ui_context.register_host(state.slots.get_dyn_mut(i));
         }
         // The layout baseline waits for the layout pass above (it clamps the
         // plate fields), and the title computed earlier must be re-read
diff --git a/src/main.rs b/src/main.rs
index 52ef29a..b8e8b3b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15445,8 +15445,7 @@ mod tests {
         let mut d = Dialog::new();
         d.set_visible(true);
         WidgetHost::set_rect(&mut d, 0.0, 0.0, 520.0, 420.0);
-        let (id, ptr) = (d.id(), d.as_ptr_mut());
-        ctx.register_widget(id, ptr);
+        ctx.register_host(&mut d);
         let plain = |i: usize| Row { id: format!("c{i}"), label: format!("Command {i}"), chord: String::new(), control: None, truncate_head: false };
         d.set_rows(vec![
             Row { id: "zoom_level".into(), label: "Zoom".into(), chord: String::new(), control: Some(Control::Slider { value: 100.0, min: 20.0, max: 320.0, dec: 0, step: 10.0, suffix: "%" }), truncate_head: false },
@@ -15519,8 +15518,7 @@ mod tests {
         let mut d = Dialog::new();
         d.set_visible(true);
         WidgetHost::set_rect(&mut d, 0.0, 0.0, 520.0, 420.0);
-        let (id, ptr) = (d.id(), d.as_ptr_mut());
-        ctx.register_widget(id, ptr);
+        ctx.register_host(&mut d);
         d.set_rows(vec![
             Row { id: "zoom_level".into(), label: "Zoom".into(), chord: String::new(), control: Some(Control::Slider { value: 100.0, min: 20.0, max: 320.0, dec: 0, step: 10.0, suffix: "%" }), truncate_head: false },
             Row { id: "show_grid".into(), label: "Show Grid".into(), chord: "Ctrl+G".into(), control: Some(Control::Toggle(true)), truncate_head: false },
diff --git a/src/render.rs b/src/render.rs
index 28ff4ff..7ffd9cb 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -227,17 +227,17 @@ impl State {
         // root plate container DISSOLVED (Phase 6as): register the widgets (registry consumers:
         // coverage/parent walks) and paint each top-level widget directly in sorted order.
         self.ui_context.clear_hierarchy();
-        let widget_ptrs: Vec<*mut (dyn WidgetHost + 'static)> = (0..WIDGET_COUNT)
-            .map(|i| self.slots.get_dyn(i) as *const (dyn WidgetHost + 'static) as *mut (dyn WidgetHost + 'static))
-            .collect();
         // Register ALL slots, visible or not (id-rooted router): the wheel loop and the
         // hidden-widget broadcasts dispatch by id over the whole roster, and visibility
         // gates behavior inside the widget — an unregistered hidden root would drop the
-        // event before that gate.
+        // event before that gate. Before the paint pointers below are taken, so the
+        // `&mut` each registration borrows does not outlive them.
         for i in 0..WIDGET_COUNT {
-            let w = self.slots.get_dyn(i);
-            self.ui_context.register_widget(w.base().id(), w as *const (dyn WidgetHost + 'static) as *mut (dyn WidgetHost + 'static));
+            self.ui_context.register_host(self.slots.get_dyn_mut(i));
         }
+        let widget_ptrs: Vec<*mut (dyn WidgetHost + 'static)> = (0..WIDGET_COUNT)
+            .map(|i| self.slots.get_dyn(i) as *const (dyn WidgetHost + 'static) as *mut (dyn WidgetHost + 'static))
+            .collect();
         // The dialog's open dropdown, AFTER the dialog and after the wipe
         // above (which would drop it from the tree, leaving an id the
         // engine's clamp cannot resolve): the clamp lets an occluder's own