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

commitf1cd939f7371e12d180abc02d1df34cf9e812b67
parentb425ab04de
authorLucas Galante <[email protected]>
date2026-08-26 12:48
spreadsheet: per-column text clamps; the pane is a subtree painter

Header and cell labels clamp to their own column (a 4px gutter short of
the divider), so long values cut off instead of running under their
neighbors — narrow panes were unreadable, every column's tail overprinting
the next. The bounds ride Text prims, which only survive the subtree-
painter path: the own-labels re-derivation keeps just widget-level
text_bounds and silently drops per-prim bounds, so the pane now authors
its complete text in paint() and passes it through verbatim (the Playbar
pattern). cce-designer is the widget's only consumer.

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

 src/widget/container/spreadsheet.rs | 24 ++++++++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/widget/container/spreadsheet.rs b/src/widget/container/spreadsheet.rs
index 12603e9..32a1aa1 100644
--- a/src/widget/container/spreadsheet.rs
+++ b/src/widget/container/spreadsheet.rs
@@ -180,6 +180,15 @@ impl Paint for Spreadsheet {
         colors::plate_border_color().map(|bc| (bc, colors::plate_border_thickness()))
     }
 
+    /// Subtree painter: `paint` authors the pane's complete text with
+    /// per-column clamp bounds, so its Text prims must pass through
+    /// `paint_self` verbatim — the own-labels re-derivation drops per-prim
+    /// bounds, which is exactly how long cell values used to overlap into
+    /// their neighbor columns on narrow panes.
+    fn paints_own_subtree(&self) -> bool {
+        true
+    }
+
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
         let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
         let scroll = self.geom(rect).map_or(0.0, |g| g.scroll);
@@ -259,6 +268,17 @@ impl Paint for Spreadsheet {
                 [1.0, 1.0, 1.0, 0.05],
             );
         }
+        // Every label clamps to its own column (a 4px gutter short of the
+        // divider), so a long value cuts off instead of running under its
+        // neighbor — the overlap that made narrow panes unreadable.
+        let col_bounds = |col: usize, top: f32, height: f32| -> Option<[f32; 4]> {
+            Some([
+                x + col_w * col as f32,
+                top,
+                x + col_w * (col + 1) as f32 - 4.0,
+                top + height,
+            ])
+        };
         for (i, header) in self.headers.iter().enumerate() {
             let cx = x + col_w * i as f32 + 8.0;
             let (label, color) = match self.sort {
@@ -268,7 +288,7 @@ impl Paint for Spreadsheet {
                 }
                 _ => (header.clone(), [0xdd, 0xdd, 0xee]),
             };
-            ctx.text(label, cx, y + 6.0, 12.0, color);
+            ctx.text_with(label, cx, y + 6.0, 12.0, color, None, col_bounds(i, y, HEADER_H));
         }
         for (i, &src) in self.order.iter().enumerate() {
             let ry = y + HEADER_H + i as f32 * ROW_H - scroll;
@@ -277,7 +297,7 @@ impl Paint for Spreadsheet {
             }
             for (col_idx, val) in self.rows[src].iter().enumerate().take(n_cols) {
                 let cx = x + col_w * col_idx as f32 + 8.0;
-                ctx.text(val.clone(), cx, ry + 6.0, 12.0, [0xbb, 0xbb, 0xcc]);
+                ctx.text_with(val.clone(), cx, ry + 6.0, 12.0, [0xbb, 0xbb, 0xcc], None, col_bounds(col_idx, ry, ROW_H));
             }
         }
     }