graphic design tool
git clone https://git.lucas.co/cce-designer.git
fix: an open dropdown in the params pane is its trigger's plate grown
The pane's popovers went through a PopoverCollector, which keeps fills as
square rects, so the expanded menu lost its relief and corners. They paint
into the frame's PaintCtx now, as cce-files' do. The dialog's choice-row
test judges the plate by where it ends rather than by a reading taken as
it opens, which a slow press had already finished.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 12 +++++++++++-
src/main.rs | 11 +++++++----
src/render.rs | 22 ++++++++--------------
3 files changed, 26 insertions(+), 19 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index d627046..13ed1d9 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -2392,6 +2392,13 @@ The rules that took a day to settle, each with the wrong version it replaced:
`highlight_primary_color` (`plate_focus_tint`); the flat `border_color`
draws only with relief off.
+**An open dropdown in the params pane is painted into the frame**
+(`append_popovers`, since 2026-10-01): `render_popover` gets the frame's
+`PaintCtx`, as cce-files' does, so the menu is the trigger's plate grown,
+relief and corners included. It went through a `PopoverCollector` until
+then, which keeps fills as square rects, and the expanded plate came out
+flat and square-cornered.
+
Two keys look like they apply and do not: `style.surface.plate.color`
feeds `plate_color`, whose one consumer is the info box, and the finish's
spec / shininess / curvature live as `relief.spec` / `.shininess` /
@@ -3534,7 +3541,10 @@ selection stays where it was:
- **Closing the dialog shuts it outright** (`open = false`), so no
shrinking plate is left reporting a popover over the panes.
`Dropdown::is_expanded` (cce-ui, the same day) is what tells a
- shrinking dropdown from one taking input. For one day before this the
+ shrinking dropdown from one taking input. The closed trigger is in the dropdown's
+ own font, not the dialog's: cce-ui's Dropdown names it on its text
+ (see its CLAUDE.md, "A dropdown's text names its font"), where a stamp
+ painted here took the dialog's and the list opened in another. For one day before this the
choice was the context menu shown as a list under the row, and before
that a click stepped the value between two chevrons.
`a_choice_row_is_a_dropdown` is the test.
diff --git a/src/main.rs b/src/main.rs
index 20bd4a9..74a0d6f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6531,9 +6531,11 @@ mod tests {
let label_idx = text_pos("Points").expect("param label in display list");
let option_idx = text_pos("Surface").expect("popover option text in display list");
assert!(option_idx > label_idx, "popover text must draw after widget labels");
+ // The trigger's plate grown — its fill and relief, whatever prims
+ // those are — between the label and the option.
let has_bg_between = list.items[label_idx..option_idx]
.iter()
- .any(|item| matches!(item.prim, cce_ui::scene::paint::Prim::Quad { .. }));
+ .any(|item| !matches!(item.prim, cce_ui::scene::paint::Prim::Text { .. }));
assert!(has_bg_between, "popover background must draw after widget labels");
}
@@ -14941,12 +14943,13 @@ mod tests {
let (tx, ty, tw, th) = state.slots.dialog.dropdown.rect();
assert!(vx >= tx && vx < tx + tw && vy >= ty - 4.0 && vy < ty + th, "laid out on the band its value was drawn in");
- // The plate grows out of the trigger into the list.
+ // The plate grows out of the trigger into the list. Judged by where
+ // it ends, not by a reading taken as it opens: the growth runs on
+ // the wall clock, and a slow press had already finished it.
let grown = |state: &State| state.slots.dialog.dropdown.popover_rect().map(|r| r.3).unwrap_or(0.0);
- let first = grown(&state);
std::thread::sleep(std::time::Duration::from_millis(250));
state.tick_frame(0.25);
- assert!(grown(&state) > first.max(th) + 24.0, "{} then {}", first, grown(&state));
+ assert!(grown(&state) > th + 24.0, "the trigger {th} grew to {}", grown(&state));
// Registered after the dialog, so the rows under it are clamped
// and its labels are not.
let _ = state.collect_display_list();
diff --git a/src/render.rs b/src/render.rs
index ee603a0..c532d4f 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -911,7 +911,7 @@ impl State {
}
/// Open popovers (the params pane's expanded dropdowns), background then
- /// text per widget. Appended after `append_frame_text` so the popover
+ /// text per widget, in the widget's own drawing. Appended after `append_frame_text` so the popover
/// occludes the widget labels underneath it — the display list is drawn
/// strictly in order, so a popover background emitted in the geometry
/// pass would sit under every label.
@@ -925,20 +925,14 @@ impl State {
if is_menubar {
continue;
}
+ // Into the frame's own PaintCtx, as cce-files and the palette
+ // paint theirs: a dropdown's expanded menu is its trigger's
+ // plate grown, relief and corners and all. Until 2026-10-01 it
+ // went through a `PopoverCollector`, which keeps fills as plain
+ // rects — the menu came out square-cornered and flat, a
+ // different thing from the control it grew out of.
if self.focused_widget == Some(i) || i == PARAM_IDX {
- let mut popover_pc = cce_ui::layout::PopoverCollector::new();
- w.render_popover(&mut popover_pc);
- for (color, px, py, pw, ph) in popover_pc.rects {
- pc.quad(rect(px, py, pw, ph), color);
- }
- for (t, size, x, y, tc, font_opt, label_bounds) in popover_pc.texts {
- let color = [
- (tc[0] * 255.0).round().clamp(0.0, 255.0) as u8,
- (tc[1] * 255.0).round().clamp(0.0, 255.0) as u8,
- (tc[2] * 255.0).round().clamp(0.0, 255.0) as u8,
- ];
- pc.text_with(t, x, y, size, color, font_opt, label_bounds);
- }
+ w.render_popover(pc);
}
}
}