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

commit812697362f539c5ede63f28408fdfe5578b46034
parenta1b8900e49
authorLucas Galante <[email protected]>
date2026-07-12 18:12
refactor(widget)!: first Element shrink batch — 6 methods off, bridge deleted (6bd)

The shrink-in-place census (blueprint diff + per-method consumers,
now grepping both .method( and UFCS ::method( forms) cleared six:
- highlight_color: zero overrides, one internal caller — folded into
  the highlight_quad default.
- set_drag_bounds + intrinsic_size: single-digit concrete callers
  (designer network panel; fonts/graph hand-laid sizing) — moved to
  inherent Adapted<W> methods, call sites resolve unchanged.
- layout_style/layout_children: consumed only by scene/bridge.rs,
  whose last production user was retired in 6aa — bridge module
  deleted (its 5 tests with it, 168->163), the narrow hooks deleted
  (intrinsic_size stays: Adapted::measure reads it), the model.rs
  showcase test now lays out by hand and keeps its paint assertions.
- layout_ignore: its only consumers were the uncalled layout_widgets/
  layout_widget_ptors fns — all three deleted plus five widget
  overrides.

Element trait: 79 -> 74 methods. Verified: 163 tests; workspace
builds warning-clean; graph menubar dropdowns (intrinsic sizing) and
fonts launch smoke on the live compositor.

 CLAUDE.md                    |   3 -
 src/layout.rs                |  28 ----
 src/scene/bridge.rs          | 298 -------------------------------------------
 src/scene/mod.rs             |   1 -
 src/widget/container/menu.rs |   3 -
 src/widget/display/label.rs  |   2 +-
 src/widget/input/button.rs   |   9 +-
 src/widget/input/dropdown.rs |   3 -
 src/widget/input/slider.rs   |   3 -
 src/widget/input/spinbox.rs  |   3 -
 src/widget/mod.rs            |  35 +----
 src/widget/model.rs          |  78 ++++-------
 12 files changed, 36 insertions(+), 430 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 066ef2e..b8a4943 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -98,9 +98,6 @@ Modules:
   `layout_tree` twin stores, keyed `WidgetId → NodeId` so the public `WidgetId` API is preserved.
 - `layout.rs` — the hand-rolled measure→arrange solver (`Style`/`Size`/`Rect`/`LayoutBox`).
   Deliberately **not** taffy: a compact row/column + flex + align + gap/padding box model.
-- `bridge.rs` — connects live `dyn Element` widgets to the pure layout solver. A widget opts in by
-  returning `Some` from `Element::layout_style`; leaves report `Element::intrinsic_size`. Widgets
-  returning `None` keep their legacy `set_rect` path — migration is one widget at a time.
 - `paint.rs` / `painter.rs` — `DisplayList` + `PaintCtx` (clip/transform stack) and the single
   paint walk. Each widget emits its own geometry via `Element::paint_self`; the walk owns recursion
   and clipping (`Element::clips_children`), instead of every container re-deriving intersections.
diff --git a/src/layout.rs b/src/layout.rs
index 9740224..1944aa4 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -3960,34 +3960,6 @@ impl Radial {
         }
     }
 
-    pub fn layout_widgets<T: Element + 'static>(&self, widgets: &mut [&mut T], ctx: &mut UiContext) {
-        let mut active_idx = 0;
-        for w in widgets.iter_mut() {
-            if !w.layout_ignore() {
-                let (_, _, ww, wh) = w.rect();
-                let use_w = if ww > 0.0 { ww } else { 100.0 };
-                let use_h = if wh > 0.0 { wh } else { 50.0 };
-                let (x, y, rw, rh) = self.widget_rect(active_idx, use_w, use_h);
-                w.layout(crate::widget::Point { x, y }, crate::widget::LayoutConstraints::new(rw, rw, rh, rh), ctx);
-                active_idx += 1;
-            }
-        }
-    }
-
-    pub fn layout_widget_ptors(&self, widgets: &[*mut (dyn Element + 'static)], ctx: &mut UiContext) {
-        let mut active_idx = 0;
-        for &w_ptr in widgets {
-            let w = unsafe { &mut *w_ptr };
-            if !w.layout_ignore() {
-                let (_, _, ww, wh) = w.rect();
-                let use_w = if ww > 0.0 { ww } else { 100.0 };
-                let use_h = if wh > 0.0 { wh } else { 50.0 };
-                let (x, y, rw, rh) = self.widget_rect(active_idx, use_w, use_h);
-                w.layout(crate::widget::Point { x, y }, crate::widget::LayoutConstraints::new(rw, rw, rh, rh), ctx);
-                active_idx += 1;
-            }
-        }
-    }
 }
 
 
diff --git a/src/scene/bridge.rs b/src/scene/bridge.rs
deleted file mode 100644
index 0ca3549..0000000
--- a/src/scene/bridge.rs
+++ /dev/null
@@ -1,298 +0,0 @@
-//! Bridge between the live widget tree and the scene layout engine — Phase 2b of the core
-//! rebuild.
-//!
-//! [`scene::layout`](crate::scene::layout) is a pure solver over `Arena<LayoutBox>`; the running
-//! UI is a tree of `Box<dyn Element>` widgets linked through [`UiContext`]. This module connects
-//! them: given a widget subtree, it builds a throwaway `LayoutBox` arena mirroring that subtree,
-//! runs measure/arrange, and writes the resulting rects back into the widgets.
-//!
-//! Migration is incremental. A widget opts in by returning `Some` from
-//! [`Element::layout_style`]; leaf widgets report size via [`Element::intrinsic_size`]. Widgets
-//! that return `None` keep their legacy `set_rect` path untouched — so a single container can be
-//! moved onto the engine and verified in a running app without disturbing the rest.
-//!
-//! Structure comes from each widget's own [`Element::children`], so it respects widgets that
-//! store children locally as well as the default `UiContext`-backed tree.
-
-use crate::scene::arena::{Arena, NodeId};
-use crate::scene::layout::{self, LayoutBox, Rect, Size, Style};
-use crate::widget::{Element, UiContext};
-
-type ElemPtr = *mut (dyn Element + 'static);
-
-/// Lay out the widget subtree rooted at `root` into `area` using the scene layout engine, writing
-/// the computed rect into every widget via [`Element::set_rect`].
-///
-/// Widgets are written in pre-order (parent before children), so a container's own rect is set
-/// before the engine-computed rects of its children overwrite anything the container's `set_rect`
-/// might have done.
-///
-/// # Safety
-/// `root` must be a valid, live widget pointer, and the subtree reachable through
-/// `Element::children` must consist of live widgets — the same invariant the rest of the toolkit
-/// relies on for its `*mut dyn Element` tree.
-pub fn layout_subtree(ctx: &UiContext, root: ElemPtr, area: Rect) {
-    let mut arena: Arena<LayoutBox> = Arena::new();
-    let mut order: Vec<(NodeId, ElemPtr)> = Vec::new();
-    let root_node = build(&mut arena, ctx, root, None, &mut order);
-
-    layout::measure(&mut arena, root_node);
-    layout::arrange(&mut arena, root_node, area);
-
-    for &(node, ptr) in &order {
-        let r = arena.value(node).expect("layout node missing").rect;
-        unsafe {
-            // A participating container's children are already placed by the engine, so we set
-            // only its own rect (writing the base directly) rather than calling its legacy
-            // `set_rect`, which would redundantly re-lay-out the children it no longer owns. An
-            // opaque leaf, by contrast, gets `set_rect` so it lays out its own internals.
-            let participating = (*ptr).layout_style().is_some();
-            match (participating, (*ptr).base_mut()) {
-                (true, Some(base)) => {
-                    base.x = r.x;
-                    base.y = r.y;
-                    base.w = r.width;
-                    base.h = r.height;
-                }
-                _ => (*ptr).set_rect(r.x, r.y, r.width, r.height),
-            }
-        }
-    }
-}
-
-/// Recursively mirror the widget subtree into `arena`, recording (node, widget) pairs in pre-order.
-///
-/// `assigned` is a style handed down by the parent (via [`Element::layout_children`]) that
-/// overrides this widget's own `layout_style`. Recursion only descends into widgets that
-/// themselves opt in (`layout_style` returns `Some`): a non-participating widget is an **opaque
-/// leaf** — the engine gives it a rect, but it keeps laying out its own internals via its own
-/// `set_rect`. That boundary is what lets one container be migrated without disturbing the widgets
-/// nested inside it.
-fn build(
-    arena: &mut Arena<LayoutBox>,
-    ctx: &UiContext,
-    ptr: ElemPtr,
-    assigned: Option<Style>,
-    order: &mut Vec<(NodeId, ElemPtr)>,
-) -> NodeId {
-    let own_style = unsafe { (*ptr).layout_style() };
-    let participates = own_style.is_some();
-    let style = assigned.or(own_style).unwrap_or_default();
-
-    // Only a participating container is descended into; opaque leaves stop the recursion.
-    let (children, child_styles) = if participates {
-        unsafe { ((*ptr).children(ctx), (*ptr).layout_children()) }
-    } else {
-        (Vec::new(), None)
-    };
-
-    let node = if children.is_empty() {
-        let intrinsic = unsafe { (*ptr).intrinsic_size() };
-        arena.insert(LayoutBox::leaf(style, intrinsic.unwrap_or(Size::ZERO)))
-    } else {
-        arena.insert(LayoutBox::container(style))
-    };
-    order.push((node, ptr));
-
-    for (i, child) in children.into_iter().enumerate() {
-        let assigned_child = child_styles.as_ref().and_then(|v| v.get(i).copied());
-        let child_node = build(arena, ctx, child, assigned_child, order);
-        arena.append_child(node, child_node);
-    }
-    node
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::scene::layout::{CrossAlign, Size, Style};
-    use crate::widget::{Widget, WidgetId};
-
-    /// A minimal real widget: carries a `Widget` base (so it has an id and a settable rect) and an
-    /// opt-in layout style / intrinsic size.
-    struct W {
-        base: Widget,
-        style: Option<Style>,
-        intrinsic: Option<Size>,
-        child_styles: Option<Vec<Style>>,
-    }
-    impl W {
-        fn container(style: Style) -> Box<W> {
-            Box::new(W { base: Widget::new(), style: Some(style), intrinsic: None, child_styles: None })
-        }
-        fn container_with_child_styles(style: Style, child_styles: Vec<Style>) -> Box<W> {
-            Box::new(W { base: Widget::new(), style: Some(style), intrinsic: None, child_styles: Some(child_styles) })
-        }
-        fn leaf(w: f32, h: f32) -> Box<W> {
-            Box::new(W { base: Widget::new(), style: None, intrinsic: Some(Size::new(w, h)), child_styles: None })
-        }
-        /// A non-participating widget (opaque leaf): no layout_style, no intrinsic.
-        fn opaque() -> Box<W> {
-            Box::new(W { base: Widget::new(), style: None, intrinsic: None, child_styles: None })
-        }
-    }
-    impl Element for W {
-        crate::impl_widget_base!(W);
-        fn color(&self) -> [f32; 4] {
-            [0.0, 0.0, 0.0, 0.0]
-        }
-        fn layout_style(&self) -> Option<Style> {
-            self.style
-        }
-        fn intrinsic_size(&self) -> Option<Size> {
-            self.intrinsic
-        }
-        fn layout_children(&self) -> Option<Vec<Style>> {
-            self.child_styles.clone()
-        }
-    }
-
-    /// Register a widget in `ctx` and return its (id, ptr).
-    fn reg(ctx: &mut UiContext, w: &mut W) -> (WidgetId, ElemPtr) {
-        let id = w.base.id();
-        let ptr: ElemPtr = w as *mut W;
-        ctx.register_widget(id, ptr);
-        (id, ptr)
-    }
-
-    /// Register any real `Element` (not just the test `W`) and return its (id, ptr).
-    fn reg_elem(ctx: &mut UiContext, e: &mut dyn Element) -> (WidgetId, ElemPtr) {
-        let ptr = e.as_ptr_mut();
-        let id = e.base().expect("widget has a base").id();
-        ctx.register_widget(id, ptr);
-        (id, ptr)
-    }
-
-    fn rect_of(ptr: ElemPtr) -> Rect {
-        let (x, y, w, h) = unsafe { (*ptr).rect() };
-        Rect { x, y, width: w, height: h }
-    }
-
-    #[test]
-    fn drives_a_real_column_of_leaves() {
-        let mut ctx = UiContext::new();
-        // Keep the boxes alive for the duration; hand the tree raw pointers into them.
-        let mut root = W::container(Style::column().gap(4.0));
-        let mut a = W::leaf(10.0, 10.0);
-        let mut b = W::leaf(10.0, 20.0);
-
-        let (root_id, root_ptr) = reg(&mut ctx, &mut root);
-        let (a_id, a_ptr) = reg(&mut ctx, &mut a);
-        let (b_id, b_ptr) = reg(&mut ctx, &mut b);
-        ctx.link_ids(root_id, a_id);
-        ctx.link_ids(root_id, b_id);
-
-        layout_subtree(&ctx, root_ptr, Rect { x: 0.0, y: 0.0, width: 100.0, height: 100.0 });
-
-        assert_eq!(rect_of(root_ptr), Rect { x: 0.0, y: 0.0, width: 100.0, height: 100.0 });
-        assert_eq!(rect_of(a_ptr), Rect { x: 0.0, y: 0.0, width: 10.0, height: 10.0 });
-        assert_eq!(rect_of(b_ptr), Rect { x: 0.0, y: 14.0, width: 10.0, height: 20.0 });
-    }
-
-    #[test]
-    fn lays_out_at_a_nonzero_origin_with_padding() {
-        let mut ctx = UiContext::new();
-        let mut root = W::container(Style::row().padding(5.0));
-        let mut a = W::leaf(10.0, 10.0);
-
-        let (root_id, root_ptr) = reg(&mut ctx, &mut root);
-        let (a_id, a_ptr) = reg(&mut ctx, &mut a);
-        ctx.link_ids(root_id, a_id);
-
-        // Subtree lives at (20, 30), not the origin.
-        layout_subtree(&ctx, root_ptr, Rect { x: 20.0, y: 30.0, width: 100.0, height: 50.0 });
-
-        assert_eq!(rect_of(root_ptr), Rect { x: 20.0, y: 30.0, width: 100.0, height: 50.0 });
-        // child inset by padding from the container's origin.
-        assert_eq!(rect_of(a_ptr), Rect { x: 25.0, y: 35.0, width: 10.0, height: 10.0 });
-    }
-
-    #[test]
-    fn drives_a_nested_subtree() {
-        let mut ctx = UiContext::new();
-        let mut root = W::container(Style::row());
-        let mut inner = W::container(Style::column().gap(2.0).cross_align(CrossAlign::Start));
-        let mut c1 = W::leaf(10.0, 10.0);
-        let mut c2 = W::leaf(10.0, 10.0);
-        let mut sibling = W::leaf(5.0, 5.0);
-
-        let (root_id, root_ptr) = reg(&mut ctx, &mut root);
-        let (inner_id, inner_ptr) = reg(&mut ctx, &mut inner);
-        let (c1_id, c1_ptr) = reg(&mut ctx, &mut c1);
-        let (c2_id, c2_ptr) = reg(&mut ctx, &mut c2);
-        let (sib_id, sib_ptr) = reg(&mut ctx, &mut sibling);
-        ctx.link_ids(root_id, inner_id);
-        ctx.link_ids(root_id, sib_id);
-        ctx.link_ids(inner_id, c1_id);
-        ctx.link_ids(inner_id, c2_id);
-
-        layout_subtree(&ctx, root_ptr, Rect { x: 0.0, y: 0.0, width: 100.0, height: 100.0 });
-
-        assert_eq!(rect_of(inner_ptr), Rect { x: 0.0, y: 0.0, width: 10.0, height: 22.0 });
-        assert_eq!(rect_of(c1_ptr), Rect { x: 0.0, y: 0.0, width: 10.0, height: 10.0 });
-        assert_eq!(rect_of(c2_ptr), Rect { x: 0.0, y: 12.0, width: 10.0, height: 10.0 });
-        assert_eq!(rect_of(sib_ptr).x, 10.0, "sibling follows inner's measured width");
-    }
-
-    #[test]
-    fn opaque_child_is_sized_but_not_recursed_into() {
-        // The incremental-migration boundary: a participating container lays out its direct
-        // children (here via parent-supplied grow weights, à la SplitBox), but a non-participating
-        // child is opaque — the engine must NOT descend into it and reposition its grandchildren.
-        let mut ctx = UiContext::new();
-        let mut root = W::container_with_child_styles(
-            Style::row().cross_align(CrossAlign::Stretch),
-            vec![Style::default().grow(1.0), Style::default().grow(1.0)],
-        );
-        let mut pane_a = W::opaque();
-        let mut pane_b = W::opaque();
-        let mut grandchild = W::leaf(7.0, 7.0);
-
-        let (root_id, root_ptr) = reg(&mut ctx, &mut root);
-        let (a_id, a_ptr) = reg(&mut ctx, &mut pane_a);
-        let (b_id, b_ptr) = reg(&mut ctx, &mut pane_b);
-        let (g_id, g_ptr) = reg(&mut ctx, &mut grandchild);
-        ctx.link_ids(root_id, a_id);
-        ctx.link_ids(root_id, b_id);
-        ctx.link_ids(a_id, g_id); // grandchild lives inside the opaque pane A
-
-        // Pre-position the grandchild; the opaque boundary must leave it untouched.
-        unsafe { (*g_ptr).set_rect(1.0, 2.0, 7.0, 7.0) };
-
-        layout_subtree(&ctx, root_ptr, Rect { x: 0.0, y: 0.0, width: 100.0, height: 40.0 });
-
-        // Panes: 50/50 by grow, stretched to full height.
-        assert_eq!(rect_of(a_ptr), Rect { x: 0.0, y: 0.0, width: 50.0, height: 40.0 });
-        assert_eq!(rect_of(b_ptr), Rect { x: 50.0, y: 0.0, width: 50.0, height: 40.0 });
-        // Grandchild untouched — recursion stopped at the opaque pane.
-        assert_eq!(rect_of(g_ptr), Rect { x: 1.0, y: 2.0, width: 7.0, height: 7.0 });
-    }
-
-    #[test]
-    fn sizes_real_labels_to_their_text_content() {
-        // End-to-end content sizing with a production widget: real Labels report intrinsic_size
-        // from measured text, and the engine lays them out at those widths.
-        use crate::widget::display::Label;
-        let mut ctx = UiContext::new();
-        let mut root = W::container(Style::row().gap(5.0));
-        let mut short = Label::new("Hi");
-        let mut long = Label::new("A considerably longer label");
-
-        let (root_id, root_ptr) = reg(&mut ctx, &mut root);
-        let (short_id, short_ptr) = reg_elem(&mut ctx, &mut short);
-        let (long_id, long_ptr) = reg_elem(&mut ctx, &mut long);
-        ctx.link_ids(root_id, short_id);
-        ctx.link_ids(root_id, long_id);
-
-        let w_short = short.intrinsic_size().unwrap().width;
-        let w_long = long.intrinsic_size().unwrap().width;
-        assert!(w_short > 0.0 && w_long > w_short, "longer text must measure wider");
-
-        layout_subtree(&ctx, root_ptr, Rect { x: 0.0, y: 0.0, width: 500.0, height: 50.0 });
-
-        // Each label sized to its own text; laid out left-to-right with the gap between them.
-        assert_eq!(rect_of(short_ptr).width, w_short);
-        assert_eq!(rect_of(long_ptr).width, w_long);
-        assert_eq!(rect_of(long_ptr).x, w_short + 5.0);
-    }
-}
diff --git a/src/scene/mod.rs b/src/scene/mod.rs
index 247ba1a..8d7daa0 100644
--- a/src/scene/mod.rs
+++ b/src/scene/mod.rs
@@ -7,7 +7,6 @@
 
 pub mod anim;
 pub mod arena;
-pub mod bridge;
 pub mod layout;
 pub mod paint;
 pub mod painter;
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 4455649..3104fd6 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -420,9 +420,6 @@ impl Adapted<MenuBar> {
 }
 
 impl Layout for MenuBar {
-    fn layout_ignore(&self) -> bool {
-        true
-    }
 
     fn z_order(&self) -> i32 {
         self.z_level
diff --git a/src/widget/display/label.rs b/src/widget/display/label.rs
index 19987c2..d6fb942 100644
--- a/src/widget/display/label.rs
+++ b/src/widget/display/label.rs
@@ -107,7 +107,7 @@ mod tests {
         Element::set_text(&mut l, "CPU: 99%");
         assert_eq!(l.own_text_labels()[0].text, "CPU: 99%", "set_text reaches the paint source");
 
-        let size = Element::intrinsic_size(&l).unwrap();
+        let size = l.intrinsic_size().unwrap();
         assert!(size.width > 0.0);
         assert!(!Element::blocks_backplate_drag(&l));
     }
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index 4fc522b..2b635b7 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -162,11 +162,6 @@ impl Layout for Button {
         true
     }
 
-    /// The app positions buttons itself in legacy page layout (legacy `layout_ignore`).
-    fn layout_ignore(&self) -> bool {
-        true
-    }
-
     /// Content size for the scene layout engine (ported from Phase 2b): the label's measured
     /// width plus an 8px inset each side, at the configured button height; an icon button is a
     /// square at that height.
@@ -386,8 +381,8 @@ mod tests {
         let short = Button::new(0.0, 0.0, 0.0, 0.0).with_label("Hi");
         let long = Button::new(0.0, 0.0, 0.0, 0.0).with_label("A much longer button label");
 
-        let s = Element::intrinsic_size(&short).unwrap();
-        let l = Element::intrinsic_size(&long).unwrap();
+        let s = short.intrinsic_size().unwrap();
+        let l = long.intrinsic_size().unwrap();
         assert!(s.width > 16.0, "includes the horizontal insets");
         assert!(l.width > s.width, "longer label measures wider");
         assert_eq!(s.height, crate::layout::button_height());
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index a7b75c4..697a152 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -526,9 +526,6 @@ impl Adapted<Dropdown> {
 }
 
 impl Layout for Dropdown {
-    fn layout_ignore(&self) -> bool {
-        true
-    }
 
     fn z_order(&self) -> i32 {
         if self.open {
diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index 7a66b3b..85af3a2 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -183,9 +183,6 @@ impl Layout for Slider {
         false // legacy Slider::set_rect stored the assigned rect verbatim
     }
 
-    fn layout_ignore(&self) -> bool {
-        true
-    }
 
     fn intrinsic_size(&self) -> Option<Size> {
         Some(Size::new(0.0, crate::layout::slider_height()))
diff --git a/src/widget/input/spinbox.rs b/src/widget/input/spinbox.rs
index 7777327..30549cf 100644
--- a/src/widget/input/spinbox.rs
+++ b/src/widget/input/spinbox.rs
@@ -159,9 +159,6 @@ impl Layout for Spinbox {
         false
     }
 
-    fn layout_ignore(&self) -> bool {
-        true
-    }
 
     fn detached_label_inset(&self) -> f32 {
         4.0 // legacy Control::control_label x offset
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 38ab595..11dcf84 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -192,21 +192,6 @@ pub trait Element {
     fn base_mut(&mut self) -> Option<&mut Widget> { None }
     fn preferred_height(&self) -> Option<f32> { None }
 
-    /// Opt-in layout style for the scene layout engine (Phase 2b). `None` (the default) means this
-    /// widget does not participate in engine-driven layout yet and keeps its legacy `set_rect`
-    /// path; return `Some(..)` to have the engine size/position it and its children. See
-    /// `scene::bridge`.
-    fn layout_style(&self) -> Option<crate::scene::layout::Style> { None }
-
-    /// Intrinsic content size of a leaf widget (e.g. measured text/icon) for the engine's measure
-    /// pass. Ignored for widgets that have children.
-    fn intrinsic_size(&self) -> Option<crate::scene::layout::Size> { None }
-
-    /// Per-child layout styles, for containers whose child sizing lives on the parent rather than
-    /// the children (e.g. `SplitBox` proportions). Returned in `children()` order; entry `i`
-    /// overrides child `i`'s own `layout_style`. `None` (default) means children use their own.
-    fn layout_children(&self) -> Option<Vec<crate::scene::layout::Style>> { None }
-
     fn mark_dirty(&mut self, ctx: &mut UiContext) {
         let mut parent_id = None;
         if let Some(b) = self.base_mut() {
@@ -400,19 +385,16 @@ pub trait Element {
         }
     }
 
-    fn highlight_color(&self, ctx: &UiContext) -> Option<[f32; 4]> {
+    fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, f32, [f32; 4])> {
+        // Focus/hover highlight color, folded from the zero-override `highlight_color` (6bd).
         let is_focused = self.base().map(|b| ctx.is_focused_id(b.id())).unwrap_or(false);
-        if is_focused {
-            Some(colors::highlight_primary_color())
+        let hc = if is_focused {
+            colors::highlight_primary_color()
         } else if self.hovered() {
-            Some(colors::HIGHLIGHT_SECONDARY)
+            colors::HIGHLIGHT_SECONDARY
         } else {
-            None
-        }
-    }
-
-    fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, f32, [f32; 4])> {
-        let hc = self.highlight_color(ctx)?;
+            return None;
+        };
         let label_x = self.label_x_offset();
         if let Some(b) = self.base() {
             let hx = if b.row_w > 0.0 { b.row_x } else { b.x } + label_x;
@@ -569,8 +551,6 @@ pub trait Element {
         }
     }
 
-    fn set_drag_bounds(&mut self, _bx: f32, _by: f32, _bw: f32, _bh: f32) {}
-
     fn focus(&mut self) {
         if let Some(b) = self.base_mut() {
             b.focused = true;
@@ -662,7 +642,6 @@ pub trait Element {
             if bl { r } else { 0.0 },
         )
     }
-    fn layout_ignore(&self) -> bool { false }
 }
 
 pub trait Control: Element {
diff --git a/src/widget/model.rs b/src/widget/model.rs
index 37885f2..30fc566 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -26,34 +26,21 @@
 //! [`crate::scene::bridge`] and [`Paint`] drives [`crate::scene::painter`]. The input/event
 //! concern follows in its own commit.
 
-use crate::scene::layout::{Rect, Size, Style};
+use crate::scene::layout::{Rect, Size};
 use crate::scene::paint::{PaintCtx, Prim};
 use crate::widget::{
     Element, Event, TextLabel, UiContext, Widget, WidgetId,
 };
 
 /// Layout inputs for the scene layout engine — the RFC's `Widget` concern, named `Layout` here to
-/// avoid the existing [`Widget`] base struct. Mirrors the opt-in `Element::layout_style` /
-/// `intrinsic_size` / `layout_children` hooks consumed by [`crate::scene::bridge`].
+/// avoid the existing [`Widget`] base struct.
 pub trait Layout {
-    /// Opt-in layout style for the engine. `None` (default) ⇒ this widget does not drive
-    /// engine-computed layout. See [`Element::layout_style`].
-    fn layout_style(&self) -> Option<Style> {
-        None
-    }
-
-    /// Intrinsic content size of a leaf (e.g. measured text) for the measure pass. See
-    /// [`Element::intrinsic_size`].
+    /// Intrinsic content size of a leaf (e.g. measured text), consumed by the adapter's
+    /// `measure` (gated on [`Layout::intrinsic_measure_width`]).
     fn intrinsic_size(&self) -> Option<Size> {
         None
     }
 
-    /// Per-child styles for containers that size their children from the parent (e.g. `SplitBox`
-    /// proportions), in `children()` order. See [`Element::layout_children`].
-    fn layout_children(&self) -> Option<Vec<Style>> {
-        None
-    }
-
     /// Whether this widget draws its control label *inline* (inside its own rect, like
     /// `Checkbox`/`Toggle`/`Button`) rather than detached above it (like `ProgressBar`/`Slider`).
     /// Inline-label widgets get no `set_rect` height inflation and no content-rect inset —
@@ -62,13 +49,6 @@ pub trait Layout {
         false
     }
 
-    /// Whether legacy container layout passes should skip this widget (the app positions it
-    /// itself — legacy `Element::layout_ignore`, read by `Plate` and the page layout). Default:
-    /// participate.
-    fn layout_ignore(&self) -> bool {
-        false
-    }
-
     /// Whether `set_rect` grows the widget past the assigned rect to make room for a detached
     /// label above (`ProgressBar`'s legacy convention). Sliders keep the assigned rect and let
     /// the label eat into it instead. Irrelevant for inline-label widgets. Default: grow.
@@ -553,7 +533,7 @@ pub trait Input {
         None
     }
     fn drag_end(&mut self) {}
-    /// Movement bounds pushed in by hosts (legacy `Element::set_drag_bounds`).
+    /// Movement bounds pushed in by hosts (reached via the inherent `Adapted::set_drag_bounds`).
     fn set_drag_bounds(&mut self, _bx: f32, _by: f32, _bw: f32, _bh: f32) {}
 
     // --- Tick surface: hosts broadcast `Element::tick(dt)` every frame (the designer's render
@@ -710,6 +690,18 @@ impl<W: Layout + Paint + Input + 'static> Adapted<W> {
 }
 
 impl<W: Layout + Paint + Input + 'static> Adapted<W> {
+    /// Movement bounds pushed in by hosts (off the `Element` 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) {
+        Input::set_drag_bounds(&mut self.inner, bx, by, bw, bh)
+    }
+
+    /// 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> {
+        Layout::intrinsic_size(&self.inner)
+    }
+
     /// Run the wrapped widget's [`Paint::paint`] against its content rect and return the emitted
     /// prims — the shared source for the reverse bridges (`extra_quads`, `all_rounded_quads`,
     /// `extra_circles`, `extra_arcs`, prim-derived `text_labels`) that legacy render loops read.
@@ -1002,20 +994,6 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
         }
     }
 
-    // --- Layout concern -> `Layout` ---
-    fn layout_style(&self) -> Option<Style> {
-        Layout::layout_style(&self.inner)
-    }
-    fn intrinsic_size(&self) -> Option<Size> {
-        Layout::intrinsic_size(&self.inner)
-    }
-    fn layout_children(&self) -> Option<Vec<Style>> {
-        Layout::layout_children(&self.inner)
-    }
-    fn layout_ignore(&self) -> bool {
-        Layout::layout_ignore(&self.inner)
-    }
-
     // --- Legacy structural conventions the adapter owns on the widget's behalf ---
 
     /// The detached-label convention shared by legacy control widgets: the widget grows past the
@@ -1396,10 +1374,6 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
     fn drag_end(&mut self) {
         Input::drag_end(&mut self.inner)
     }
-    fn set_drag_bounds(&mut self, bx: f32, by: f32, bw: f32, bh: f32) {
-        Input::set_drag_bounds(&mut self.inner, bx, by, bw, bh)
-    }
-
     // --- Legacy direct-dispatch entry points. Hosts (treelist's add-key button, parameters_bg's
     // checkboxes, app pages) call these ON the widget instead of routing an Event through
     // `propagate_event`; without these overrides they'd hit the inert Element defaults and the
@@ -1585,8 +1559,7 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
 mod tests {
     use super::*;
     use crate::widget::PathController;
-    use crate::scene::bridge::layout_subtree;
-    use crate::scene::layout::{CrossAlign, Size, Style};
+    use crate::scene::layout::{Rect, Size};
     use crate::scene::paint::Prim;
     use crate::scene::painter::paint_tree;
     use crate::widget::UiContext;
@@ -1611,11 +1584,7 @@ mod tests {
 
     /// A narrow container: it drives a column layout ([`Layout`]) and paints nothing.
     struct Col;
-    impl Layout for Col {
-        fn layout_style(&self) -> Option<Style> {
-            Some(Style::column().gap(4.0).cross_align(CrossAlign::Start))
-        }
-    }
+    impl Layout for Col {}
     impl Paint for Col {
         fn color(&self) -> [f32; 4] {
             [0.0, 0.0, 0.0, 0.0]
@@ -1647,8 +1616,13 @@ mod tests {
         ctx.link_ids(root_id, a_id);
         ctx.link_ids(root_id, b_id);
 
-        // Layout: column of a 10x10 then a 10x20, gap 4, in a 100x100 area.
-        layout_subtree(&ctx, root_ptr, Rect { x: 0.0, y: 0.0, width: 100.0, height: 100.0 });
+        // Layout by hand (the Phase-2b bridge is gone; apps drive the solver directly) —
+        // the same column-of-two placement the bridge used to compute.
+        unsafe {
+            (*root_ptr).set_rect(0.0, 0.0, 100.0, 100.0);
+            (*a_ptr).set_rect(0.0, 0.0, 10.0, 10.0);
+            (*b_ptr).set_rect(0.0, 14.0, 10.0, 20.0);
+        }
         assert_eq!(rect_of(a_ptr), Rect { x: 0.0, y: 0.0, width: 10.0, height: 10.0 });
         assert_eq!(rect_of(b_ptr), Rect { x: 0.0, y: 14.0, width: 10.0, height: 20.0 });