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

commitaa0343fe10a3decacd575eac0b0514a87bcb5e49
parentabec18e2fc
authorLucas Galante <[email protected]>
date2026-07-13 10:14
refactor(widget)!: as_ptr/as_ptr_mut leave WidgetHost — 50->48

The transitional pointer getters live on as inherent Adapted<W> methods
(registration-bridge class); concrete call sites resolve unchanged, and
dyn/generic receivers use the plain cast the impl always was.
impl_widget_base! stops generating them.

Verified: 165 tests + workspace suite; designer render pixel-identical
(empty 8% mask); probes canary-silent.

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

 docs/rfc-core-rebuild.md | 11 +++++++++++
 src/context.rs           |  2 +-
 src/layout.rs            |  4 ++--
 src/scene/painter.rs     |  4 ++--
 src/widget/core.rs       |  6 ------
 src/widget/mod.rs        | 10 ++++++----
 src/widget/model.rs      | 18 +++++++++++-------
 7 files changed, 33 insertions(+), 22 deletions(-)

diff --git a/docs/rfc-core-rebuild.md b/docs/rfc-core-rebuild.md
index 581624e..d1b30bb 100644
--- a/docs/rfc-core-rebuild.md
+++ b/docs/rfc-core-rebuild.md
@@ -1877,6 +1877,17 @@ Constraint respected: **each crate still builds standalone** — the new core is
        click lands through the tree link; TI page-selector crop
        byte-identical (no double-draw); designer /state + full panel
        text intact; settings/files/demo canary-silent.
+       FOLLOW-UP: **`as_ptr`/`as_ptr_mut` left the trait (50→48).**
+       They live on as inherent `Adapted<W>` methods (the
+       registration-bridge class) — every concrete call site resolved
+       unchanged; dyn/generic receivers became the plain casts the
+       impl always was (`w as *mut (dyn WidgetHost + 'static)`;
+       `impl_widget_base!` no longer generates them). Designer's
+       `find_widget_index` now honestly takes the thin `*const ()`
+       address its body always compared. Verified: 165 tests +
+       workspace suite; designer capture pixel-identical to the
+       prior slice's (empty 8% mask) + /state live; TI click probe
+       canary-silent.
     5. window_runner render plumbing + remaining `as_ptr` sites; then
        the `Element` + `Adapted` endgame (own design pass).
     Stored-pointer state remaining after slices 1–3, all deliberate:
diff --git a/src/context.rs b/src/context.rs
index 4e4802c..04a15da 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -898,7 +898,7 @@ mod tests {
     fn drag_allowed_everywhere_except_blocking_widgets() {
         let mut ctx = UiContext::new();
         let mut w = Block { base: Widget::new_rect(10.0, 10.0, 50.0, 50.0) };
-        let ptr = w.as_ptr_mut();
+        let ptr = &mut w as *mut _ as *mut (dyn crate::widget::WidgetHost + 'static);
         ctx.register_widget(w.base.id(), ptr);
         ctx.rebuild_spatial_grid();
 
diff --git a/src/layout.rs b/src/layout.rs
index 931ec54..3f368f8 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -3144,7 +3144,7 @@ impl RenderTarget for PopoverCollector {
 pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut T, x: f32, y: f32, ww: f32, wh: f32, ctx: &mut UiContext) {
     let id = Some(w.base().id());
     if let Some(w_id) = id {
-        ctx.register_widget(w_id, w.as_ptr_mut());
+        ctx.register_widget(w_id, w as *mut T as *mut (dyn WidgetHost + 'static));
     }
     w.layout(crate::widget::Point { x, y }, crate::widget::LayoutConstraints::new(ww, ww, wh, wh), ctx);
     let (style_r, corners) = w.corner_style();
@@ -3203,7 +3203,7 @@ pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut
     // drew the geometry via `all_quads`/`all_rounded_quads` above, so we take only the Text prims
     // from the walk. This drops the legacy `widget_font` + `text_labels_with_font_and_bounds`
     // getters from render_widget — the prim already carries the per-widget font+bounds.
-    let w_ptr = w.as_ptr_mut();
+    let w_ptr = w as *mut T as *mut (dyn WidgetHost + 'static);
     let mut text_scratch = crate::scene::paint::PaintCtx::new();
     crate::scene::painter::paint_root_into(&*ctx, w_ptr, &mut text_scratch);
     for item in text_scratch.finish().items {
diff --git a/src/scene/painter.rs b/src/scene/painter.rs
index eec3e18..d322694 100644
--- a/src/scene/painter.rs
+++ b/src/scene/painter.rs
@@ -209,7 +209,7 @@ mod tests {
     }
 
     fn reg(ctx: &mut UiContext, w: &mut P) -> (crate::widget::WidgetId, ElemPtr) {
-        let ptr = w.as_ptr_mut();
+        let ptr = &mut *w as *mut _ as *mut (dyn crate::widget::WidgetHost + 'static);
         let id = w.base.id();
         ctx.register_widget(id, ptr);
         (id, ptr)
@@ -346,7 +346,7 @@ mod tests {
         let mut w = Rounded { base: Widget::new() };
         w.base.w = 20.0;
         w.base.h = 10.0;
-        let ptr = w.as_ptr_mut();
+        let ptr = &mut w as *mut _ as *mut (dyn crate::widget::WidgetHost + 'static);
         ctx.register_widget(w.base.id(), ptr);
 
         let list = paint_tree(&ctx, ptr);
diff --git a/src/widget/core.rs b/src/widget/core.rs
index 7904164..ac23e02 100644
--- a/src/widget/core.rs
+++ b/src/widget/core.rs
@@ -639,11 +639,5 @@ macro_rules! impl_widget_base {
         fn base_mut(&mut self) -> &mut $crate::widget::Widget { &mut self.base }
         fn as_any(&self) -> &dyn std::any::Any { self }
         fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
-        fn as_ptr(&self) -> *mut (dyn $crate::widget::WidgetHost + 'static) {
-            self as *const Self as *mut Self as *mut (dyn $crate::widget::WidgetHost + 'static)
-        }
-        fn as_ptr_mut(&mut self) -> *mut (dyn $crate::widget::WidgetHost + 'static) {
-            self as *mut Self as *mut (dyn $crate::widget::WidgetHost + 'static)
-        }
     };
 }
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index bead867..09927e9 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -189,12 +189,14 @@ pub trait WidgetHost {
         }
     }
 
-    // Required (the flip): the old defaults manufactured DummyAny/null-DummyElement
-    // stand-ins nothing could legitimately use. `impl_widget_base!` provides all four.
+    // Required (the flip): the old defaults manufactured DummyAny stand-ins nothing
+    // could legitimately use. `impl_widget_base!` provides both. `as_ptr`/`as_ptr_mut`
+    // are GONE from the trait (the plumbing retype): a pointer to a widget you already
+    // hold is a plain cast (`w as *mut (dyn WidgetHost + 'static)`); concrete
+    // registration sites ride the inherent `Adapted<W>` methods (the registration
+    // bridge — derived from a live borrow, never stored beyond the registry).
     fn as_any(&self) -> &dyn std::any::Any;
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
-    fn as_ptr(&self) -> *mut (dyn WidgetHost + 'static);
-    fn as_ptr_mut(&mut self) -> *mut (dyn WidgetHost + 'static);
 
     fn handle_event(&mut self, event: &Event, ctx: &mut UiContext) -> bool {
         // The default serves test shims only (Adapted overrides this): base hover
diff --git a/src/widget/model.rs b/src/widget/model.rs
index 0732d59..9e3724e 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -719,6 +719,17 @@ impl<W: Layout + Paint + Input + 'static> Adapted<W> {
 }
 
 impl<W: Layout + Paint + Input + 'static> Adapted<W> {
+    /// This widget as a type-erased host pointer (off the `WidgetHost` trait — the
+    /// plumbing retype). Registration-bridge material: derived from a live borrow at the
+    /// call, stored only in the `WidgetTree` registry.
+    pub fn as_ptr(&self) -> *mut (dyn WidgetHost + 'static) {
+        self as *const Self as *mut Self as *mut (dyn WidgetHost + 'static)
+    }
+
+    pub fn as_ptr_mut(&mut self) -> *mut (dyn WidgetHost + 'static) {
+        self as *mut Self as *mut (dyn WidgetHost + 'static)
+    }
+
     /// Movement bounds pushed in by hosts (off the `WidgetHost` trait since 6bd — the one
     /// production caller is concrete: designer's network panel).
     pub fn set_drag_bounds(&mut self, bx: f32, by: f32, bw: f32, bh: f32) {
@@ -1004,13 +1015,6 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
         &mut self.inner
     }
-    fn as_ptr(&self) -> *mut (dyn WidgetHost + 'static) {
-        self as *const Self as *mut Self as *mut (dyn WidgetHost + 'static)
-    }
-    fn as_ptr_mut(&mut self) -> *mut (dyn WidgetHost + 'static) {
-        self as *mut Self as *mut (dyn WidgetHost + 'static)
-    }
-
     fn set_visible(&mut self, visible: bool) {
         if self.visible != visible {
             self.visible = visible;