Skip to content

Commit

Permalink
Fix closing PopupWindow when the click opens another PopupWindow
Browse files Browse the repository at this point in the history
Fix #7322
  • Loading branch information
ogoffart authored Jan 10, 2025
1 parent 9504a4f commit e34c193
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 43 deletions.
28 changes: 13 additions & 15 deletions internal/backends/qt/qt_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<const SlintWidget*>(parent())) {
while (auto pp = dynamic_cast<const SlintWidget*>(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;
}
}
Expand All @@ -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);
});
}
}
Expand Down Expand Up @@ -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();

Expand Down
58 changes: 30 additions & 28 deletions internal/core/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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()));

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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()
Expand Down
115 changes: 115 additions & 0 deletions tests/cases/elements/popupwindow_nested_close-on-click.slint
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright © SixtyFPS GmbH <[email protected]>
// 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 <int> popup1-clicked;
in-out property <int> popup2-clicked;
in-out property <int> 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);
```
*/

0 comments on commit e34c193

Please sign in to comment.