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