mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 18:58:36 +00:00
Menu API changes
`ContextMenu` -> `ContextMenuArea` `ContextMenu` must have a `Menu` child. `MenuItem` can no longer be put dirrectly in `MenuBar` and can no longer have children `Menu` is used now for sub menus
This commit is contained in:
parent
2b6938bce8
commit
39191e5acd
19 changed files with 352 additions and 238 deletions
|
@ -183,13 +183,16 @@ export component SwipeGestureHandler {
|
|||
//-default_size_binding:expands_to_parent_geometry
|
||||
}
|
||||
|
||||
// Fake
|
||||
component MenuItem {
|
||||
in property <string> title;
|
||||
callback activated();
|
||||
|
||||
//-disallow_global_types_as_child_elements
|
||||
//-is_non_item_type
|
||||
}
|
||||
component Menu {
|
||||
in property <string> title;
|
||||
MenuItem {}
|
||||
|
||||
Menu {}
|
||||
//-disallow_global_types_as_child_elements
|
||||
//-is_non_item_type
|
||||
}
|
||||
|
@ -198,14 +201,9 @@ component MenuItem {
|
|||
component MenuBar {
|
||||
//-is_non_item_type
|
||||
//-disallow_global_types_as_child_elements
|
||||
|
||||
MenuItem {}
|
||||
|
||||
// Currently experimental
|
||||
Menu {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// The NativeItem, exported as ContextMenuInternal for the style
|
||||
component ContextMenu inherits Empty {
|
||||
callback activated(entry: MenuEntry);
|
||||
|
@ -225,15 +223,14 @@ export component ContextMenuInternal inherits ContextMenu {
|
|||
// The public ContextMenu which is lowered in the lower_menus pass. See that pass documentation for more info
|
||||
// Note that this element cannot be named `ContextMenu` because that's the same name as a native item,
|
||||
// and the load_builtins code doesn't allow that. So use a placeholder name and re-export under `ContextMenu`
|
||||
component ActualContextMenuElement inherits Empty {
|
||||
export component ContextMenuArea inherits Empty {
|
||||
// This is actually function as part of out interface, but a callback as much is the runtime concerned
|
||||
callback show(position: Point);
|
||||
function close() {}
|
||||
//-default_size_binding:expands_to_parent_geometry
|
||||
MenuItem {}
|
||||
Menu {}
|
||||
}
|
||||
|
||||
export { ActualContextMenuElement as ContextMenu }
|
||||
|
||||
component WindowItem {
|
||||
in-out property <length> width;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 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
|
||||
|
||||
//! Passe lower the `MenuBar` and `ContextMenu` as well as all their contents
|
||||
//! Passe lower the `MenuBar` and `ContextMenuArea` as well as all their contents
|
||||
//!
|
||||
//! We can't have properties of type model because that is not binary compatible with C++,
|
||||
//! so all the code that handle model of MenuEntry need to be handle by code in the generated code
|
||||
|
@ -12,14 +12,17 @@
|
|||
//! ```slint
|
||||
//! Window {
|
||||
//! menu-bar := MenuBar {
|
||||
//! Menu {
|
||||
//! title: "File";
|
||||
//! MenuItem {
|
||||
//! title: "A";
|
||||
//! activated => { ... }
|
||||
//! }
|
||||
//! MenuItem {
|
||||
//! Menu {
|
||||
//! title: "B";
|
||||
//! MenuItem { title: "C"; }
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//! content := ...
|
||||
//! }
|
||||
|
@ -28,8 +31,11 @@
|
|||
//! ```slint
|
||||
//! Window {
|
||||
//! menu-bar := VerticalLayout {
|
||||
//! property <[MenuEntry]> entries : [ { id: "1", title: "A" }, { id: "2", title: "B", has-sub-menu: true } ];
|
||||
//! callback sub-menu(entry: MenuEntry) => { if(entry.id == "2") { return [ { id: "3", title: "C" } ]; } else { return []; } }
|
||||
//! property <[MenuEntry]> entries : [ { id: "0", title: "File", has-sub-menu: true } ];
|
||||
//! callback sub-menu(entry: MenuEntry) => {
|
||||
//! if(entry.id == "0") { return [ { id: "1", title: "A" }, { id: "2", title: "B", has-sub-menu: true } ]; }
|
||||
//! else if(entry.id == "2") { return [ { id: "3", title: "C" } ]; } else { return []; }
|
||||
//! }
|
||||
//! callback activated() => { if (entry.id == "2") { ... } }
|
||||
//! if !Builtin.supports_native_menu_bar() : MenuBarImpl {
|
||||
//! entries: parent.entries
|
||||
|
@ -50,7 +56,7 @@
|
|||
//! ## ContextMenuInternal
|
||||
//!
|
||||
//! ```slint
|
||||
//! menu := ContextMenu {
|
||||
//! menu := ContextMenuInternal {
|
||||
//! entries: [...]
|
||||
//! sub-menu => ...
|
||||
//! activated => ...
|
||||
|
@ -69,7 +75,7 @@
|
|||
//! callback show(point) => { Builtin.show_context_menu(entries, sub-menu, activated, point) }
|
||||
//! }
|
||||
//!
|
||||
//! ## ContextMenu
|
||||
//! ## ContextMenuArea
|
||||
//!
|
||||
//! This is the same as ContextMenuInternal, but entries, sub-menu, and activated are generated
|
||||
//! from the MenuItem similar to MenuBar
|
||||
|
@ -95,6 +101,7 @@ struct UsefulMenuComponents {
|
|||
context_menu_internal: ElementType,
|
||||
empty: ElementType,
|
||||
menu_entry: Type,
|
||||
menu_item_element: ElementType,
|
||||
}
|
||||
|
||||
pub async fn lower_menus(
|
||||
|
@ -109,13 +116,29 @@ pub async fn lower_menus(
|
|||
.import_component("std-widgets.slint", "MenuBarImpl", &mut build_diags_to_ignore)
|
||||
.await
|
||||
.expect("MenuBarImpl should be in std-widgets.slint");
|
||||
|
||||
let menu_item_element = type_loader
|
||||
.global_type_registry
|
||||
.borrow()
|
||||
.lookup_builtin_element("ContextMenuArea")
|
||||
.unwrap()
|
||||
.as_builtin()
|
||||
.additional_accepted_child_types
|
||||
.get("Menu")
|
||||
.expect("ContextMenuArea should accept Menu")
|
||||
.additional_accepted_child_types
|
||||
.get("MenuItem")
|
||||
.expect("Menu should accept MenuItem")
|
||||
.clone()
|
||||
.into();
|
||||
|
||||
let useful_menu_component = UsefulMenuComponents {
|
||||
menubar_impl: menubar_impl.clone().into(),
|
||||
context_menu_internal: type_loader
|
||||
.global_type_registry
|
||||
.borrow()
|
||||
.lookup_builtin_element("ContextMenuInternal")
|
||||
.expect("ContextMenu is a builtin type"),
|
||||
.expect("ContextMenuInternal is a builtin type"),
|
||||
vertical_layout: type_loader
|
||||
.global_type_registry
|
||||
.borrow()
|
||||
|
@ -123,6 +146,7 @@ pub async fn lower_menus(
|
|||
.expect("VerticalLayout is a builtin type"),
|
||||
empty: type_loader.global_type_registry.borrow().empty_type(),
|
||||
menu_entry: type_loader.global_type_registry.borrow().lookup("MenuEntry"),
|
||||
menu_item_element,
|
||||
};
|
||||
assert!(matches!(&useful_menu_component.menu_entry, Type::Struct(..)));
|
||||
|
||||
|
@ -133,7 +157,7 @@ pub async fn lower_menus(
|
|||
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "Window") {
|
||||
has_menu |= process_window(elem, &useful_menu_component, diag);
|
||||
}
|
||||
if matches!(&elem.borrow().builtin_type(), Some(b) if matches!(b.name.as_str(), "ContextMenu" | "ContextMenuInternal")) {
|
||||
if matches!(&elem.borrow().builtin_type(), Some(b) if matches!(b.name.as_str(), "ContextMenuArea" | "ContextMenuInternal")) {
|
||||
has_menu |= process_context_menu(elem, &useful_menu_component, diag);
|
||||
}
|
||||
})
|
||||
|
@ -161,7 +185,7 @@ pub async fn lower_menus(
|
|||
}
|
||||
|
||||
recurse_elem_including_sub_components_no_borrow(&popup_menu_impl, &(), &mut |elem, _| {
|
||||
if matches!(&elem.borrow().builtin_type(), Some(b) if matches!(b.name.as_str(), "ContextMenu" | "ContextMenuInternal"))
|
||||
if matches!(&elem.borrow().builtin_type(), Some(b) if matches!(b.name.as_str(), "ContextMenuArea" | "ContextMenuInternal"))
|
||||
{
|
||||
process_context_menu(elem, &useful_menu_component, diag);
|
||||
}
|
||||
|
@ -178,33 +202,53 @@ fn process_context_menu(
|
|||
let is_internal = matches!(&context_menu_elem.borrow().base_type, ElementType::Builtin(b) if b.name == "ContextMenuInternal");
|
||||
|
||||
let item_tree_root = if !is_internal {
|
||||
// Lower MenuItem's into entries
|
||||
let menu_item = context_menu_elem
|
||||
// Lower Menu into entries
|
||||
let menu_element_type = context_menu_elem
|
||||
.borrow()
|
||||
.base_type
|
||||
.as_builtin()
|
||||
.additional_accepted_child_types
|
||||
.get("MenuItem")
|
||||
.expect("ContextMenu should accept MenuItem")
|
||||
.get("Menu")
|
||||
.expect("ContextMenu should accept Menu")
|
||||
.clone()
|
||||
.into();
|
||||
|
||||
context_menu_elem.borrow_mut().base_type = components.context_menu_internal.clone();
|
||||
|
||||
let mut items = vec![];
|
||||
let mut menu_elem = None;
|
||||
context_menu_elem.borrow_mut().children.retain(|x| {
|
||||
if x.borrow().base_type == menu_item {
|
||||
items.push(x.clone());
|
||||
if x.borrow().base_type == menu_element_type {
|
||||
if menu_elem.is_some() {
|
||||
diag.push_error(
|
||||
"Only one Menu is allowed in a ContextMenu".into(),
|
||||
&*x.borrow(),
|
||||
);
|
||||
} else {
|
||||
menu_elem = Some(x.clone());
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
let item_tree_root = if !items.is_empty() {
|
||||
lower_menu_items(context_menu_elem, items, components)
|
||||
let item_tree_root = if let Some(menu_elem) = menu_elem {
|
||||
if menu_elem.borrow().repeated.is_some() {
|
||||
diag.push_error(
|
||||
"ContextMenuArea's root Menu cannot be in a conditional or repeated element"
|
||||
.into(),
|
||||
&*menu_elem.borrow(),
|
||||
);
|
||||
}
|
||||
|
||||
let children = std::mem::take(&mut menu_elem.borrow_mut().children);
|
||||
lower_menu_items(context_menu_elem, children, components)
|
||||
.map(|c| Expression::ElementReference(Rc::downgrade(&c.root_element)))
|
||||
} else {
|
||||
diag.push_error(
|
||||
"ContextMenuArea should have a Menu".into(),
|
||||
&*context_menu_elem.borrow(),
|
||||
);
|
||||
None
|
||||
};
|
||||
|
||||
|
@ -252,7 +296,7 @@ fn process_context_menu(
|
|||
.bindings
|
||||
.insert(SmolStr::new_static(SHOW), RefCell::new(expr.into()));
|
||||
if let Some(old) = old {
|
||||
diag.push_error("'show' is not a callback in ContextMenu".into(), &old.borrow().span);
|
||||
diag.push_error("'show' is not a callback in ContextMenuArea".into(), &old.borrow().span);
|
||||
}
|
||||
|
||||
true
|
||||
|
@ -420,7 +464,7 @@ fn process_window(
|
|||
true
|
||||
}
|
||||
|
||||
/// Lower the MenuItem to either
|
||||
/// Lower the MenuItem's and Menu's to either
|
||||
/// - `entries` and `activated` and `sub-menu` properties/callback, in which cases it returns None
|
||||
/// - or a Component which is a tree of MenuItem, in which case returns the component that is within the enclosing component's menu_item_trees
|
||||
fn lower_menu_items(
|
||||
|
@ -487,6 +531,8 @@ fn lower_menu_items(
|
|||
));
|
||||
element.borrow_mut().enclosing_component = component_weak.clone();
|
||||
element.borrow_mut().geometry_props = None;
|
||||
// Menu -> MenuItem
|
||||
element.borrow_mut().base_type = components.menu_item_element.clone();
|
||||
}
|
||||
false
|
||||
});
|
||||
|
@ -550,7 +596,11 @@ fn generate_menu_entries(
|
|||
|
||||
for item in menu_items {
|
||||
let mut borrow_mut = item.borrow_mut();
|
||||
assert_eq!(borrow_mut.base_type.type_name(), Some("MenuItem"));
|
||||
let base_name = borrow_mut.base_type.type_name().unwrap();
|
||||
let is_sub_menu = base_name == "Menu";
|
||||
if !is_sub_menu {
|
||||
assert_eq!(base_name, "MenuItem");
|
||||
}
|
||||
|
||||
borrow_mut
|
||||
.enclosing_component
|
||||
|
@ -577,9 +627,10 @@ fn generate_menu_entries(
|
|||
state.activate.push((id_str.clone(), callback.into_inner().expression));
|
||||
}
|
||||
|
||||
let sub_entries =
|
||||
generate_menu_entries(std::mem::take(&mut borrow_mut.children).into_iter(), state);
|
||||
if !sub_entries.is_empty() {
|
||||
if is_sub_menu {
|
||||
let sub_entries =
|
||||
generate_menu_entries(std::mem::take(&mut borrow_mut.children).into_iter(), state);
|
||||
|
||||
state.sub_menu.push((
|
||||
id_str,
|
||||
Expression::Array { element_ty: state.menu_entry.clone(), values: sub_entries },
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
export component A {
|
||||
ContextMenu {
|
||||
ContextMenuArea {
|
||||
show => {
|
||||
// ^error{'show' is not a callback in ContextMenu}
|
||||
// ^error{'show' is not a callback in ContextMenuArea}
|
||||
debug("hello");
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
Menu {
|
||||
}
|
||||
|
||||
|
||||
property <int> entries: 45;
|
||||
// ^error{Cannot re-define internal property 'entries'}
|
||||
property <int> sub-menu: 45;
|
||||
|
@ -21,5 +22,27 @@ export component A {
|
|||
property <string> xyz: "me";
|
||||
|
||||
}
|
||||
|
||||
ContextMenuArea { Rectangle {} }
|
||||
// ^error{ContextMenuArea should have a Menu}
|
||||
|
||||
ContextMenuArea {
|
||||
Menu {
|
||||
MenuItem { title: "ok"; }
|
||||
}
|
||||
Menu {
|
||||
// ^error{Only one Menu is allowed in a ContextMenu}
|
||||
MenuItem { title: "hello"; }
|
||||
}
|
||||
}
|
||||
|
||||
ContextMenuArea {
|
||||
if false : Menu {}
|
||||
// ^error{ContextMenuArea's root Menu cannot be in a conditional or repeated element}
|
||||
}
|
||||
ContextMenuArea {
|
||||
for _ in [1,2,3] : Menu {}
|
||||
// ^error{ContextMenuArea's root Menu cannot be in a conditional or repeated element}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
export component A {
|
||||
cb := ContextMenu {
|
||||
cb := ContextMenuArea {
|
||||
entries: [];
|
||||
// ^error{Unknown property entries in ContextMenu}
|
||||
sub-menu => {
|
||||
|
@ -10,10 +10,15 @@ export component A {
|
|||
debug("hello");
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
Menu {
|
||||
preferred-height: 45px;
|
||||
// ^error{Unknown property preferred-height in Menu}
|
||||
entries: [];
|
||||
// ^error{Unknown property entries in MenuItem}
|
||||
// ^error{Unknown property entries in Menu}
|
||||
MenuItem {
|
||||
entries: [];
|
||||
// ^error{Unknown property entries in MenuItem}
|
||||
|
||||
title: "ok";
|
||||
sub-menu => {}
|
||||
// ^error{'sub-menu' is not a callback in MenuItem}
|
||||
|
@ -23,14 +28,16 @@ export component A {
|
|||
// ^error{Unknown property col in MenuItem}
|
||||
}
|
||||
}
|
||||
MenuItem {}
|
||||
// ^error{Unknown element 'MenuItem'}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
cb.activated({});
|
||||
// ^error{Element 'ContextMenu' does not have a property 'activated'}
|
||||
// ^error{Element 'ContextMenuArea' does not have a property 'activated'}
|
||||
debug(cb.entries);
|
||||
// ^error{Element 'ContextMenu' does not have a property 'entries'}
|
||||
// ^error{Element 'ContextMenuArea' does not have a property 'entries'}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,5 +45,7 @@ export component A {
|
|||
ContextMenuInternal {
|
||||
// ^error{Unknown element 'ContextMenuInternal'}
|
||||
}
|
||||
ContextMenu {}
|
||||
// ^error{Unknown element 'ContextMenu'}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ export component MyMenu inherits MenuBar {
|
|||
export component A inherits Window {
|
||||
mb := MenuBar {
|
||||
Rectangle {}
|
||||
// ^error{Rectangle is not allowed within MenuBar. Only MenuItem are valid children}
|
||||
// ^error{Rectangle is not allowed within MenuBar. Only Menu are valid children}
|
||||
x: 45px;
|
||||
// ^error{Unknown property x in MenuBar}
|
||||
width: 45px;
|
||||
|
@ -19,16 +19,16 @@ export component A inherits Window {
|
|||
init => {}
|
||||
// ^error{'init' is not a callback in MenuBar}
|
||||
|
||||
MenuItem {
|
||||
Menu {
|
||||
title: "hello";
|
||||
x: 0px;
|
||||
// ^error{Unknown property x in MenuItem}
|
||||
// ^error{Unknown property x in Menu}
|
||||
max-height: 13px;
|
||||
// ^error{Unknown property max-height in MenuItem}
|
||||
// ^error{Unknown property max-height in Menu}
|
||||
opacity: 0.4;
|
||||
// ^error{Unknown property opacity in MenuItem}
|
||||
// ^error{Unknown property opacity in Menu}
|
||||
Rectangle {
|
||||
// ^error{Rectangle is not allowed within MenuItem. Only MenuItem are valid children}
|
||||
// ^error{Rectangle is not allowed within Menu. Only Menu MenuItem are valid children}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
|
@ -36,9 +36,15 @@ export component A inherits Window {
|
|||
mb.activated({});
|
||||
// ^error{Element 'MenuBar' does not have a property 'activated'}
|
||||
}
|
||||
|
||||
TouchArea {}
|
||||
// ^error{MenuItem cannot have children elements}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MenuItem {}
|
||||
// ^error{MenuItem is not allowed within MenuBar. Only Menu are valid children}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -46,6 +52,15 @@ export component A inherits Window {
|
|||
MenuBar {
|
||||
// ^error{MenuBar can only be within a Window element}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
// ^error{Unknown element 'MenuItem'}
|
||||
Menu {}
|
||||
// ^error{Menu can only be within a ContextMenuArea element}
|
||||
}
|
||||
|
||||
Menu {}
|
||||
// ^error{Menu can only be within a ContextMenuArea element}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,22 +67,24 @@ export component LineEditBase inherits Rectangle {
|
|||
accessible-role: none;
|
||||
}
|
||||
|
||||
ContextMenu {
|
||||
MenuItem {
|
||||
title: @tr("Cut");
|
||||
activated => { text-input.cut(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Copy");
|
||||
activated => { text-input.copy(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Paste");
|
||||
activated => { text-input.paste(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Select All");
|
||||
activated => { text-input.select-all(); }
|
||||
ContextMenuArea {
|
||||
Menu {
|
||||
MenuItem {
|
||||
title: @tr("Cut");
|
||||
activated => { text-input.cut(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Copy");
|
||||
activated => { text-input.copy(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Paste");
|
||||
activated => { text-input.paste(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Select All");
|
||||
activated => { text-input.select-all(); }
|
||||
}
|
||||
}
|
||||
|
||||
text-input := TextInput {
|
||||
|
|
|
@ -58,22 +58,24 @@ export component TextEditBase inherits Rectangle {
|
|||
|
||||
forward-focus: text-input;
|
||||
|
||||
ContextMenu {
|
||||
MenuItem {
|
||||
title: @tr("Cut");
|
||||
activated => { text-input.cut(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Copy");
|
||||
activated => { text-input.copy(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Paste");
|
||||
activated => { text-input.paste(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Select All");
|
||||
activated => { text-input.select-all(); }
|
||||
ContextMenuArea {
|
||||
Menu {
|
||||
MenuItem {
|
||||
title: @tr("Cut");
|
||||
activated => { text-input.cut(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Copy");
|
||||
activated => { text-input.copy(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Paste");
|
||||
activated => { text-input.paste(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Select All");
|
||||
activated => { text-input.select-all(); }
|
||||
}
|
||||
}
|
||||
|
||||
scroll-view := ScrollView {
|
||||
|
|
|
@ -139,22 +139,24 @@ export component TextEdit {
|
|||
border-width: 1px;
|
||||
}
|
||||
|
||||
ContextMenu {
|
||||
MenuItem {
|
||||
title: @tr("Cut");
|
||||
activated => { text-input.cut(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Copy");
|
||||
activated => { text-input.copy(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Paste");
|
||||
activated => { text-input.paste(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Select All");
|
||||
activated => { text-input.select-all(); }
|
||||
ContextMenuArea {
|
||||
Menu {
|
||||
MenuItem {
|
||||
title: @tr("Cut");
|
||||
activated => { text-input.cut(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Copy");
|
||||
activated => { text-input.copy(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Paste");
|
||||
activated => { text-input.paste(); }
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Select All");
|
||||
activated => { text-input.select-all(); }
|
||||
}
|
||||
}
|
||||
|
||||
scroll-view := ScrollView {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue