Skip to content

Commit 2d944b2

Browse files
committed
Support multiple PopupWindow
Fixes #4356 Still not perfect: - Calling several times `popup.show()` on the same popup, will open that popup multiple times (instead of being a noop once opened) - Calling `some-popup.close()` will always close the top of the stack, without considering if it is the `some-popup` or another popup. Both problems are because we don't remember whether a particular popup is open and we don't associate `close()` with a particular popup
1 parent 3ce0e49 commit 2d944b2

File tree

3 files changed

+416
-39
lines changed

3 files changed

+416
-39
lines changed

internal/backends/qt/qt_window.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ cpp! {{
159159

160160
void *parent_of_popup_to_close = nullptr;
161161
if (auto p = dynamic_cast<const SlintWidget*>(parent())) {
162+
while (auto pp = dynamic_cast<const SlintWidget*>(p->parent())) {
163+
p = pp;
164+
}
162165
void *parent_window = p->rust_window;
163166
bool inside = rect().contains(event->pos());
164167
bool close_on_click = rust!(Slint_mouseReleaseEventPopup [parent_window: &QtWindow as "void*", inside: bool as "bool"] -> bool as "bool" {

internal/core/window.rs

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ use crate::input::{
1717
};
1818
use crate::item_tree::ItemRc;
1919
use crate::item_tree::{ItemTreeRc, ItemTreeRef, ItemTreeVTable, ItemTreeWeak};
20-
use crate::items::PopupClosePolicy;
21-
use crate::items::{ColorScheme, InputType, ItemRef, MouseCursor};
20+
use crate::items::{ColorScheme, InputType, ItemRef, MouseCursor, PopupClosePolicy};
2221
use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, SizeLengths};
2322
use crate::properties::{Property, PropertyTracker};
2423
use crate::renderer::Renderer;
2524
use crate::{Callback, Coord, SharedString};
2625
#[cfg(not(feature = "std"))]
2726
use alloc::boxed::Box;
2827
use alloc::rc::{Rc, Weak};
28+
#[cfg(not(feature = "std"))]
29+
use alloc::vec::Vec;
2930
use core::cell::{Cell, RefCell};
3031
use core::pin::Pin;
3132
use euclid::num::Zero;
@@ -431,7 +432,8 @@ pub struct WindowInner {
431432
maximized: Cell<bool>,
432433
minimized: Cell<bool>,
433434

434-
active_popup: RefCell<Option<PopupWindow>>,
435+
/// Stack of currently active popups
436+
active_popups: RefCell<Vec<PopupWindow>>,
435437
had_popup_on_press: Cell<bool>,
436438
close_requested: Callback<(), CloseRequestResponse>,
437439
click_state: ClickState,
@@ -492,7 +494,7 @@ impl WindowInner {
492494
focus_item: Default::default(),
493495
last_ime_text: Default::default(),
494496
cursor_blinker: Default::default(),
495-
active_popup: Default::default(),
497+
active_popups: Default::default(),
496498
had_popup_on_press: Default::default(),
497499
close_requested: Default::default(),
498500
click_state: ClickState::default(),
@@ -575,7 +577,7 @@ impl WindowInner {
575577
}
576578

577579
if pressed_event {
578-
self.had_popup_on_press.set(self.active_popup.borrow().is_some());
580+
self.had_popup_on_press.set(!self.active_popups.borrow().is_empty());
579581
}
580582

581583
let close_policy = self.close_policy();
@@ -588,7 +590,7 @@ impl WindowInner {
588590
location: PopupWindowLocation::ChildWindow(coordinates),
589591
component,
590592
..
591-
}) = self.active_popup.borrow().as_ref()
593+
}) = self.active_popups.borrow().last()
592594
{
593595
let geom = ItemTreeRc::borrow_pin(component).as_ref().item_geometry(0);
594596

@@ -800,7 +802,7 @@ impl WindowInner {
800802

801803
fn move_focus(&self, start_item: ItemRc, forward: impl Fn(ItemRc) -> ItemRc) -> Option<ItemRc> {
802804
let mut current_item = start_item;
803-
let mut visited = alloc::vec::Vec::new();
805+
let mut visited = Vec::new();
804806

805807
loop {
806808
if current_item.is_visible()
@@ -899,32 +901,29 @@ impl WindowInner {
899901
&self,
900902
render_components: impl FnOnce(&[(&ItemTreeRc, LogicalPoint)]) -> T,
901903
) -> Option<T> {
902-
let draw_fn = || {
903-
let component_rc = self.try_component()?;
904-
905-
let popup_component =
906-
self.active_popup.borrow().as_ref().and_then(|popup| match popup.location {
907-
PopupWindowLocation::TopLevel(..) => None,
908-
PopupWindowLocation::ChildWindow(coordinates) => {
909-
Some((popup.component.clone(), coordinates))
904+
let component_rc = self.try_component()?;
905+
Some(self.pinned_fields.as_ref().project_ref().redraw_tracker.evaluate_as_dependency_root(
906+
|| {
907+
if !self
908+
.active_popups
909+
.borrow()
910+
.iter()
911+
.any(|p| matches!(p.location, PopupWindowLocation::ChildWindow(..)))
912+
{
913+
render_components(&[(&component_rc, LogicalPoint::default())])
914+
} else {
915+
let borrow = self.active_popups.borrow();
916+
let mut cmps = Vec::with_capacity(borrow.len() + 1);
917+
cmps.push((&component_rc, LogicalPoint::default()));
918+
for popup in borrow.iter() {
919+
if let PopupWindowLocation::ChildWindow(location) = &popup.location {
920+
cmps.push((&popup.component, *location));
921+
}
910922
}
911-
});
912-
913-
Some(if let Some((popup_component, popup_coordinates)) = popup_component {
914-
render_components(&[
915-
(&component_rc, LogicalPoint::default()),
916-
(&popup_component, popup_coordinates),
917-
])
918-
} else {
919-
render_components(&[(&component_rc, LogicalPoint::default())])
920-
})
921-
};
922-
923-
self.pinned_fields
924-
.as_ref()
925-
.project_ref()
926-
.redraw_tracker
927-
.evaluate_as_dependency_root(draw_fn)
923+
render_components(&cmps)
924+
}
925+
},
926+
))
928927
}
929928

930929
/// Registers the window with the windowing system, in order to render the component's items and react
@@ -983,6 +982,34 @@ impl WindowInner {
983982
let position = parent_item.map_to_window(
984983
parent_item.geometry().origin + LogicalPoint::from_untyped(position).to_vector(),
985984
);
985+
let root_of = |mut item_tree: ItemTreeRc| loop {
986+
if ItemRc::new(item_tree.clone(), 0).downcast::<crate::items::WindowItem>().is_some() {
987+
return item_tree;
988+
}
989+
let mut r = crate::item_tree::ItemWeak::default();
990+
ItemTreeRc::borrow_pin(&item_tree).as_ref().parent_node(&mut r);
991+
match r.upgrade() {
992+
None => return item_tree,
993+
Some(x) => item_tree = x.item_tree().clone(),
994+
}
995+
};
996+
let parent_root_item_tree = root_of(parent_item.item_tree().clone());
997+
let (parent_window_adapter, position) = if let Some(parent_popup) = self
998+
.active_popups
999+
.borrow()
1000+
.iter()
1001+
.find(|p| ItemTreeRc::ptr_eq(&p.component, &parent_root_item_tree))
1002+
{
1003+
match &parent_popup.location {
1004+
PopupWindowLocation::TopLevel(wa) => (wa.clone(), position),
1005+
PopupWindowLocation::ChildWindow(offset) => {
1006+
(self.window_adapter(), position + offset.to_vector())
1007+
}
1008+
}
1009+
} else {
1010+
(self.window_adapter(), position)
1011+
};
1012+
9861013
let popup_component = ItemTreeRc::borrow_pin(popup_componentrc);
9871014
let popup_root = popup_component.as_ref().get_item_ref(0);
9881015

@@ -1019,8 +1046,7 @@ impl WindowInner {
10191046
height_property.set(size.height_length());
10201047
};
10211048

1022-
let location = match self
1023-
.window_adapter()
1049+
let location = match parent_window_adapter
10241050
.internal(crate::InternalToken)
10251051
.and_then(|x| x.create_popup(LogicalRect::new(position, size)))
10261052
{
@@ -1042,17 +1068,17 @@ impl WindowInner {
10421068
}
10431069
};
10441070

1045-
self.active_popup.replace(Some(PopupWindow {
1071+
self.active_popups.borrow_mut().push(PopupWindow {
10461072
location,
10471073
component: popup_componentrc.clone(),
10481074
close_policy,
1049-
}));
1075+
});
10501076
}
10511077

10521078
/// Removes any active popup.
10531079
/// TODO: this function should take a component ref as parameter, to close a specific popup - i.e. when popup menus create a hierarchy of popups.
10541080
pub fn close_popup(&self) {
1055-
if let Some(current_popup) = self.active_popup.replace(None) {
1081+
if let Some(current_popup) = self.active_popups.borrow_mut().pop() {
10561082
match current_popup.location {
10571083
PopupWindowLocation::ChildWindow(offset) => {
10581084
// Refresh the area that was previously covered by the popup.
@@ -1077,9 +1103,9 @@ impl WindowInner {
10771103

10781104
/// Returns the close policy of the active popup. PopupClosePolicy::NoAutoClose if there is no active popup.
10791105
pub fn close_policy(&self) -> PopupClosePolicy {
1080-
self.active_popup
1106+
self.active_popups
10811107
.borrow()
1082-
.as_ref()
1108+
.last()
10831109
.map_or(PopupClosePolicy::NoAutoClose, |popup| popup.close_policy)
10841110
}
10851111

0 commit comments

Comments
 (0)