GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat(scene): content sizing — Label::intrinsic_size + engine proof (Phase 2b)
Give Label an intrinsic_size that measures its text via the FontSystem-free, memoized measure_text_width (width) and font line-height (height), so the layout engine can size content-driven widgets — the capability that unlocks migrating most real widgets, not just fixed/grow ones. Add a bridge test laying out real Labels in a row and asserting each is sized to its measured text. Inert until a container routes Labels through the bridge. 110 tests.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
src/scene/bridge.rs | 36 ++++++++++++++++++++++++++++++++++++
src/widget/display/label.rs | 11 +++++++++++
2 files changed, 47 insertions(+)
diff --git a/src/scene/bridge.rs b/src/scene/bridge.rs
index e8f9096..0ca3549 100644
--- a/src/scene/bridge.rs
+++ b/src/scene/bridge.rs
@@ -155,6 +155,14 @@ mod tests {
(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 }
@@ -259,4 +267,32 @@ mod tests {
// 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/widget/display/label.rs b/src/widget/display/label.rs
index b0b9520..2135876 100644
--- a/src/widget/display/label.rs
+++ b/src/widget/display/label.rs
@@ -45,6 +45,17 @@ impl Element for Label {
fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
+ /// Content size for the scene layout engine (Phase 2b). Width is the measured text extent
+ /// (via the FontSystem-free `measure_text_width`); height is one line at this font size.
+ fn intrinsic_size(&self) -> Option<crate::scene::layout::Size> {
+ let (family, _) = crate::layout::control_label_font_parsed();
+ let text = self.base.label.as_deref().unwrap_or("");
+ let width = crate::widget::display::measure_text_width(text, &family, self.font_size);
+ // Match the line-height factor used elsewhere in the toolkit (e.g. text_box).
+ let height = self.font_size * 1.333;
+ Some(crate::scene::layout::Size::new(width, height))
+ }
+
fn text_labels(&self) -> Vec<TextLabel> {
vec![TextLabel {
text: self.base.label.clone().unwrap_or_default(),