git.lucas.co / cce-graph
node-based graph editor
git clone https://git.lucas.co/cce-graph.git

commitef2fd76c40ba022637b1210ccf92f24ae193a365
parent97cacb2ffb
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-07 22:07
refactor: hold widgets in Owned boxes (cce-ui Owned<W>)

The UiContext registry keeps raw pointers to registered widgets, and a
widget that moved while registered (its Vec reallocating, its struct
returned by value) left one pointing at the old address. Each widget
field is now an Owned<Adapted<..>> — a heap box whose allocation, not the
widget, carries the liveness token the registry watches — so the
registered address never moves and is dropped from the registry when the
box is freed. Field access is unchanged through Deref; construction
sites wrap in Owned::new.

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

 src/main.rs  | 27 ++++++++++++++-------------
 src/vault.rs |  5 +++--
 2 files changed, 17 insertions(+), 15 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 2709917..8a9d2b6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
 use wayland_client::QueueHandle;
+use cce_ui::widget::Owned;
 use cce_ui::engine::{Application, EngineState, LogicalPosition, LogicalSize, WindowSettings};
 mod linkgraph;
 mod vault;
@@ -52,12 +53,12 @@ struct LoadedImage {
 }
 
 struct GraphApp {
-    menu_bar: Adapted<MenuBar>,
-    dropdown_file: Adapted<Dropdown>,
-    dropdown_edit: Adapted<Dropdown>,
-    dropdown_view: Adapted<Dropdown>,
+    menu_bar: Owned<Adapted<MenuBar>>,
+    dropdown_file: Owned<Adapted<Dropdown>>,
+    dropdown_edit: Owned<Adapted<Dropdown>>,
+    dropdown_view: Owned<Adapted<Dropdown>>,
 
-    graph: Adapted<Graph>,
+    graph: Owned<Adapted<Graph>>,
     needs_rebuild: bool,
     width: u32,
     height: u32,
@@ -81,7 +82,7 @@ struct GraphApp {
     panel_drag_ox: f32,
     panel_drag_oy: f32,
     show_control_panel: bool,
-    control_panel_label: cce_ui::widget::Adapted<cce_ui::widget::Label>,
+    control_panel_label: Owned<cce_ui::widget::Adapted<cce_ui::widget::Label>>,
 }
 
 fn get_default_project_path() -> std::path::PathBuf {
@@ -864,11 +865,11 @@ impl Application for GraphApp {
 
         let mut app = Self {
 
-            menu_bar,
-            dropdown_file,
-            dropdown_edit,
-            dropdown_view,
-            graph,
+            menu_bar: Owned::new(menu_bar),
+            dropdown_file: Owned::new(dropdown_file),
+            dropdown_edit: Owned::new(dropdown_edit),
+            dropdown_view: Owned::new(dropdown_view),
+            graph: Owned::new(graph),
             needs_rebuild: true,
             width: 1024,
             height: 768,
@@ -890,7 +891,7 @@ impl Application for GraphApp {
             panel_drag_ox: 0.0,
             panel_drag_oy: 0.0,
             show_control_panel,
-            control_panel_label,
+            control_panel_label: Owned::new(control_panel_label),
         };
         
 
@@ -1557,7 +1558,7 @@ impl Application for GraphApp {
                             // A press on a port can complete a wire drawn
                             // with the mouse; write it into the node.
                             if let Some((to, from, port)) =
-                                GraphController::take_pending_connection_to_port(&mut *self.graph)
+                                GraphController::take_pending_connection_to_port(&mut **self.graph)
                             {
                                 let mut nodes = self.graph.get_nodes();
                                 if wiring::connect(&mut nodes, &to, &from, port) {
diff --git a/src/vault.rs b/src/vault.rs
index bbefd2c..f695440 100644
--- a/src/vault.rs
+++ b/src/vault.rs
@@ -18,6 +18,7 @@ use std::path::PathBuf;
 use std::sync::OnceLock;
 use std::time::{Duration, Instant};
 
+use cce_ui::widget::Owned;
 use cce_ui::engine::{Application, CursorIcon, EngineState, LogicalPosition, LogicalSize, WindowSettings};
 use cce_ui::scene::layout::Rect;
 use cce_ui::scene::paint::{Cap, DisplayList, PaintCtx};
@@ -173,7 +174,7 @@ pub struct VaultApp {
     center: Option<String>,
     next_poll: Instant,
 
-    filter_input: Adapted<TextBox>,
+    filter_input: Owned<Adapted<TextBox>>,
     filter_seen: String,
 
     /// World point at the middle of the graph area, and px per world unit.
@@ -561,7 +562,7 @@ impl Application for VaultApp {
             depth: 1,
             center: None,
             next_poll: Instant::now(),
-            filter_input: TextBox::new(String::new()).with_placeholder("Filter: words, #tag, path:"),
+            filter_input: Owned::new(TextBox::new(String::new()).with_placeholder("Filter: words, #tag, path:")),
             filter_seen: String::new(),
             cam: (0.0, 0.0),
             zoom: 1.0,