Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for manually closing PopupWindows #2810

Merged
merged 3 commits into from Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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 `select-all()`, `cut()`, `copy()`, and `paste() to `TextInput`, `LineEdit`, and `TextEdit`.
- Added functions on color: `transparentize`, `mix`, and `with-alpha`.
- Added a `close()` function and a `close-on-click` boolean property to `PopupWindow`.

### Rust

Expand Down
1 change: 1 addition & 0 deletions api/cpp/cbindgen.rs
Expand Up @@ -331,6 +331,7 @@ fn gen_corelib(
"slint_windowrc_set_focus_item",
"slint_windowrc_set_component",
"slint_windowrc_show_popup",
"slint_windowrc_close_popup",
"slint_windowrc_set_rendering_notifier",
"slint_windowrc_request_redraw",
"slint_windowrc_on_close_requested",
Expand Down
7 changes: 5 additions & 2 deletions api/cpp/include/slint.h
Expand Up @@ -161,13 +161,16 @@ class WindowAdapterRc
}

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
{
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>
std::optional<SetRenderingNotifierError> set_rendering_notifier(F callback) const
{
Expand Down
6 changes: 6 additions & 0 deletions docs/language/src/builtins/elements.md
Expand Up @@ -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`.

### 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

- **`show()`** Show the popup on the screen.
- **`close()`** Closes the popup. Use this if you set the `close-on-click` property to false.

### Example

Expand Down
23 changes: 18 additions & 5 deletions internal/backends/qt/qt_window.rs
Expand Up @@ -138,18 +138,27 @@ cpp! {{
}
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();
int button = event->button();
rust!(Slint_mouseReleaseEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint", button: u32 as "int" ] {
let position = LogicalPoint::new(pos.x as _, pos.y as _);
let button = from_qt_button(button);
rust_window.mouse_event(MouseEvent::Released{ position, button, click_count: 0 })
});
if (auto p = dynamic_cast<const SlintWidget*>(parent())) {
// FIXME: better way to close the popup
void *parent_window = p->rust_window;
rust!(Slint_mouseReleaseEventPopup [parent_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint"] {
parent_window.close_popup();
if (parent_of_popup_to_close) {
rust!(Slint_mouseReleaseEventClosePopup [parent_of_popup_to_close: &QtWindow as "void*"] {
parent_of_popup_to_close.close_popup();
});
}
}
Expand Down Expand Up @@ -1497,6 +1506,10 @@ impl QtWindow {
fn close_popup(&self) {
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 {
Expand Down
1 change: 1 addition & 0 deletions internal/compiler/builtins.slint
Expand Up @@ -387,6 +387,7 @@ export component PopupWindow {
in property <length> anchor_y;
in property <length> anchor_height;
in property <length> anchor_width;*/
in property <bool> close-on-click: true;
//show() is hardcoded in typeregister.rs
}

Expand Down
15 changes: 9 additions & 6 deletions internal/compiler/expression_tree.rs
Expand Up @@ -39,6 +39,7 @@ pub enum BuiltinFunction {
Pow,
SetFocusItem,
ShowPopupWindow,
ClosePopupWindow,
/// A function that belongs to an item (such as TextInput's select-all function).
ItemMemberFunction(String),
/// the "42".to_float()
Expand Down Expand Up @@ -123,10 +124,12 @@ impl BuiltinFunction {
return_type: Box::new(Type::Void),
args: vec![Type::ElementReference],
},
BuiltinFunction::ShowPopupWindow => Type::Function {
return_type: Box::new(Type::Void),
args: vec![Type::ElementReference],
},
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => {
Type::Function {
return_type: Box::new(Type::Void),
args: vec![Type::ElementReference],
}
}
BuiltinFunction::ItemMemberFunction(..) => Type::Function {
return_type: Box::new(Type::Void),
args: vec![Type::ElementReference],
Expand Down Expand Up @@ -225,7 +228,7 @@ impl BuiltinFunction {
| BuiltinFunction::Pow
| BuiltinFunction::ATan => true,
BuiltinFunction::SetFocusItem => false,
BuiltinFunction::ShowPopupWindow => false,
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
BuiltinFunction::ItemMemberFunction(..) => false,
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
BuiltinFunction::ColorBrighter
Expand Down Expand Up @@ -276,7 +279,7 @@ impl BuiltinFunction {
| BuiltinFunction::Pow
| BuiltinFunction::ATan => true,
BuiltinFunction::SetFocusItem => false,
BuiltinFunction::ShowPopupWindow => false,
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
BuiltinFunction::ItemMemberFunction(..) => false,
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
BuiltinFunction::ColorBrighter
Expand Down
9 changes: 7 additions & 2 deletions internal/compiler/generator/cpp.rs
Expand Up @@ -2744,7 +2744,7 @@ fn compile_builtin_function_call(
format!("{}.text_input_focused()", access_window_field(ctx))
}
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
{
let mut parent_ctx = ctx;
Expand All @@ -2764,13 +2764,18 @@ fn compile_builtin_function_call(
let parent_component = access_item_rc(parent_ref, ctx);
let x = compile_expression(x, ctx);
let y = compile_expression(y, ctx);
let close_on_click = compile_expression(close_on_click, ctx);
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 {
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
}
}
BuiltinFunction::ClosePopupWindow => {
let window = access_window_field(ctx);
format!("{window}.close_popup()")
}
BuiltinFunction::ItemMemberFunction(name) => {
if let [llr::Expression::PropertyReference(pr)] = arguments {
let item = access_member(pr, ctx);
Expand Down
16 changes: 14 additions & 2 deletions internal/compiler/generator/rust.rs
Expand Up @@ -2233,7 +2233,7 @@ fn compile_builtin_function_call(
}
}
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
{
let mut parent_ctx = ctx;
Expand All @@ -2252,18 +2252,30 @@ fn compile_builtin_function_call(
let parent_component = access_item_rc(parent_ref, ctx);
let x = compile_expression(x, 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);
quote!(
slint::private_unstable_api::re_exports::WindowInner::from_pub(#window_adapter_tokens.window()).show_popup(
&VRc::into_dyn(#popup_window_id::new(#component_access_tokens.self_weak.get().unwrap().clone()).into()),
&VRc::into_dyn({
let instance = #popup_window_id::new(#component_access_tokens.self_weak.get().unwrap().clone());
#popup_window_id::user_init(slint::private_unstable_api::re_exports::VRc::map(instance.clone(), |x| x));
instance.into()
}),
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
)
)
} else {
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) => {
if let [Expression::PropertyReference(pr)] = arguments {
let item = access_member(pr, ctx);
Expand Down
8 changes: 7 additions & 1 deletion internal/compiler/llr/lower_expression.rs
Expand Up @@ -355,7 +355,13 @@ fn lower_show_popup(args: &[tree_Expression], ctx: &ExpressionContext) -> llr_Ex
);
llr_Expression::BuiltinFunctionCall {
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 {
panic!("invalid arguments to ShowPopupWindow");
Expand Down
2 changes: 1 addition & 1 deletion internal/compiler/llr/optim_passes/inline_expressions.rs
Expand Up @@ -87,7 +87,7 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
BuiltinFunction::Log => 10,
BuiltinFunction::Pow => 10,
BuiltinFunction::SetFocusItem => isize::MAX,
BuiltinFunction::ShowPopupWindow => isize::MAX,
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => isize::MAX,
BuiltinFunction::ItemMemberFunction(..) => isize::MAX,
BuiltinFunction::StringToFloat => 50,
BuiltinFunction::StringIsFloat => 50,
Expand Down
1 change: 1 addition & 0 deletions internal/compiler/object_tree.rs
Expand Up @@ -203,6 +203,7 @@ pub struct PopupWindow {
pub component: Rc<Component>,
pub x: NamedReference,
pub y: NamedReference,
pub close_on_click: bool,
pub parent_element: ElementRc,
}

Expand Down
3 changes: 3 additions & 0 deletions internal/compiler/passes/collect_init_code.rs
Expand Up @@ -26,4 +26,7 @@ pub fn collect_init_code(component: &Rc<Component>) {
.push(init_callback.into_inner().expression);
}
});
for popup in component.popup_windows.borrow().iter() {
collect_init_code(&popup.component);
}
}
1 change: 1 addition & 0 deletions internal/compiler/passes/inlining.rs
Expand Up @@ -344,6 +344,7 @@ fn duplicate_popup(
PopupWindow {
x: p.x.clone(),
y: p.y.clone(),
close_on_click: p.close_on_click.clone(),
component: duplicate_sub_component(&p.component, &parent, mapping, priority_delta),
parent_element: mapping
.get(&element_key(p.parent_element.clone()))
Expand Down
23 changes: 23 additions & 0 deletions internal/compiler/passes/lower_popups.rs
Expand Up @@ -68,6 +68,28 @@ fn lower_popup_window(
parent_element.borrow_mut().has_popup_child = true;

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 {
root_element: popup_window_element.clone(),
Expand Down Expand Up @@ -105,6 +127,7 @@ fn lower_popup_window(
component: popup_comp,
x: coord_x,
y: coord_y,
close_on_click,
parent_element: parent_element.clone(),
});
}
Expand Down
14 changes: 14 additions & 0 deletions internal/compiler/tests/syntax/elements/popup.slint
@@ -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}
}
}
9 changes: 9 additions & 0 deletions internal/compiler/typeregister.rs
Expand Up @@ -259,7 +259,16 @@ impl TypeRegister {
.unwrap()
.member_functions
.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!(),
};

Expand Down