git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commitbdf1823d7766d82bb4ad49a5e69075c5f9105bd4
parenta121d2ad4d
authorLucas Galante <[email protected]>
date2026-07-12 19:12
refactor(widget)!: set_parent/add_child off Element — linking is a tree op (6bd)

Element 69 -> 67 (series count). Both methods move to inherent Adapted<W>
(files' ten set_parent(None) sites resolve unchanged); the dyn callers were
three:

- focus::link_parent_child body: the add_child + set_parent pair became the
  register + tree.link + tree.set_parent ops it always was (the container
  parent-back extra produced the same symmetric link).
- TI's page-selector/StatusBar pair of dyn roster calls -> one
  link_parent_child call.
- TI ControlPanel arrange_children's per-child dummy-ctx set_parent: deleted —
  every effect was discarded with the dummy ctx (same class as the ramp
  rituals).

Adapted::add_child keeps the has_container_children parent-back extra as
direct tree ops.

Verified: 163 tests; workspace builds; A/B TI gallery (the flat-walk
parent() skip still resolves — no page-selector double-draw) and dm greeter
(link_parent_child consumer) — both empty 8% masks.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018u7qTwzX95dd5ysAkaSCLk

 src/widget/core.rs  | 10 ++++---
 src/widget/mod.rs   | 32 +++-------------------
 src/widget/model.rs | 76 +++++++++++++++++++++++++++++------------------------
 3 files changed, 51 insertions(+), 67 deletions(-)

diff --git a/src/widget/core.rs b/src/widget/core.rs
index 45208cb..0a65d0e 100644
--- a/src/widget/core.rs
+++ b/src/widget/core.rs
@@ -83,11 +83,13 @@ pub mod focus {
             std::mem::transmute::<*mut dyn Element, *mut (dyn Element + 'static)>(child as *mut dyn Element)
         };
         if let (Some(p_base), Some(c_base)) = (parent.base(), child.base()) {
-            ctx.register_widget(p_base.id(), parent_ptr);
-            ctx.register_widget(c_base.id(), child_ptr);
+            let (p_id, c_id) = (p_base.id(), c_base.id());
+            ctx.register_widget(p_id, parent_ptr);
+            ctx.register_widget(c_id, child_ptr);
+            // The old add_child + set_parent pair, as the tree ops they always were.
+            ctx.tree.link(p_id, c_id);
+            ctx.tree.set_parent(c_id, Some(p_id));
         }
-        parent.add_child(child_ptr, ctx);
-        child.set_parent(Some(parent_ptr), ctx);
     }
 
     /// Keyboard tree navigation from the focused widget. `ctx` resolves the focused id to a
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index ad60b09..3e17518 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -574,24 +574,9 @@ pub trait Element {
     }
 
 
-    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, ctx: &mut UiContext) {
-        if let Some(base) = self.base() {
-            let id = base.id();
-            if let Some(p_ptr) = parent {
-                if let Some(p_base) = unsafe { (*p_ptr).base() } {
-                    let p_id = p_base.id();
-                    ctx.register_widget(p_id, p_ptr);
-                    let self_ptr = self.as_ptr();
-                    ctx.register_widget(id, self_ptr);
-                    // Symmetric link (Phase 1b): unlike the legacy `parents.insert` this also
-                    // records the child under the parent, keeping `children()` consistent.
-                    ctx.tree.set_parent(id, Some(p_id));
-                }
-            } else {
-                ctx.tree.set_parent(id, None);
-            }
-        }
-    }
+    // `set_parent`/`add_child` are GONE from the trait (6bd batch 4): linking is a tree
+    // operation — concrete callers ride the inherent `Adapted` methods, dyn callers go
+    // through `focus::link_parent_child` or `ctx.tree` directly.
 
     fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
         match self.base() {
@@ -600,17 +585,6 @@ pub trait Element {
         }
     }
 
-    fn add_child(&mut self, child: *mut (dyn Element + 'static), ctx: &mut UiContext) {
-        if let (Some(p_base), Some(c_base)) = (self.base(), unsafe { (*child).base() }) {
-            let p_id = p_base.id();
-            let c_id = c_base.id();
-            let self_ptr = self.as_ptr();
-            ctx.register_widget(p_id, self_ptr);
-            ctx.register_widget(c_id, child);
-            ctx.tree.link(p_id, c_id);
-        }
-    }
-
     fn z_index(&self) -> i32 { 0 }
     fn is_scrollable(&self) -> bool { false }
     fn blocks_backplate_drag(&self) -> bool { true }
diff --git a/src/widget/model.rs b/src/widget/model.rs
index 5862d5e..55d9f03 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -689,6 +689,48 @@ impl<W: Layout + Paint + Input + 'static> Adapted<W> {
         ctx.clear_children_ids(self.base.id());
     }
 
+    /// Register + link a child under this widget (off `Element` in 6bd batch 4; dyn callers
+    /// went to `focus::link_parent_child`/tree ops).
+    pub fn add_child(&mut self, child: *mut (dyn Element + 'static), ctx: &mut UiContext) {
+        // The old Element default's tree link…
+        if let Some(c_base) = unsafe { (*child).base() } {
+            let c_id = c_base.id();
+            let p_id = self.base.id();
+            let self_ptr = self.as_ptr();
+            ctx.register_widget(p_id, self_ptr);
+            ctx.register_widget(c_id, child);
+            ctx.tree.link(p_id, c_id);
+        }
+        // …plus, for containers, the legacy container extra: parent the child back (Layer,
+        // Switcher) — the symmetric tree link the child's own set_parent used to make.
+        if Layout::has_container_children(&self.inner) {
+            let self_ptr = self.as_ptr_mut();
+            if let Some(c_base) = unsafe { (*child).base() } {
+                let c_id = c_base.id();
+                ctx.register_widget(self.base.id(), self_ptr);
+                ctx.register_widget(c_id, child);
+                ctx.tree.set_parent(c_id, Some(self.base.id()));
+            }
+        }
+    }
+
+    /// Register + (un)link this widget under a parent (off `Element` in 6bd batch 4).
+    pub fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, ctx: &mut UiContext) {
+        // Replica of the old Element default: symmetric tree link.
+        let id = self.base.id();
+        if let Some(p_ptr) = parent {
+            if let Some(p_base) = unsafe { (*p_ptr).base() } {
+                let p_id = p_base.id();
+                ctx.register_widget(p_id, p_ptr);
+                let self_ptr = self.as_ptr();
+                ctx.register_widget(id, self_ptr);
+                ctx.tree.set_parent(id, Some(p_id));
+            }
+        } else {
+            ctx.tree.set_parent(id, None);
+        }
+    }
+
     /// The model's intrinsic content size (off the `Element` trait since 6bd — the concrete
     /// callers are fonts'/graph's hand-laid button/dropdown sizing).
     pub fn intrinsic_size(&self) -> Option<Size> {
@@ -876,40 +918,6 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
         ctx.tree.children_ptrs(self.base.id())
     }
 
-    fn add_child(&mut self, child: *mut (dyn Element + 'static), ctx: &mut UiContext) {
-        // The Element default's tree link…
-        if let Some(c_base) = unsafe { (*child).base() } {
-            let c_id = c_base.id();
-            let p_id = self.base.id();
-            let self_ptr = self.as_ptr();
-            ctx.register_widget(p_id, self_ptr);
-            ctx.register_widget(c_id, child);
-            ctx.tree.link(p_id, c_id);
-        }
-        // …plus, for containers, the legacy container extra: parent the child back (Layer,
-        // Switcher). (The model-Vec record died with ParametersBg.children — zero overrides.)
-        if Layout::has_container_children(&self.inner) {
-            let self_ptr = self.as_ptr_mut();
-            unsafe { (*child).set_parent(Some(self_ptr), ctx) };
-        }
-    }
-
-    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, ctx: &mut UiContext) {
-        // Replica of the Element default: symmetric tree link.
-        let id = self.base.id();
-        if let Some(p_ptr) = parent {
-            if let Some(p_base) = unsafe { (*p_ptr).base() } {
-                let p_id = p_base.id();
-                ctx.register_widget(p_id, p_ptr);
-                let self_ptr = self.as_ptr();
-                ctx.register_widget(id, self_ptr);
-                ctx.tree.set_parent(id, Some(p_id));
-            }
-        } else {
-            ctx.tree.set_parent(id, None);
-        }
-    }
-
     fn is_child_visible(&self, child_id: WidgetId) -> bool {
         if !Layout::has_container_children(&self.inner) {
             return true;