git.lucas.co / cce-gallery
widget gallery and compositor test bench

commit03aa8d39a886e5120bf5b3715872856517f972f2
parenta668e83700
authorLucas Galante <[email protected]>
date2026-09-09 11:09
fix: the flat run starts below the Flat lasso's headroom

The Flat lasso's title tab landed on the exhibit above its first member:
the tab and padding need 28px above the hull, the strategies leave 24
between blocks, and a lasso is an overlay the strategy never sees. The
exhibits are now laid out in two passes of the selected strategy — the rest,
then the flat run one gap plus `Group::headroom` lower — so the tab clears
the block above by exactly a gap, under every strategy.

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

 README.md   |  4 +++-
 src/main.rs | 20 ++++++++++++++++++--
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 9a57045..d3cc259 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,9 @@ hex field, ProgressBar, UsageBar and Trackpad — show their flat look too (`wit
 StatusDot statuses and the plain Label round it off. When a widget gains a
 style, it gains an exhibit here. The flat looks come last, in one run,
 so the page reads them as a section; a `Group` lasso titled `Flat` gathers
-them. Two more lassos lie over the named exhibits — `Group`, loose around the
+them, and the run is laid out as a second pass of the same strategy, one gap
+plus the lasso's headroom (`Group::headroom`, its padding and title tab)
+below the rest, so the tab clears the exhibit above it. Two more lassos lie over the named exhibits — `Group`, loose around the
 FontSelector and the StatusDot, and `Group (fitted)` around the top row,
 fitted to the exhibit area's edges. A lasso is laid out by its members, not
 by the strategy: under Mosaic it is the hull of wherever they landed.
diff --git a/src/main.rs b/src/main.rs
index 3aa946b..13c7d4c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -983,8 +983,24 @@ impl State {
             children.push(widget as *mut (dyn WidgetHost + 'static));
             indices.push(idx);
         }
-        let strategy = (LAYOUTS.get(self.layout_idx).unwrap_or(&LAYOUTS[DEFAULT_LAYOUT]).1)();
-        let content_h = strategy.layout(x, y, w, h, &children, &mut self.ui_context);
+        let make = LAYOUTS.get(self.layout_idx).unwrap_or(&LAYOUTS[DEFAULT_LAYOUT]).1;
+        // Two passes of the one strategy: the flat run (`FLAT_FIRST..`, the tail of the
+        // exhibits) starts one gap plus the Flat lasso's headroom below the rest, so the
+        // lasso's title tab clears the block above it — the room a strategy would reserve
+        // for the title if the lasso were its child. A lasso is an overlay the strategy
+        // never sees, so the gallery reserves it.
+        let flat_from = GALLERY_COUNT + FLAT_FIRST;
+        let (split, headroom) = match &self.roster {
+            Roster::Gallery(s) => (
+                indices.iter().position(|&i| i >= flat_from).unwrap_or(indices.len()),
+                s.group_flat.headroom(),
+            ),
+            _ => (indices.len(), 0.0),
+        };
+        let h1 = if split > 0 { make().layout(x, y, w, h, &children[..split], &mut self.ui_context) } else { 0.0 };
+        let y2 = if split > 0 { y + h1 + GAP + headroom } else { y };
+        let h2 = if split < children.len() { make().layout(x, y2, w, h, &children[split..], &mut self.ui_context) } else { 0.0 };
+        let content_h = (y2 - y) + h2;
         self.exhibit_scroll.update_bounds(content_h, y, h);
         let scroll_y = self.exhibit_scroll.scroll_y;
         for (&child, &idx) in children.iter().zip(&indices) {