diff --git a/internal/backends/qt/qt_window.rs b/internal/backends/qt/qt_window.rs index dc6e52d5424..08e8ffe4c3b 100644 --- a/internal/backends/qt/qt_window.rs +++ b/internal/backends/qt/qt_window.rs @@ -158,17 +158,23 @@ cpp! {{ isMouseButtonDown = false; void *parent_of_popup_to_close = nullptr; + int popup_id_to_close = 0; if (auto p = dynamic_cast(parent())) { while (auto pp = dynamic_cast(p->parent())) { p = pp; } void *parent_window = p->rust_window; bool inside = rect().contains(event->pos()); - bool close_on_click = rust!(Slint_mouseReleaseEventPopup [parent_window: &QtWindow as "void*", inside: bool as "bool"] -> bool as "bool" { - let close_policy = parent_window.top_close_policy(); - close_policy == PopupClosePolicy::CloseOnClick || (close_policy == PopupClosePolicy::CloseOnClickOutside && !inside) + popup_id_to_close = rust!(Slint_mouseReleaseEventPopup [parent_window: &QtWindow as "void*", inside: bool as "bool"] -> u32 as "int" { + let active_popups = WindowInner::from_pub(&parent_window.window).active_popups(); + if let Some(popup) = active_popups.last() { + if popup.close_policy == PopupClosePolicy::CloseOnClick || (popup.close_policy == PopupClosePolicy::CloseOnClickOutside && !inside) { + return popup.popup_id.get(); + } + } + 0 }); - if (close_on_click) { + if (popup_id_to_close) { parent_of_popup_to_close = parent_window; } } @@ -180,9 +186,9 @@ cpp! {{ let button = from_qt_button(button); rust_window.mouse_event(MouseEvent::Released{ position, button, click_count: 0 }) }); - if (parent_of_popup_to_close) { - rust!(Slint_mouseReleaseEventClosePopup [parent_of_popup_to_close: &QtWindow as "void*"] { - parent_of_popup_to_close.close_top_popup(); + if (popup_id_to_close) { + rust!(Slint_mouseReleaseEventClosePopup [parent_of_popup_to_close: &QtWindow as "void*", popup_id_to_close: std::num::NonZeroU32 as "int"] { + WindowInner::from_pub(&parent_of_popup_to_close.window).close_popup(popup_id_to_close); }); } } @@ -1750,14 +1756,6 @@ impl QtWindow { timer_event(); } - fn close_top_popup(&self) { - WindowInner::from_pub(&self.window).close_top_popup(); - } - - fn top_close_policy(&self) -> PopupClosePolicy { - WindowInner::from_pub(&self.window).top_close_policy() - } - fn window_state_event(&self) { let widget_ptr = self.widget_ptr(); diff --git a/internal/core/window.rs b/internal/core/window.rs index e25a80076e9..2ce23d83c84 100644 --- a/internal/core/window.rs +++ b/internal/core/window.rs @@ -393,13 +393,13 @@ pub enum PopupWindowLocation { #[derive(Clone)] pub struct PopupWindow { /// The ID of the associated popup. - popup_id: NonZeroU32, + pub popup_id: NonZeroU32, /// The location defines where the pop up is rendered. pub location: PopupWindowLocation, /// The component that is responsible for providing the popup content. pub component: ItemTreeRc, /// Defines the close behaviour of the popup. - close_policy: PopupClosePolicy, + pub close_policy: PopupClosePolicy, /// the item that had the focus in the parent window when the popup was opened focus_item_in_parent: ItemWeak, /// The item from where the Popup was invoked from @@ -596,8 +596,30 @@ impl WindowInner { self.had_popup_on_press.set(!self.active_popups.borrow().is_empty()); } - let close_policy = self.top_close_policy(); - let mut mouse_inside_popup = false; + let popup_to_close = self.active_popups.borrow().last().and_then(|popup| { + let mouse_inside_popup = || { + if let PopupWindowLocation::ChildWindow(coordinates) = &popup.location { + event.position().map_or(true, |pos| { + ItemTreeRc::borrow_pin(&popup.component) + .as_ref() + .item_geometry(0) + .contains(pos - coordinates.to_vector()) + }) + } else { + false + } + }; + match popup.close_policy { + PopupClosePolicy::CloseOnClick => { + let mouse_inside_popup = mouse_inside_popup(); + (mouse_inside_popup && released_event && self.had_popup_on_press.get()) + || (!mouse_inside_popup && pressed_event) + } + PopupClosePolicy::CloseOnClickOutside => !mouse_inside_popup() && pressed_event, + PopupClosePolicy::NoAutoClose => false, + } + .then_some(popup.popup_id) + }); mouse_input_state = if let Some(mut event) = crate::input::handle_mouse_grab(event, &window_adapter, &mut mouse_input_state) @@ -610,7 +632,7 @@ impl WindowInner { { let geom = ItemTreeRc::borrow_pin(component).as_ref().item_geometry(0); - mouse_inside_popup = event + let mouse_inside_popup = event .position() .map_or(true, |pos| geom.contains(pos - coordinates.to_vector())); @@ -655,21 +677,9 @@ impl WindowInner { self.mouse_input_state.set(mouse_input_state); - match close_policy { - PopupClosePolicy::CloseOnClick => { - if (mouse_inside_popup && released_event && self.had_popup_on_press.get()) - || (!mouse_inside_popup && pressed_event) - { - self.close_top_popup(); - } - } - PopupClosePolicy::CloseOnClickOutside => { - if !mouse_inside_popup && pressed_event { - self.close_top_popup(); - } - } - PopupClosePolicy::NoAutoClose => {} - }; + if let Some(popup_id) = popup_to_close { + self.close_popup(popup_id); + } crate::properties::ChangeTracker::run_change_handlers(); } @@ -1203,14 +1213,6 @@ impl WindowInner { } } - /// Returns the close policy of the top-most popup. PopupClosePolicy::NoAutoClose if there is no active popup. - pub fn top_close_policy(&self) -> PopupClosePolicy { - self.active_popups - .borrow() - .last() - .map_or(PopupClosePolicy::NoAutoClose, |popup| popup.close_policy) - } - /// Returns the scale factor set on the window, as provided by the windowing system. pub fn scale_factor(&self) -> f32 { self.pinned_fields.as_ref().project_ref().scale_factor.get() diff --git a/tests/cases/elements/popupwindow_nested_close-on-click.slint b/tests/cases/elements/popupwindow_nested_close-on-click.slint new file mode 100644 index 00000000000..69a3a60ff3f --- /dev/null +++ b/tests/cases/elements/popupwindow_nested_close-on-click.slint @@ -0,0 +1,115 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +// https://github.com/slint-ui/slint/issues/7322 +// A PopupWindow with the default close-on-click policy should close when clicked, even if the click open a nested popup + +export component TestCase { + width: 300px; + height: 300px; + + in-out property popup1-clicked; + in-out property popup2-clicked; + in-out property root-clicked; + + popup1 := PopupWindow { + Rectangle { + background: yellow; + } + + x: 10px; + y: 10px; + height: 50px; + width: 50px; + + TouchArea { + clicked => { + popup1-clicked += 1; + popup2.show(); + } + } + } + + popup2 := PopupWindow { + Rectangle { + background: red; + } + + x: 40px; + y: 40px; + height: 50px; + width: 50px; + + TouchArea { + clicked => { + popup2-clicked += 1; + } + } + } + + TouchArea { + clicked => { + root-clicked += 1; + popup1.show(); + } + } +} + +/* + +```rust + +let instance = TestCase::new().unwrap(); + + +slint_testing::send_mouse_click(&instance, 15., 15.); +assert_eq!(instance.get_root_clicked(), 1); +assert_eq!(instance.get_popup1_clicked(), 0); +assert_eq!(instance.get_popup2_clicked(), 0); + +// popup1 is open + +slint_testing::send_mouse_click(&instance, 15., 15.); +assert_eq!(instance.get_root_clicked(), 1); +assert_eq!(instance.get_popup1_clicked(), 1); +assert_eq!(instance.get_popup2_clicked(), 0); + +// popup2 is open, popup1 is closed + +slint_testing::send_mouse_click(&instance, 45., 45.); +assert_eq!(instance.get_root_clicked(), 1); +assert_eq!(instance.get_popup1_clicked(), 1); +assert_eq!(instance.get_popup2_clicked(), 1); + +// all popup closed + +slint_testing::send_mouse_click(&instance, 45., 45.); +assert_eq!(instance.get_root_clicked(), 2); +assert_eq!(instance.get_popup1_clicked(), 1); +assert_eq!(instance.get_popup2_clicked(), 1); + +// popup1 is open +// click where popup2 will be + +slint_testing::send_mouse_click(&instance, 45., 45.); +assert_eq!(instance.get_root_clicked(), 2); +assert_eq!(instance.get_popup1_clicked(), 2); +assert_eq!(instance.get_popup2_clicked(), 1); + +// popup2 is open, popup1 is closed + +slint_testing::send_mouse_click(&instance, 45., 45.); +assert_eq!(instance.get_root_clicked(), 2); +assert_eq!(instance.get_popup1_clicked(), 2); +assert_eq!(instance.get_popup2_clicked(), 2); + +// all popup closed + +slint_testing::send_mouse_click(&instance, 45., 45.); +assert_eq!(instance.get_root_clicked(), 3); +assert_eq!(instance.get_popup1_clicked(), 2); +assert_eq!(instance.get_popup2_clicked(), 2); + +``` + +*/ \ No newline at end of file