git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit62020a5217825eac9ba25919cd72006146f18b9a
parent5eadbb3d63
authorLucas Galante <[email protected]>
date2026-08-24 12:34
dropdown/params: pane dropdowns animate, and releases stop leaking through

Three defects stacked into one user-visible bug (picking an entry in the
designer's Open dropdown opened a save chooser instead of loading the file):

1. ParametersBg::tick cascaded to toggles, sliders, ramps and colors but
   never to `choices` — and the pane's internal widgets aren't in the host
   tree, so the Dropdown's tick-receiver registration points at an id
   ctx.tick can't resolve. Net: NO path ever advanced a pane dropdown's
   anim_snap. Every params-pane dropdown in every host opened at zero drawn
   extent — logically open, invisible, popover_rect a trigger-height sliver
   — and the next click toggled it closed. The pane now ticks its choice
   rows like the other animated rows.

2. The Dropdown handles presses only, so the paired RELEASE of a consumed
   press fell through the host's dispatch to whatever the popover covers —
   in the designer, the Save As button row. Releases are now consumed while
   the menu is open or closing.

3. (Diagnosis aid, kept:) CCE_PARAM_DEBUG traces the pane's press dispatch —
   which branch consumed, and the popover rect at gate time — alongside the
   existing CCE_DD_DEBUG/CCE_SCROLL_DEBUG family.

Live-verified in the designer: one click opens the fully drawn menu, one
click on a recent entry loads it (title "Designer - gears"), no chooser.

 src/widget/container/parameters_bg.rs | 25 ++++++++++++++++++++++++-
 src/widget/input/dropdown.rs          | 14 ++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 064f171..2b6f23a 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -1533,6 +1533,19 @@ impl Input for ParametersBg {
         }
         let mut changed = false;
         let mut dummy = crate::context::UiContext::new();
+        // Choice rows tick their Dropdowns' open/close animation. This was the
+        // ONLY path that can advance a pane dropdown's anim_snap (the widget's
+        // tick-receiver registration points at an id pane internals never put
+        // in the host tree), and without it every params-pane dropdown opened
+        // at zero drawn extent: logically open, invisible, reporting a sliver
+        // popover rect — and the next click toggled it closed again.
+        for c_opt in &mut self.choices {
+            if let Some(d) = c_opt {
+                if d.tick(dt, &mut dummy) {
+                    changed = true;
+                }
+            }
+        }
         for cb_opt in &mut self.toggles {
             if let Some(cb) = cb_opt {
                 if cb.tick(dt, &mut dummy) {
@@ -1797,8 +1810,15 @@ impl Input for ParametersBg {
                         continue;
                     }
                     if let Some(d) = d_opt {
+                        if std::env::var("CCE_PARAM_DEBUG").is_ok() {
+                            eprintln!("[pdbg] press ({px:.0},{py:.0}) choice[{i}] open-priority: popover_rect={:?}", d.popover_rect());
+                        }
                         if d.popover_rect().is_some() {
-                            if d.mouse_input(button, state, px, py, ui) {
+                            let consumed = d.mouse_input(button, state, px, py, ui);
+                            if std::env::var("CCE_PARAM_DEBUG").is_ok() {
+                                eprintln!("[pdbg]   -> open dropdown consumed={consumed}");
+                            }
+                            if consumed {
                                 if d.take_change() {
                                     if let Some(val) = d.get_value_string() {
                                         self.display_params[i].1 = val;
@@ -1846,6 +1866,9 @@ impl Input for ParametersBg {
                     } else if p.2 == "button" {
                         if let Some(b) = &mut self.buttons[i] {
                             if b.mouse_input(button, state, px, py, ui) {
+                                if std::env::var("CCE_PARAM_DEBUG").is_ok() {
+                                    eprintln!("[pdbg] press ({px:.0},{py:.0}) BUTTON[{i}] '{}' consumed", p.0);
+                                }
                                 if b.take_click() {
                                     p.1 = "clicked".to_string();
                                 }
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 2607396..35d79e0 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -1109,6 +1109,20 @@ impl Input for Dropdown {
     fn on_event(&mut self, event: &Event, ectx: &mut EventCtx) -> bool {
         self.settle_anim();
         match event {
+            Event::MouseButton {
+                button: MouseButton::Left,
+                state: ElementState::Released,
+                ..
+            } => {
+                // While the menu is open (or shrinking closed), the paired
+                // release of any press this dropdown handled must not leak to
+                // widgets stacked beneath the popover: an unconsumed release
+                // falling through the host's dispatch landed on the button
+                // whose row the menu covers — in the designer's params pane,
+                // picking an "Open" entry fired the Save As button underneath
+                // and opened a save chooser on top of the load.
+                self.open || self.closing
+            }
             Event::MouseButton {
                 button: MouseButton::Left,
                 state: ElementState::Pressed,