mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-03 05:12:55 +00:00
Experimental support for MenuBar
Introduces `MenuBar{ ... }` that can be put in a Window
This commit is contained in:
parent
20443ec0df
commit
5bd20def0e
38 changed files with 1023 additions and 69 deletions
|
|
@ -221,6 +221,10 @@ fn default_config() -> cbindgen::Config {
|
|||
("PointArg".into(), "slint::LogicalPosition".into()),
|
||||
("FloatArg".into(), "float".into()),
|
||||
("IntArg".into(), "int".into()),
|
||||
("MenuEntryArg".into(), "MenuEntry".into()),
|
||||
// Note: these types are not the same, but they are only used in callback return types that are only used in C++ (set and called)
|
||||
// therefore it is ok to reinterpret_cast
|
||||
("MenuEntryModel".into(), "std::shared_ptr<slint::Model<MenuEntry>>".into()),
|
||||
("Coord".into(), "float".into()),
|
||||
]
|
||||
.iter()
|
||||
|
|
@ -293,6 +297,7 @@ fn gen_corelib(
|
|||
"Rotate",
|
||||
"Opacity",
|
||||
"Layer",
|
||||
"ContextMenu",
|
||||
];
|
||||
|
||||
config.export.include = [
|
||||
|
|
@ -370,6 +375,8 @@ fn gen_corelib(
|
|||
"PointerScrollEventArg",
|
||||
"PointArg",
|
||||
"Point",
|
||||
"MenuEntryModel",
|
||||
"MenuEntryArg",
|
||||
"slint_color_brighter",
|
||||
"slint_color_darker",
|
||||
"slint_color_transparentize",
|
||||
|
|
@ -500,7 +507,7 @@ fn gen_corelib(
|
|||
"slint_image_set_nine_slice_edges",
|
||||
"slint_image_to_rgb8",
|
||||
"slint_image_to_rgba8",
|
||||
"slint_image_to_rgba8_premultiplied",
|
||||
"slint_image_to_rgba8_premultiplied",
|
||||
"SharedPixelBuffer",
|
||||
"SharedImageBuffer",
|
||||
"StaticTextures",
|
||||
|
|
@ -590,7 +597,7 @@ fn gen_corelib(
|
|||
"slint_image_set_nine_slice_edges",
|
||||
"slint_image_to_rgb8",
|
||||
"slint_image_to_rgba8",
|
||||
"slint_image_to_rgba8_premultiplied",
|
||||
"slint_image_to_rgba8_premultiplied",
|
||||
"slint_image_from_embedded_textures",
|
||||
"slint_image_compare_equal",
|
||||
]
|
||||
|
|
@ -644,7 +651,7 @@ fn gen_corelib(
|
|||
public_config.export.item_types = vec![cbindgen::ItemType::Enums, cbindgen::ItemType::Structs];
|
||||
// Previously included types are now excluded (to avoid duplicates)
|
||||
public_config.export.exclude = private_exported_types.into_iter().collect();
|
||||
public_config.export.exclude.push("Point".into());
|
||||
public_config.export.exclude.push("LogicalPosition".into());
|
||||
public_config.export.include = public_exported_types.into_iter().map(str::to_string).collect();
|
||||
public_config.export.body.insert(
|
||||
"Rgb8Pixel".to_owned(),
|
||||
|
|
@ -760,6 +767,7 @@ namespace slint {
|
|||
struct ItemVTable;
|
||||
using types::IntRect;
|
||||
}
|
||||
template<typename ModelData> class Model;
|
||||
}",
|
||||
)
|
||||
.with_trailer(gen_item_declarations(&items))
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ inline void assert_main_thread()
|
|||
}
|
||||
|
||||
using ItemTreeRc = vtable::VRc<cbindgen_private::ItemTreeVTable>;
|
||||
using slint::LogicalPosition;
|
||||
|
||||
class WindowAdapterRc
|
||||
{
|
||||
|
|
@ -111,7 +112,7 @@ public:
|
|||
cbindgen_private::ItemRc parent_item) const
|
||||
{
|
||||
auto popup = Component::create(parent_component);
|
||||
cbindgen_private::Point p = pos(popup);
|
||||
auto p = pos(popup);
|
||||
auto popup_dyn = popup.into_dyn();
|
||||
return cbindgen_private::slint_windowrc_show_popup(&inner, &popup_dyn, p, close_policy,
|
||||
&parent_item);
|
||||
|
|
@ -124,6 +125,20 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
template<typename Component, typename SharedGlobals, typename InitFn>
|
||||
uint32_t show_popup_menu(SharedGlobals *globals, LogicalPosition pos,
|
||||
cbindgen_private::ItemRc context_menu_rc, InitFn init) const
|
||||
{
|
||||
// if (cbindgen_private::slint_windowrc_show_native_context_menu(....)) { return }
|
||||
|
||||
auto popup = Component::create(globals);
|
||||
init(&*popup);
|
||||
auto popup_dyn = popup.into_dyn();
|
||||
return cbindgen_private::slint_windowrc_show_popup(
|
||||
&inner, &popup_dyn, pos, cbindgen_private::PopupClosePolicy::CloseOnClickOutside,
|
||||
&context_menu_rc);
|
||||
}
|
||||
|
||||
template<std::invocable<RenderingState, GraphicsAPI> F>
|
||||
std::optional<SetRenderingNotifierError> set_rendering_notifier(F callback) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ pub mod re_exports {
|
|||
AccessibilityAction, AccessibleStringProperty, SupportedAccessibilityAction,
|
||||
};
|
||||
pub use i_slint_core::animations::{animation_tick, EasingCurve};
|
||||
pub use i_slint_core::api::LogicalPosition;
|
||||
pub use i_slint_core::callbacks::Callback;
|
||||
pub use i_slint_core::date_time::*;
|
||||
pub use i_slint_core::graphics::*;
|
||||
|
|
|
|||
|
|
@ -182,6 +182,25 @@ macro_rules! for_each_builtin_structs {
|
|||
private {
|
||||
}
|
||||
}
|
||||
|
||||
/// An item in the menu of a menu bar or context menu
|
||||
struct MenuEntry {
|
||||
@name = "slint::private_api::MenuEntry"
|
||||
export {
|
||||
/// The text of the menu entry
|
||||
title: SharedString,
|
||||
// /// the icon associated with the menu entry
|
||||
// icon: Image,
|
||||
/// an opaque id that can be used to identify the menu entry
|
||||
id: SharedString,
|
||||
// keyboard_shortcut: KeySequence,
|
||||
// /// whether the menu entry is enabled
|
||||
// enabled: bool,
|
||||
/// Sub menu
|
||||
has_sub_menu: bool,
|
||||
}
|
||||
private {}
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,6 +183,31 @@ export component SwipeGestureHandler {
|
|||
//-default_size_binding:expands_to_parent_geometry
|
||||
}
|
||||
|
||||
// Lowered to MenuBarImpl from the style
|
||||
component MenuBar {
|
||||
//-is_non_item_type
|
||||
//-disallow_global_types_as_child_elements
|
||||
in property <[MenuEntry]> entries;
|
||||
|
||||
callback activated(MenuEntry);
|
||||
callback sub-menu(MenuEntry) -> [MenuEntry];
|
||||
|
||||
// Currently experimental
|
||||
//-is_internal
|
||||
}
|
||||
|
||||
// Lowered to a call that generate a ContextMenuImpl
|
||||
export component ContextMenu {
|
||||
function show() {}
|
||||
//in property <[MenuEntry]> entries;
|
||||
callback activated(MenuEntry);
|
||||
callback sub-menu(MenuEntry) -> [MenuEntry];
|
||||
//-default_size_binding:expands_to_parent_geometry
|
||||
|
||||
// Currently experimental
|
||||
//-is_internal
|
||||
}
|
||||
|
||||
component WindowItem {
|
||||
in-out property <length> width;
|
||||
in-out property <length> height;
|
||||
|
|
@ -198,7 +223,9 @@ component WindowItem {
|
|||
in property <image> icon;
|
||||
}
|
||||
|
||||
export component Window inherits WindowItem { }
|
||||
export component Window inherits WindowItem {
|
||||
MenuBar {}
|
||||
}
|
||||
|
||||
export component BoxShadow inherits Empty {
|
||||
in property <length> border_radius;
|
||||
|
|
@ -418,7 +445,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;
|
||||
in property <bool> close-on-click;
|
||||
in property <PopupClosePolicy> close-policy; // constexpr hardcoded in typeregister.rs
|
||||
//show() is hardcoded in typeregister.rs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ pub enum BuiltinFunction {
|
|||
ClearFocusItem,
|
||||
ShowPopupWindow,
|
||||
ClosePopupWindow,
|
||||
ShowPopupMenu,
|
||||
SetSelectionOffsets,
|
||||
/// A function that belongs to an item (such as TextInput's select-all function).
|
||||
ItemMemberFunction(SmolStr),
|
||||
|
|
@ -156,6 +157,7 @@ declare_builtin_function_types!(
|
|||
ClearFocusItem: (Type::ElementReference) -> Type::Void,
|
||||
ShowPopupWindow: (Type::ElementReference) -> Type::Void,
|
||||
ClosePopupWindow: (Type::ElementReference) -> Type::Void,
|
||||
ShowPopupMenu: (Type::ElementReference, Type::Model, crate::typeregister::logical_point_type()) -> Type::Void,
|
||||
ItemMemberFunction(..): (Type::ElementReference) -> Type::Void,
|
||||
SetSelectionOffsets: (Type::ElementReference, Type::Int32, Type::Int32) -> Type::Void,
|
||||
ItemFontMetrics: (Type::ElementReference) -> crate::typeregister::font_metrics_type(),
|
||||
|
|
@ -264,7 +266,9 @@ impl BuiltinFunction {
|
|||
| BuiltinFunction::ATan
|
||||
| BuiltinFunction::ATan2 => true,
|
||||
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
|
||||
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
|
||||
BuiltinFunction::ShowPopupWindow
|
||||
| BuiltinFunction::ClosePopupWindow
|
||||
| BuiltinFunction::ShowPopupMenu => false,
|
||||
BuiltinFunction::SetSelectionOffsets => false,
|
||||
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||
BuiltinFunction::ItemFontMetrics => false, // depends also on Window's font properties
|
||||
|
|
@ -331,7 +335,9 @@ impl BuiltinFunction {
|
|||
| BuiltinFunction::ATan
|
||||
| BuiltinFunction::ATan2 => true,
|
||||
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
|
||||
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
|
||||
BuiltinFunction::ShowPopupWindow
|
||||
| BuiltinFunction::ClosePopupWindow
|
||||
| BuiltinFunction::ShowPopupMenu => false,
|
||||
BuiltinFunction::SetSelectionOffsets => false,
|
||||
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||
BuiltinFunction::ItemFontMetrics => true,
|
||||
|
|
|
|||
|
|
@ -819,6 +819,24 @@ pub fn generate(
|
|||
}
|
||||
file.declarations.push(Declaration::Struct(globals_struct));
|
||||
|
||||
if let Some(popup_menu) = &llr.popup_menu {
|
||||
let component_id = ident(&popup_menu.item_tree.root.name);
|
||||
let mut popup_struct = Struct { name: component_id.clone(), ..Default::default() };
|
||||
generate_item_tree(
|
||||
&mut popup_struct,
|
||||
&popup_menu.item_tree,
|
||||
&llr,
|
||||
None,
|
||||
true,
|
||||
component_id,
|
||||
Access::Public,
|
||||
&mut file,
|
||||
&conditional_includes,
|
||||
);
|
||||
file.definitions.extend(popup_struct.extract_definitions().collect::<Vec<_>>());
|
||||
file.declarations.push(Declaration::Struct(popup_struct));
|
||||
};
|
||||
|
||||
for p in &llr.public_components {
|
||||
generate_public_component(&mut file, &conditional_includes, p, &llr);
|
||||
}
|
||||
|
|
@ -1199,6 +1217,7 @@ fn generate_public_component(
|
|||
&component.item_tree,
|
||||
unit,
|
||||
None,
|
||||
false,
|
||||
component_id,
|
||||
Access::Private, // Hide properties and other fields from the C++ API
|
||||
file,
|
||||
|
|
@ -1289,6 +1308,7 @@ fn generate_item_tree(
|
|||
sub_tree: &llr::ItemTree,
|
||||
root: &llr::CompilationUnit,
|
||||
parent_ctx: Option<ParentCtx>,
|
||||
is_popup_menu: bool,
|
||||
item_tree_class_name: SmolStr,
|
||||
field_access: Access,
|
||||
file: &mut File,
|
||||
|
|
@ -1708,21 +1728,24 @@ fn generate_item_tree(
|
|||
"self->self_weak = vtable::VWeak(self_rc).into_dyn();".into(),
|
||||
];
|
||||
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
if let Some(translations) = &root.translations {
|
||||
let lang_len = translations.languages.len();
|
||||
create_code.push(format!(
|
||||
"std::array<slint::cbindgen_private::Slice<uint8_t>, {lang_len}> languages {{ {} }};",
|
||||
translations
|
||||
.languages
|
||||
.iter()
|
||||
.map(|l| format!("slint::private_api::string_to_slice({l:?})"))
|
||||
.join(", ")
|
||||
));
|
||||
create_code.push(format!("slint::cbindgen_private::slint_translate_set_bundled_languages({{ languages.data(), {lang_len} }});"));
|
||||
}
|
||||
if is_popup_menu {
|
||||
create_code.push("self->globals = globals;".into());
|
||||
create_parameters.push("const SharedGlobals *globals".into());
|
||||
} else if parent_ctx.is_none() {
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
if let Some(translations) = &root.translations {
|
||||
let lang_len = translations.languages.len();
|
||||
create_code.push(format!(
|
||||
"std::array<slint::cbindgen_private::Slice<uint8_t>, {lang_len}> languages {{ {} }};",
|
||||
translations
|
||||
.languages
|
||||
.iter()
|
||||
.map(|l| format!("slint::private_api::string_to_slice({l:?})"))
|
||||
.join(", ")
|
||||
));
|
||||
create_code.push(format!("slint::cbindgen_private::slint_translate_set_bundled_languages({{ languages.data(), {lang_len} }});"));
|
||||
}
|
||||
|
||||
if parent_ctx.is_none() {
|
||||
create_code.push("self->globals = &self->m_globals;".into());
|
||||
create_code.push("self->m_globals.root_weak = self->self_weak;".into());
|
||||
create_code.push("slint::cbindgen_private::slint_ensure_backend();".into());
|
||||
|
|
@ -1865,6 +1888,7 @@ fn generate_sub_component(
|
|||
&popup.item_tree,
|
||||
root,
|
||||
Some(ParentCtx::new(&ctx, None)),
|
||||
false,
|
||||
component_id,
|
||||
Access::Public,
|
||||
file,
|
||||
|
|
@ -2404,6 +2428,7 @@ fn generate_repeated_component(
|
|||
&repeated.sub_tree,
|
||||
root,
|
||||
Some(parent_ctx),
|
||||
false,
|
||||
repeater_id.clone(),
|
||||
Access::Public,
|
||||
file,
|
||||
|
|
@ -3661,6 +3686,53 @@ fn compile_builtin_function_call(
|
|||
panic!("internal error: invalid args to ClosePopupWindow {:?}", arguments)
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinFunction::ShowPopupMenu => {
|
||||
let [llr::Expression::PropertyReference(context_menu_ref), entries, position] = arguments
|
||||
else {
|
||||
panic!("internal error: invalid args to ShowPopupMenu {arguments:?}")
|
||||
};
|
||||
|
||||
let context_menu = access_member(context_menu_ref, ctx);
|
||||
let context_menu_rc = access_item_rc(context_menu_ref, ctx);
|
||||
let position = compile_expression(position, ctx);
|
||||
let entries = compile_expression(entries, ctx);
|
||||
let popup = ctx
|
||||
.compilation_unit
|
||||
.popup_menu
|
||||
.as_ref()
|
||||
.expect("there should be a popup menu if we want to show it");
|
||||
let popup_id = ident(&popup.item_tree.root.name);
|
||||
let window = access_window_field(ctx);
|
||||
|
||||
let popup_ctx = EvaluationContext::new_sub_component(
|
||||
ctx.compilation_unit,
|
||||
&popup.item_tree.root,
|
||||
CppGeneratorContext { global_access: "self->globals".into(), conditional_includes: ctx.generator_state.conditional_includes },
|
||||
None,
|
||||
);
|
||||
let access_entries = access_member(&popup.entries, &popup_ctx);
|
||||
let forward_callback = |pr, cb| {
|
||||
let access = access_member(pr, &popup_ctx);
|
||||
format!("{access}.set_handler(
|
||||
[context_menu](const auto &entry) {{
|
||||
return context_menu->{cb}.call(entry);
|
||||
}});")
|
||||
};
|
||||
let fw_sub_menu = forward_callback(&popup.sub_menu, "sub_menu");
|
||||
let fw_activated = forward_callback(&popup.activated, "activated");
|
||||
let init = format!(r"
|
||||
auto entries = {entries};
|
||||
const slint::cbindgen_private::ContextMenu *context_menu = &({context_menu});
|
||||
{{
|
||||
auto self = popup_menu;
|
||||
{access_entries}.set(std::move(entries));
|
||||
{fw_sub_menu}
|
||||
{fw_activated}
|
||||
}}");
|
||||
|
||||
format!("{window}.show_popup_menu<{popup_id}>({globals}, {position}, {{ {context_menu_rc} }}, [self](auto popup_menu) {{ {init} }})", globals = ctx.generator_state.global_access)
|
||||
}
|
||||
BuiltinFunction::SetSelectionOffsets => {
|
||||
if let [llr::Expression::PropertyReference(pr), from, to] = arguments {
|
||||
let item = access_member(pr, ctx);
|
||||
|
|
|
|||
|
|
@ -186,6 +186,9 @@ pub fn generate(
|
|||
let public_components =
|
||||
llr.public_components.iter().map(|p| generate_public_component(p, &llr));
|
||||
|
||||
let popup_menu =
|
||||
llr.popup_menu.as_ref().map(|p| generate_item_tree(&p.item_tree, &llr, None, None, true));
|
||||
|
||||
let version_check = format_ident!(
|
||||
"VersionCheck_{}_{}_{}",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
|
|
@ -229,6 +232,7 @@ pub fn generate(
|
|||
#(#structs_and_enum_def)*
|
||||
#(#globals)*
|
||||
#(#sub_compos)*
|
||||
#popup_menu
|
||||
#(#public_components)*
|
||||
#shared_globals
|
||||
#(#resource_symbols)*
|
||||
|
|
@ -249,7 +253,7 @@ fn generate_public_component(
|
|||
let public_component_id = ident(&llr.name);
|
||||
let inner_component_id = inner_component_id(&llr.item_tree.root);
|
||||
|
||||
let component = generate_item_tree(&llr.item_tree, unit, None, None);
|
||||
let component = generate_item_tree(&llr.item_tree, unit, None, None, false);
|
||||
|
||||
let ctx = EvaluationContext {
|
||||
compilation_unit: unit,
|
||||
|
|
@ -680,7 +684,13 @@ fn generate_sub_component(
|
|||
.popup_windows
|
||||
.iter()
|
||||
.map(|popup| {
|
||||
generate_item_tree(&popup.item_tree, root, Some(ParentCtx::new(&ctx, None)), None)
|
||||
generate_item_tree(
|
||||
&popup.item_tree,
|
||||
root,
|
||||
Some(ParentCtx::new(&ctx, None)),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
@ -1479,6 +1489,7 @@ fn generate_item_tree(
|
|||
root: &llr::CompilationUnit,
|
||||
parent_ctx: Option<ParentCtx>,
|
||||
index_property: Option<llr::PropertyIndex>,
|
||||
is_popup_menu: bool,
|
||||
) -> TokenStream {
|
||||
let sub_comp = generate_sub_component(&sub_tree.root, root, parent_ctx, index_property, true);
|
||||
let inner_component_id = self::inner_component_id(&sub_tree.root);
|
||||
|
|
@ -1491,11 +1502,14 @@ fn generate_item_tree(
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let globals = if parent_ctx.is_some() {
|
||||
let globals = if is_popup_menu {
|
||||
quote!(globals)
|
||||
} else if parent_ctx.is_some() {
|
||||
quote!(parent.upgrade().unwrap().globals.get().unwrap().clone())
|
||||
} else {
|
||||
quote!(sp::Rc::new(SharedGlobals::new(sp::VRc::downgrade(&self_dyn_rc))))
|
||||
};
|
||||
let globals_arg = is_popup_menu.then(|| quote!(globals: sp::Rc<SharedGlobals>));
|
||||
|
||||
let embedding_function = if parent_ctx.is_some() {
|
||||
quote!(todo!("Components written in Rust can not get embedded yet."))
|
||||
|
|
@ -1577,7 +1591,7 @@ fn generate_item_tree(
|
|||
#sub_comp
|
||||
|
||||
impl #inner_component_id {
|
||||
pub fn new(#(parent: #parent_component_type)*) -> core::result::Result<sp::VRc<sp::ItemTreeVTable, Self>, slint::PlatformError> {
|
||||
fn new(#(parent: #parent_component_type,)* #globals_arg) -> core::result::Result<sp::VRc<sp::ItemTreeVTable, Self>, slint::PlatformError> {
|
||||
#![allow(unused)]
|
||||
slint::private_unstable_api::ensure_backend()?;
|
||||
let mut _self = Self::default();
|
||||
|
|
@ -1736,7 +1750,7 @@ fn generate_repeated_component(
|
|||
parent_ctx: ParentCtx,
|
||||
) -> TokenStream {
|
||||
let component =
|
||||
generate_item_tree(&repeated.sub_tree, unit, Some(parent_ctx), repeated.index_prop);
|
||||
generate_item_tree(&repeated.sub_tree, unit, Some(parent_ctx), repeated.index_prop, false);
|
||||
|
||||
let ctx = EvaluationContext {
|
||||
compilation_unit: unit,
|
||||
|
|
@ -2016,6 +2030,7 @@ fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) ->
|
|||
/// Helper to access a member property/callback of a component.
|
||||
///
|
||||
/// Because the parent can be deleted (issue #3464), this might be an option when accessing the parent
|
||||
#[derive(Clone)]
|
||||
enum MemberAccess {
|
||||
/// The token stream is just an expression to the member
|
||||
Direct(TokenStream),
|
||||
|
|
@ -2724,6 +2739,68 @@ fn compile_builtin_function_call(
|
|||
panic!("internal error: invalid args to ClosePopupWindow {:?}", arguments)
|
||||
}
|
||||
}
|
||||
BuiltinFunction::ShowPopupMenu => {
|
||||
let [Expression::PropertyReference(context_menu_ref), entries, position] = arguments
|
||||
else {
|
||||
panic!("internal error: invalid args to ShowPopupMenu {arguments:?}")
|
||||
};
|
||||
|
||||
let context_menu = access_member(context_menu_ref, ctx);
|
||||
let context_menu_rc = access_item_rc(context_menu_ref, ctx);
|
||||
let position = compile_expression(position, ctx);
|
||||
let entries = compile_expression(entries, ctx);
|
||||
let popup = ctx
|
||||
.compilation_unit
|
||||
.popup_menu
|
||||
.as_ref()
|
||||
.expect("there should be a popup menu if we want to show it");
|
||||
let popup_id = inner_component_id(&popup.item_tree.root);
|
||||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||
|
||||
let popup_ctx = EvaluationContext::new_sub_component(
|
||||
ctx.compilation_unit,
|
||||
&popup.item_tree.root,
|
||||
RustGeneratorContext { global_access: quote!(_self.globals.get().unwrap()) },
|
||||
None,
|
||||
);
|
||||
let access_entries = access_member(&popup.entries, &popup_ctx).unwrap();
|
||||
let forward_callback = |pr, cb| {
|
||||
let access = access_member(pr, &popup_ctx).unwrap();
|
||||
let call = context_menu
|
||||
.clone()
|
||||
.map_or_default(|context_menu| quote!(#context_menu.#cb.call(entry)));
|
||||
quote!(
|
||||
let self_weak = parent_weak.clone();
|
||||
#access.set_handler(move |entry| {
|
||||
let self_rc = self_weak.upgrade().unwrap();
|
||||
let _self = self_rc.as_pin_ref();
|
||||
#call
|
||||
}
|
||||
))
|
||||
};
|
||||
let fw_sub_menu = forward_callback(&popup.sub_menu, quote!(sub_menu));
|
||||
let fw_activated = forward_callback(&popup.activated, quote!(activated));
|
||||
quote!({
|
||||
let entries = #entries;
|
||||
let position = #position;
|
||||
let popup_instance = #popup_id::new(_self.globals.get().unwrap().clone()).unwrap();
|
||||
let popup_instance_vrc = sp::VRc::map(popup_instance.clone(), |x| x);
|
||||
let parent_weak = _self.self_weak.get().unwrap().clone();
|
||||
{
|
||||
let _self = popup_instance_vrc.as_pin_ref();
|
||||
#access_entries.set(entries);
|
||||
#fw_sub_menu;
|
||||
#fw_activated;
|
||||
};
|
||||
#popup_id::user_init(popup_instance_vrc.clone());
|
||||
sp::WindowInner::from_pub(#window_adapter_tokens.window()).show_popup(
|
||||
&sp::VRc::into_dyn(popup_instance.into()),
|
||||
position,
|
||||
sp::PopupClosePolicy::CloseOnClickOutside,
|
||||
#context_menu_rc,
|
||||
);
|
||||
})
|
||||
}
|
||||
BuiltinFunction::SetSelectionOffsets => {
|
||||
if let [llr::Expression::PropertyReference(pr), from, to] = arguments {
|
||||
let item = access_member(pr, ctx);
|
||||
|
|
|
|||
|
|
@ -380,7 +380,7 @@ impl BuiltinPropertyInfo {
|
|||
}
|
||||
|
||||
/// The base of an element
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, derive_more::From)]
|
||||
pub enum ElementType {
|
||||
/// The element is based of a component
|
||||
Component(Rc<Component>),
|
||||
|
|
|
|||
|
|
@ -275,6 +275,14 @@ pub struct PopupWindow {
|
|||
pub position: MutExpression,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PopupMenu {
|
||||
pub item_tree: ItemTree,
|
||||
pub sub_menu: PropertyReference,
|
||||
pub activated: PropertyReference,
|
||||
pub entries: PropertyReference,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Timer {
|
||||
pub interval: MutExpression,
|
||||
|
|
@ -348,7 +356,7 @@ impl std::fmt::Debug for SubComponentInstance {
|
|||
pub struct ItemTree {
|
||||
pub root: SubComponent,
|
||||
pub tree: TreeNode,
|
||||
/// This tree has a parent. e.g: it is a Repeater or a PopupMenu whose property can access
|
||||
/// This tree has a parent. e.g: it is a Repeater or a PopupWindow whose property can access
|
||||
/// the parent ItemTree.
|
||||
/// The String is the type of the parent ItemTree
|
||||
pub parent_context: Option<SmolStr>,
|
||||
|
|
@ -367,6 +375,7 @@ pub struct CompilationUnit {
|
|||
pub public_components: Vec<PublicComponent>,
|
||||
pub sub_components: Vec<Rc<SubComponent>>,
|
||||
pub globals: Vec<GlobalComponent>,
|
||||
pub popup_menu: Option<PopupMenu>,
|
||||
pub has_debug_info: bool,
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
pub translations: Option<super::translations::Translations>,
|
||||
|
|
@ -408,6 +417,9 @@ impl CompilationUnit {
|
|||
for p in &self.public_components {
|
||||
visit_component(self, &p.item_tree.root, visitor, None);
|
||||
}
|
||||
if let Some(p) = &self.popup_menu {
|
||||
visit_component(self, &p.item_tree.root, visitor, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_each_expression<'a>(
|
||||
|
|
|
|||
|
|
@ -62,6 +62,30 @@ pub fn lower_to_item_tree(
|
|||
})
|
||||
.collect();
|
||||
|
||||
let popup_menu = document.popup_menu_impl.as_ref().map(|c| {
|
||||
let sc = lower_sub_component(&c, &state, None, compiler_config);
|
||||
let item_tree = ItemTree {
|
||||
tree: make_tree(&state, &c.root_element, &sc, &[]),
|
||||
root: Rc::try_unwrap(sc.sub_component).unwrap(),
|
||||
parent_context: None,
|
||||
};
|
||||
PopupMenu {
|
||||
item_tree,
|
||||
sub_menu: sc.mapping.map_property_reference(
|
||||
&NamedReference::new(&c.root_element, SmolStr::new_static("sub-menu")),
|
||||
&state,
|
||||
),
|
||||
activated: sc.mapping.map_property_reference(
|
||||
&NamedReference::new(&c.root_element, SmolStr::new_static("activated")),
|
||||
&state,
|
||||
),
|
||||
entries: sc.mapping.map_property_reference(
|
||||
&NamedReference::new(&c.root_element, SmolStr::new_static("entries")),
|
||||
&state,
|
||||
),
|
||||
}
|
||||
});
|
||||
|
||||
let root = CompilationUnit {
|
||||
public_components,
|
||||
globals,
|
||||
|
|
@ -75,6 +99,7 @@ pub fn lower_to_item_tree(
|
|||
})
|
||||
.collect(),
|
||||
has_debug_info: compiler_config.debug_info,
|
||||
popup_menu,
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
translations: state.translation_builder.take().map(|x| x.into_inner().result()),
|
||||
};
|
||||
|
|
@ -691,7 +716,7 @@ fn lower_popup_component(
|
|||
|
||||
use super::Expression::PropertyReference as PR;
|
||||
let position = super::lower_expression::make_struct(
|
||||
"Point",
|
||||
"LogicalPosition",
|
||||
[
|
||||
("x", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.x, ctx.state))),
|
||||
("y", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.y, ctx.state))),
|
||||
|
|
|
|||
|
|
@ -97,7 +97,9 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
|
|||
BuiltinFunction::Log => 10,
|
||||
BuiltinFunction::Pow => 10,
|
||||
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => isize::MAX,
|
||||
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => isize::MAX,
|
||||
BuiltinFunction::ShowPopupWindow
|
||||
| BuiltinFunction::ClosePopupWindow
|
||||
| BuiltinFunction::ShowPopupMenu => isize::MAX,
|
||||
BuiltinFunction::SetSelectionOffsets => isize::MAX,
|
||||
BuiltinFunction::ItemMemberFunction(..) => isize::MAX,
|
||||
BuiltinFunction::ItemFontMetrics => PROPERTY_ACCESS_COST,
|
||||
|
|
|
|||
|
|
@ -390,6 +390,7 @@ mod plural_rule_parser {
|
|||
globals: Vec::new(),
|
||||
has_debug_info: false,
|
||||
translations: None,
|
||||
popup_menu: None,
|
||||
},
|
||||
current_sub_component: None,
|
||||
current_global: None,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@ pub struct Document {
|
|||
|
||||
/// The list of used extra types used recursively.
|
||||
pub used_types: RefCell<UsedSubTypes>,
|
||||
|
||||
/// The popup_menu_impl
|
||||
pub popup_menu_impl: Option<Rc<Component>>,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
|
|
@ -243,6 +246,7 @@ impl Document {
|
|||
exports,
|
||||
embedded_file_resources: Default::default(),
|
||||
used_types: Default::default(),
|
||||
popup_menu_impl: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -272,6 +276,9 @@ impl Document {
|
|||
for c in &used_types.globals {
|
||||
v(c);
|
||||
}
|
||||
if let Some(c) = &self.popup_menu_impl {
|
||||
v(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ mod lower_absolute_coordinates;
|
|||
mod lower_accessibility;
|
||||
mod lower_component_container;
|
||||
mod lower_layout;
|
||||
mod lower_menus;
|
||||
mod lower_popups;
|
||||
mod lower_property_to_element;
|
||||
mod lower_shadows;
|
||||
|
|
@ -96,6 +97,7 @@ pub async fn run_passes(
|
|||
);
|
||||
});
|
||||
lower_tabwidget::lower_tabwidget(doc, type_loader, diag).await;
|
||||
lower_menus::lower_menus(doc, type_loader, diag).await;
|
||||
collect_subcomponents::collect_subcomponents(doc);
|
||||
|
||||
doc.visit_all_used_components(|component| {
|
||||
|
|
|
|||
|
|
@ -24,12 +24,10 @@ pub fn collect_globals(doc: &Document, _diag: &mut BuildDiagnostics) {
|
|||
}
|
||||
}
|
||||
}
|
||||
for component in doc.exported_roots() {
|
||||
collect_in_component(&component, &mut set, &mut sorted_globals);
|
||||
}
|
||||
for component in &doc.used_types.borrow().sub_components {
|
||||
collect_in_component(component, &mut set, &mut sorted_globals);
|
||||
}
|
||||
doc.visit_all_used_components(|component| {
|
||||
collect_in_component(component, &mut set, &mut sorted_globals)
|
||||
});
|
||||
|
||||
doc.used_types.borrow_mut().globals = sorted_globals;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use std::rc::Rc;
|
|||
pub fn collect_subcomponents(doc: &Document) {
|
||||
let mut result = vec![];
|
||||
let mut hash = HashSet::new();
|
||||
for component in doc.exported_roots() {
|
||||
for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
|
||||
collect_subcomponents_recursive(&component, &mut result, &mut hash);
|
||||
}
|
||||
doc.used_types.borrow_mut().sub_components = result;
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ pub fn inline(doc: &Document, inline_selection: InlineSelection, diag: &mut Buil
|
|||
}
|
||||
let mut roots = HashSet::new();
|
||||
if inline_selection == InlineSelection::InlineOnlyRequiredComponents {
|
||||
for component in doc.exported_roots() {
|
||||
for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
|
||||
roots.insert(ByAddress(component.clone()));
|
||||
}
|
||||
}
|
||||
for component in doc.exported_roots() {
|
||||
for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
|
||||
inline_components_recursively(&component, &roots, inline_selection, diag);
|
||||
let mut init_code = component.init_code.borrow_mut();
|
||||
let inlined_init_code = core::mem::take(&mut init_code.inlined_init_code);
|
||||
|
|
|
|||
164
internal/compiler/passes/lower_menus.rs
Normal file
164
internal/compiler/passes/lower_menus.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// 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
|
||||
//!
|
||||
//! Must be done before inlining and many other passes because the lowered code must
|
||||
//! be further inlined as it may expends to native widget that needs inlining
|
||||
|
||||
use crate::diagnostics::BuildDiagnostics;
|
||||
use crate::expression_tree::NamedReference;
|
||||
use crate::langtype::ElementType;
|
||||
use crate::object_tree::*;
|
||||
use smol_str::{format_smolstr, SmolStr};
|
||||
|
||||
struct UsefulMenuComponents {
|
||||
menubar_impl: ElementType,
|
||||
vertical_layout: ElementType,
|
||||
empty: ElementType,
|
||||
}
|
||||
|
||||
pub async fn lower_menus(
|
||||
doc: &mut Document,
|
||||
type_loader: &mut crate::typeloader::TypeLoader,
|
||||
diag: &mut BuildDiagnostics,
|
||||
) {
|
||||
// Ignore import errors
|
||||
let mut build_diags_to_ignore = BuildDiagnostics::default();
|
||||
|
||||
let menubar_impl = type_loader
|
||||
.import_component("std-widgets.slint", "MenuBarImpl", &mut build_diags_to_ignore)
|
||||
.await
|
||||
.expect("MenuBarImpl should be in std-widgets.slint");
|
||||
let useful_menu_component = UsefulMenuComponents {
|
||||
menubar_impl: menubar_impl.clone().into(),
|
||||
vertical_layout: type_loader
|
||||
.global_type_registry
|
||||
.borrow()
|
||||
.lookup_builtin_element("VerticalLayout")
|
||||
.expect("VerticalLayout is a builtin type"),
|
||||
empty: type_loader.global_type_registry.borrow().empty_type(),
|
||||
};
|
||||
|
||||
let mut has_menu = false;
|
||||
|
||||
doc.visit_all_used_components(|component| {
|
||||
recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
|
||||
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 b.name == "ContextMenu") {
|
||||
has_menu |= process_context_menu(elem, &useful_menu_component, diag);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if has_menu {
|
||||
let popup_menu_impl = type_loader
|
||||
.import_component("std-widgets.slint", "PopupMenuImpl", &mut build_diags_to_ignore)
|
||||
.await
|
||||
.expect("PopupMenuImpl should be in std-widgets.slint");
|
||||
{
|
||||
let mut root = popup_menu_impl.root_element.borrow_mut();
|
||||
|
||||
for prop in ["entries", "sub-menu", "activated"] {
|
||||
match root.property_declarations.get_mut(prop) {
|
||||
Some(d) => d.expose_in_public_api = true,
|
||||
None => diag.push_error(format!("PopupMenuImpl doesn't have {prop}"), &*root),
|
||||
}
|
||||
}
|
||||
root.property_analysis.borrow_mut().entry("entries".into()).or_default().is_set = true;
|
||||
}
|
||||
|
||||
recurse_elem_including_sub_components_no_borrow(&popup_menu_impl, &(), &mut |elem, _| {
|
||||
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "ContextMenu") {
|
||||
process_context_menu(elem, &useful_menu_component, diag);
|
||||
}
|
||||
});
|
||||
recurse_elem_including_sub_components_no_borrow(&menubar_impl, &(), &mut |elem, _| {
|
||||
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "ContextMenu") {
|
||||
process_context_menu(elem, &useful_menu_component, diag);
|
||||
}
|
||||
});
|
||||
doc.popup_menu_impl = popup_menu_impl.into();
|
||||
}
|
||||
}
|
||||
|
||||
fn process_context_menu(
|
||||
_context_menu_elem: &ElementRc,
|
||||
_useful_menu_components: &UsefulMenuComponents,
|
||||
_diag: &mut BuildDiagnostics,
|
||||
) -> bool {
|
||||
// TODO:
|
||||
true
|
||||
}
|
||||
|
||||
fn process_window(
|
||||
win: &ElementRc,
|
||||
components: &UsefulMenuComponents,
|
||||
diag: &mut BuildDiagnostics,
|
||||
) -> bool {
|
||||
/* if matches!(&elem.borrow_mut().base_type, ElementType::Builtin(_)) {
|
||||
// That's the TabWidget re-exported from the style, it doesn't need to be processed
|
||||
return;
|
||||
}*/
|
||||
|
||||
let mut window = win.borrow_mut();
|
||||
let mut menu_bar = None;
|
||||
window.children.retain(|x| {
|
||||
if matches!(&x.borrow().base_type, ElementType::Builtin(b) if b.name == "MenuBar") {
|
||||
if menu_bar.is_some() {
|
||||
diag.push_error("Only one MenuBar is allowed in a Window".into(), &*x.borrow());
|
||||
} else {
|
||||
menu_bar = Some(x.clone());
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
let Some(menu_bar) = menu_bar else {
|
||||
return false;
|
||||
};
|
||||
menu_bar.borrow_mut().base_type = components.menubar_impl.clone();
|
||||
|
||||
// Create a child that contains all the child but the menubar
|
||||
let child = Element {
|
||||
id: format_smolstr!("{}-child", window.id),
|
||||
base_type: components.empty.clone(),
|
||||
enclosing_component: window.enclosing_component.clone(),
|
||||
children: std::mem::take(&mut window.children),
|
||||
..Default::default()
|
||||
}
|
||||
.make_rc();
|
||||
|
||||
const HEIGHT: &str = "height";
|
||||
let child_height = NamedReference::new(&child, SmolStr::new_static(HEIGHT));
|
||||
|
||||
// Create a layout
|
||||
let layout = Element {
|
||||
id: format_smolstr!("{}-menulayout", window.id),
|
||||
base_type: components.vertical_layout.clone(),
|
||||
enclosing_component: window.enclosing_component.clone(),
|
||||
children: vec![menu_bar, child],
|
||||
..Default::default()
|
||||
}
|
||||
.make_rc();
|
||||
|
||||
window.children.push(layout);
|
||||
let component = window.enclosing_component.upgrade().unwrap();
|
||||
|
||||
drop(window);
|
||||
|
||||
// Rename every access to `root.height` into `child.height`
|
||||
let win_height = NamedReference::new(win, SmolStr::new_static(HEIGHT));
|
||||
crate::object_tree::visit_all_named_references(&component, &mut |nr| {
|
||||
if nr == &win_height {
|
||||
*nr = child_height.clone()
|
||||
}
|
||||
});
|
||||
// except for the actual geometry
|
||||
win.borrow_mut().geometry_props.as_mut().unwrap().height = win_height;
|
||||
true
|
||||
}
|
||||
|
|
@ -13,13 +13,11 @@ use std::rc::Rc;
|
|||
/// It currently does so by adding a number to the existing id
|
||||
pub fn assign_unique_id(doc: &Document) {
|
||||
let mut count = 0;
|
||||
for component in doc.exported_roots() {
|
||||
assign_unique_id_in_component(&component, &mut count);
|
||||
}
|
||||
for c in &doc.used_types.borrow().sub_components {
|
||||
assign_unique_id_in_component(c, &mut count);
|
||||
}
|
||||
|
||||
doc.visit_all_used_components(|component| {
|
||||
if !component.is_global() {
|
||||
assign_unique_id_in_component(component, &mut count)
|
||||
}
|
||||
});
|
||||
rename_globals(doc, count);
|
||||
}
|
||||
|
||||
|
|
|
|||
32
internal/compiler/tests/syntax/elements/menubar.slint
Normal file
32
internal/compiler/tests/syntax/elements/menubar.slint
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// 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
|
||||
|
||||
|
||||
export component A inherits Window {
|
||||
mb := MenuBar {
|
||||
}
|
||||
MenuBar {
|
||||
// ^error{Only one MenuBar is allowed in a Window}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: 45px;
|
||||
}
|
||||
Rectangle {
|
||||
x: mb.absolute-position.x;
|
||||
// ^error{Element 'MenuBar' does not have a property 'absolute-position'}
|
||||
y: mb.height;
|
||||
// ^error{Element 'MenuBar' does not have a property 'height'}
|
||||
init => {
|
||||
mb.focus();
|
||||
// ^error{Element 'MenuBar' does not have a property 'focus'}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// TESTS TODO
|
||||
// - test that setting the window height sets it to the window height plus that of the menubar
|
||||
30
internal/compiler/tests/syntax/elements/menubar2.slint
Normal file
30
internal/compiler/tests/syntax/elements/menubar2.slint
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// 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
|
||||
|
||||
|
||||
export component MyMenu inherits MenuBar {
|
||||
// ^error{MenuBar can only be within a Window element}
|
||||
}
|
||||
|
||||
export component A inherits Window {
|
||||
MenuBar {
|
||||
Rectangle {}
|
||||
// ^error{MenuBar cannot have children elements}
|
||||
x: 45px;
|
||||
// ^error{Unknown property x in MenuBar}
|
||||
width: 45px;
|
||||
// ^error{Unknown property width in MenuBar}
|
||||
entries: [];
|
||||
init => {}
|
||||
// ^error{'init' is not a callback in MenuBar}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: 45px;
|
||||
MenuBar {
|
||||
// ^error{MenuBar can only be within a Window element}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -261,6 +261,9 @@ impl Snapshotter {
|
|||
document.inner_components.iter().for_each(|ic| {
|
||||
let _ = self.create_component(ic);
|
||||
});
|
||||
if let Some(popup_menu_impl) = &document.popup_menu_impl {
|
||||
let _ = self.create_component(popup_menu_impl);
|
||||
}
|
||||
}
|
||||
|
||||
fn snapshot_document(&mut self, document: &object_tree::Document) -> object_tree::Document {
|
||||
|
|
@ -284,6 +287,10 @@ impl Snapshotter {
|
|||
exports,
|
||||
embedded_file_resources: document.embedded_file_resources.clone(),
|
||||
used_types: RefCell::new(self.snapshot_used_sub_types(&document.used_types.borrow())),
|
||||
popup_menu_impl: document.popup_menu_impl.as_ref().map(|p| {
|
||||
Weak::upgrade(&self.use_component(p))
|
||||
.expect("Components can get upgraded at this point")
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -399,6 +399,7 @@ impl TypeRegister {
|
|||
($pub_type:ident, i32) => { Type::Int32 };
|
||||
($pub_type:ident, f32) => { Type::Float32 };
|
||||
($pub_type:ident, SharedString) => { Type::String };
|
||||
($pub_type:ident, Image) => { Type::Image };
|
||||
($pub_type:ident, Coord) => { Type::LogicalLength };
|
||||
($pub_type:ident, KeyboardModifiers) => { $pub_type.clone() };
|
||||
($pub_type:ident, $_:ident) => {
|
||||
|
|
@ -472,6 +473,19 @@ impl TypeRegister {
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match &mut register.elements.get_mut("ContextMenu").unwrap() {
|
||||
ElementType::Builtin(ref mut b) => {
|
||||
let b = Rc::get_mut(b).unwrap();
|
||||
b.properties.insert(
|
||||
"show".into(),
|
||||
BuiltinPropertyInfo::new(Type::Function(BuiltinFunction::ShowPopupMenu.ty())),
|
||||
);
|
||||
b.member_functions.insert("show".into(), BuiltinFunction::ShowPopupMenu);
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let font_metrics_prop = crate::langtype::BuiltinPropertyInfo {
|
||||
ty: font_metrics_type(),
|
||||
property_visibility: PropertyVisibility::Output,
|
||||
|
|
|
|||
112
internal/compiler/widgets/common/menus.slint
Normal file
112
internal/compiler/widgets/common/menus.slint
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// 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
|
||||
|
||||
//! This file contains a generic implementation of the MenuBar and ContextMenu
|
||||
|
||||
import { Palette } from "std-widgets-impl.slint";
|
||||
|
||||
export component PopupMenuImpl inherits Window {
|
||||
property <length> px: 1rem / 14;
|
||||
in property <[MenuEntry]> entries: [];
|
||||
callback sub-menu(MenuEntry) -> [MenuEntry];
|
||||
callback activated(MenuEntry);
|
||||
|
||||
Rectangle {
|
||||
border-radius: 7*px;
|
||||
border-color: Palette.border;
|
||||
background: Palette.background;
|
||||
drop-shadow-blur: 2px;
|
||||
drop-shadow-color: Palette.foreground.transparentize(0.5);
|
||||
min-width: 10rem;
|
||||
VerticalLayout {
|
||||
padding: 5px;
|
||||
for entry in entries: Rectangle {
|
||||
background: ita.has-hover || ita.pressed ? Palette.alternate-background : transparent;
|
||||
border-radius: 3*px;
|
||||
border-width: 1px;
|
||||
HorizontalLayout {
|
||||
spacing: 7*px;
|
||||
padding: 11*px;
|
||||
padding-top: 4*px;
|
||||
padding-bottom: 6*px;
|
||||
Text {
|
||||
text: entry.title;
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
if entry.has-sub-menu : Text {
|
||||
text: "▶";
|
||||
horizontal-stretch: 0;
|
||||
}
|
||||
}
|
||||
ita := TouchArea {
|
||||
clicked => {
|
||||
if entry.has-sub-menu {
|
||||
subMenu.show(sub-menu(entry), {
|
||||
x: root.width,
|
||||
y: self.absolute-position.y - subMenu.absolute-position.y,
|
||||
});
|
||||
} else {
|
||||
activated(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subMenu := ContextMenu {
|
||||
x: 0; y: 0; width: 0; height: 0;
|
||||
sub-menu(entry) => { root.sub-menu(entry); }
|
||||
activated(entry) => { root.activated(entry); }
|
||||
}
|
||||
}
|
||||
|
||||
export component MenuBarImpl {
|
||||
callback activated(MenuEntry);
|
||||
callback sub-menu(MenuEntry) -> [MenuEntry];
|
||||
|
||||
property <[MenuEntry]> entries;
|
||||
|
||||
property <length> px: 1rem / 14;
|
||||
|
||||
preferred-width: 100%;
|
||||
height: l.preferred-height;
|
||||
l := HorizontalLayout {
|
||||
padding: 5*px;
|
||||
alignment: start;
|
||||
spacing: 1*px;
|
||||
for entry in entries: e := Rectangle {
|
||||
background: ta.has-hover || ta.pressed ? Palette.alternate-background : transparent;
|
||||
border-radius: 3*px;
|
||||
HorizontalLayout {
|
||||
padding: 11*px;
|
||||
padding-top: 4*px;
|
||||
padding-bottom: 6*px;
|
||||
Text {
|
||||
text: entry.title;
|
||||
}
|
||||
}
|
||||
ta := TouchArea {
|
||||
clicked => {
|
||||
cm.show(sub-menu(entry), { x: e.x, y: root.height });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For the default size when there is no entries
|
||||
Rectangle {
|
||||
HorizontalLayout {
|
||||
padding-top: 0.4rem;
|
||||
padding-bottom: 0.6rem;
|
||||
Text { text: ""; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cm := ContextMenu {
|
||||
activated(entry) => { root.activated(entry); }
|
||||
sub-menu(entry) => { root.sub-menu(entry); }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -22,5 +22,5 @@ export { Switch } from "switch.slint";
|
|||
export { TextEdit } from "textedit.slint";
|
||||
export { TimePickerPopup, Time } from "time-picker.slint";
|
||||
export { DatePickerPopup, Date } from "datepicker.slint";
|
||||
|
||||
export { MenuBarImpl, PopupMenuImpl } from "../common/menus.slint";
|
||||
export * from "tableview.slint";
|
||||
|
|
|
|||
|
|
@ -22,4 +22,5 @@ export { Switch } from "switch.slint";
|
|||
export { TextEdit } from "textedit.slint";
|
||||
export { TimePickerPopup, Time } from "time-picker.slint";
|
||||
export { DatePickerPopup, Date } from "./datepicker.slint";
|
||||
export { MenuBarImpl, PopupMenuImpl } from "../common/menus.slint";
|
||||
export * from "tableview.slint";
|
||||
|
|
|
|||
|
|
@ -22,4 +22,5 @@ export { Switch } from "switch.slint";
|
|||
export { TextEdit } from "textedit.slint";
|
||||
export { TimePickerPopup, Time } from "time-picker.slint";
|
||||
export { DatePickerPopup, Date } from "datepicker.slint";
|
||||
export { MenuBarImpl, PopupMenuImpl } from "../common/menus.slint";
|
||||
export * from "tableview.slint";
|
||||
|
|
|
|||
|
|
@ -21,3 +21,4 @@ export { Spinner } from "spinner.slint";
|
|||
export { TextEdit } from "textedit.slint";
|
||||
export { TimePickerPopup, Time } from "time-picker.slint";
|
||||
export { DatePickerPopup, Date } from "./datepicker.slint";
|
||||
export { MenuBarImpl, PopupMenuImpl } from "../common/menus.slint";
|
||||
|
|
@ -20,3 +20,4 @@ export { TimePickerPopup, Time } from "time-picker.slint";
|
|||
export { StandardListView, ListView } from "../common/listview.slint";
|
||||
export { TextEdit } from "textedit.slint";
|
||||
export { DatePickerPopup, Date } from "./datepicker.slint";
|
||||
export { MenuBarImpl, PopupMenuImpl } from "../common/menus.slint";
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ use crate::lengths::{
|
|||
#[cfg(feature = "rtti")]
|
||||
use crate::rtti::*;
|
||||
use crate::window::{WindowAdapter, WindowAdapterRc};
|
||||
use crate::{Coord, Property, SharedString};
|
||||
use crate::{Callback, Coord, Property, SharedString};
|
||||
use alloc::rc::Rc;
|
||||
use const_field_offset::FieldOffsets;
|
||||
use core::pin::Pin;
|
||||
|
|
@ -66,6 +66,8 @@ pub type KeyEventArg = (KeyEvent,);
|
|||
type PointerEventArg = (PointerEvent,);
|
||||
type PointerScrollEventArg = (PointerScrollEvent,);
|
||||
type PointArg = (Point,);
|
||||
type MenuEntryArg = (MenuEntry,);
|
||||
type MenuEntryModel = crate::model::ModelRc<MenuEntry>;
|
||||
|
||||
#[cfg(all(feature = "ffi", windows))]
|
||||
#[macro_export]
|
||||
|
|
@ -1055,6 +1057,85 @@ declare_item_vtable! {
|
|||
fn slint_get_WindowItemVTable() -> WindowItemVTable for WindowItem
|
||||
}
|
||||
|
||||
/// The implementation of the `Window` element
|
||||
#[repr(C)]
|
||||
#[derive(FieldOffsets, Default, SlintElement)]
|
||||
#[pin]
|
||||
pub struct ContextMenu {
|
||||
//pub entries: Property<crate::model::ModelRc<MenuEntry>>,
|
||||
pub sub_menu: Callback<MenuEntryArg, MenuEntryModel>,
|
||||
pub activated: Callback<MenuEntryArg>,
|
||||
pub cached_rendering_data: CachedRenderingData,
|
||||
}
|
||||
|
||||
impl Item for ContextMenu {
|
||||
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
|
||||
|
||||
fn layout_info(
|
||||
self: Pin<&Self>,
|
||||
_orientation: Orientation,
|
||||
_window_adapter: &Rc<dyn WindowAdapter>,
|
||||
) -> LayoutInfo {
|
||||
LayoutInfo::default()
|
||||
}
|
||||
|
||||
fn input_event_filter_before_children(
|
||||
self: Pin<&Self>,
|
||||
_: MouseEvent,
|
||||
_window_adapter: &Rc<dyn WindowAdapter>,
|
||||
_self_rc: &ItemRc,
|
||||
) -> InputEventFilterResult {
|
||||
InputEventFilterResult::ForwardAndIgnore
|
||||
}
|
||||
|
||||
fn input_event(
|
||||
self: Pin<&Self>,
|
||||
_event: MouseEvent,
|
||||
_window_adapter: &Rc<dyn WindowAdapter>,
|
||||
_self_rc: &ItemRc,
|
||||
) -> InputEventResult {
|
||||
InputEventResult::EventIgnored
|
||||
}
|
||||
|
||||
fn key_event(
|
||||
self: Pin<&Self>,
|
||||
_: &KeyEvent,
|
||||
_window_adapter: &Rc<dyn WindowAdapter>,
|
||||
_self_rc: &ItemRc,
|
||||
) -> KeyEventResult {
|
||||
KeyEventResult::EventIgnored
|
||||
}
|
||||
|
||||
fn focus_event(
|
||||
self: Pin<&Self>,
|
||||
_: &FocusEvent,
|
||||
_window_adapter: &Rc<dyn WindowAdapter>,
|
||||
_self_rc: &ItemRc,
|
||||
) -> FocusEventResult {
|
||||
FocusEventResult::FocusIgnored
|
||||
}
|
||||
|
||||
fn render(
|
||||
self: Pin<&Self>,
|
||||
_backend: &mut ItemRendererRef,
|
||||
_self_rc: &ItemRc,
|
||||
_size: LogicalSize,
|
||||
) -> RenderingResult {
|
||||
RenderingResult::ContinueRenderingChildren
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextMenu {}
|
||||
|
||||
impl ItemConsts for ContextMenu {
|
||||
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
||||
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
||||
}
|
||||
|
||||
declare_item_vtable! {
|
||||
fn slint_get_ContextMenuVTable() -> ContextMenuVTable for ContextMenu
|
||||
}
|
||||
|
||||
/// The implementation of the `BoxShadow` element
|
||||
#[repr(C)]
|
||||
#[derive(FieldOffsets, Default, SlintElement)]
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ macro_rules! declare_ValueType_2 {
|
|||
crate::component_factory::ComponentFactory,
|
||||
crate::api::LogicalPosition,
|
||||
crate::items::FontMetrics,
|
||||
crate::items::MenuEntry,
|
||||
crate::model::ModelRc<crate::items::MenuEntry>,
|
||||
$(crate::items::$Name,)*
|
||||
];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ use crate::api::{
|
|||
CloseRequestResponse, LogicalPosition, PhysicalPosition, PhysicalSize, PlatformError, Window,
|
||||
WindowPosition, WindowSize,
|
||||
};
|
||||
use crate::graphics::Point;
|
||||
use crate::input::{
|
||||
key_codes, ClickState, InternalKeyboardModifierState, KeyEvent, KeyEventType, MouseEvent,
|
||||
MouseInputState, TextCursorBlinker,
|
||||
|
|
@ -981,13 +980,12 @@ impl WindowInner {
|
|||
pub fn show_popup(
|
||||
&self,
|
||||
popup_componentrc: &ItemTreeRc,
|
||||
position: Point,
|
||||
position: LogicalPosition,
|
||||
close_policy: PopupClosePolicy,
|
||||
parent_item: &ItemRc,
|
||||
) -> NonZeroU32 {
|
||||
let position = parent_item.map_to_window(
|
||||
parent_item.geometry().origin + LogicalPoint::from_untyped(position).to_vector(),
|
||||
);
|
||||
let position = parent_item
|
||||
.map_to_window(parent_item.geometry().origin + position.to_euclid().to_vector());
|
||||
let root_of = |mut item_tree: ItemTreeRc| loop {
|
||||
if ItemRc::new(item_tree.clone(), 0).downcast::<crate::items::WindowItem>().is_some() {
|
||||
return item_tree;
|
||||
|
|
@ -1087,6 +1085,20 @@ impl WindowInner {
|
|||
popup_id
|
||||
}
|
||||
|
||||
/// Attempt to show a native popup menu
|
||||
///
|
||||
/// context_menu_item is an instance of a ContextMenu
|
||||
///
|
||||
/// Returns false if the native platform doesn't support it
|
||||
pub fn show_native_popup_menu(
|
||||
&self,
|
||||
_context_menu_item: &ItemRc,
|
||||
_position: LogicalPosition,
|
||||
) -> bool {
|
||||
// TODO
|
||||
false
|
||||
}
|
||||
|
||||
// Close the popup associated with the given popup window.
|
||||
fn close_popup_impl(&self, current_popup: &PopupWindow) {
|
||||
match ¤t_popup.location {
|
||||
|
|
@ -1401,7 +1413,7 @@ pub mod ffi {
|
|||
pub unsafe extern "C" fn slint_windowrc_show_popup(
|
||||
handle: *const WindowAdapterRcOpaque,
|
||||
popup: &ItemTreeRc,
|
||||
position: crate::graphics::Point,
|
||||
position: LogicalPosition,
|
||||
close_policy: PopupClosePolicy,
|
||||
parent_item: &ItemRc,
|
||||
) -> NonZeroU32 {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use i_slint_compiler::{generator, object_tree, parser, CompilerConfiguration};
|
|||
use i_slint_core::accessibility::{
|
||||
AccessibilityAction, AccessibleStringProperty, SupportedAccessibilityAction,
|
||||
};
|
||||
use i_slint_core::api::LogicalPosition;
|
||||
use i_slint_core::component_factory::ComponentFactory;
|
||||
use i_slint_core::item_tree::{
|
||||
IndexRange, ItemTree, ItemTreeRef, ItemTreeRefPin, ItemTreeVTable, ItemTreeWeak,
|
||||
|
|
@ -43,6 +44,7 @@ use smol_str::{SmolStr, ToSmolStr};
|
|||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::num::NonZeroU32;
|
||||
use std::rc::Weak;
|
||||
use std::{pin::Pin, rc::Rc};
|
||||
|
||||
pub const SPECIAL_PROPERTY_INDEX: &str = "$index";
|
||||
|
|
@ -415,6 +417,8 @@ pub struct ItemTreeDescription<'id> {
|
|||
/// Map of element IDs to their active popup's ID
|
||||
popup_ids: std::cell::RefCell<HashMap<SmolStr, NonZeroU32>>,
|
||||
|
||||
pub(crate) popup_menu_description: PopupMenuDescription,
|
||||
|
||||
/// The collection of compiled globals
|
||||
compiled_globals: Option<Rc<CompiledGlobalCollection>>,
|
||||
|
||||
|
|
@ -430,6 +434,20 @@ pub struct ItemTreeDescription<'id> {
|
|||
std::cell::OnceCell<Option<std::rc::Rc<i_slint_compiler::typeloader::TypeLoader>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, derive_more::From)]
|
||||
pub(crate) enum PopupMenuDescription {
|
||||
Rc(Rc<ErasedItemTreeDescription>),
|
||||
Weak(Weak<ErasedItemTreeDescription>),
|
||||
}
|
||||
impl PopupMenuDescription {
|
||||
pub fn unerase<'id>(&self, guard: generativity::Guard<'id>) -> Rc<ItemTreeDescription<'id>> {
|
||||
match self {
|
||||
PopupMenuDescription::Rc(rc) => rc.unerase(guard).clone(),
|
||||
PopupMenuDescription::Weak(weak) => weak.upgrade().unwrap().unerase(guard).clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_properties_to_public<'a>(
|
||||
prop_iter: impl Iterator<Item = (&'a SmolStr, &'a PropertyDeclaration)> + 'a,
|
||||
) -> impl Iterator<Item = (SmolStr, i_slint_compiler::langtype::Type)> + 'a {
|
||||
|
|
@ -890,10 +908,31 @@ pub async fn load(
|
|||
let compiled_globals = Rc::new(CompiledGlobalCollection::compile(doc));
|
||||
let mut components = HashMap::new();
|
||||
|
||||
let popup_menu_description = if let Some(popup_menu_impl) = &doc.popup_menu_impl {
|
||||
PopupMenuDescription::Rc(Rc::new_cyclic(|weak| {
|
||||
generativity::make_guard!(guard);
|
||||
ErasedItemTreeDescription::from(generate_item_tree(
|
||||
popup_menu_impl,
|
||||
Some(compiled_globals.clone()),
|
||||
PopupMenuDescription::Weak(weak.clone()),
|
||||
true,
|
||||
guard,
|
||||
))
|
||||
}))
|
||||
} else {
|
||||
PopupMenuDescription::Weak(Default::default())
|
||||
};
|
||||
|
||||
for c in doc.exported_roots() {
|
||||
generativity::make_guard!(guard);
|
||||
#[allow(unused_mut)]
|
||||
let mut it = generate_item_tree(&c, Some(compiled_globals.clone()), guard);
|
||||
let mut it = generate_item_tree(
|
||||
&c,
|
||||
Some(compiled_globals.clone()),
|
||||
popup_menu_description.clone(),
|
||||
false,
|
||||
guard,
|
||||
);
|
||||
#[cfg(feature = "highlight")]
|
||||
{
|
||||
let _ = it.type_loader.set(loader.clone());
|
||||
|
|
@ -966,6 +1005,7 @@ fn generate_rtti() -> HashMap<&'static str, Rc<ItemRTTI>> {
|
|||
rtti_for::<Rotate>(),
|
||||
rtti_for::<Opacity>(),
|
||||
rtti_for::<Layer>(),
|
||||
rtti_for::<ContextMenu>(),
|
||||
]
|
||||
.iter()
|
||||
.cloned(),
|
||||
|
|
@ -996,6 +1036,8 @@ fn generate_rtti() -> HashMap<&'static str, Rc<ItemRTTI>> {
|
|||
pub(crate) fn generate_item_tree<'id>(
|
||||
component: &Rc<object_tree::Component>,
|
||||
compiled_globals: Option<Rc<CompiledGlobalCollection>>,
|
||||
popup_menu_description: PopupMenuDescription,
|
||||
is_popup_menu_impl: bool,
|
||||
guard: generativity::Guard<'id>,
|
||||
) -> Rc<ItemTreeDescription<'id>> {
|
||||
//dbg!(&*component.root_element.borrow());
|
||||
|
|
@ -1014,6 +1056,7 @@ pub(crate) fn generate_item_tree<'id>(
|
|||
repeater: Vec<ErasedRepeaterWithinComponent<'id>>,
|
||||
repeater_names: HashMap<SmolStr, usize>,
|
||||
change_callbacks: Vec<(NamedReference, Expression)>,
|
||||
popup_menu_description: PopupMenuDescription,
|
||||
}
|
||||
impl<'id> generator::ItemTreeBuilder for TreeBuilder<'id> {
|
||||
type SubComponentState = ();
|
||||
|
|
@ -1033,7 +1076,13 @@ pub(crate) fn generate_item_tree<'id>(
|
|||
generativity::make_guard!(guard);
|
||||
self.repeater.push(
|
||||
RepeaterWithinItemTree {
|
||||
item_tree_to_repeat: generate_item_tree(base_component, None, guard),
|
||||
item_tree_to_repeat: generate_item_tree(
|
||||
base_component,
|
||||
None,
|
||||
self.popup_menu_description.clone(),
|
||||
false,
|
||||
guard,
|
||||
),
|
||||
offset: self.type_builder.add_field_type::<Repeater<ErasedItemTreeBox>>(),
|
||||
model: item.repeated.as_ref().unwrap().model.clone(),
|
||||
}
|
||||
|
|
@ -1126,6 +1175,7 @@ pub(crate) fn generate_item_tree<'id>(
|
|||
repeater: vec![],
|
||||
repeater_names: HashMap::new(),
|
||||
change_callbacks: vec![],
|
||||
popup_menu_description,
|
||||
};
|
||||
|
||||
if !component.is_global() {
|
||||
|
|
@ -1272,11 +1322,12 @@ pub(crate) fn generate_item_tree<'id>(
|
|||
}
|
||||
}
|
||||
|
||||
let parent_item_tree_offset = if component.parent_element.upgrade().is_some() {
|
||||
Some(builder.type_builder.add_field_type::<OnceCell<ErasedItemTreeBoxWeak>>())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let parent_item_tree_offset =
|
||||
if component.parent_element.upgrade().is_some() || is_popup_menu_impl {
|
||||
Some(builder.type_builder.add_field_type::<OnceCell<ErasedItemTreeBoxWeak>>())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let root_offset = builder.type_builder.add_field_type::<OnceCell<ErasedItemTreeBoxWeak>>();
|
||||
|
||||
|
|
@ -1345,6 +1396,7 @@ pub(crate) fn generate_item_tree<'id>(
|
|||
change_trackers,
|
||||
timers,
|
||||
popup_ids: std::cell::RefCell::new(HashMap::new()),
|
||||
popup_menu_description: builder.popup_menu_description,
|
||||
#[cfg(feature = "highlight")]
|
||||
type_loader: std::cell::OnceCell::new(),
|
||||
#[cfg(feature = "highlight")]
|
||||
|
|
@ -2340,7 +2392,7 @@ pub fn show_popup(
|
|||
element: ElementRc,
|
||||
instance: InstanceRef,
|
||||
popup: &object_tree::PopupWindow,
|
||||
pos_getter: impl FnOnce(InstanceRef<'_, '_>) -> i_slint_core::graphics::Point,
|
||||
pos_getter: impl FnOnce(InstanceRef<'_, '_>) -> LogicalPosition,
|
||||
close_policy: PopupClosePolicy,
|
||||
parent_comp: ErasedItemTreeBoxWeak,
|
||||
parent_window_adapter: WindowAdapterRc,
|
||||
|
|
@ -2348,7 +2400,13 @@ pub fn show_popup(
|
|||
) {
|
||||
generativity::make_guard!(guard);
|
||||
// FIXME: we should compile once and keep the cached compiled component
|
||||
let compiled = generate_item_tree(&popup.component, None, guard);
|
||||
let compiled = generate_item_tree(
|
||||
&popup.component,
|
||||
None,
|
||||
parent_comp.upgrade().unwrap().0.description().popup_menu_description.clone(),
|
||||
false,
|
||||
guard,
|
||||
);
|
||||
let inst = instantiate(
|
||||
compiled,
|
||||
Some(parent_comp),
|
||||
|
|
|
|||
|
|
@ -623,7 +623,7 @@ fn call_builtin_function(
|
|||
load_property_helper(comp, &popup.x.element(), popup.x.name()).unwrap();
|
||||
let y =
|
||||
load_property_helper(comp, &popup.y.element(), popup.y.name()).unwrap();
|
||||
i_slint_core::graphics::Point::new(
|
||||
corelib::api::LogicalPosition::new(
|
||||
x.try_into().unwrap(),
|
||||
y.try_into().unwrap(),
|
||||
)
|
||||
|
|
@ -659,6 +659,93 @@ fn call_builtin_function(
|
|||
panic!("internal error: argument to ClosePopupWindow must be an element")
|
||||
}
|
||||
}
|
||||
BuiltinFunction::ShowPopupMenu => {
|
||||
let [Expression::ElementReference(element), entries, position] = arguments else {
|
||||
panic!("internal error: incorrect argument count to ShowPopupMenu")
|
||||
};
|
||||
let position = eval_expression(position, local_context)
|
||||
.try_into()
|
||||
.expect("internal error: popup menu position argument should be a point");
|
||||
|
||||
let component = match local_context.component_instance {
|
||||
ComponentInstance::InstanceRef(c) => c,
|
||||
ComponentInstance::GlobalComponent(_) => {
|
||||
panic!("Cannot show popup from a global component")
|
||||
}
|
||||
};
|
||||
let elem = element.upgrade().unwrap();
|
||||
generativity::make_guard!(guard);
|
||||
let enclosing_component = enclosing_component_for_element(&elem, component, guard);
|
||||
let description = enclosing_component.description;
|
||||
let item_info = &description.items[elem.borrow().id.as_str()];
|
||||
let item_comp = enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
|
||||
let item_rc = corelib::items::ItemRc::new(
|
||||
vtable::VRc::into_dyn(item_comp),
|
||||
item_info.item_index(),
|
||||
);
|
||||
|
||||
if component.access_window(|window| window.show_native_popup_menu(&item_rc, position)) {
|
||||
return Value::Void;
|
||||
}
|
||||
|
||||
generativity::make_guard!(guard);
|
||||
let compiled = enclosing_component.description.popup_menu_description.unerase(guard);
|
||||
let inst = crate::dynamic_item_tree::instantiate(
|
||||
compiled.clone(),
|
||||
Some(enclosing_component.self_weak().get().unwrap().clone()),
|
||||
None,
|
||||
Some(&crate::dynamic_item_tree::WindowOptions::UseExistingWindow(
|
||||
component.window_adapter(),
|
||||
)),
|
||||
Default::default(),
|
||||
);
|
||||
inst.run_setup_code();
|
||||
|
||||
generativity::make_guard!(guard);
|
||||
let inst_ref = inst.unerase(guard);
|
||||
let entries = eval_expression(entries, local_context);
|
||||
compiled.set_property(inst_ref.borrow(), "entries", entries).unwrap();
|
||||
let item_rc_ = item_rc.clone();
|
||||
compiled
|
||||
.set_callback_handler(
|
||||
inst_ref.borrow(),
|
||||
"sub-menu",
|
||||
Box::new(move |args: &[Value]| -> Value {
|
||||
item_rc_
|
||||
.downcast::<corelib::items::ContextMenu>()
|
||||
.unwrap()
|
||||
.sub_menu
|
||||
.call(&(args[0].clone().try_into().unwrap(),))
|
||||
.into()
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
let item_rc_ = item_rc.clone();
|
||||
compiled
|
||||
.set_callback_handler(
|
||||
inst_ref.borrow(),
|
||||
"activated",
|
||||
Box::new(move |args: &[Value]| -> Value {
|
||||
item_rc_
|
||||
.downcast::<corelib::items::ContextMenu>()
|
||||
.unwrap()
|
||||
.activated
|
||||
.call(&(args[0].clone().try_into().unwrap(),))
|
||||
.into()
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
component.access_window(|window| {
|
||||
window.show_popup(
|
||||
&vtable::VRc::into_dyn(inst),
|
||||
position,
|
||||
corelib::items::PopupClosePolicy::CloseOnClickOutside,
|
||||
&item_rc,
|
||||
)
|
||||
});
|
||||
Value::Void
|
||||
}
|
||||
BuiltinFunction::SetSelectionOffsets => {
|
||||
if arguments.len() != 3 {
|
||||
panic!("internal error: incorrect argument count to select range function call")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ use std::collections::{BTreeMap, HashMap};
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::api::Value;
|
||||
use crate::dynamic_item_tree::{ErasedItemTreeBox, ErasedItemTreeDescription};
|
||||
use crate::dynamic_item_tree::{
|
||||
ErasedItemTreeBox, ErasedItemTreeDescription, PopupMenuDescription,
|
||||
};
|
||||
use crate::SetPropertyError;
|
||||
use i_slint_compiler::langtype::ElementType;
|
||||
use i_slint_compiler::namedreference::NamedReference;
|
||||
|
|
@ -317,8 +319,14 @@ fn generate(component: &Rc<Component>) -> CompiledGlobal {
|
|||
ElementType::Global => {
|
||||
generativity::make_guard!(guard);
|
||||
CompiledGlobal::Component {
|
||||
component: crate::dynamic_item_tree::generate_item_tree(component, None, guard)
|
||||
.into(),
|
||||
component: crate::dynamic_item_tree::generate_item_tree(
|
||||
component,
|
||||
None,
|
||||
PopupMenuDescription::Weak(Default::default()),
|
||||
false,
|
||||
guard,
|
||||
)
|
||||
.into(),
|
||||
public_properties: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
68
tests/cases/widgets/menubar.slint
Normal file
68
tests/cases/widgets/menubar.slint
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
|
||||
// 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 { AboutSlint, Button } from "std-widgets.slint";
|
||||
export component TestCase inherits Window {
|
||||
MenuBar {
|
||||
entries: [
|
||||
{ title: "File" },
|
||||
{ title: "Edit" },
|
||||
];
|
||||
sub-menu(entry) => {
|
||||
if entry.title == "File" {
|
||||
return [
|
||||
{ title: "New" },
|
||||
{ title: "Open" },
|
||||
{ title: "Save" },
|
||||
{ title: "Open Recent", has-sub-menu: true },
|
||||
];
|
||||
} else if entry.title == "Edit" {
|
||||
return [
|
||||
{ title: "Copy" },
|
||||
{ title: "Paste" },
|
||||
{ title: "Xxx", has-sub-menu: true },
|
||||
];
|
||||
} else if entry.title == "Open Recent" {
|
||||
return [
|
||||
{ title: "Recent 1" },
|
||||
{ title: "Recent 2" },
|
||||
{ title: "Recent 3" },
|
||||
{ title: "Recent 4" },
|
||||
];
|
||||
} else if entry.title == "Xxx" {
|
||||
return [
|
||||
{ title: "Aaa" },
|
||||
{ title: "Xxx", has-sub-menu: true },
|
||||
{ title: "Yyy" },
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
activated(entry) => {
|
||||
debug("Activated", entry);
|
||||
}
|
||||
}
|
||||
vl := VerticalLayout {
|
||||
AboutSlint {}
|
||||
Button { text: "Hello"; }
|
||||
}
|
||||
|
||||
out property <bool> check-geometry: vl.x == 0 && vl.y == 0 && vl.width == root.width && vl.height == root.height;
|
||||
|
||||
out property <bool> test: check-geometry;
|
||||
}
|
||||
|
||||
/*
|
||||
```rust
|
||||
let instance = TestCase::new().unwrap();
|
||||
assert!(instance.get_test());
|
||||
```
|
||||
|
||||
```cpp
|
||||
auto handle = TestCase::create();
|
||||
const TestCase &instance = *handle;
|
||||
assert(instance.get_test());
|
||||
```
|
||||
*/
|
||||
|
|
@ -165,7 +165,10 @@ pub fn extract_builtin_structs() -> std::collections::BTreeMap<String, StructDoc
|
|||
stringify!(string)
|
||||
};
|
||||
(Coord) => {
|
||||
stringify!(length)
|
||||
"length"
|
||||
};
|
||||
(Image) => {
|
||||
"image"
|
||||
};
|
||||
($pub_type:ident) => {
|
||||
stringify!($pub_type)
|
||||
|
|
@ -211,6 +214,8 @@ pub fn extract_builtin_structs() -> std::collections::BTreeMap<String, StructDoc
|
|||
|
||||
// `StateInfo` should not be in the documentation, so remove it again
|
||||
structs.remove("StateInfo");
|
||||
// Experimental type
|
||||
structs.remove("MenuEntry");
|
||||
|
||||
structs
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue