mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-01 04:18:14 +00:00
Add support for manually closing PopupWindows
This patch adds a `close()` function that can be called to close a popup window, and a `close-to-click` boolean that can be set to false to disable the default behavior.
This commit is contained in:
parent
a0a5294e49
commit
0f54b9599b
20 changed files with 349 additions and 24 deletions
|
|
@ -20,6 +20,7 @@ All notable changes to this project are documented in this file.
|
||||||
- Added boolean `font-italic` property to `Text` and `TextInput`.
|
- Added boolean `font-italic` property to `Text` and `TextInput`.
|
||||||
- Added `select-all()`, `cut()`, `copy()`, and `paste() to `TextInput`, `LineEdit`, and `TextEdit`.
|
- Added `select-all()`, `cut()`, `copy()`, and `paste() to `TextInput`, `LineEdit`, and `TextEdit`.
|
||||||
- Added functions on color: `transparentize`, `mix`, and `with-alpha`.
|
- Added functions on color: `transparentize`, `mix`, and `with-alpha`.
|
||||||
|
- Added a `close()` function and a `close-on-click` boolean property to `PopupWindow`.
|
||||||
|
|
||||||
### Rust
|
### Rust
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,7 @@ fn gen_corelib(
|
||||||
"slint_windowrc_set_focus_item",
|
"slint_windowrc_set_focus_item",
|
||||||
"slint_windowrc_set_component",
|
"slint_windowrc_set_component",
|
||||||
"slint_windowrc_show_popup",
|
"slint_windowrc_show_popup",
|
||||||
|
"slint_windowrc_close_popup",
|
||||||
"slint_windowrc_set_rendering_notifier",
|
"slint_windowrc_set_rendering_notifier",
|
||||||
"slint_windowrc_request_redraw",
|
"slint_windowrc_request_redraw",
|
||||||
"slint_windowrc_on_close_requested",
|
"slint_windowrc_on_close_requested",
|
||||||
|
|
|
||||||
|
|
@ -161,13 +161,16 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Component, typename Parent>
|
template<typename Component, typename Parent>
|
||||||
void show_popup(const Parent *parent_component, cbindgen_private::Point p,
|
void show_popup(const Parent *parent_component, cbindgen_private::Point p, bool close_on_click,
|
||||||
cbindgen_private::ItemRc parent_item) const
|
cbindgen_private::ItemRc parent_item) const
|
||||||
{
|
{
|
||||||
auto popup = Component::create(parent_component).into_dyn();
|
auto popup = Component::create(parent_component).into_dyn();
|
||||||
cbindgen_private::slint_windowrc_show_popup(&inner, &popup, p, &parent_item);
|
cbindgen_private::slint_windowrc_show_popup(&inner, &popup, p, close_on_click,
|
||||||
|
&parent_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void close_popup() const { cbindgen_private::slint_windowrc_close_popup(&inner); }
|
||||||
|
|
||||||
template<std::invocable<RenderingState, GraphicsAPI> F>
|
template<std::invocable<RenderingState, GraphicsAPI> F>
|
||||||
std::optional<SetRenderingNotifierError> set_rendering_notifier(F callback) const
|
std::optional<SetRenderingNotifierError> set_rendering_notifier(F callback) const
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -495,9 +495,15 @@ Use this element to show a popup window like a tooltip or a popup menu.
|
||||||
|
|
||||||
Note: It isn't allowed to access properties of elements within the popup from outside of the `PopupWindow`.
|
Note: It isn't allowed to access properties of elements within the popup from outside of the `PopupWindow`.
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
- **`close-on-click`** (_in_ _bool_): By default, a PopupWindow closes when the user clicks. Set this
|
||||||
|
to false to prevent that behavior and close it manually using the `close()` function. (default value: true)
|
||||||
|
|
||||||
### Functions
|
### Functions
|
||||||
|
|
||||||
- **`show()`** Show the popup on the screen.
|
- **`show()`** Show the popup on the screen.
|
||||||
|
- **`close()`** Closes the popup. Use this if you set the `close-on-click` property to false.
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,17 @@ cpp! {{
|
||||||
}
|
}
|
||||||
isMouseButtonDown = false;
|
isMouseButtonDown = false;
|
||||||
|
|
||||||
|
void *parent_of_popup_to_close = nullptr;
|
||||||
|
if (auto p = dynamic_cast<const SlintWidget*>(parent())) {
|
||||||
|
void *parent_window = p->rust_window;
|
||||||
|
bool close_popup = rust!(Slint_mouseReleaseEventPopup [parent_window: &QtWindow as "void*"] -> bool as "bool" {
|
||||||
|
parent_window.close_popup_after_click()
|
||||||
|
});
|
||||||
|
if (close_popup) {
|
||||||
|
parent_of_popup_to_close = parent_window;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QPoint pos = event->pos();
|
QPoint pos = event->pos();
|
||||||
int button = event->button();
|
int button = event->button();
|
||||||
rust!(Slint_mouseReleaseEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint", button: u32 as "int" ] {
|
rust!(Slint_mouseReleaseEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint", button: u32 as "int" ] {
|
||||||
|
|
@ -145,11 +156,9 @@ cpp! {{
|
||||||
let button = from_qt_button(button);
|
let button = from_qt_button(button);
|
||||||
rust_window.mouse_event(MouseEvent::Released{ position, button, click_count: 0 })
|
rust_window.mouse_event(MouseEvent::Released{ position, button, click_count: 0 })
|
||||||
});
|
});
|
||||||
if (auto p = dynamic_cast<const SlintWidget*>(parent())) {
|
if (parent_of_popup_to_close) {
|
||||||
// FIXME: better way to close the popup
|
rust!(Slint_mouseReleaseEventClosePopup [parent_of_popup_to_close: &QtWindow as "void*"] {
|
||||||
void *parent_window = p->rust_window;
|
parent_of_popup_to_close.close_popup();
|
||||||
rust!(Slint_mouseReleaseEventPopup [parent_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint"] {
|
|
||||||
parent_window.close_popup();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1497,6 +1506,10 @@ impl QtWindow {
|
||||||
fn close_popup(&self) {
|
fn close_popup(&self) {
|
||||||
WindowInner::from_pub(&self.window).close_popup();
|
WindowInner::from_pub(&self.window).close_popup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_popup_after_click(&self) -> bool {
|
||||||
|
WindowInner::from_pub(&self.window).close_popup_after_click()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowAdapter for QtWindow {
|
impl WindowAdapter for QtWindow {
|
||||||
|
|
|
||||||
|
|
@ -387,6 +387,7 @@ export component PopupWindow {
|
||||||
in property <length> anchor_y;
|
in property <length> anchor_y;
|
||||||
in property <length> anchor_height;
|
in property <length> anchor_height;
|
||||||
in property <length> anchor_width;*/
|
in property <length> anchor_width;*/
|
||||||
|
in property <bool> close-on-click: true;
|
||||||
//show() is hardcoded in typeregister.rs
|
//show() is hardcoded in typeregister.rs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ pub enum BuiltinFunction {
|
||||||
Pow,
|
Pow,
|
||||||
SetFocusItem,
|
SetFocusItem,
|
||||||
ShowPopupWindow,
|
ShowPopupWindow,
|
||||||
|
ClosePopupWindow,
|
||||||
/// A function that belongs to an item (such as TextInput's select-all function).
|
/// A function that belongs to an item (such as TextInput's select-all function).
|
||||||
ItemMemberFunction(String),
|
ItemMemberFunction(String),
|
||||||
/// the "42".to_float()
|
/// the "42".to_float()
|
||||||
|
|
@ -123,10 +124,12 @@ impl BuiltinFunction {
|
||||||
return_type: Box::new(Type::Void),
|
return_type: Box::new(Type::Void),
|
||||||
args: vec![Type::ElementReference],
|
args: vec![Type::ElementReference],
|
||||||
},
|
},
|
||||||
BuiltinFunction::ShowPopupWindow => Type::Function {
|
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => {
|
||||||
return_type: Box::new(Type::Void),
|
Type::Function {
|
||||||
args: vec![Type::ElementReference],
|
return_type: Box::new(Type::Void),
|
||||||
},
|
args: vec![Type::ElementReference],
|
||||||
|
}
|
||||||
|
}
|
||||||
BuiltinFunction::ItemMemberFunction(..) => Type::Function {
|
BuiltinFunction::ItemMemberFunction(..) => Type::Function {
|
||||||
return_type: Box::new(Type::Void),
|
return_type: Box::new(Type::Void),
|
||||||
args: vec![Type::ElementReference],
|
args: vec![Type::ElementReference],
|
||||||
|
|
@ -225,7 +228,7 @@ impl BuiltinFunction {
|
||||||
| BuiltinFunction::Pow
|
| BuiltinFunction::Pow
|
||||||
| BuiltinFunction::ATan => true,
|
| BuiltinFunction::ATan => true,
|
||||||
BuiltinFunction::SetFocusItem => false,
|
BuiltinFunction::SetFocusItem => false,
|
||||||
BuiltinFunction::ShowPopupWindow => false,
|
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
|
||||||
BuiltinFunction::ItemMemberFunction(..) => false,
|
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||||
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
||||||
BuiltinFunction::ColorBrighter
|
BuiltinFunction::ColorBrighter
|
||||||
|
|
@ -276,7 +279,7 @@ impl BuiltinFunction {
|
||||||
| BuiltinFunction::Pow
|
| BuiltinFunction::Pow
|
||||||
| BuiltinFunction::ATan => true,
|
| BuiltinFunction::ATan => true,
|
||||||
BuiltinFunction::SetFocusItem => false,
|
BuiltinFunction::SetFocusItem => false,
|
||||||
BuiltinFunction::ShowPopupWindow => false,
|
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
|
||||||
BuiltinFunction::ItemMemberFunction(..) => false,
|
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||||
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
||||||
BuiltinFunction::ColorBrighter
|
BuiltinFunction::ColorBrighter
|
||||||
|
|
|
||||||
|
|
@ -2744,7 +2744,7 @@ fn compile_builtin_function_call(
|
||||||
format!("{}.text_input_focused()", access_window_field(ctx))
|
format!("{}.text_input_focused()", access_window_field(ctx))
|
||||||
}
|
}
|
||||||
BuiltinFunction::ShowPopupWindow => {
|
BuiltinFunction::ShowPopupWindow => {
|
||||||
if let [llr::Expression::NumberLiteral(popup_index), x, y, llr::Expression::PropertyReference(parent_ref)] =
|
if let [llr::Expression::NumberLiteral(popup_index), x, y, close_on_click, llr::Expression::PropertyReference(parent_ref)] =
|
||||||
arguments
|
arguments
|
||||||
{
|
{
|
||||||
let mut parent_ctx = ctx;
|
let mut parent_ctx = ctx;
|
||||||
|
|
@ -2764,13 +2764,18 @@ fn compile_builtin_function_call(
|
||||||
let parent_component = access_item_rc(parent_ref, ctx);
|
let parent_component = access_item_rc(parent_ref, ctx);
|
||||||
let x = compile_expression(x, ctx);
|
let x = compile_expression(x, ctx);
|
||||||
let y = compile_expression(y, ctx);
|
let y = compile_expression(y, ctx);
|
||||||
|
let close_on_click = compile_expression(close_on_click, ctx);
|
||||||
format!(
|
format!(
|
||||||
"{window}.show_popup<{popup_window_id}>({component_access}, {{ static_cast<float>({x}), static_cast<float>({y}) }}, {{ {parent_component} }})"
|
"{window}.show_popup<{popup_window_id}>({component_access}, {{ static_cast<float>({x}), static_cast<float>({y}) }}, {close_on_click}, {{ {parent_component} }})"
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
|
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::ClosePopupWindow => {
|
||||||
|
let window = access_window_field(ctx);
|
||||||
|
format!("{window}.close_popup()")
|
||||||
|
}
|
||||||
BuiltinFunction::ItemMemberFunction(name) => {
|
BuiltinFunction::ItemMemberFunction(name) => {
|
||||||
if let [llr::Expression::PropertyReference(pr)] = arguments {
|
if let [llr::Expression::PropertyReference(pr)] = arguments {
|
||||||
let item = access_member(pr, ctx);
|
let item = access_member(pr, ctx);
|
||||||
|
|
|
||||||
|
|
@ -2233,7 +2233,7 @@ fn compile_builtin_function_call(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BuiltinFunction::ShowPopupWindow => {
|
BuiltinFunction::ShowPopupWindow => {
|
||||||
if let [Expression::NumberLiteral(popup_index), x, y, Expression::PropertyReference(parent_ref)] =
|
if let [Expression::NumberLiteral(popup_index), x, y, close_on_click, Expression::PropertyReference(parent_ref)] =
|
||||||
arguments
|
arguments
|
||||||
{
|
{
|
||||||
let mut parent_ctx = ctx;
|
let mut parent_ctx = ctx;
|
||||||
|
|
@ -2252,6 +2252,7 @@ fn compile_builtin_function_call(
|
||||||
let parent_component = access_item_rc(parent_ref, ctx);
|
let parent_component = access_item_rc(parent_ref, ctx);
|
||||||
let x = compile_expression(x, ctx);
|
let x = compile_expression(x, ctx);
|
||||||
let y = compile_expression(y, ctx);
|
let y = compile_expression(y, ctx);
|
||||||
|
let close_on_click = compile_expression(close_on_click, ctx);
|
||||||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||||
quote!(
|
quote!(
|
||||||
slint::private_unstable_api::re_exports::WindowInner::from_pub(#window_adapter_tokens.window()).show_popup(
|
slint::private_unstable_api::re_exports::WindowInner::from_pub(#window_adapter_tokens.window()).show_popup(
|
||||||
|
|
@ -2261,6 +2262,7 @@ fn compile_builtin_function_call(
|
||||||
instance.into()
|
instance.into()
|
||||||
}),
|
}),
|
||||||
Point::new(#x as slint::private_unstable_api::re_exports::Coord, #y as slint::private_unstable_api::re_exports::Coord),
|
Point::new(#x as slint::private_unstable_api::re_exports::Coord, #y as slint::private_unstable_api::re_exports::Coord),
|
||||||
|
#close_on_click,
|
||||||
#parent_component
|
#parent_component
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -2268,6 +2270,12 @@ fn compile_builtin_function_call(
|
||||||
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
|
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::ClosePopupWindow => {
|
||||||
|
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||||
|
quote!(
|
||||||
|
slint::private_unstable_api::re_exports::WindowInner::from_pub(#window_adapter_tokens.window()).close_popup()
|
||||||
|
)
|
||||||
|
}
|
||||||
BuiltinFunction::ItemMemberFunction(name) => {
|
BuiltinFunction::ItemMemberFunction(name) => {
|
||||||
if let [Expression::PropertyReference(pr)] = arguments {
|
if let [Expression::PropertyReference(pr)] = arguments {
|
||||||
let item = access_member(pr, ctx);
|
let item = access_member(pr, ctx);
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,13 @@ fn lower_show_popup(args: &[tree_Expression], ctx: &ExpressionContext) -> llr_Ex
|
||||||
);
|
);
|
||||||
llr_Expression::BuiltinFunctionCall {
|
llr_Expression::BuiltinFunctionCall {
|
||||||
function: BuiltinFunction::ShowPopupWindow,
|
function: BuiltinFunction::ShowPopupWindow,
|
||||||
arguments: vec![llr_Expression::NumberLiteral(popup_index as _), x, y, item_ref],
|
arguments: vec![
|
||||||
|
llr_Expression::NumberLiteral(popup_index as _),
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
llr_Expression::BoolLiteral(popup.close_on_click),
|
||||||
|
item_ref,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("invalid arguments to ShowPopupWindow");
|
panic!("invalid arguments to ShowPopupWindow");
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
|
||||||
BuiltinFunction::Log => 10,
|
BuiltinFunction::Log => 10,
|
||||||
BuiltinFunction::Pow => 10,
|
BuiltinFunction::Pow => 10,
|
||||||
BuiltinFunction::SetFocusItem => isize::MAX,
|
BuiltinFunction::SetFocusItem => isize::MAX,
|
||||||
BuiltinFunction::ShowPopupWindow => isize::MAX,
|
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => isize::MAX,
|
||||||
BuiltinFunction::ItemMemberFunction(..) => isize::MAX,
|
BuiltinFunction::ItemMemberFunction(..) => isize::MAX,
|
||||||
BuiltinFunction::StringToFloat => 50,
|
BuiltinFunction::StringToFloat => 50,
|
||||||
BuiltinFunction::StringIsFloat => 50,
|
BuiltinFunction::StringIsFloat => 50,
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,7 @@ pub struct PopupWindow {
|
||||||
pub component: Rc<Component>,
|
pub component: Rc<Component>,
|
||||||
pub x: NamedReference,
|
pub x: NamedReference,
|
||||||
pub y: NamedReference,
|
pub y: NamedReference,
|
||||||
|
pub close_on_click: bool,
|
||||||
pub parent_element: ElementRc,
|
pub parent_element: ElementRc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,7 @@ fn duplicate_popup(
|
||||||
PopupWindow {
|
PopupWindow {
|
||||||
x: p.x.clone(),
|
x: p.x.clone(),
|
||||||
y: p.y.clone(),
|
y: p.y.clone(),
|
||||||
|
close_on_click: p.close_on_click.clone(),
|
||||||
component: duplicate_sub_component(&p.component, &parent, mapping, priority_delta),
|
component: duplicate_sub_component(&p.component, &parent, mapping, priority_delta),
|
||||||
parent_element: mapping
|
parent_element: mapping
|
||||||
.get(&element_key(p.parent_element.clone()))
|
.get(&element_key(p.parent_element.clone()))
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,28 @@ fn lower_popup_window(
|
||||||
parent_element.borrow_mut().has_popup_child = true;
|
parent_element.borrow_mut().has_popup_child = true;
|
||||||
|
|
||||||
popup_window_element.borrow_mut().base_type = window_type.clone();
|
popup_window_element.borrow_mut().base_type = window_type.clone();
|
||||||
|
popup_window_element.borrow_mut().property_declarations.insert(
|
||||||
|
"close-on-click".into(),
|
||||||
|
PropertyDeclaration { property_type: Type::Bool, ..PropertyDeclaration::default() },
|
||||||
|
);
|
||||||
|
|
||||||
|
let close_on_click =
|
||||||
|
match popup_window_element.borrow_mut().bindings.remove("close-on-click").map_or_else(
|
||||||
|
|| Ok(true),
|
||||||
|
|binding| match binding.borrow().expression {
|
||||||
|
Expression::BoolLiteral(value) => Ok(value),
|
||||||
|
_ => Err(binding.borrow().span.clone()),
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(coc) => coc,
|
||||||
|
Err(location) => {
|
||||||
|
diag.push_error(
|
||||||
|
"The close-on-click property only supports constants at the moment".into(),
|
||||||
|
&location,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let popup_comp = Rc::new(Component {
|
let popup_comp = Rc::new(Component {
|
||||||
root_element: popup_window_element.clone(),
|
root_element: popup_window_element.clone(),
|
||||||
|
|
@ -105,6 +127,7 @@ fn lower_popup_window(
|
||||||
component: popup_comp,
|
component: popup_comp,
|
||||||
x: coord_x,
|
x: coord_x,
|
||||||
y: coord_y,
|
y: coord_y,
|
||||||
|
close_on_click,
|
||||||
parent_element: parent_element.clone(),
|
parent_element: parent_element.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
internal/compiler/tests/syntax/elements/popup.slint
Normal file
14
internal/compiler/tests/syntax/elements/popup.slint
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
|
|
||||||
|
export Bar := Rectangle {
|
||||||
|
in property <bool> external;
|
||||||
|
PopupWindow {
|
||||||
|
close-on-click: true;
|
||||||
|
}
|
||||||
|
PopupWindow {
|
||||||
|
close-on-click: root.external;
|
||||||
|
// ^error{The close-on-click property only supports constants at the moment}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -259,7 +259,16 @@ impl TypeRegister {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.member_functions
|
.member_functions
|
||||||
.insert("show".into(), BuiltinFunction::ShowPopupWindow);
|
.insert("show".into(), BuiltinFunction::ShowPopupWindow);
|
||||||
|
Rc::get_mut(b).unwrap().properties.insert(
|
||||||
|
"close".into(),
|
||||||
|
BuiltinPropertyInfo::new(BuiltinFunction::ClosePopupWindow.ty()),
|
||||||
|
);
|
||||||
|
Rc::get_mut(b)
|
||||||
|
.unwrap()
|
||||||
|
.member_functions
|
||||||
|
.insert("close".into(), BuiltinFunction::ClosePopupWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,9 @@ struct PopupWindow {
|
||||||
location: PopupWindowLocation,
|
location: PopupWindowLocation,
|
||||||
/// The component that is responsible for providing the popup content.
|
/// The component that is responsible for providing the popup content.
|
||||||
component: ComponentRc,
|
component: ComponentRc,
|
||||||
|
/// If true, Slint will close the popup after any mouse click within the popup.
|
||||||
|
/// Set to false and call close() on the PopupWindow to close it manually.
|
||||||
|
close_on_click: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
|
|
@ -397,6 +400,8 @@ impl WindowInner {
|
||||||
// handle multiple press release
|
// handle multiple press release
|
||||||
event = self.click_state.check_repeat(event);
|
event = self.click_state.check_repeat(event);
|
||||||
|
|
||||||
|
let close_popup_after_click = self.close_popup_after_click();
|
||||||
|
|
||||||
let embedded_popup_component =
|
let embedded_popup_component =
|
||||||
self.active_popup.borrow().as_ref().and_then(|popup| match popup.location {
|
self.active_popup.borrow().as_ref().and_then(|popup| match popup.location {
|
||||||
PopupWindowLocation::TopLevel(_) => None,
|
PopupWindowLocation::TopLevel(_) => None,
|
||||||
|
|
@ -439,9 +444,7 @@ impl WindowInner {
|
||||||
self.mouse_input_state.take(),
|
self.mouse_input_state.take(),
|
||||||
));
|
));
|
||||||
|
|
||||||
if embedded_popup_component.is_some() {
|
if embedded_popup_component.is_some() && close_popup_after_click {
|
||||||
//FIXME: currently the ComboBox is the only thing that uses the popup, and it should close automatically
|
|
||||||
// on release. But ideally, there would be API to close the popup rather than always closing it on release
|
|
||||||
if matches!(event, MouseEvent::Released { .. }) {
|
if matches!(event, MouseEvent::Released { .. }) {
|
||||||
self.close_popup();
|
self.close_popup();
|
||||||
}
|
}
|
||||||
|
|
@ -721,6 +724,7 @@ impl WindowInner {
|
||||||
&self,
|
&self,
|
||||||
popup_componentrc: &ComponentRc,
|
popup_componentrc: &ComponentRc,
|
||||||
position: Point,
|
position: Point,
|
||||||
|
close_on_click: bool,
|
||||||
parent_item: &ItemRc,
|
parent_item: &ItemRc,
|
||||||
) {
|
) {
|
||||||
let position = parent_item.map_to_window(
|
let position = parent_item.map_to_window(
|
||||||
|
|
@ -774,11 +778,15 @@ impl WindowInner {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.active_popup
|
self.active_popup.replace(Some(PopupWindow {
|
||||||
.replace(Some(PopupWindow { location, component: popup_componentrc.clone() }));
|
location,
|
||||||
|
component: popup_componentrc.clone(),
|
||||||
|
close_on_click,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes any active popup.
|
/// 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) {
|
pub fn close_popup(&self) {
|
||||||
if let Some(current_popup) = self.active_popup.replace(None) {
|
if let Some(current_popup) = self.active_popup.replace(None) {
|
||||||
if let PopupWindowLocation::ChildWindow(offset) = current_popup.location {
|
if let PopupWindowLocation::ChildWindow(offset) = current_popup.location {
|
||||||
|
|
@ -798,6 +806,11 @@ impl WindowInner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the currently active popup is configured to close on click. None if there is no active popup.
|
||||||
|
pub fn close_popup_after_click(&self) -> bool {
|
||||||
|
self.active_popup.borrow().as_ref().map_or(false, |popup| popup.close_on_click)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the scale factor set on the window, as provided by the windowing system.
|
/// Returns the scale factor set on the window, as provided by the windowing system.
|
||||||
pub fn scale_factor(&self) -> f32 {
|
pub fn scale_factor(&self) -> f32 {
|
||||||
self.pinned_fields.as_ref().project_ref().scale_factor.get()
|
self.pinned_fields.as_ref().project_ref().scale_factor.get()
|
||||||
|
|
@ -1015,12 +1028,19 @@ pub mod ffi {
|
||||||
handle: *const WindowAdapterRcOpaque,
|
handle: *const WindowAdapterRcOpaque,
|
||||||
popup: &ComponentRc,
|
popup: &ComponentRc,
|
||||||
position: crate::graphics::Point,
|
position: crate::graphics::Point,
|
||||||
|
close_on_click: bool,
|
||||||
parent_item: &ItemRc,
|
parent_item: &ItemRc,
|
||||||
) {
|
) {
|
||||||
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
|
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
|
||||||
WindowInner::from_pub(window_adapter.window()).show_popup(popup, position, parent_item);
|
WindowInner::from_pub(window_adapter.window()).show_popup(
|
||||||
|
popup,
|
||||||
|
position,
|
||||||
|
close_on_click,
|
||||||
|
parent_item,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
/// Close the current popup
|
/// Close the current popup
|
||||||
|
#[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) {
|
||||||
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
|
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();
|
||||||
|
|
|
||||||
|
|
@ -1745,6 +1745,7 @@ impl<'a, 'id> InstanceRef<'a, 'id> {
|
||||||
pub fn show_popup(
|
pub fn show_popup(
|
||||||
popup: &object_tree::PopupWindow,
|
popup: &object_tree::PopupWindow,
|
||||||
pos: i_slint_core::graphics::Point,
|
pos: i_slint_core::graphics::Point,
|
||||||
|
close_on_click: bool,
|
||||||
parent_comp: ComponentRefPin,
|
parent_comp: ComponentRefPin,
|
||||||
parent_window_adapter: &Rc<dyn WindowAdapter>,
|
parent_window_adapter: &Rc<dyn WindowAdapter>,
|
||||||
parent_item: &ItemRc,
|
parent_item: &ItemRc,
|
||||||
|
|
@ -1757,6 +1758,7 @@ pub fn show_popup(
|
||||||
WindowInner::from_pub(parent_window_adapter.window()).show_popup(
|
WindowInner::from_pub(parent_window_adapter.window()).show_popup(
|
||||||
&vtable::VRc::into_dyn(inst),
|
&vtable::VRc::into_dyn(inst),
|
||||||
pos,
|
pos,
|
||||||
|
close_on_click,
|
||||||
parent_item,
|
parent_item,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -572,6 +572,7 @@ fn call_builtin_function(
|
||||||
x.try_into().unwrap(),
|
x.try_into().unwrap(),
|
||||||
y.try_into().unwrap(),
|
y.try_into().unwrap(),
|
||||||
),
|
),
|
||||||
|
popup.close_on_click,
|
||||||
component.borrow(),
|
component.borrow(),
|
||||||
window_adapter_ref(component).unwrap(),
|
window_adapter_ref(component).unwrap(),
|
||||||
&parent_item,
|
&parent_item,
|
||||||
|
|
@ -581,6 +582,18 @@ fn call_builtin_function(
|
||||||
panic!("internal error: argument to SetFocusItem must be an element")
|
panic!("internal error: argument to SetFocusItem must be an element")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::ClosePopupWindow => {
|
||||||
|
let component = match local_context.component_instance {
|
||||||
|
ComponentInstance::InstanceRef(c) => c,
|
||||||
|
ComponentInstance::GlobalComponent(_) => {
|
||||||
|
panic!("Cannot show popup from a global component")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window_ref(component).unwrap().close_popup();
|
||||||
|
|
||||||
|
Value::Void
|
||||||
|
}
|
||||||
BuiltinFunction::ItemMemberFunction(name) => {
|
BuiltinFunction::ItemMemberFunction(name) => {
|
||||||
if arguments.len() != 1 {
|
if arguments.len() != 1 {
|
||||||
panic!("internal error: incorrect argument count to item member function call")
|
panic!("internal error: incorrect argument count to item member function call")
|
||||||
|
|
|
||||||
195
tests/cases/elements/popupwindow_close.slint
Normal file
195
tests/cases/elements/popupwindow_close.slint
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
|
export component TestCase {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
|
||||||
|
in-out property <bool> popup-created;
|
||||||
|
in-out property <int> click-count;
|
||||||
|
in-out property <int> popup-selector: 0;
|
||||||
|
|
||||||
|
default-popup := PopupWindow {
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
Text {
|
||||||
|
text: "I'm a default";
|
||||||
|
}
|
||||||
|
init => {
|
||||||
|
root.popup-created = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self-closing-popup := PopupWindow {
|
||||||
|
close-on-click: false;
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
Text {
|
||||||
|
text: "I'm a self-closing popup";
|
||||||
|
}
|
||||||
|
TouchArea {
|
||||||
|
clicked => {
|
||||||
|
self-closing-popup.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init => {
|
||||||
|
root.popup-created = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
never-closing-popup := PopupWindow {
|
||||||
|
close-on-click: false;
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
Text {
|
||||||
|
text: "I'm a popup that never closes";
|
||||||
|
}
|
||||||
|
TouchArea {
|
||||||
|
clicked => {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init => {
|
||||||
|
root.popup-created = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchArea {
|
||||||
|
clicked => {
|
||||||
|
root.click-count = root.click-count + 1;
|
||||||
|
if (root.popup-selector == 0) {
|
||||||
|
root.popup-selector = 3;
|
||||||
|
default-popup.show();
|
||||||
|
} else if (root.popup-selector == 1) {
|
||||||
|
root.popup-selector = 3;
|
||||||
|
self-closing-popup.show();
|
||||||
|
} else if (root.popup-selector == 2) {
|
||||||
|
root.popup-selector = 3;
|
||||||
|
never-closing-popup.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let instance = TestCase::new().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(instance.get_click_count(), 0);
|
||||||
|
assert_eq!(instance.get_popup_created(), false);
|
||||||
|
|
||||||
|
instance.set_popup_selector(0);
|
||||||
|
instance.set_popup_created(false);
|
||||||
|
instance.set_click_count(0);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq!(instance.get_click_count(), 1);
|
||||||
|
assert_eq!(instance.get_popup_created(), true);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq!(instance.get_click_count(), 1);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq!(instance.get_click_count(), 2);
|
||||||
|
|
||||||
|
instance.set_popup_selector(1);
|
||||||
|
instance.set_popup_created(false);
|
||||||
|
instance.set_click_count(0);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq!(instance.get_click_count(), 1);
|
||||||
|
assert_eq!(instance.get_popup_created(), true);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq!(instance.get_click_count(), 1);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq!(instance.get_click_count(), 2);
|
||||||
|
|
||||||
|
instance.set_popup_selector(2);
|
||||||
|
instance.set_popup_created(false);
|
||||||
|
instance.set_click_count(0);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq!(instance.get_click_count(), 1);
|
||||||
|
assert_eq!(instance.get_popup_created(), true);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq!(instance.get_click_count(), 1);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq!(instance.get_click_count(), 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto handle = TestCase::create();
|
||||||
|
const TestCase &instance = *handle;
|
||||||
|
|
||||||
|
assert_eq(instance.get_click_count(), 0);
|
||||||
|
assert_eq(instance.get_popup_created(), false);
|
||||||
|
|
||||||
|
instance.set_popup_selector(0);
|
||||||
|
instance.set_popup_created(false);
|
||||||
|
instance.set_click_count(0);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq(instance.get_click_count(), 1);
|
||||||
|
assert_eq(instance.get_popup_created(), true);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq(instance.get_click_count(), 1);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq(instance.get_click_count(), 2);
|
||||||
|
|
||||||
|
instance.set_popup_selector(1);
|
||||||
|
instance.set_popup_created(false);
|
||||||
|
instance.set_click_count(0);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq(instance.get_click_count(), 1);
|
||||||
|
assert_eq(instance.get_popup_created(), true);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq(instance.get_click_count(), 1);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq(instance.get_click_count(), 2);
|
||||||
|
|
||||||
|
instance.set_popup_selector(2);
|
||||||
|
instance.set_popup_created(false);
|
||||||
|
instance.set_click_count(0);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq(instance.get_click_count(), 1);
|
||||||
|
assert_eq(instance.get_popup_created(), true);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq(instance.get_click_count(), 1);
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq(instance.get_click_count(), 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
var instance = new slint.TestCase({});
|
||||||
|
|
||||||
|
assert.equal(instance.click_count, 0);
|
||||||
|
assert.equal(instance.popup_created, false);
|
||||||
|
|
||||||
|
instance.popup_selector = 0;
|
||||||
|
instance.popup_created = false;
|
||||||
|
instance.click_count = 0;
|
||||||
|
instance.send_mouse_click(5., 5.);
|
||||||
|
assert.equal(instance.click_count, 1);
|
||||||
|
assert.equal(instance.popup_created, true);
|
||||||
|
instance.send_mouse_click(5., 5.);
|
||||||
|
assert.equal(instance.click_count, 1);
|
||||||
|
instance.send_mouse_click(5., 5.);
|
||||||
|
assert.equal(instance.click_count, 2);
|
||||||
|
|
||||||
|
instance.popup_selector = 1;
|
||||||
|
instance.popup_created = false;
|
||||||
|
instance.click_count = 0;
|
||||||
|
instance.send_mouse_click(5., 5.);
|
||||||
|
assert.equal(instance.click_count, 1);
|
||||||
|
assert.equal(instance.popup_created, true);
|
||||||
|
instance.send_mouse_click(5., 5.);
|
||||||
|
assert.equal(instance.click_count, 1);
|
||||||
|
instance.send_mouse_click(5., 5.);
|
||||||
|
assert.equal(instance.click_count, 2);
|
||||||
|
|
||||||
|
instance.popup_selector = 2;
|
||||||
|
instance.popup_created = false;
|
||||||
|
instance.click_count = 0;
|
||||||
|
instance.send_mouse_click(5., 5.);
|
||||||
|
assert.equal(instance.click_count, 1);
|
||||||
|
assert.equal(instance.popup_created, true);
|
||||||
|
instance.send_mouse_click(5., 5.);
|
||||||
|
assert.equal(instance.click_count, 1);
|
||||||
|
instance.send_mouse_click(5., 5.);
|
||||||
|
assert.equal(instance.click_count, 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
*/
|
||||||
Loading…
Add table
Add a link
Reference in a new issue