git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commit60df0fab1dc497336961435247b370a47cb1f4bd
parentd2768dfdbe
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-01 20:27
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);
             }
         }
     }