widget gallery and compositor test bench
feat: exhibits laid out as content boxes, labels in the gap
cce-ui@ac3658c: a strategy places content boxes and the detached label hangs in the gap
above, so the exhibits re-land through `layout` after scrolling (the content
origin a strip below the occupied top), the Layout dropdown lands the same way, and
the Slider2D exhibit takes the width its label tab needs from `measure`.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
README.md | 9 +++++----
src/main.rs | 41 ++++++++++++++++++++++++++++-------------
2 files changed, 33 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
index f725ab3..9b46ef0 100644
--- a/README.md
+++ b/README.md
@@ -44,10 +44,11 @@ the page is a record of the defaults; only the draw-in canvases (Trackpad,
Plate, TreeList, the previews, the Ramp) are sized here. The `Layout` dropdown
lays the exhibits out with the toolkit's own `ContainerLayout` strategies —
Vertical, Columns, Grid, Adaptive Grid, Mosaic, Reverse Mosaic, Overlay — at
-their default spacing (`layout::CONTROL_GAP`, the one gap every strategy and
-row builder uses), so what you see is what a container using that strategy
-does: every control lands in exactly the box it is allotted, label included,
-whichever label convention it follows, and every detached label sits at the
+their default spacing (`layout::CONTROL_GAP`, one control height, the one gap
+every strategy and row builder uses in both axes), so what you see is what a
+container using that strategy does: a strategy places controls' CONTENT boxes,
+a detached label hangs in the gap above its control, mixed labeled and
+unlabeled controls line up by content, and every detached label sits at the
same `DETACHED_LABEL_INSET`. 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
diff --git a/src/main.rs b/src/main.rs
index 252c045..ad3cb55 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,7 +8,7 @@ use cce_ui::widget::{
};
mod gallery_widgets;
use gallery_widgets::{RootPlate, Plate};
-use cce_ui::widget::Adapted;
+use cce_ui::widget::{Adapted, LayoutConstraints, Point};
use cce_ui::widget::input::Slider2D;
use cce_ui::engine::{LogicalSize, LogicalPosition, LayerAnchor, LayerKeyboardInteractivity, LayerKind, LayerSettings};
use cce_ui::scene::paint::Prim;
@@ -216,10 +216,9 @@ pub struct Exhibit {
}
impl Exhibit {
- /// Sized by the widget's own preferred content height (its occupied height less the
- /// label strip an inflating widget adds back), `fallback_h` when it declares none.
+ /// Sized by the widget's own preferred (content) height, `fallback_h` when it declares none.
fn new<W: WidgetHost + 'static>(widget: W, w: f32, fallback_h: f32) -> Self {
- let h = widget.preferred_height().map(|p| p - widget.label_inflation()).unwrap_or(fallback_h);
+ let h = widget.preferred_height().unwrap_or(fallback_h);
Exhibit { widget: Box::new(widget), w, h, content_w: None }
}
@@ -865,6 +864,11 @@ impl State {
// or drag in, sized here and only here.
const CANVAS_H: f32 = 120.0;
const W: f32 = 190.0;
+ let s2d_w = self
+ .roster
+ .get_dyn(20)
+ .measure(LayoutConstraints::new(0.0, f32::MAX, 0.0, f32::MAX), &self.ui_context)
+ .width;
// A StatusDot is 12px square; its exhibit is as wide as its label
// (`content_width` hands the dot its own size back after layout).
const DOT_W: f32 = 150.0;
@@ -875,7 +879,7 @@ impl State {
(4, W, tgh), // Toggle
(6, W, slh), // Slider
(8, W, rsh), // RangeSlider
- (20, 64.0, 64.0), // Slider2D (its intrinsic square)
+ (20, s2d_w, 64.0), // Slider2D (a 64px pad, wide enough for its label)
(7, W, sph), // Spinbox
(21, W, Float3::preferred_height(false)), // Float3 (three slider rows)
(10, W, tbh), // TextBox
@@ -945,13 +949,19 @@ impl State {
let scroll_y = self.exhibit_scroll.scroll_y;
for (&child, &idx) in children.iter().zip(&indices) {
let widget = unsafe { &mut *child };
- // The strategy landed each exhibit in exactly the box it allotted; shift it
- // by the scroll and hand `set_rect` the content height (it re-adds the
- // label strip an inflating widget grows by — `label_inflation`).
+ // The strategy placed each exhibit's content box (its label hanging in the
+ // strip above); re-land it through `layout` shifted by the scroll — the
+ // landed rect is the occupied one, so the content origin is `strip` below
+ // its top and the content height is the rest.
let (cx, cy, cw, ch) = widget.rect();
- let content = ch - widget.label_inflation();
+ let strip = widget.label_strip();
+ let content = ch - strip;
let cw = self.content_width(idx).unwrap_or(cw);
- widget.set_rect(cx, cy - scroll_y, cw, content);
+ widget.layout(
+ Point { x: cx, y: cy + strip - scroll_y },
+ LayoutConstraints::new(cw, cw, content, content),
+ &mut self.ui_context,
+ );
}
}
@@ -988,9 +998,14 @@ impl State {
let visible = self.is_widget_visible(i);
let (x, y, w, h) = self.positions[i];
let widget = self.roster.get_dyn_mut(i);
- // The Layout dropdown takes the height the toolkit says a labeled
- // dropdown needs — its default height plus the label strip.
- let h = if i == 15 { widget.preferred_height().unwrap_or(h) } else { h };
+ if visible && i == 15 {
+ // The Layout dropdown lands through `layout` at its own preferred
+ // height: `y` is where its label goes, the content sits a strip below.
+ let h = widget.preferred_height().unwrap_or(h);
+ let strip = widget.label_strip();
+ widget.layout(Point { x, y: y + strip }, LayoutConstraints::new(w, w, h, h), &mut self.ui_context);
+ continue;
+ }
if visible {
widget.set_rect(x, y, w, h);
} else {