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

commit2f265000d1d5bbc8d6812883499f28f5ed7f71c4
parentc790b50c75
authorLucas Galante <[email protected]>
date2026-07-12 19:04
refactor(widget)!: stored parent pointers retired — the 6bd pre-flip snapshot change

The five widget-side `Option<*mut dyn Element>` fields are gone; the census
showed every one production-dead (the 6bc "written every tick" note was wrong
for menu/status — nothing ever set_parent's them; ramp's per-tick re-parents
fed a write-only field through a dummy ctx whose every other effect was
discarded, matching legacy):

- TextBox.parent: never written, never read — deleted.
- Dropdown.parent -> `parent_snapshot: Option<ParentSnapshot>` (rect/is_ramp/
  color read-DATA): same direct-write-only activation as the legacy pointer
  (no production writer; the Ramp-clamp unit test adapted). tracked_parent
  (write-only since 6s corner_frame) deleted with its parent_changed override.
- MenuBar.parent / StatusBar.parent: deleted with the parent_changed /
  tracked_parent overrides and MenuBar's never-firing adjust_rect clamp;
  corner_style reports the 0.0 radius production always read.
- Layout::parent_changed + Layout::tracked_parent hooks deleted (no impls
  left); Adapted::parent is tree-only; Adapted::set_parent is pure tree link.
- Ramp/ColorRamp tick_ctx re-parent rituals deleted (provably inert).

Stored `*mut dyn Element` now lives ONLY in the WidgetTree registry payload,
EventCtx's transient host ptr, and TI's ControlPanel (recorded endgame item).

Verified: 163 tests; workspace builds; A/B text-editor (menubar+statusbar+
textbox) and TI Ramp-child static + preset-popover-OPEN frames — empty 8%
masks (cursor sprites/corner artifact only).

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

 src/widget/container/menu.rs     | 40 +++---------------------
 src/widget/display/status_bar.rs | 22 +++-----------
 src/widget/input/dropdown.rs     | 66 ++++++++++++++++++++--------------------
 src/widget/input/ramp.rs         | 22 +++-----------
 src/widget/input/text_box.rs     |  2 --
 src/widget/model.rs              | 17 -----------
 6 files changed, 47 insertions(+), 122 deletions(-)

diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index e22589b..b00ff09 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -52,7 +52,6 @@ pub struct MenuBar {
     pub context_hovered_item: Option<usize>,
     pub context_title_hovered: bool,
     pub right_align_title: bool,
-    pub parent: Option<*mut (dyn Element + 'static)>,
     pub layout_dirty: bool,
     pub on_context_change_cb: Option<Box<dyn Fn(usize) + Send + Sync>>,
     pub on_menu_click_cb: Option<Box<dyn Fn(usize, usize) + Send + Sync>>,
@@ -88,7 +87,6 @@ impl MenuBar {
             context_hovered_item: None,
             context_title_hovered: false,
             right_align_title: false,
-            parent: None,
             layout_dirty: true,
             on_context_change_cb: None,
             on_menu_click_cb: None,
@@ -425,33 +423,8 @@ impl Layout for MenuBar {
         self.z_level
     }
 
-    fn tracked_parent(&self) -> Option<Option<*mut (dyn Element + 'static)>> {
-        Some(self.parent)
-    }
-
-    fn parent_changed(&mut self, parent: Option<*mut (dyn Element + 'static)>) {
-        self.parent = parent;
-    }
-
-    /// Legacy `set_rect` clamped into the parent rect (no zero floor, unlike Switcher).
-    fn adjust_rect(&self, requested: Rect) -> Rect {
-        let Some(parent_ptr) = self.parent else {
-            return requested;
-        };
-        let (px, py, pw, ph) = unsafe { (*parent_ptr).rect() };
-        let cx = requested.x.clamp(px, px + pw);
-        let cy = requested.y.clamp(py, py + ph);
-        let cw = requested.width.min(px + pw - cx);
-        let ch = requested.height.min(py + ph - cy);
-        Rect { x: cx, y: cy, width: cw, height: ch }
-    }
-
-    fn arrange_children(&mut self, rect: Rect, host: *mut (dyn Element + 'static)) {
+    fn arrange_children(&mut self, rect: Rect, _host: *mut (dyn Element + 'static)) {
         self.layout_strip(rect);
-        // The strip walks its parent chain for backplate-aware styling; through the adapter
-        // the chain is host -> tracked parent (a dummy-ctx-safe walk, as legacy relied on).
-        let mut dummy = crate::context::UiContext::new();
-        self.menus.set_parent(Some(host), &mut dummy);
     }
 }
 
@@ -461,13 +434,10 @@ impl Paint for MenuBar {
     }
 
     fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
-        // Corners never round (the backplate-adjacency source is gone); the radius is still
-        // reported for children that read it through the parent pointer.
-        let radius = match self.parent {
-            Some(p_ptr) => unsafe { (*p_ptr).corner_style().0 },
-            None => 0.0,
-        };
-        Some((radius, (false, false, false, false)))
+        // Corners never round (the backplate-adjacency source is gone). The radius was the
+        // parent's, read through a stored pointer — but nothing ever set_parent's a MenuBar,
+        // so 0.0 is what production always read (6bd: the dead pointer field is gone).
+        Some((0.0, (false, false, false, false)))
     }
 
     fn widget_font(&self) -> Option<String> {
diff --git a/src/widget/display/status_bar.rs b/src/widget/display/status_bar.rs
index 037a8ce..88b9c06 100644
--- a/src/widget/display/status_bar.rs
+++ b/src/widget/display/status_bar.rs
@@ -11,7 +11,7 @@ use crate::colors;
 use crate::scene::layout::Rect;
 use crate::scene::paint::PaintCtx;
 use crate::widget::display::make_widget_text_buffer;
-use crate::widget::{Adapted, Element, Input, Layout, Paint};
+use crate::widget::{Adapted, Input, Layout, Paint};
 
 pub struct StatusBar {
     rect: Rect,
@@ -20,7 +20,6 @@ pub struct StatusBar {
     pub text_offset_x: Option<f32>,
     pub text_color: Option<[f32; 4]>,
     pub bg_color: Option<[f32; 4]>,
-    pub parent: Option<*mut (dyn Element + 'static)>,
 }
 
 impl StatusBar {
@@ -32,7 +31,6 @@ impl StatusBar {
             text_offset_x: None,
             text_color: None,
             bg_color: None,
-            parent: None,
         })
     }
 
@@ -95,13 +93,6 @@ impl Layout for StatusBar {
         self.rect = rect;
     }
 
-    fn parent_changed(&mut self, parent: Option<*mut (dyn Element + 'static)>) {
-        self.parent = parent;
-    }
-
-    fn tracked_parent(&self) -> Option<Option<*mut (dyn Element + 'static)>> {
-        Some(self.parent)
-    }
 }
 
 impl Paint for StatusBar {
@@ -110,13 +101,10 @@ impl Paint for StatusBar {
     }
 
     fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
-        // Corners never round (the backplate-adjacency source is gone); the radius is still
-        // reported for children that read it through the parent pointer.
-        let radius = match self.parent {
-            Some(p_ptr) => unsafe { (*p_ptr).corner_style().0 },
-            None => 0.0,
-        };
-        Some((radius, (false, false, false, false)))
+        // Corners never round (the backplate-adjacency source is gone). The radius was the
+        // parent's, read through a stored pointer — but nothing ever set_parent's a StatusBar,
+        // so 0.0 is what production always read (6bd: the dead pointer field is gone).
+        Some((0.0, (false, false, false, false)))
     }
 
     /// `Element::set_text` lands here: swap the text and drop the shaped buffer so
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 697a152..bc917d4 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -4,13 +4,12 @@
 //! `Layout::detached_label_inset`, side-label inset computed from the synced label.
 //!
 //! Parity notes (all legacy-faithful, verified against the pre-migration impl):
-//! - `parent` stays a public, direct-write-only field: legacy `set_parent` never wrote it (the
-//!   Element default only touched the tree — Ramp's dummy-ctx `set_parent` calls were silently
-//!   discarded), so the Ramp popover clamp and the fade-blend parent color activate only for
-//!   callers that assign the field, exactly as before. The backplate-concentric corner walk
-//!   (which legacy ran over the ctx tree) instead starts from a separate pointer captured by
-//!   `Layout::parent_changed` and hops legacy field-based `parent(&dummy)` impls — exact for
-//!   the real consumer (cce-graph: Dropdown → Plate → Backplate, both field-based).
+//! - `parent_snapshot` is the data form of the legacy public, direct-write-only `parent`
+//!   pointer: legacy `set_parent` never wrote it (the Element default only touched the tree —
+//!   Ramp's dummy-ctx `set_parent` calls were silently discarded), so the Ramp popover clamp
+//!   and the fade-blend parent color activate only for callers that assign the field, exactly
+//!   as before — no production writer exists. The backplate-concentric corner walk it once
+//!   anchored is gone outright (replaced by the app-owned `corner_frame`, Phase 6s).
 //! - The row-rect hit expansion (`base.row_x/row_w`) is dropped, consistent with every other
 //!   migrated control: `Input::hit` tests the widget rect plus the open popover.
 //! - `Layout::intrinsic_measure_width` (new hook) preserves the `auto_width` measure behavior
@@ -24,6 +23,17 @@ use crate::widget::{
     Control, Element, ElementState, Event, Key, MouseButton, NamedKey,
 };
 
+/// Read-data stand-in for the legacy direct-write `parent` pointer (6bd — no stored widget
+/// pointers): the popover clamp and bg fade-blend read the host's rect/kind/color ctx-less at
+/// paint time. Callers that want the Ramp clamp assign it directly, same activation model as
+/// the old field.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct ParentSnapshot {
+    pub rect: (f32, f32, f32, f32),
+    pub is_ramp: bool,
+    pub color: [f32; 4],
+}
+
 /// Side-layout label inset — the legacy `Element::label_x_offset` default for non-exempt
 /// widgets (Dropdown was never in the exempt list).
 fn side_offset(label: &Option<String>) -> f32 {
@@ -41,14 +51,10 @@ pub struct Dropdown {
     pub open: bool,
     pub(crate) hovered_item: Option<usize>,
     just_changed: bool,
-    /// Legacy-faithful parent pointer: written ONLY by direct assignment (the Ramp-clamp unit
-    /// test; no production writer). Read by the Ramp popover clamp and the fade-blend parent
-    /// color, like legacy. NOT the corner-walk pointer — see `tracked_parent`.
-    pub parent: Option<*mut (dyn Element + 'static)>,
-    /// Captured by [`Layout::parent_changed`] whenever a container `set_parent`s this widget —
-    /// the model-side stand-in for the ctx-tree head the legacy backplate corner walk started
-    /// from (`paint` has no ctx to reach the real tree).
-    tracked_parent: Option<*mut (dyn Element + 'static)>,
+    /// Host read-data, written ONLY by direct assignment (the Ramp-clamp unit test; no
+    /// production writer). Read by the Ramp popover clamp and the fade-blend parent color,
+    /// like the legacy `parent` pointer it replaces.
+    pub parent_snapshot: Option<ParentSnapshot>,
     pub font_family: String,
     pub custom_display_text: Option<String>,
     pub open_upward: Option<bool>,
@@ -75,8 +81,7 @@ impl Dropdown {
             open: false,
             hovered_item: None,
             just_changed: false,
-            parent: None,
-            tracked_parent: None,
+            parent_snapshot: None,
             font_family: "sans-serif".to_string(),
             custom_display_text: None,
             open_upward: None,
@@ -148,14 +153,11 @@ impl Dropdown {
             content.y + content.height
         };
 
-        let mut is_ramp = false;
-        if let Some(parent_ptr) = self.parent {
-            is_ramp = unsafe { (*parent_ptr).as_any().is::<crate::widget::Ramp>() };
-        }
+        let is_ramp = self.parent_snapshot.map_or(false, |s| s.is_ramp);
 
         if is_ramp {
-            if let Some(parent_ptr) = self.parent {
-                let (px, py, pw_parent, ph_parent) = unsafe { (*parent_ptr).rect() };
+            if let Some(snap) = self.parent_snapshot {
+                let (px, py, pw_parent, ph_parent) = snap.rect;
                 if pw_parent > 0.0 && ph_parent > 0.0 {
                     let dy_down = content.y + content.height;
                     let dy_up = content.y - rh;
@@ -310,10 +312,8 @@ impl Dropdown {
         ];
         let bg_color = colors::dropdown_background_color();
         let mut parent_color = colors::page_color();
-        if let Some(parent_ptr) = self.parent {
-            unsafe {
-                parent_color = (*parent_ptr).color();
-            }
+        if let Some(snap) = self.parent_snapshot {
+            parent_color = snap.color;
         }
         let alpha = 1.0; // The dropdown background is drawn fully opaque
         let bg_rgb = [
@@ -553,10 +553,6 @@ impl Layout for Dropdown {
     fn intrinsic_measure_width(&self) -> bool {
         self.auto_width
     }
-
-    fn parent_changed(&mut self, parent: Option<*mut (dyn Element + 'static)>) {
-        self.tracked_parent = parent;
-    }
 }
 
 impl Paint for Dropdown {
@@ -938,8 +934,12 @@ mod tests {
         let mut dd = Dropdown::new(options, 0).with_label("Preset");
         dd.set_rect(30.0, 125.0, 110.0, 20.0);
 
-        // Link the dropdown parent pointer to the Ramp (the legacy direct-write path)
-        dd.parent = Some(crate::widget::Element::as_ptr_mut(&mut ramp));
+        // Link the dropdown to the Ramp's read-data (the legacy direct-write path)
+        dd.parent_snapshot = Some(ParentSnapshot {
+            rect: crate::widget::Element::rect(&ramp),
+            is_ramp: true,
+            color: crate::widget::Element::color(&ramp),
+        });
 
         // Compute geometry
         let (rx, ry, rw, rh) = dd.get_popover_geom();
diff --git a/src/widget/input/ramp.rs b/src/widget/input/ramp.rs
index ebf00fd..f9ca94f 100644
--- a/src/widget/input/ramp.rs
+++ b/src/widget/input/ramp.rs
@@ -489,15 +489,8 @@ impl Input for ColorRamp {
     }
 
     fn tick_ctx(&mut self, dt: f32, ectx: &mut EventCtx) -> bool {
-        // Keep the field widgets' tracked parent pointing at the adapter (their label
-        // fade blends against the parent's color; legacy set_rect re-parented them).
-        if let Some(host) = ectx.host_ptr() {
-            let mut dummy = crate::context::UiContext::new();
-            self.r_slider.set_parent(Some(host), &mut dummy);
-            self.g_slider.set_parent(Some(host), &mut dummy);
-            self.b_slider.set_parent(Some(host), &mut dummy);
-            self.del_button.set_parent(Some(host), &mut dummy);
-        }
+        // (The per-tick field-widget re-parenting is gone, 6bd: it was a dummy-ctx
+        // `set_parent` whose every effect was discarded — legacy behaved the same.)
         let Some(ui) = ectx.ui.as_deref_mut() else {
             return false;
         };
@@ -919,15 +912,8 @@ impl Input for Ramp {
     }
 
     fn tick_ctx(&mut self, dt: f32, ectx: &mut EventCtx) -> bool {
-        // Keep the field widgets' tracked parent pointing at the adapter (the dropdowns'
-        // label fade blends against the parent's color; legacy set_rect re-parented them).
-        if let Some(host) = ectx.host_ptr() {
-            let mut dummy = crate::context::UiContext::new();
-            self.preset_dropdown.set_parent(Some(host), &mut dummy);
-            self.line_type_dropdown.set_parent(Some(host), &mut dummy);
-            self.val_slider.set_parent(Some(host), &mut dummy);
-            self.del_button.set_parent(Some(host), &mut dummy);
-        }
+        // (The per-tick field-widget re-parenting is gone, 6bd: it was a dummy-ctx
+        // `set_parent` whose every effect was discarded — legacy behaved the same.)
         let Some(ui) = ectx.ui.as_deref_mut() else {
             return false;
         };
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index f06f4a2..7b0987e 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -58,7 +58,6 @@ pub struct TextBox {
     pub dragging: bool,
     pub just_focused: bool,
     pub drag_start_idx: Option<usize>,
-    pub parent: Option<*mut (dyn Element + 'static)>,
     pub max_width: Option<f32>,
     pub width: Option<f32>,
     pub is_password: bool,
@@ -102,7 +101,6 @@ impl TextBox {
             dragging: false,
             just_focused: false,
             drag_start_idx: None,
-            parent: None,
             max_width: None,
             width: None,
             is_password: false,
diff --git a/src/widget/model.rs b/src/widget/model.rs
index 6804914..5862d5e 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -112,10 +112,6 @@ pub trait Layout {
         Vec::new()
     }
 
-    /// The parent pointer changed through `Element::set_parent` (containers that clamp their
-    /// rect to the parent's keep a copy — the tree default needs a ctx that `set_rect` lacks).
-    fn parent_changed(&mut self, _parent: Option<*mut (dyn Element + 'static)>) {}
-
     /// Adjust a rect assignment before it lands on the base (Switcher clamps to its parent).
     /// Default: identity.
     fn adjust_rect(&self, requested: Rect) -> Rect {
@@ -139,15 +135,6 @@ pub trait Layout {
         0
     }
 
-    /// `Some(parent)` when the model tracks its parent pointer itself (via
-    /// [`parent_changed`](Layout::parent_changed)) — the adapter then serves `Element::parent`
-    /// from it instead of the tree. Legacy widgets with parent-dependent styling walk the
-    /// chain with a DUMMY ctx (MenuBar/ButtonStrip backplate checks), which a tree lookup
-    /// cannot answer. `None` (default): use the tree.
-    fn tracked_parent(&self) -> Option<Option<*mut (dyn Element + 'static)>> {
-        None
-    }
-
     /// Republish value-embedded legacy children into the ctx registry (Paginator's ButtonStrip
     /// + Pages). Legacy value-owning containers re-registered their children on EVERY `tick` and
     /// `layout` because the children's addresses move with the owning struct (host struct moves,
@@ -908,7 +895,6 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
     }
 
     fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, ctx: &mut UiContext) {
-        Layout::parent_changed(&mut self.inner, parent);
         // Replica of the Element default: symmetric tree link.
         let id = self.base.id();
         if let Some(p_ptr) = parent {
@@ -943,9 +929,6 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
     }
 
     fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
-        if let Some(tracked) = Layout::tracked_parent(&self.inner) {
-            return tracked;
-        }
         ctx.tree.parent_ptr(self.base.id())
     }