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

commit005a53f30ff84f6b4b2a2ea8db50800c6ddff0ee
parentdf59416b1e
authorLucas Galante <[email protected]>
date2026-07-10 14:58
feat(label): StyledLabel::into_prim + LabelPrim — emit a measured label as a display-list Text prim

Phase 6ak support. StyledLabel measures text (builds a glyphon buffer for its
width) and, until now, could only be drawn via the legacy `draw()` into a
TextItem list. Add the source-retaining fields (src_text/src_size/src_family and
a prim_layout for the vertical/boxed case) and `into_prim(x, y) -> LabelPrim` so
a caller on the display-list path can measure-then-emit: the engine reshapes the
text through the shared cache, and the vertical bar's per-char centered wrap
rides the boxed-text TextLayout. LabelPrim mirrors PaintCtx::text_with /
text_boxed args. Re-exported through display/mod.rs and widget/mod.rs.

The legacy draw() path is untouched, so nothing else changes.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/widget/display/label.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++
 src/widget/display/mod.rs   |  2 +-
 src/widget/mod.rs           |  2 +-
 3 files changed, 61 insertions(+), 2 deletions(-)

diff --git a/src/widget/display/label.rs b/src/widget/display/label.rs
index 04ffa5a..ba43333 100644
--- a/src/widget/display/label.rs
+++ b/src/widget/display/label.rs
@@ -153,6 +153,26 @@ pub struct StyledLabel {
     pub g_color: glyphon::Color,
     pub strikethrough: bool,
     pub strikethrough_color: Option<[f32; 4]>,
+    // Source retained so the label can be re-emitted as a display-list Text prim (Phase 6ak):
+    // the (possibly vertical-transformed) text, its size, family, and the box layout for the
+    // vertical case (per-char lines + centered wrap). `buffer` above is kept for the legacy
+    // draw()/width measurement path.
+    src_text: String,
+    src_size: f32,
+    src_family: String,
+    prim_layout: Option<crate::scene::paint::TextLayout>,
+}
+
+/// The data to emit a [`StyledLabel`] as a display-list `Prim::Text`: what
+/// [`StyledLabel::into_prim`] returns, matching `PaintCtx::text_with` / `text_boxed` args.
+pub struct LabelPrim {
+    pub text: String,
+    pub size: f32,
+    pub x: f32,
+    pub y: f32,
+    pub color: [u8; 3],
+    pub font: Option<String>,
+    pub layout: Option<crate::scene::paint::TextLayout>,
 }
 
 impl StyledLabel {
@@ -186,6 +206,21 @@ impl StyledLabel {
             (color[1] * 255.0) as u8,
             (color[2] * 255.0) as u8,
         );
+        // Vertical text is boxed: the per-char-newline `final_text` wrapped to the bar
+        // thickness and centered — the same set_size + center-align the buffer path applied.
+        let prim_layout = if is_vert {
+            let bar_thickness = crate::BAR_THICKNESS.load(std::sync::atomic::Ordering::Relaxed) as f32;
+            Some(crate::scene::paint::TextLayout {
+                // Effectively unbounded height (the legacy vertical path used height None);
+                // align_v Top means no vertical offset, so only set_size's height sees this.
+                wrap_width: Some(bar_thickness),
+                box_height: 100_000.0,
+                align_h: crate::scene::paint::AlignH::Center,
+                align_v: crate::scene::paint::AlignV::Top,
+            })
+        } else {
+            None
+        };
         Self {
             buffer,
             w,
@@ -193,6 +228,30 @@ impl StyledLabel {
             g_color,
             strikethrough: false,
             strikethrough_color: None,
+            src_text: final_text,
+            src_size: size,
+            src_family: family.to_string(),
+            prim_layout,
+        }
+    }
+
+    /// Consume the label and return the data to emit it as a display-list `Prim::Text`
+    /// (Phase 6ak): the source text, size, family, and — for vertical bars — the box layout.
+    /// `x, y` are the draw position; vertical labels pin `y` to 0 (as `draw` did).
+    pub fn into_prim(self, x: f32, y: f32) -> LabelPrim {
+        let is_vert = crate::IS_VERTICAL.load(std::sync::atomic::Ordering::Relaxed);
+        LabelPrim {
+            text: self.src_text,
+            size: self.src_size,
+            x,
+            y: if is_vert { 0.0 } else { y },
+            color: [
+                (self.color[0] * 255.0) as u8,
+                (self.color[1] * 255.0) as u8,
+                (self.color[2] * 255.0) as u8,
+            ],
+            font: Some(self.src_family),
+            layout: self.prim_layout,
         }
     }
 
diff --git a/src/widget/display/mod.rs b/src/widget/display/mod.rs
index 7ed2505..061b52f 100644
--- a/src/widget/display/mod.rs
+++ b/src/widget/display/mod.rs
@@ -25,7 +25,7 @@ pub(crate) use self::text_label::make_widget_text_buffer;
 pub use self::sidebar::Sidebar;
 pub use self::panel::Panel;
 pub use self::node::Node;
-pub use self::label::{Label, SectionHeader, StyledLabel};
+pub use self::label::{Label, SectionHeader, StyledLabel, LabelPrim};
 pub use self::svg::Svg;
 pub use self::float3::Float3;
 pub use self::progress_bar::ProgressBar;
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 47043ff..a454d13 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -846,7 +846,7 @@ pub use self::container::{
     SplitBox, SplitDirection
 };
 pub use self::display::{
-    TextLabel, Label, SectionHeader, StyledLabel, TextItem, Svg, UsageBar,
+    TextLabel, Label, SectionHeader, StyledLabel, LabelPrim, TextItem, Svg, UsageBar,
     LayoutPreview, FontPreview, InfoBox, StatusDot, InteractiveListItem,
     GraphNode, Graph, Float3, ProgressBar, StatusBar, Splitter, Node, Separator,
     DotStatus, PreviewLayoutMode, Sidebar, Panel, PreviewState, ImagePreviewData, serialize_widgets,