GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
UiContext::popover_owner_at — press priority for open popovers
Positional dispatch knows nothing about z-order: an app iterating its
roots can hand a press to a CLOSED sibling whose trigger band sits under
an open menu, which then swallows the click and the menu closes
unselected. Found on the settings Default Apps page — the Terminal
dropdown's popover covers the Images row's trigger, and the roots
iterate bottom-up — but latent for any page whose popover overlaps a
later-dispatched widget.
popover_owner_at(x, y) returns the registered widget whose OPEN popover
(popover_rect, the drawn animated surface) contains the point — the
press-priority companion to close_popovers_missed_by_press, scanning
the registry the same way since popover registration is optional and
spotty across apps. Apps route MouseButton events to this owner before
their positional dispatch.
Also: CCE_DD_DEBUG=1 traces dropdown press geometry (rect/popover/
inside flags) and key-selection — the probe that found the bug.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/context.rs | 23 +++++++++++++++++++++++
src/widget/input/dropdown.rs | 9 +++++++++
2 files changed, 32 insertions(+)
diff --git a/src/context.rs b/src/context.rs
index bcde4e5..126a76d 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -635,6 +635,29 @@ impl UiContext {
}
}
+ /// The registered widget whose OPEN popover contains `(x, y)`, if any — the
+ /// press-priority companion to
+ /// [`close_popovers_missed_by_press`](Self::close_popovers_missed_by_press).
+ /// A popover paints OVER whatever sits beneath it, but positional dispatch
+ /// knows nothing about z-order: an app iterating its roots can hand the
+ /// press to a closed sibling whose trigger band lies under the open menu
+ /// (the Default Apps page's Terminal dropdown covering the Images row).
+ /// Apps route a `MouseButton` to this owner before their positional
+ /// dispatch. Scans the registry like the missed-press walk — popover
+ /// registration is optional and spotty, so `active_popovers` alone cannot
+ /// be trusted to know about every open menu.
+ pub fn popover_owner_at(&self, x: f32, y: f32) -> Option<WidgetId> {
+ self.tree.iter_registered().find_map(|(id, ptr)| unsafe {
+ ptr.as_ref().and_then(|w| {
+ if !w.visible() {
+ return None;
+ }
+ let (rx, ry, rw, rh) = w.popover_rect()?;
+ (x >= rx && x <= rx + rw && y >= ry && y <= ry + rh).then_some(id)
+ })
+ })
+ }
+
/// Register an open popover. Takes `&mut` so the registry can be refreshed with the
/// pointer we are handed (the occlusion walks resolve the stored id through the tree).
pub fn register_popover(&mut self, w: &mut (dyn WidgetHost + 'static)) {
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 3a66c99..cb9c096 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -801,6 +801,9 @@ impl Dropdown {
true
}
Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Space) => {
+ if std::env::var_os("CCE_DD_DEBUG").is_some() {
+ eprintln!("[dd] key-select hovered={:?} selected={}", self.hovered_item, self.selected);
+ }
if let Some(idx) = self.hovered_item {
if idx < self.options.len() && self.options[idx] != "-" {
if self.selected != idx || self.custom_display_text.is_some() {
@@ -1121,6 +1124,12 @@ impl Input for Dropdown {
&& !self.closing
&& *px >= rx && *px <= rx + rw && *py >= ry && *py <= ry + rh;
+ if std::env::var_os("CCE_DD_DEBUG").is_some() {
+ eprintln!(
+ "[dd] press ({px},{py}) rect=({:.0},{:.0},{:.0},{:.0}) popover=({rx:.0},{ry:.0},{rw:.0},{rh:.0}) open={} in_trig={inside_trigger} in_pop={inside_popover}",
+ content.x, content.y, content.width, content.height, self.open
+ );
+ }
if inside_popover {
let idx = ((py - ry) / 24.0) as usize;
if idx < self.options.len() {