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

commit5380de6ac9d0ab29f38e972902888b974861a9db
parent558b4ef7a0
authorLucas Galante <[email protected]>
date2026-09-09 14:34
feat: the exhibit area is a well sunk into the root plate

well_floor under the exhibits, well_rim over them (flat: the shared hairline
frame), one side margin in from the window and the same above the status
bar. The exhibits are laid out on the floor inside the wall — the wall taken
in both styles so the page lays out the same across the Style switch — and
the fitted lasso's plate is that floor, so it snaps to the foot of the wall
with a concentric corner. Geometry scrolled to the edge slides under the
rim; text is cut at the wall's foot, the TreeList's rule.

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

 README.md   |  8 ++++++--
 src/main.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++---------
 2 files changed, 52 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md
index 50da0f5..7212970 100644
--- a/README.md
+++ b/README.md
@@ -65,8 +65,12 @@ one label convention: drawn in the strip above the control at the same
 tab for its label; the recess is the control alone) — and every well, trough and raised plateau is carved
 inside its widget's rect (`layout::carve_inside`) so a widget's footprint is
 its rect. The exhibit
-area below the dropdown scrolls (wheel, or the scrollbar at its right edge)
-when a layout runs past the window. `Color Ramp...` and `Ramp...` open the
+area below the dropdowns is a well sunk into the root plate (`well_floor` under
+the exhibits, `well_rim` over them; flat, the shared hairline frame): the
+exhibits are laid out on its floor inside the wall, and the area scrolls (wheel,
+or the scrollbar at its right edge) when a layout runs past the window — an
+exhibit scrolled to the edge slides under the wall, its text cut at the wall's
+foot. `Color Ramp...` and `Ramp...` open the
 ColorRamp and Ramp editors in their own child windows.
 
 ## Child windows
diff --git a/src/main.rs b/src/main.rs
index 01ac4f4..71fb83d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -914,14 +914,36 @@ impl State {
     /// Lay the Controls exhibits out with the toolkit strategy the Layout dropdown selects
     /// (`LAYOUTS`), then clip whatever runs past the status bar.
     /// The exhibits' viewport: below the Layout dropdown, above the status bar.
+    /// The exhibit area: a well sunk into the root plate, one side margin in from
+    /// the window's sides and the same margin above the status bar.
     fn exhibit_viewport(&self) -> (f32, f32, f32, f32) {
         let (dx, dy, _, dh) = self.roster.get_dyn(15).rect();
         let (x, y) = (dx, dy + dh + GAP);
         let w = (self.width - 2.0 * x).max(300.0);
-        let h = ((self.height - 24.0) - y).max(100.0);
+        let h = ((self.height - 24.0 - x) - y).max(100.0);
         (x, y, w, h)
     }
 
+    /// The well's rect, outer corner radius and wall depth. The wall is every
+    /// well's rule (`well_rim`: the DE bevel width capped at a fifth of the
+    /// height), taken in both styles so the page lays out the same whichever the
+    /// Style dropdown selects — flat, it is the margin inside the hairline frame.
+    fn exhibit_well(&self) -> (cce_ui::scene::layout::Rect, f32, f32) {
+        let (x, y, w, h) = self.exhibit_viewport();
+        let rect = cce_ui::scene::layout::Rect { x, y, width: w, height: h };
+        let radius = cce_ui::layout::plate_corner_radius();
+        let wall = cce_ui::layout::bevel_width().min(h * 0.2);
+        (rect, radius, wall)
+    }
+
+    /// The well's floor inside its wall — the plate the exhibits sit on and the
+    /// fitted lasso snaps to — with the floor's corner radius.
+    fn exhibit_floor(&self) -> (cce_ui::scene::layout::Rect, f32) {
+        let (rect, radius, wall) = self.exhibit_well();
+        let (floor, radii) = cce_ui::layout::carve_inside(rect, (radius, radius, radius, radius), wall);
+        (floor, radii.0)
+    }
+
     fn in_exhibit_viewport(&self, px: f32, py: f32) -> bool {
         let (x, y, w, h) = self.exhibit_viewport();
         px >= x && px <= x + w && py >= y && py <= y + h
@@ -934,9 +956,11 @@ impl State {
     fn layout_exhibits(&mut self) {
         let (x, y, w, h) = self.exhibit_viewport();
         self.exhibit_scroll.set_rect(x, y, w, h);
-        // The fitted lasso's plate is the exhibit area: its sides snap to these edges.
+        // The exhibits sit on the well's floor, inside its wall; the fitted lasso's
+        // plate is that floor, so its sides snap to the foot of the wall.
+        let (floor, floor_r) = self.exhibit_floor();
         if let Roster::Gallery(s) = &mut self.roster {
-            s.group_fitted.inner_mut().set_plate(cce_ui::scene::layout::Rect { x, y, width: w, height: h }, cce_ui::color::root_plate_corner_radius());
+            s.group_fitted.inner_mut().set_plate(floor, floor_r);
         }
         let mut children: Vec<*mut (dyn WidgetHost + 'static)> = Vec::new();
         let mut indices: Vec<usize> = Vec::new();
@@ -965,8 +989,9 @@ impl State {
             _ => (0.0, 0.0),
         };
         let strategy = (LAYOUTS.get(self.layout_idx).unwrap_or(&LAYOUTS[DEFAULT_LAYOUT]).1)();
-        let content_h = strategy.layout(x + inset_x, y + inset_top, w - 2.0 * inset_x, h - inset_top, &children, &mut self.ui_context);
-        self.exhibit_scroll.update_bounds(content_h + inset_top, y, h);
+        let (lx, ly) = (floor.x + inset_x, floor.y + inset_top);
+        let content_h = strategy.layout(lx, ly, floor.width - 2.0 * inset_x, floor.y + floor.height - ly, &children, &mut self.ui_context);
+        self.exhibit_scroll.update_bounds(content_h + (ly - y), y, h);
         let scroll_y = self.exhibit_scroll.scroll_y;
         for (&child, &idx) in children.iter().zip(&indices) {
             let widget = unsafe { &mut *child };
@@ -1392,6 +1417,13 @@ impl cce_ui::engine::Application for State {
             let bg_color = cce_ui::color::page_low_color();
             pc.rounded_rect(Rect { x: 0.0, y: 0.0, width: sw, height: sh }, r, (true, true, true, true), bg_color);
         }
+        // The exhibit area is a well in the root plate: its floor under the
+        // exhibits here, its rim over them below (`well_rim`), so an exhibit
+        // scrolled to the edge slides under the wall rather than sitting on it.
+        if !self.is_child {
+            let (well, radius, _) = self.exhibit_well();
+            pc.well_floor(well, radius, false);
+        }
 
         let push_rounded = |pc: &mut cce_ui::scene::paint::PaintCtx, qx: f32, qy: f32, qw: f32, qh: f32, qr: f32, qc: [f32; 4], qcorners: (bool, bool, bool, bool)| {
             let rect = Rect { x: qx, y: qy, width: qw, height: qh };
@@ -1445,10 +1477,12 @@ impl cce_ui::engine::Application for State {
                 }
             }
         }
-        // The exhibit area's scrollbar, over the exhibits: the toolkit's relief
-        // scrollbar (a groove track, a raised thumb — the TreeList's), the flat
-        // quads only when relief is off.
+        // The well's rim over the exhibits, then the area's scrollbar over the
+        // rim: the toolkit's relief scrollbar (a groove track, a raised thumb —
+        // the TreeList's), the flat quads only when relief is off.
         if !self.is_child {
+            let (well, radius, _) = self.exhibit_well();
+            pc.well_rim(well, radius, cce_ui::layout::control_relief());
             if cce_ui::layout::control_relief() {
                 self.exhibit_scroll.paint_scrollbar_relief(&mut pc);
             } else {
@@ -1519,8 +1553,11 @@ impl cce_ui::engine::Application for State {
             if self.ui_context.tree.parent_ptr(w.base().id()).is_some() {
                 continue;
             }
+            // Text is cut at the foot of the wall: geometry slides under the rim,
+            // words do not sit on it (the TreeList's rule).
             let cp_clip = if !self.is_child && (is_exhibit(i) || is_overlay(i)) {
-                let (vx, vy, vw, vh) = self.exhibit_viewport();
+                let (f, _) = self.exhibit_floor();
+                let (vx, vy, vw, vh) = (f.x, f.y, f.width, f.height);
                 Some([vx, vy, vx + vw, vy + vh])
             } else {
                 None