GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
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())
}