mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-01 20:31:27 +00:00
Associate PopupWindows with an ID for their active popup (#6693)
Popups are stored in a HashMap and are assigned an ID so popup.close(); closes the correct popup and so a single PopupWindow cannot be opened multiple times
This commit is contained in:
parent
d30dfc0175
commit
6da0f55b05
11 changed files with 459 additions and 74 deletions
|
|
@ -106,18 +106,23 @@ public:
|
|||
}
|
||||
|
||||
template<typename Component, typename Parent, typename PosGetter>
|
||||
void show_popup(const Parent *parent_component, PosGetter pos,
|
||||
uint32_t show_popup(const Parent *parent_component, PosGetter pos,
|
||||
cbindgen_private::PopupClosePolicy close_policy,
|
||||
cbindgen_private::ItemRc parent_item) const
|
||||
{
|
||||
auto popup = Component::create(parent_component);
|
||||
cbindgen_private::Point p = pos(popup);
|
||||
auto popup_dyn = popup.into_dyn();
|
||||
cbindgen_private::slint_windowrc_show_popup(&inner, &popup_dyn, p, close_policy,
|
||||
return cbindgen_private::slint_windowrc_show_popup(&inner, &popup_dyn, p, close_policy,
|
||||
&parent_item);
|
||||
}
|
||||
|
||||
void close_popup() const { cbindgen_private::slint_windowrc_close_popup(&inner); }
|
||||
void close_popup(uint32_t popup_id) const
|
||||
{
|
||||
if (popup_id > 0) {
|
||||
cbindgen_private::slint_windowrc_close_popup(&inner, popup_id);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::invocable<RenderingState, GraphicsAPI> F>
|
||||
std::optional<SetRenderingNotifierError> set_rendering_notifier(F callback) const
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ cpp! {{
|
|||
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.close_policy();
|
||||
let close_policy = parent_window.top_close_policy();
|
||||
close_policy == PopupClosePolicy::CloseOnClick || (close_policy == PopupClosePolicy::CloseOnClickOutside && !inside)
|
||||
});
|
||||
if (close_on_click) {
|
||||
|
|
@ -182,7 +182,7 @@ cpp! {{
|
|||
});
|
||||
if (parent_of_popup_to_close) {
|
||||
rust!(Slint_mouseReleaseEventClosePopup [parent_of_popup_to_close: &QtWindow as "void*"] {
|
||||
parent_of_popup_to_close.close_popup();
|
||||
parent_of_popup_to_close.close_top_popup();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1746,12 +1746,12 @@ impl QtWindow {
|
|||
timer_event();
|
||||
}
|
||||
|
||||
fn close_popup(&self) {
|
||||
WindowInner::from_pub(&self.window).close_popup();
|
||||
fn close_top_popup(&self) {
|
||||
WindowInner::from_pub(&self.window).close_top_popup();
|
||||
}
|
||||
|
||||
fn close_policy(&self) -> PopupClosePolicy {
|
||||
WindowInner::from_pub(&self.window).close_policy()
|
||||
fn top_close_policy(&self) -> PopupClosePolicy {
|
||||
WindowInner::from_pub(&self.window).top_close_policy()
|
||||
}
|
||||
|
||||
fn window_state_event(&self) {
|
||||
|
|
|
|||
|
|
@ -1969,6 +1969,17 @@ fn generate_sub_component(
|
|||
));
|
||||
}
|
||||
|
||||
for (i, _) in component.popup_windows.iter().enumerate() {
|
||||
target_struct.members.push((
|
||||
field_access,
|
||||
Declaration::Var(Var {
|
||||
ty: ident("mutable uint32_t"),
|
||||
name: format_smolstr!("popup_id_{}", i),
|
||||
..Default::default()
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
for (prop1, prop2) in &component.two_way_bindings {
|
||||
init.push(format!(
|
||||
"slint::private_api::Property<{ty}>::link_two_way(&{p1}, &{p2});",
|
||||
|
|
@ -3613,15 +3624,28 @@ fn compile_builtin_function_call(
|
|||
let position = compile_expression(&popup.position.borrow(), &popup_ctx);
|
||||
let close_policy = compile_expression(close_policy, ctx);
|
||||
format!(
|
||||
"{window}.show_popup<{popup_window_id}>({component_access}, [=](auto self) {{ return {position}; }}, {close_policy}, {{ {parent_component} }})"
|
||||
"{window}.close_popup({component_access}->popup_id_{popup_index}); {component_access}->popup_id_{popup_index} = {window}.show_popup<{popup_window_id}>({component_access}, [=](auto self) {{ return {position}; }}, {close_policy}, {{ {parent_component} }})"
|
||||
)
|
||||
} else {
|
||||
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
|
||||
}
|
||||
}
|
||||
BuiltinFunction::ClosePopupWindow => {
|
||||
if let [llr::Expression::NumberLiteral(popup_index), llr::Expression::PropertyReference(parent_ref)] = arguments {
|
||||
let mut parent_ctx = ctx;
|
||||
let mut component_access = "self".into();
|
||||
|
||||
if let llr::PropertyReference::InParent { level, .. } = parent_ref {
|
||||
for _ in 0..level.get() {
|
||||
component_access = format!("{}->parent", component_access);
|
||||
parent_ctx = parent_ctx.parent.as_ref().unwrap().ctx;
|
||||
}
|
||||
};
|
||||
let window = access_window_field(ctx);
|
||||
format!("{window}.close_popup()")
|
||||
format!("{window}.close_popup({component_access}->popup_id_{popup_index})")
|
||||
} else {
|
||||
panic!("internal error: invalid args to ClosePopupWindow {:?}", arguments)
|
||||
}
|
||||
}
|
||||
BuiltinFunction::SetSelectionOffsets => {
|
||||
if let [llr::Expression::PropertyReference(pr), from, to] = arguments {
|
||||
|
|
|
|||
|
|
@ -1001,6 +1001,9 @@ fn generate_sub_component(
|
|||
sub_component_types.push(sub_component_id);
|
||||
}
|
||||
|
||||
let popup_id_names =
|
||||
component.popup_windows.iter().enumerate().map(|(i, _)| internal_popup_id(i));
|
||||
|
||||
for (prop1, prop2) in &component.two_way_bindings {
|
||||
let p1 = access_member(prop1, &ctx);
|
||||
let p2 = access_member(prop2, &ctx);
|
||||
|
|
@ -1116,6 +1119,7 @@ fn generate_sub_component(
|
|||
struct #inner_component_id {
|
||||
#(#item_names : sp::#item_types,)*
|
||||
#(#sub_component_names : #sub_component_types,)*
|
||||
#(#popup_id_names : ::core::cell::Cell<sp::Option<::core::num::NonZeroU32>>,)*
|
||||
#(#declared_property_vars : sp::Property<#declared_property_types>,)*
|
||||
#(#declared_callbacks : sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)*
|
||||
#(#repeated_element_names : sp::Repeater<#repeated_element_components>,)*
|
||||
|
|
@ -1819,6 +1823,12 @@ fn inner_component_id(component: &llr::SubComponent) -> proc_macro2::Ident {
|
|||
format_ident!("Inner{}", ident(&component.name))
|
||||
}
|
||||
|
||||
fn internal_popup_id(index: usize) -> proc_macro2::Ident {
|
||||
let mut name = index.to_string();
|
||||
name.insert_str(0, "popup_id_");
|
||||
ident(&name)
|
||||
}
|
||||
|
||||
fn global_inner_name(g: &llr::GlobalComponent) -> TokenStream {
|
||||
if g.is_builtin {
|
||||
let i = ident(&g.name);
|
||||
|
|
@ -2660,27 +2670,51 @@ fn compile_builtin_function_call(
|
|||
|
||||
let close_policy = compile_expression(close_policy, ctx);
|
||||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||
let popup_id_name = internal_popup_id(*popup_index as usize);
|
||||
quote!({
|
||||
let popup_instance = #popup_window_id::new(#component_access_tokens.self_weak.get().unwrap().clone()).unwrap();
|
||||
let popup_instance_vrc = sp::VRc::map(popup_instance.clone(), |x| x);
|
||||
#popup_window_id::user_init(popup_instance_vrc.clone());
|
||||
let position = { let _self = popup_instance_vrc.as_pin_ref(); #position };
|
||||
if let Some(current_id) = #component_access_tokens.#popup_id_name.take() {
|
||||
sp::WindowInner::from_pub(#window_adapter_tokens.window()).close_popup(current_id);
|
||||
}
|
||||
#component_access_tokens.#popup_id_name.set(Some(
|
||||
sp::WindowInner::from_pub(#window_adapter_tokens.window()).show_popup(
|
||||
&sp::VRc::into_dyn(popup_instance.into()),
|
||||
position,
|
||||
#close_policy,
|
||||
#parent_component
|
||||
)
|
||||
))
|
||||
);
|
||||
})
|
||||
} else {
|
||||
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
|
||||
}
|
||||
}
|
||||
BuiltinFunction::ClosePopupWindow => {
|
||||
if let [Expression::NumberLiteral(popup_index), Expression::PropertyReference(parent_ref)] =
|
||||
arguments
|
||||
{
|
||||
let mut parent_ctx = ctx;
|
||||
let mut component_access_tokens = quote!(_self);
|
||||
if let llr::PropertyReference::InParent { level, .. } = parent_ref {
|
||||
for _ in 0..level.get() {
|
||||
component_access_tokens =
|
||||
quote!(#component_access_tokens.parent.upgrade().unwrap().as_pin_ref());
|
||||
parent_ctx = parent_ctx.parent.as_ref().unwrap().ctx;
|
||||
}
|
||||
}
|
||||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||
let popup_id_name = internal_popup_id(*popup_index as usize);
|
||||
quote!(
|
||||
sp::WindowInner::from_pub(#window_adapter_tokens.window()).close_popup()
|
||||
if let Some(current_id) = #component_access_tokens.#popup_id_name.take() {
|
||||
sp::WindowInner::from_pub(#window_adapter_tokens.window()).close_popup(current_id);
|
||||
}
|
||||
)
|
||||
} else {
|
||||
panic!("internal error: invalid args to ClosePopupWindow {:?}", arguments)
|
||||
}
|
||||
}
|
||||
BuiltinFunction::SetSelectionOffsets => {
|
||||
if let [llr::Expression::PropertyReference(pr), from, to] = arguments {
|
||||
|
|
|
|||
|
|
@ -122,11 +122,7 @@ pub fn lower_expression(
|
|||
lower_show_popup(arguments, ctx)
|
||||
}
|
||||
tree_Expression::BuiltinFunctionReference(BuiltinFunction::ClosePopupWindow, _) => {
|
||||
// FIXME: right now, `popup.close()` will close any visible popup, as the popup argument is ignored
|
||||
llr_Expression::BuiltinFunctionCall {
|
||||
function: BuiltinFunction::ClosePopupWindow,
|
||||
arguments: vec![],
|
||||
}
|
||||
lower_close_popup(arguments, ctx)
|
||||
}
|
||||
tree_Expression::BuiltinFunctionReference(f, _) => {
|
||||
let mut arguments =
|
||||
|
|
@ -399,6 +395,38 @@ fn lower_show_popup(args: &[tree_Expression], ctx: &ExpressionContext) -> llr_Ex
|
|||
}
|
||||
}
|
||||
|
||||
fn lower_close_popup(args: &[tree_Expression], ctx: &ExpressionContext) -> llr_Expression {
|
||||
if let [tree_Expression::ElementReference(e)] = args {
|
||||
let popup_window = e.upgrade().unwrap();
|
||||
let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap();
|
||||
let parent_component = pop_comp
|
||||
.parent_element
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.enclosing_component
|
||||
.upgrade()
|
||||
.unwrap();
|
||||
let popup_list = parent_component.popup_windows.borrow();
|
||||
let (popup_index, popup) = popup_list
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, p)| Rc::ptr_eq(&p.component, &pop_comp))
|
||||
.unwrap();
|
||||
let item_ref = lower_expression(
|
||||
&tree_Expression::ElementReference(Rc::downgrade(&popup.parent_element)),
|
||||
ctx,
|
||||
);
|
||||
|
||||
llr_Expression::BuiltinFunctionCall {
|
||||
function: BuiltinFunction::ClosePopupWindow,
|
||||
arguments: vec![llr_Expression::NumberLiteral(popup_index as _), item_ref],
|
||||
}
|
||||
} else {
|
||||
panic!("invalid arguments to ShowPopupWindow");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lower_animation(a: &PropertyAnimation, ctx: &ExpressionContext<'_>) -> Animation {
|
||||
fn lower_animation_element(a: &ElementRc, ctx: &ExpressionContext<'_>) -> llr_Expression {
|
||||
llr_Expression::Struct {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use alloc::rc::{Rc, Weak};
|
|||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use core::cell::{Cell, RefCell};
|
||||
use core::num::NonZeroU32;
|
||||
use core::pin::Pin;
|
||||
use euclid::num::Zero;
|
||||
use vtable::VRcMapped;
|
||||
|
|
@ -384,6 +385,8 @@ enum PopupWindowLocation {
|
|||
/// This structure defines a graphical element that is designed to pop up from the surrounding
|
||||
/// UI content, for example to show a context menu.
|
||||
struct PopupWindow {
|
||||
/// The ID of the associated popup.
|
||||
popup_id: NonZeroU32,
|
||||
/// The location defines where the pop up is rendered.
|
||||
location: PopupWindowLocation,
|
||||
/// The component that is responsible for providing the popup content.
|
||||
|
|
@ -434,6 +437,7 @@ pub struct WindowInner {
|
|||
|
||||
/// Stack of currently active popups
|
||||
active_popups: RefCell<Vec<PopupWindow>>,
|
||||
next_popup_id: Cell<NonZeroU32>,
|
||||
had_popup_on_press: Cell<bool>,
|
||||
close_requested: Callback<(), CloseRequestResponse>,
|
||||
click_state: ClickState,
|
||||
|
|
@ -495,6 +499,7 @@ impl WindowInner {
|
|||
last_ime_text: Default::default(),
|
||||
cursor_blinker: Default::default(),
|
||||
active_popups: Default::default(),
|
||||
next_popup_id: Cell::new(NonZeroU32::MIN),
|
||||
had_popup_on_press: Default::default(),
|
||||
close_requested: Default::default(),
|
||||
click_state: ClickState::default(),
|
||||
|
|
@ -510,7 +515,7 @@ impl WindowInner {
|
|||
/// Associates this window with the specified component. Further event handling and rendering, etc. will be
|
||||
/// done with that component.
|
||||
pub fn set_component(&self, component: &ItemTreeRc) {
|
||||
self.close_popup();
|
||||
self.close_all_popups();
|
||||
self.focus_item.replace(Default::default());
|
||||
self.mouse_input_state.replace(Default::default());
|
||||
self.modifiers.replace(Default::default());
|
||||
|
|
@ -580,7 +585,7 @@ impl WindowInner {
|
|||
self.had_popup_on_press.set(!self.active_popups.borrow().is_empty());
|
||||
}
|
||||
|
||||
let close_policy = self.close_policy();
|
||||
let close_policy = self.top_close_policy();
|
||||
let mut mouse_inside_popup = false;
|
||||
|
||||
mouse_input_state = if let Some(mut event) =
|
||||
|
|
@ -644,12 +649,12 @@ impl WindowInner {
|
|||
if (mouse_inside_popup && released_event && self.had_popup_on_press.get())
|
||||
|| (!mouse_inside_popup && pressed_event)
|
||||
{
|
||||
self.close_popup();
|
||||
self.close_top_popup();
|
||||
}
|
||||
}
|
||||
PopupClosePolicy::CloseOnClickOutside => {
|
||||
if !mouse_inside_popup && pressed_event {
|
||||
self.close_popup();
|
||||
self.close_top_popup();
|
||||
}
|
||||
}
|
||||
PopupClosePolicy::NoAutoClose => {}
|
||||
|
|
@ -971,14 +976,15 @@ impl WindowInner {
|
|||
.map_or(ColorScheme::Unknown, |x| x.color_scheme())
|
||||
}
|
||||
|
||||
/// Show a popup at the given position relative to the item
|
||||
/// Show a popup at the given position relative to the item and returns its ID.
|
||||
/// The returned ID will always be non-zero.
|
||||
pub fn show_popup(
|
||||
&self,
|
||||
popup_componentrc: &ItemTreeRc,
|
||||
position: Point,
|
||||
close_policy: PopupClosePolicy,
|
||||
parent_item: &ItemRc,
|
||||
) {
|
||||
) -> NonZeroU32 {
|
||||
let position = parent_item.map_to_window(
|
||||
parent_item.geometry().origin + LogicalPoint::from_untyped(position).to_vector(),
|
||||
);
|
||||
|
|
@ -1046,6 +1052,9 @@ impl WindowInner {
|
|||
height_property.set(size.height_length());
|
||||
};
|
||||
|
||||
let popup_id = self.next_popup_id.get();
|
||||
self.next_popup_id.set(self.next_popup_id.get().checked_add(1).unwrap());
|
||||
|
||||
let location = match parent_window_adapter
|
||||
.internal(crate::InternalToken)
|
||||
.and_then(|x| x.create_popup(LogicalRect::new(position, size)))
|
||||
|
|
@ -1069,17 +1078,18 @@ impl WindowInner {
|
|||
};
|
||||
|
||||
self.active_popups.borrow_mut().push(PopupWindow {
|
||||
popup_id,
|
||||
location,
|
||||
component: popup_componentrc.clone(),
|
||||
close_policy,
|
||||
});
|
||||
|
||||
popup_id
|
||||
}
|
||||
|
||||
/// Removes any active popup.
|
||||
/// 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.
|
||||
pub fn close_popup(&self) {
|
||||
if let Some(current_popup) = self.active_popups.borrow_mut().pop() {
|
||||
match current_popup.location {
|
||||
// Close the popup associated with the given popup window.
|
||||
fn close_popup_impl(&self, current_popup: &PopupWindow) {
|
||||
match ¤t_popup.location {
|
||||
PopupWindowLocation::ChildWindow(offset) => {
|
||||
// Refresh the area that was previously covered by the popup.
|
||||
let popup_region = crate::properties::evaluate_no_tracking(|| {
|
||||
|
|
@ -1099,10 +1109,34 @@ impl WindowInner {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the popup matching the given ID.
|
||||
pub fn close_popup(&self, popup_id: NonZeroU32) {
|
||||
let mut active_popups = self.active_popups.borrow_mut();
|
||||
let maybe_index = active_popups.iter().position(|popup| popup.popup_id == popup_id);
|
||||
|
||||
if let Some(popup_index) = maybe_index {
|
||||
self.close_popup_impl(&active_popups[popup_index]);
|
||||
active_popups.remove(popup_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the close policy of the active popup. PopupClosePolicy::NoAutoClose if there is no active popup.
|
||||
pub fn close_policy(&self) -> PopupClosePolicy {
|
||||
/// Close all active popups.
|
||||
pub fn close_all_popups(&self) {
|
||||
for popup in self.active_popups.take() {
|
||||
self.close_popup_impl(&popup);
|
||||
}
|
||||
}
|
||||
|
||||
/// Close the top-most popup.
|
||||
pub fn close_top_popup(&self) {
|
||||
if let Some(popup) = self.active_popups.borrow_mut().pop() {
|
||||
self.close_popup_impl(&popup);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()
|
||||
|
|
@ -1361,7 +1395,7 @@ pub mod ffi {
|
|||
WindowInner::from_pub(window_adapter.window()).set_component(component)
|
||||
}
|
||||
|
||||
/// Show a popup.
|
||||
/// Show a popup and return its ID. The returned ID will always be non-zero.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn slint_windowrc_show_popup(
|
||||
handle: *const WindowAdapterRcOpaque,
|
||||
|
|
@ -1369,20 +1403,24 @@ pub mod ffi {
|
|||
position: crate::graphics::Point,
|
||||
close_policy: PopupClosePolicy,
|
||||
parent_item: &ItemRc,
|
||||
) {
|
||||
) -> NonZeroU32 {
|
||||
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
|
||||
WindowInner::from_pub(window_adapter.window()).show_popup(
|
||||
return WindowInner::from_pub(window_adapter.window()).show_popup(
|
||||
popup,
|
||||
position,
|
||||
close_policy,
|
||||
parent_item,
|
||||
);
|
||||
}
|
||||
/// Close the current popup
|
||||
|
||||
/// Close the popup by the given ID.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn slint_windowrc_close_popup(handle: *const WindowAdapterRcOpaque) {
|
||||
pub unsafe extern "C" fn slint_windowrc_close_popup(
|
||||
handle: *const WindowAdapterRcOpaque,
|
||||
popup_id: NonZeroU32,
|
||||
) {
|
||||
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
|
||||
WindowInner::from_pub(window_adapter.window()).close_popup();
|
||||
WindowInner::from_pub(window_adapter.window()).close_popup(popup_id);
|
||||
}
|
||||
|
||||
/// C binding to the set_rendering_notifier() API of Window
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ use once_cell::unsync::{Lazy, OnceCell};
|
|||
use smol_str::{SmolStr, ToSmolStr};
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::num::NonZeroU32;
|
||||
use std::{pin::Pin, rc::Rc};
|
||||
|
||||
pub const SPECIAL_PROPERTY_INDEX: &str = "$index";
|
||||
|
|
@ -411,6 +412,8 @@ pub struct ItemTreeDescription<'id> {
|
|||
Vec<(NamedReference, Expression)>,
|
||||
)>,
|
||||
timers: Vec<FieldOffset<Instance<'id>, Timer>>,
|
||||
/// Map of element IDs to their active popup's ID
|
||||
popup_ids: std::cell::RefCell<HashMap<SmolStr, NonZeroU32>>,
|
||||
|
||||
/// The collection of compiled globals
|
||||
compiled_globals: Option<Rc<CompiledGlobalCollection>>,
|
||||
|
|
@ -1341,6 +1344,7 @@ pub(crate) fn generate_item_tree<'id>(
|
|||
compiled_globals,
|
||||
change_trackers,
|
||||
timers,
|
||||
popup_ids: std::cell::RefCell::new(HashMap::new()),
|
||||
#[cfg(feature = "highlight")]
|
||||
type_loader: std::cell::OnceCell::new(),
|
||||
#[cfg(feature = "highlight")]
|
||||
|
|
@ -2330,6 +2334,8 @@ impl<'a, 'id> InstanceRef<'a, 'id> {
|
|||
|
||||
/// Show the popup at the given location
|
||||
pub fn show_popup(
|
||||
element: ElementRc,
|
||||
instance: InstanceRef,
|
||||
popup: &object_tree::PopupWindow,
|
||||
pos_getter: impl FnOnce(InstanceRef<'_, '_>) -> i_slint_core::graphics::Point,
|
||||
close_policy: PopupClosePolicy,
|
||||
|
|
@ -2355,14 +2361,30 @@ pub fn show_popup(
|
|||
let instance_ref = compo_box.borrow_instance();
|
||||
pos_getter(instance_ref)
|
||||
};
|
||||
close_popup(element.clone(), instance, parent_window_adapter.clone());
|
||||
instance.description.popup_ids.borrow_mut().insert(
|
||||
element.borrow().id.clone(),
|
||||
WindowInner::from_pub(parent_window_adapter.window()).show_popup(
|
||||
&vtable::VRc::into_dyn(inst),
|
||||
pos,
|
||||
close_policy,
|
||||
parent_item,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn close_popup(
|
||||
element: ElementRc,
|
||||
instance: InstanceRef,
|
||||
parent_window_adapter: WindowAdapterRc,
|
||||
) {
|
||||
if let Some(current_id) =
|
||||
instance.description.popup_ids.borrow_mut().remove(&element.borrow().id)
|
||||
{
|
||||
WindowInner::from_pub(parent_window_adapter.window()).close_popup(current_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_timers(instance: InstanceRef) {
|
||||
let ts = instance.description.original.timers.borrow();
|
||||
for (desc, offset) in ts.iter().zip(&instance.description.timers) {
|
||||
|
|
|
|||
|
|
@ -614,6 +614,8 @@ fn call_builtin_function(
|
|||
.expect("Invalid internal enumeration representation for close policy");
|
||||
|
||||
crate::dynamic_item_tree::show_popup(
|
||||
popup_window,
|
||||
component,
|
||||
popup,
|
||||
|instance_ref| {
|
||||
let comp = ComponentInstance::InstanceRef(instance_ref);
|
||||
|
|
@ -633,7 +635,7 @@ fn call_builtin_function(
|
|||
);
|
||||
Value::Void
|
||||
} else {
|
||||
panic!("internal error: argument to SetFocusItem must be an element")
|
||||
panic!("internal error: argument to ShowPopupWindow must be an element")
|
||||
}
|
||||
}
|
||||
BuiltinFunction::ClosePopupWindow => {
|
||||
|
|
@ -644,9 +646,18 @@ fn call_builtin_function(
|
|||
}
|
||||
};
|
||||
|
||||
component.access_window(|window| window.close_popup());
|
||||
if let Expression::ElementReference(popup_window) = &arguments[0] {
|
||||
let popup_window = popup_window.upgrade().unwrap();
|
||||
crate::dynamic_item_tree::close_popup(
|
||||
popup_window,
|
||||
component,
|
||||
component.window_adapter(),
|
||||
);
|
||||
|
||||
Value::Void
|
||||
} else {
|
||||
panic!("internal error: argument to ClosePopupWindow must be an element")
|
||||
}
|
||||
}
|
||||
BuiltinFunction::SetSelectionOffsets => {
|
||||
if arguments.len() != 3 {
|
||||
|
|
|
|||
|
|
@ -263,6 +263,33 @@ instance.set_result("".into());
|
|||
|
||||
slint_testing::send_mouse_click(&instance, 20., 310.);
|
||||
assert_eq!(instance.get_result(), "Root");
|
||||
instance.set_result("".into());
|
||||
|
||||
// open both popups
|
||||
slint_testing::send_mouse_click(&instance, 380., 10.);
|
||||
|
||||
// close popup1
|
||||
slint_testing::send_mouse_click(&instance, 150., 210. + 40.);
|
||||
assert_eq!(instance.get_result(), "C1");
|
||||
instance.set_result("".into());
|
||||
|
||||
// popup2 is still open and can be closed
|
||||
slint_testing::send_mouse_click(&instance, 40., 210. + 40.);
|
||||
assert_eq!(instance.get_result(), "C2");
|
||||
instance.set_result("".into());
|
||||
|
||||
// open both popups
|
||||
slint_testing::send_mouse_click(&instance, 380., 10.);
|
||||
|
||||
// close popup2
|
||||
slint_testing::send_mouse_click(&instance, 40., 210. + 40.);
|
||||
assert_eq!(instance.get_result(), "C2");
|
||||
instance.set_result("".into());
|
||||
|
||||
// popup1 is still open
|
||||
slint_testing::send_mouse_click(&instance, 210., 90.);
|
||||
assert_eq!(instance.get_result(), "P1");
|
||||
instance.set_result("".into());
|
||||
```
|
||||
|
||||
```cpp
|
||||
|
|
@ -343,6 +370,33 @@ instance.set_result("");
|
|||
|
||||
slint_testing::send_mouse_click(&instance, 20., 310.);
|
||||
assert_eq(instance.get_result(), "Root");
|
||||
instance.set_result("");
|
||||
|
||||
// open both popups
|
||||
slint_testing::send_mouse_click(&instance, 380., 10.);
|
||||
|
||||
// close popup1
|
||||
slint_testing::send_mouse_click(&instance, 150., 210. + 40.);
|
||||
assert_eq(instance.get_result(), "C1");
|
||||
instance.set_result("");
|
||||
|
||||
// popup2 is still open and can be closed
|
||||
slint_testing::send_mouse_click(&instance, 40., 210. + 40.);
|
||||
assert_eq(instance.get_result(), "C2");
|
||||
instance.set_result("");
|
||||
|
||||
// open both popups
|
||||
slint_testing::send_mouse_click(&instance, 380., 10.);
|
||||
|
||||
// close popup2
|
||||
slint_testing::send_mouse_click(&instance, 40., 210. + 40.);
|
||||
assert_eq(instance.get_result(), "C2");
|
||||
instance.set_result("");
|
||||
|
||||
// popup1 is still open
|
||||
slint_testing::send_mouse_click(&instance, 210., 90.);
|
||||
assert_eq(instance.get_result(), "P1");
|
||||
instance.set_result("");
|
||||
```
|
||||
|
||||
*/
|
||||
|
|
|
|||
169
tests/cases/elements/popupwindow_open_twice.slint
Normal file
169
tests/cases/elements/popupwindow_open_twice.slint
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
import { Button } from "std-widgets.slint";
|
||||
|
||||
component PopupContainer inherits Rectangle {
|
||||
width: 400px;
|
||||
height: 300px;
|
||||
|
||||
in-out property <int> popup_click_count;
|
||||
|
||||
callback first_reached;
|
||||
callback second_reached;
|
||||
|
||||
property <length> popup_x;
|
||||
property <bool> first_sent: false;
|
||||
property <bool> second_sent: false;
|
||||
|
||||
public function show(second: bool) {
|
||||
popup_x = !second ? 0px : 200px;
|
||||
popup.show();
|
||||
}
|
||||
|
||||
public function close() {
|
||||
popup.close();
|
||||
}
|
||||
|
||||
popup := PopupWindow {
|
||||
x: popup_x;
|
||||
y: 100px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
close-policy: no-auto-close;
|
||||
|
||||
Rectangle {
|
||||
background: red;
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
popup_click_count += 1;
|
||||
|
||||
if (popup_x == 0px) {
|
||||
if (!first_sent) {
|
||||
first_reached();
|
||||
first_sent = true;
|
||||
}
|
||||
} else {
|
||||
if (!second_sent ) {
|
||||
second_reached();
|
||||
second_sent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component TestCase inherits Window {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
|
||||
in-out property <int> popup_click_count;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
HorizontalLayout {
|
||||
Button {
|
||||
text: "Open";
|
||||
clicked => {
|
||||
cnt1.show(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cnt1 := PopupContainer {
|
||||
x: 0px;
|
||||
y: 100px;
|
||||
popup_click_count <=> popup_click_count;
|
||||
|
||||
first_reached => {
|
||||
cnt1.show(true);
|
||||
}
|
||||
|
||||
second_reached => {
|
||||
cnt2.show(false);
|
||||
}
|
||||
}
|
||||
|
||||
cnt2 := PopupContainer {
|
||||
x: 0px;
|
||||
y: 100px;
|
||||
popup_click_count <=> popup_click_count;
|
||||
|
||||
first_reached => {
|
||||
cnt2.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
```rust
|
||||
#[allow(unused)]
|
||||
use slint::{platform::WindowEvent, platform::PointerEventButton, LogicalPosition};
|
||||
let instance = TestCase::new().unwrap();
|
||||
|
||||
// open on the left
|
||||
slint_testing::send_mouse_click(&instance, 20., 10.);
|
||||
|
||||
// use left popup to open on the right
|
||||
slint_testing::send_mouse_click(&instance, 10., 210.);
|
||||
assert_eq!(instance.get_popup_click_count(), 1);
|
||||
|
||||
// no longer open on the left
|
||||
slint_testing::send_mouse_click(&instance, 10., 210.);
|
||||
assert_eq!(instance.get_popup_click_count(), 1);
|
||||
|
||||
// popup is now open on the right
|
||||
// open a second popup on the left in the second container
|
||||
slint_testing::send_mouse_click(&instance, 210., 210.);
|
||||
assert_eq!(instance.get_popup_click_count(), 2);
|
||||
|
||||
// second popup is still open on the left
|
||||
// close the second popup
|
||||
slint_testing::send_mouse_click(&instance, 10., 210.);
|
||||
assert_eq!(instance.get_popup_click_count(), 3);
|
||||
|
||||
// first popup is still open as multiple of the same "PopupWindow" across different component instances is fine
|
||||
slint_testing::send_mouse_click(&instance, 210., 210.);
|
||||
assert_eq!(instance.get_popup_click_count(), 4);
|
||||
```
|
||||
|
||||
```cpp
|
||||
auto handle = TestCase::create();
|
||||
TestCase &instance = *handle;
|
||||
|
||||
// open on the left
|
||||
slint_testing::send_mouse_click(&instance, 20., 10.);
|
||||
|
||||
// use left popup to open on the right
|
||||
slint_testing::send_mouse_click(&instance, 10., 210.);
|
||||
assert_eq(instance.get_popup_click_count(), 1);
|
||||
|
||||
// no longer open on the left
|
||||
slint_testing::send_mouse_click(&instance, 10., 210.);
|
||||
assert_eq(instance.get_popup_click_count(), 1);
|
||||
|
||||
// popup is now open on the right
|
||||
// open a second popup on the left in the second container
|
||||
slint_testing::send_mouse_click(&instance, 210., 210.);
|
||||
assert_eq(instance.get_popup_click_count(), 2);
|
||||
|
||||
// open on the left from the second container
|
||||
slint_testing::send_mouse_click(&instance, 380., 10.);
|
||||
|
||||
// second popup is still open on the left
|
||||
// close the second popup
|
||||
slint_testing::send_mouse_click(&instance, 10., 210.);
|
||||
assert_eq(instance.get_popup_click_count(), 3);
|
||||
|
||||
// first popup is still open as multiple of the same "PopupWindow" across different component instances is fine
|
||||
slint_testing::send_mouse_click(&instance, 210., 210.);
|
||||
assert_eq(instance.get_popup_click_count(), 4);
|
||||
```
|
||||
|
||||
*/
|
||||
|
|
@ -1358,8 +1358,8 @@ fn set_preview_factory(
|
|||
callback: Box<dyn Fn(ComponentInstance)>,
|
||||
behavior: LoadBehavior,
|
||||
) {
|
||||
// Ensure that the popup is closed as it is related to the old factory
|
||||
i_slint_core::window::WindowInner::from_pub(ui.window()).close_popup();
|
||||
// Ensure that any popups are closed as they are related to the old factory
|
||||
i_slint_core::window::WindowInner::from_pub(ui.window()).close_all_popups();
|
||||
|
||||
let factory = slint::ComponentFactory::new(move |ctx: FactoryContext| {
|
||||
let instance = compiled.create_embedded(ctx).unwrap();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue