MenuItem with for and if

This commit is contained in:
Olivier Goffart 2025-01-28 14:02:45 +01:00
parent e75415554a
commit 010126992e
24 changed files with 889 additions and 258 deletions

View file

@ -44,6 +44,11 @@ pub enum BuiltinFunction {
ClearFocusItem,
ShowPopupWindow,
ClosePopupWindow,
/// Show a context popup menu.
/// Arguments are `(parent, entries, position)`
///
/// The second argument (entries) can either be of type Array of MenuEntry, or a reference to a MenuItem tree.
/// When it is a menu item tree, it is a ElementReference to the root of the tree, and in the LLR, a NumberLiteral to an index in [`crate::llr::SubComponent::menu_item_trees`]
ShowPopupMenu,
SetSelectionOffsets,
/// A function that belongs to an item (such as TextInput's select-all function).
@ -70,6 +75,10 @@ pub enum BuiltinFunction {
Hsv,
ColorScheme,
SupportsNativeMenuBar,
/// Setup the native menu bar, or the item-tree based menu bar
/// arguments ate: `(ref entries, ref sub-menu, ref activated, item_tree_root?)`
/// When there are 4 arguments, the last one is a reference to the MenuItem tree root (just like the entries in the [`Self::ShowPopupMenu`] call)
/// then the code will assign the callback handler and properties
SetupNativeMenuBar,
Use24HourFormat,
MonthDayCount,

View file

@ -1306,6 +1306,9 @@ fn generate_public_component(
for popup in &sc.popup_windows {
add_friends(friends, unit, popup.item_tree.root, false)
}
for menu in &sc.menu_item_trees {
add_friends(friends, unit, menu.root, false)
}
}
file.definitions.extend(component_struct.extract_definitions().collect::<Vec<_>>());
@ -1915,9 +1918,26 @@ fn generate_sub_component(
file,
conditional_includes,
);
file.definitions.extend(popup_struct.extract_definitions().collect::<Vec<_>>());
file.definitions.extend(popup_struct.extract_definitions());
file.declarations.push(Declaration::Struct(popup_struct));
});
for menu in &component.menu_item_trees {
let component_id = ident(&root.sub_components[menu.root].name);
let mut menu_struct = Struct { name: component_id.clone(), ..Default::default() };
generate_item_tree(
&mut menu_struct,
menu,
root,
Some(ParentCtx::new(&ctx, None)),
false,
component_id,
Access::Public,
file,
conditional_includes,
);
file.definitions.extend(menu_struct.extract_definitions());
file.declarations.push(Declaration::Struct(menu_struct));
}
for property in component.properties.iter().filter(|p| p.use_count.get() > 0) {
let cpp_name = ident(&property.name);
@ -3645,17 +3665,37 @@ fn compile_builtin_function_call(
}
BuiltinFunction::SetupNativeMenuBar => {
let window = access_window_field(ctx);
let [entries, llr::Expression::PropertyReference(sub_menu), llr::Expression::PropertyReference(activated)] =
arguments
else {
if let [llr::Expression::PropertyReference(entries_r), llr::Expression::PropertyReference(sub_menu_r), llr::Expression::PropertyReference(activated_r), llr::Expression::NumberLiteral(tree_index)] = arguments {
let current_sub_component = ctx.current_sub_component().unwrap();
let item_tree_id = ident(&ctx.compilation_unit.sub_components[current_sub_component.menu_item_trees[*tree_index as usize].root].name);
let access_entries = access_member(entries_r, ctx);
let access_sub_menu = access_member(sub_menu_r, ctx);
let access_activated = access_member(activated_r, ctx);
format!(r"
if ({window}.supports_native_menu_bar()) {{
auto item_tree = {item_tree_id}::create(self);
auto item_tree_dyn = item_tree.into_dyn();
vtable::VBox<slint::cbindgen_private::MenuVTable> box{{}};
slint::cbindgen_private::slint_menus_create_wrapper(&item_tree_dyn, &box);
slint::cbindgen_private::slint_windowrc_setup_native_menu_bar(&{window}.handle(), const_cast<slint::cbindgen_private::MenuVTable*>(box.vtable), box.instance);
// The ownership of the VBox is transferred to slint_windowrc_setup_native_menu_bar
box.instance = nullptr;
box.vtable = nullptr;
}} else {{
auto item_tree = {item_tree_id}::create(self);
auto item_tree_dyn = item_tree.into_dyn();
slint::private_api::setup_popup_menu_from_menu_item_tree(item_tree_dyn, {access_entries}, {access_sub_menu}, {access_activated});
}}")
} else if let [entries, llr::Expression::PropertyReference(sub_menu), llr::Expression::PropertyReference(activated)] = arguments {
let entries = compile_expression(entries, ctx);
let sub_menu = access_member(sub_menu, ctx);
let activated = access_member(activated, ctx);
format!("{window}.setup_native_menu_bar(self,
[](auto &self, const slint::cbindgen_private::MenuEntry *parent){{ return parent ? {sub_menu}.call(*parent) : {entries}; }},
[](auto &self, const slint::cbindgen_private::MenuEntry &entry){{ {activated}.call(entry); }})")
} else {
panic!("internal error: incorrect arguments to SetupNativeMenuBar")
};
let entries = compile_expression(entries, ctx);
let sub_menu = access_member(sub_menu, ctx);
let activated = access_member(activated, ctx);
format!("{window}.setup_native_menu_bar(self,
[](auto &self, const slint::cbindgen_private::MenuEntry *parent){{ return parent ? {sub_menu}.call(*parent) : {entries}; }},
[](auto &self, const slint::cbindgen_private::MenuEntry &entry){{ {activated}.call(entry); }})")
}
}
BuiltinFunction::Use24HourFormat => {
format!("slint::cbindgen_private::slint_date_time_use_24_hour_format()")
@ -3754,7 +3794,6 @@ fn compile_builtin_function_call(
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
@ -3770,25 +3809,39 @@ fn compile_builtin_function_call(
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});
{{
let access_sub_menu = access_member(&popup.sub_menu, &popup_ctx);
let access_activated = access_member(&popup.activated, &popup_ctx);
let init = if let llr::Expression::NumberLiteral(tree_index) = entries {
// We have an MenuItem tree
let current_sub_component = ctx.current_sub_component().unwrap();
let item_tree_id = ident(&ctx.compilation_unit.sub_components[current_sub_component.menu_item_trees[*tree_index as usize].root].name);
format!(r"
auto item_tree = {item_tree_id}::create(self);
auto item_tree_dyn = item_tree.into_dyn();
auto self = popup_menu;
{access_entries}.set(std::move(entries));
{fw_sub_menu}
{fw_activated}
}}");
slint::private_api::setup_popup_menu_from_menu_item_tree(item_tree_dyn, {access_entries}, {access_sub_menu}, {access_activated});
")
} else {
let forward_callback = |access, cb| {
format!("{access}.set_handler(
[context_menu](const auto &entry) {{
return context_menu->{cb}.call(entry);
}});")
};
let fw_sub_menu = forward_callback(access_sub_menu, "sub_menu");
let fw_activated = forward_callback(access_activated, "activated");
let entries = compile_expression(entries, ctx);
format!(r"
const slint::cbindgen_private::ContextMenu *context_menu = &({context_menu});
auto entries = {entries};
{{
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 => {

View file

@ -696,6 +696,9 @@ fn generate_sub_component(
false,
)
})
.chain(component.menu_item_trees.iter().map(|tree| {
generate_item_tree(&tree, root, Some(ParentCtx::new(&ctx, None)), None, false)
}))
.collect::<Vec<_>>();
let mut declared_property_vars = vec![];
@ -2769,7 +2772,7 @@ fn compile_builtin_function_call(
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
@ -2786,34 +2789,71 @@ fn compile_builtin_function_call(
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 access_sub_menu = access_member(&popup.sub_menu, &popup_ctx).unwrap();
let access_activated = access_member(&popup.activated, &popup_ctx).unwrap();
let init_popup = if let Expression::NumberLiteral(tree_index) = entries {
// We have an MenuItem tree
let current_sub_component = ctx.current_sub_component().unwrap();
let item_tree_id = inner_component_id(
&ctx.compilation_unit.sub_components
[current_sub_component.menu_item_trees[*tree_index as usize].root],
);
quote!({
let menu_item_tree_instance = #item_tree_id::new(_self.self_weak.get().unwrap().clone()).unwrap();
let context_menu_item_tree = sp::Rc::new(sp::MenuFromItemTree::new(sp::VRc::into_dyn(menu_item_tree_instance)));
let mut entries = sp::SharedVector::default();
sp::Menu::sub_menu(&*context_menu_item_tree, sp::Option::None, &mut entries);
let _self = popup_instance_vrc.as_pin_ref();
#access_entries.set(sp::ModelRc::new(sp::SharedVectorModel::from(entries)));
let context_menu_item_tree_ = context_menu_item_tree.clone();
#access_sub_menu.set_handler(move |entry| {
let mut entries = sp::SharedVector::default();
sp::Menu::sub_menu(&*context_menu_item_tree_, sp::Option::Some(&entry.0), &mut entries);
sp::ModelRc::new(sp::SharedVectorModel::from(entries))
});
#access_activated.set_handler(move |entry| {
sp::Menu::activate(&*context_menu_item_tree, &entry.0);
});
})
} else {
// entries should be an expression of type array of MenuEntry
debug_assert!(
matches!(entries.ty(ctx), Type::Array(ty) if matches!(&*ty, Type::Struct{..}))
);
let entries = compile_expression(entries, ctx);
let forward_callback = |access, cb| {
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(access_sub_menu, quote!(sub_menu));
let fw_activated = forward_callback(access_activated, quote!(activated));
quote! {
let entries = #entries;
{
let _self = popup_instance_vrc.as_pin_ref();
#access_entries.set(entries);
#fw_sub_menu
#fw_activated
}
))
}
};
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;
};
#init_popup
#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()),
@ -3023,41 +3063,78 @@ fn compile_builtin_function_call(
}
BuiltinFunction::SetupNativeMenuBar => {
let window_adapter_tokens = access_window_adapter_field(ctx);
let [entries, Expression::PropertyReference(sub_menu), Expression::PropertyReference(activated)] =
if let [Expression::PropertyReference(entries_r), Expression::PropertyReference(sub_menu_r), Expression::PropertyReference(activated_r), Expression::NumberLiteral(tree_index)] =
arguments
else {
panic!("internal error: incorrect arguments to SetupNativeMenuBar")
};
let entries = compile_expression(entries, ctx);
let sub_menu = access_member(sub_menu, ctx).unwrap();
let activated = access_member(activated, ctx).unwrap();
let inner_component_id = self::inner_component_id(ctx.current_sub_component().unwrap());
quote! {
if sp::WindowInner::from_pub(#window_adapter_tokens.window()).supports_native_menu_bar() {
// May seem overkill to have an instance of the struct for each call, but there should only be one call per component anyway
struct MenuBarWrapper(sp::VWeakMapped<sp::ItemTreeVTable, #inner_component_id>);
const _ : () = {
use slint::private_unstable_api::re_exports::*;
MenuVTable_static!(static VT for MenuBarWrapper);
};
impl sp::Menu for MenuBarWrapper {
fn sub_menu(&self, parent: sp::Option<&sp::MenuEntry>, result: &mut sp::SharedVector<sp::MenuEntry>) {
let Some(self_rc) = self.0.upgrade() else { return };
let _self = self_rc.as_pin_ref();
let model = match parent {
None => #entries,
Some(parent) => #sub_menu.call(&(parent.clone(),))
};
*result = model.iter().map(|v| v.try_into().unwrap()).collect();
}
fn activate(&self, entry: &sp::MenuEntry) {
let Some(self_rc) = self.0.upgrade() else { return };
let _self = self_rc.as_pin_ref();
#activated.call(&(entry.clone(),))
}
{
// We have an MenuItem tree
let current_sub_component = ctx.current_sub_component().unwrap();
let item_tree_id = inner_component_id(
&ctx.compilation_unit.sub_components
[current_sub_component.menu_item_trees[*tree_index as usize].root],
);
let access_entries = access_member(entries_r, ctx).unwrap();
let access_sub_menu = access_member(sub_menu_r, ctx).unwrap();
let access_activated = access_member(activated_r, ctx).unwrap();
quote!({
let menu_item_tree_instance = #item_tree_id::new(_self.self_weak.get().unwrap().clone()).unwrap();
let menu_item_tree = sp::MenuFromItemTree::new(sp::VRc::into_dyn(menu_item_tree_instance));
if sp::WindowInner::from_pub(#window_adapter_tokens.window()).supports_native_menu_bar() {
sp::WindowInner::from_pub(#window_adapter_tokens.window()).setup_menubar(sp::VBox::new(menu_item_tree));
} else {
let menu_item_tree = sp::Rc::new(menu_item_tree);
let mut entries = sp::SharedVector::default();
sp::Menu::sub_menu(&*menu_item_tree, sp::Option::None, &mut entries);
#access_entries.set(sp::ModelRc::new(sp::SharedVectorModel::from(entries)));
let menu_item_tree_ = menu_item_tree.clone();
#access_sub_menu.set_handler(move |entry| {
let mut entries = sp::SharedVector::default();
sp::Menu::sub_menu(&*menu_item_tree_, sp::Option::Some(&entry.0), &mut entries);
sp::ModelRc::new(sp::SharedVectorModel::from(entries))
});
#access_activated.set_handler(move |entry| {
sp::Menu::activate(&*menu_item_tree, &entry.0);
});
}
})
} else if let [entries, Expression::PropertyReference(sub_menu), Expression::PropertyReference(activated)] =
arguments
{
let entries = compile_expression(entries, ctx);
let sub_menu = access_member(sub_menu, ctx).unwrap();
let activated = access_member(activated, ctx).unwrap();
let inner_component_id =
self::inner_component_id(ctx.current_sub_component().unwrap());
quote! {
if sp::WindowInner::from_pub(#window_adapter_tokens.window()).supports_native_menu_bar() {
// May seem overkill to have an instance of the struct for each call, but there should only be one call per component anyway
struct MenuBarWrapper(sp::VWeakMapped<sp::ItemTreeVTable, #inner_component_id>);
const _ : () = {
use slint::private_unstable_api::re_exports::*;
MenuVTable_static!(static VT for MenuBarWrapper);
};
impl sp::Menu for MenuBarWrapper {
fn sub_menu(&self, parent: sp::Option<&sp::MenuEntry>, result: &mut sp::SharedVector<sp::MenuEntry>) {
let Some(self_rc) = self.0.upgrade() else { return };
let _self = self_rc.as_pin_ref();
let model = match parent {
None => #entries,
Some(parent) => #sub_menu.call(&(parent.clone(),))
};
*result = model.iter().map(|v| v.try_into().unwrap()).collect();
}
fn activate(&self, entry: &sp::MenuEntry) {
let Some(self_rc) = self.0.upgrade() else { return };
let _self = self_rc.as_pin_ref();
#activated.call(&(entry.clone(),))
}
}
sp::WindowInner::from_pub(#window_adapter_tokens.window()).setup_menubar(sp::VBox::new(MenuBarWrapper(_self.self_weak.get().unwrap().clone())));
}
sp::WindowInner::from_pub(#window_adapter_tokens.window()).setup_menubar(sp::VBox::new(MenuBarWrapper(_self.self_weak.get().unwrap().clone())));
}
} else {
panic!("internal error: incorrect arguments to SetupNativeMenuBar")
}
}
BuiltinFunction::MonthDayCount => {

View file

@ -256,6 +256,8 @@ pub struct SubComponent {
pub repeated: TiVec<RepeatedElementIdx, RepeatedElement>,
pub component_containers: Vec<ComponentContainerElement>,
pub popup_windows: Vec<PopupWindow>,
/// The MenuItem trees. The index is stored in a Expression::NumberLiteral in the arguments of BuiltinFunction::ShowPopupMenu and BuiltinFunction::SetupNativeMenuBar
pub menu_item_trees: Vec<ItemTree>,
pub timers: Vec<Timer>,
pub sub_components: TiVec<SubComponentInstanceIdx, SubComponentInstance>,
/// The initial value or binding for properties.
@ -417,6 +419,9 @@ impl CompilationUnit {
Some(ParentCtx::new(&ctx, None)),
);
}
for menu_tree in &sc.menu_item_trees {
visit_component(root, menu_tree.root, visitor, Some(ParentCtx::new(&ctx, None)));
}
}
for c in &self.used_sub_components {
visit_component(self, *c, visitor, None);

View file

@ -7,7 +7,6 @@ use std::num::NonZeroUsize;
use std::rc::{Rc, Weak};
use itertools::Either;
use smol_str::{format_smolstr, SmolStr};
use super::lower_to_item_tree::{LoweredElement, LoweredSubComponentMapping, LoweringState};
@ -81,11 +80,25 @@ pub fn lower_expression(
llr_Expression::PropertyReference(ctx.map_property_reference(nr))
}
tree_Expression::ElementReference(e) => {
let elem = e.upgrade().unwrap();
let enclosing = elem.borrow().enclosing_component.upgrade().unwrap();
// When within a ShowPopupMenu builtin function, this is a reference to the root of the menu item tree
if Rc::ptr_eq(&elem, &enclosing.root_element) {
if let Some(idx) = ctx
.component
.menu_item_tree
.borrow()
.iter()
.position(|c| Rc::ptr_eq(c, &enclosing))
{
return llr_Expression::NumberLiteral(idx as _);
}
}
// We map an element reference to a reference to the property "" inside that native item
llr_Expression::PropertyReference(ctx.map_property_reference(&NamedReference::new(
&e.upgrade().unwrap(),
SmolStr::default(),
)))
llr_Expression::PropertyReference(
ctx.map_property_reference(&NamedReference::new(&elem, SmolStr::default())),
)
}
tree_Expression::RepeaterIndexReference { element } => {
repeater_special_property(element, ctx.component, 1usize.into())
@ -118,9 +131,11 @@ pub fn lower_expression(
llr_Expression::CodeBlock(expr.iter().map(|e| lower_expression(e, ctx)).collect::<_>())
}
tree_Expression::FunctionCall { function, arguments, .. } => match function {
Callable::Builtin(BuiltinFunction::ShowPopupWindow) => lower_show_popup(arguments, ctx),
Callable::Builtin(BuiltinFunction::ShowPopupWindow) => {
lower_show_popup_window(arguments, ctx)
}
Callable::Builtin(BuiltinFunction::ClosePopupWindow) => {
lower_close_popup(arguments, ctx)
lower_close_popup_window(arguments, ctx)
}
Callable::Builtin(f) => {
let mut arguments =
@ -354,7 +369,10 @@ fn repeater_special_property(
llr_Expression::PropertyReference(r)
}
fn lower_show_popup(args: &[tree_Expression], ctx: &mut ExpressionLoweringCtx) -> llr_Expression {
fn lower_show_popup_window(
args: &[tree_Expression],
ctx: &mut ExpressionLoweringCtx,
) -> llr_Expression {
if let [tree_Expression::ElementReference(e)] = args {
let popup_window = e.upgrade().unwrap();
let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap();
@ -390,7 +408,10 @@ fn lower_show_popup(args: &[tree_Expression], ctx: &mut ExpressionLoweringCtx) -
}
}
fn lower_close_popup(args: &[tree_Expression], ctx: &mut ExpressionLoweringCtx) -> llr_Expression {
fn lower_close_popup_window(
args: &[tree_Expression],
ctx: &mut ExpressionLoweringCtx,
) -> llr_Expression {
if let [tree_Expression::ElementReference(e)] = args {
let popup_window = e.upgrade().unwrap();
let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap();

View file

@ -43,7 +43,7 @@ pub fn lower_to_item_tree(
for c in &document.used_types.borrow().sub_components {
let sc = lower_sub_component(c, &mut state, None, compiler_config);
let idx = state.sub_components.push_and_get_key(sc);
let idx = state.push_sub_component(sc);
state.sub_component_mapping.insert(ByAddress(c.clone()), idx);
}
@ -55,7 +55,7 @@ pub fn lower_to_item_tree(
sc.sub_component.name = component.id.clone();
let item_tree = ItemTree {
tree: make_tree(&state, &component.root_element, &sc, &[]),
root: state.sub_components.push_and_get_key(sc),
root: state.push_sub_component(sc),
parent_context: None,
};
// For C++ codegen, the root component must have the same name as the public component
@ -84,7 +84,7 @@ pub fn lower_to_item_tree(
);
let item_tree = ItemTree {
tree: make_tree(&state, &c.root_element, &sc, &[]),
root: state.sub_components.push_and_get_key(sc),
root: state.push_sub_component(sc),
parent_context: None,
};
PopupMenu { item_tree, sub_menu, activated, entries }
@ -182,7 +182,7 @@ pub struct LoweredSubComponent {
pub struct LoweringState {
global_properties: HashMap<NamedReference, PropertyReference>,
sub_components: TiVec<SubComponentIdx, LoweredSubComponent>,
sub_component_mapping: HashMap<ByAddress<Rc<Component>>, SubComponentIdx>,
pub sub_component_mapping: HashMap<ByAddress<Rc<Component>>, SubComponentIdx>,
#[cfg(feature = "bundle-translations")]
pub translation_builder: Option<super::translations::TranslationsBuilder>,
}
@ -252,6 +252,7 @@ fn lower_sub_component(
repeated: Default::default(),
component_containers: Default::default(),
popup_windows: Default::default(),
menu_item_trees: Vec::new(),
timers: Default::default(),
sub_components: Default::default(),
property_init: Default::default(),
@ -402,8 +403,10 @@ fn lower_sub_component(
Some(element.clone())
});
let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: parent_context, component };
let mut ctx = ExpressionLoweringCtx { inner, state };
crate::generator::handle_property_bindings_init(component, |e, p, binding| {
let nr = NamedReference::new(e, p.clone());
let prop = ctx.map_property_reference(&nr);
@ -498,6 +501,20 @@ fn lower_sub_component(
.map(|popup| lower_popup_component(popup, &mut ctx, compiler_config))
.collect();
sub_component.menu_item_trees = component
.menu_item_tree
.borrow()
.iter()
.map(|c| {
let sc = lower_sub_component(c, ctx.state, Some(&ctx.inner), compiler_config);
ItemTree {
tree: make_tree(&ctx.state, &c.root_element, &sc, &[]),
root: ctx.state.push_sub_component(sc),
parent_context: None,
}
})
.collect();
sub_component.timers =
component.timers.borrow().iter().map(|t| lower_timer(t, &mut ctx)).collect();
@ -651,16 +668,17 @@ fn lower_repeated_component(
let sc = lower_sub_component(&component, ctx.state, Some(&ctx.inner), compiler_config);
let geom = component.root_element.borrow().geometry_props.clone().unwrap();
let listview = repeated.is_listview.as_ref().map(|lv| ListViewInfo {
viewport_y: ctx.map_property_reference(&lv.viewport_y),
viewport_height: ctx.map_property_reference(&lv.viewport_height),
viewport_width: ctx.map_property_reference(&lv.viewport_width),
listview_height: ctx.map_property_reference(&lv.listview_height),
listview_width: ctx.map_property_reference(&lv.listview_width),
prop_y: sc.mapping.map_property_reference(&geom.y, ctx.state),
prop_height: sc.mapping.map_property_reference(&geom.height, ctx.state),
let listview = repeated.is_listview.as_ref().map(|lv| {
let geom = component.root_element.borrow().geometry_props.clone().unwrap();
ListViewInfo {
viewport_y: ctx.map_property_reference(&lv.viewport_y),
viewport_height: ctx.map_property_reference(&lv.viewport_height),
viewport_width: ctx.map_property_reference(&lv.viewport_width),
listview_height: ctx.map_property_reference(&lv.listview_height),
listview_width: ctx.map_property_reference(&lv.listview_width),
prop_y: sc.mapping.map_property_reference(&geom.y, ctx.state),
prop_height: sc.mapping.map_property_reference(&geom.height, ctx.state),
}
});
RepeatedElement {

View file

@ -103,6 +103,10 @@ impl<'a> PrettyPrinter<'a> {
write!(self.writer, "for in {} : ", DisplayExpression(&r.model.borrow(), &ctx))?;
self.print_component(root, r.sub_tree.root, Some(ParentCtx::new(&ctx, Some(idx))))?
}
for t in &sc.menu_item_trees {
self.indent()?;
self.print_component(root, t.root, Some(ParentCtx::new(&ctx, None)))?
}
for w in &sc.popup_windows {
self.indent()?;
self.print_component(root, w.item_tree.root, Some(ParentCtx::new(&ctx, None)))?

View file

@ -383,6 +383,7 @@ pub struct Component {
pub popup_windows: RefCell<Vec<PopupWindow>>,
pub timers: RefCell<Vec<Timer>>,
pub menu_item_tree: RefCell<Vec<Rc<Component>>>,
/// This component actually inherits PopupWindow (although that has been changed to a Window by the lower_popups pass)
pub inherits_popup_window: Cell<bool>,
@ -2153,7 +2154,12 @@ pub fn recurse_elem_including_sub_components<State>(
.popup_windows
.borrow()
.iter()
.for_each(|p| recurse_elem_including_sub_components(&p.component, state, vis))
.for_each(|p| recurse_elem_including_sub_components(&p.component, state, vis));
component
.menu_item_tree
.borrow()
.iter()
.for_each(|c| recurse_elem_including_sub_components(c, state, vis));
}
/// Same as recurse_elem, but will take the children from the element as to not keep the element borrow

View file

@ -30,6 +30,10 @@ pub fn default_geometry(root_component: &Rc<Component>, diag: &mut BuildDiagnost
if elem.borrow().repeated.is_some() {
return None;
}
if elem.borrow().geometry_props.is_none() {
// Not an element with geometry, skip it
return None;
}
// whether the width, or height, is filling the parent
let (mut w100, mut h100) = (false, false);

View file

@ -41,6 +41,9 @@ pub fn generate_item_indices(component: &Rc<Component>) {
for p in component.popup_windows.borrow().iter() {
generate_item_indices(&p.component)
}
for c in component.menu_item_tree.borrow().iter() {
generate_item_indices(c);
}
}
struct Helper {

View file

@ -402,6 +402,7 @@ fn duplicate_sub_component(
init_code: component_to_duplicate.init_code.clone(),
popup_windows: Default::default(),
timers: component_to_duplicate.timers.clone(),
menu_item_tree: Default::default(),
exported_global_names: component_to_duplicate.exported_global_names.clone(),
used: component_to_duplicate.used.clone(),
private_properties: Default::default(),
@ -431,6 +432,16 @@ fn duplicate_sub_component(
fixup_reference(&mut t.running, mapping);
fixup_reference(&mut t.triggered, mapping);
}
*new_component.menu_item_tree.borrow_mut() = component_to_duplicate
.menu_item_tree
.borrow()
.iter()
.map(|it| {
let new_parent =
mapping.get(&element_key(it.parent_element.upgrade().unwrap())).unwrap().clone();
duplicate_sub_component(it, &new_parent, mapping, priority_delta)
})
.collect();
new_component
.root_constraints
.borrow_mut()

View file

@ -81,7 +81,7 @@ use crate::object_tree::*;
use core::cell::RefCell;
use smol_str::{format_smolstr, SmolStr};
use std::collections::HashMap;
use std::rc::Rc;
use std::rc::{Rc, Weak};
const HEIGHT: &str = "height";
const ENTRIES: &str = "entries";
@ -153,7 +153,11 @@ pub async fn lower_menus(
None => diag.push_error(format!("PopupMenuImpl doesn't have {prop}"), &*root),
}
}
root.property_analysis.borrow_mut().entry("entries".into()).or_default().is_set = true;
root.property_analysis
.borrow_mut()
.entry(SmolStr::new_static(ENTRIES))
.or_default()
.is_set = true;
}
recurse_elem_including_sub_components_no_borrow(&popup_menu_impl, &(), &mut |elem, _| {
@ -173,7 +177,7 @@ fn process_context_menu(
) -> bool {
let is_internal = matches!(&context_menu_elem.borrow().base_type, ElementType::Builtin(b) if b.name == "ContextMenuInternal");
if !is_internal {
let item_tree_root = if !is_internal {
// Lower MenuItem's into entries
let menu_item = context_menu_elem
.borrow()
@ -196,9 +200,13 @@ fn process_context_menu(
true
}
});
if !items.is_empty() {
lower_menu_items(context_menu_elem, items, &components.menu_entry, diag);
}
let item_tree_root = if !items.is_empty() {
lower_menu_items(context_menu_elem, items, &components)
.map(|c| Expression::ElementReference(Rc::downgrade(&c.root_element)))
} else {
None
};
for (name, _) in &components.context_menu_internal.property_list() {
if let Some(decl) = context_menu_elem.borrow().property_declarations.get(name) {
@ -208,13 +216,25 @@ fn process_context_menu(
);
}
}
}
// Materialize the entries property
context_menu_elem.borrow_mut().property_declarations.insert(
SmolStr::new_static(ENTRIES),
Type::Array(components.menu_entry.clone().into()).into(),
);
item_tree_root
} else {
None
};
let entries = if let Some(item_tree_root) = item_tree_root {
item_tree_root
} else {
// Materialize the entries property
context_menu_elem.borrow_mut().property_declarations.insert(
SmolStr::new_static(ENTRIES),
Type::Array(components.menu_entry.clone().into()).into(),
);
Expression::PropertyReference(NamedReference::new(
&context_menu_elem,
SmolStr::new_static(ENTRIES),
))
};
// generate the show callback
let source_location = Some(context_menu_elem.borrow().to_source_location());
@ -222,10 +242,7 @@ fn process_context_menu(
function: BuiltinFunction::ShowPopupMenu.into(),
arguments: vec![
Expression::ElementReference(Rc::downgrade(context_menu_elem)),
Expression::PropertyReference(NamedReference::new(
&context_menu_elem,
SmolStr::new_static(ENTRIES),
)),
entries,
Expression::FunctionParameterReference {
index: 0,
ty: crate::typeregister::logical_point_type(),
@ -276,9 +293,12 @@ fn process_window(
// Lower MenuItem's into entries
let children = std::mem::take(&mut menu_bar.borrow_mut().children);
if !children.is_empty() {
lower_menu_items(&menu_bar, children, &components.menu_entry, diag);
}
let item_tree_root = if !children.is_empty() {
lower_menu_items(&menu_bar, children, &components)
.map(|c| Expression::ElementReference(Rc::downgrade(&c.root_element)))
} else {
None
};
let menubar_impl = Element {
id: format_smolstr!("{}-menulayout", window.id),
@ -353,22 +373,34 @@ fn process_window(
menu_bar.borrow_mut().base_type = components.vertical_layout.clone();
menu_bar.borrow_mut().children = vec![menubar_impl, child];
let mut arguments = vec![
Expression::PropertyReference(NamedReference::new(&menu_bar, SmolStr::new_static(ENTRIES))),
Expression::PropertyReference(NamedReference::new(
&menu_bar,
SmolStr::new_static(SUB_MENU),
)),
Expression::PropertyReference(NamedReference::new(
&menu_bar,
SmolStr::new_static(ACTIVATED),
)),
];
if let Some(item_tree_root) = item_tree_root {
arguments.push(item_tree_root.into());
for prop in [ENTRIES, SUB_MENU, ACTIVATED] {
menu_bar
.borrow()
.property_analysis
.borrow_mut()
.entry(SmolStr::new_static(prop))
.or_default()
.is_set = true;
}
}
let setup_menubar = Expression::FunctionCall {
function: BuiltinFunction::SetupNativeMenuBar.into(),
arguments: vec![
Expression::PropertyReference(NamedReference::new(
&menu_bar,
SmolStr::new_static(ENTRIES),
)),
Expression::PropertyReference(NamedReference::new(
&menu_bar,
SmolStr::new_static(SUB_MENU),
)),
Expression::PropertyReference(NamedReference::new(
&menu_bar,
SmolStr::new_static(ACTIVATED),
)),
],
arguments,
source_location: source_location.clone(),
};
@ -390,43 +422,94 @@ fn process_window(
true
}
/// Lower the MenuItem 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(
parent: &ElementRc,
children: Vec<ElementRc>,
menu_entry: &Type,
diag: &mut BuildDiagnostics,
) {
let mut state = GenMenuState {
id: 0,
menu_entry: menu_entry.clone(),
diag,
activate: Vec::new(),
sub_menu: Vec::new(),
};
parent.borrow_mut().bindings.insert(
ENTRIES.into(),
RefCell::new(
Expression::Array {
element_ty: menu_entry.clone(),
values: generate_menu_entries(children.into_iter(), &mut state),
components: &UsefulMenuComponents,
) -> Option<Rc<Component>> {
let mut has_repeated = false;
for i in &children {
recurse_elem(&i, &(), &mut |e, _| {
if e.borrow().repeated.is_some() {
has_repeated = true;
}
.into(),
),
);
let entry_id = Expression::StructFieldAccess {
base: Expression::FunctionParameterReference { index: 0, ty: menu_entry.clone() }.into(),
name: SmolStr::new_static("id"),
};
});
if has_repeated {
break;
}
}
if !has_repeated {
let menu_entry = &components.menu_entry;
let mut state = GenMenuState {
id: 0,
menu_entry: menu_entry.clone(),
activate: Vec::new(),
sub_menu: Vec::new(),
};
let entries = generate_menu_entries(children.into_iter(), &mut state);
parent.borrow_mut().bindings.insert(
ENTRIES.into(),
RefCell::new(
Expression::Array { element_ty: menu_entry.clone(), values: entries }.into(),
),
);
let entry_id = Expression::StructFieldAccess {
base: Expression::FunctionParameterReference { index: 0, ty: menu_entry.clone() }
.into(),
name: SmolStr::new_static("id"),
};
let sub_entries = build_cases_function(
&entry_id,
Expression::Array { element_ty: menu_entry.clone(), values: vec![] },
state.sub_menu,
);
parent.borrow_mut().bindings.insert(SUB_MENU.into(), RefCell::new(sub_entries.into()));
let sub_entries = build_cases_function(
&entry_id,
Expression::Array { element_ty: menu_entry.clone(), values: vec![] },
state.sub_menu,
);
parent.borrow_mut().bindings.insert(SUB_MENU.into(), RefCell::new(sub_entries.into()));
let activated = build_cases_function(&entry_id, Expression::CodeBlock(vec![]), state.activate);
parent.borrow_mut().bindings.insert(ACTIVATED.into(), RefCell::new(activated.into()));
let activated =
build_cases_function(&entry_id, Expression::CodeBlock(vec![]), state.activate);
parent.borrow_mut().bindings.insert(ACTIVATED.into(), RefCell::new(activated.into()));
None
} else {
let component = Rc::new_cyclic(|component_weak| {
let root_element = Rc::new(RefCell::new(Element {
base_type: components.empty.clone(),
children,
enclosing_component: component_weak.clone(),
..Default::default()
}));
recurse_elem(&root_element, &true, &mut |element: &ElementRc, is_root| {
if !is_root {
debug_assert!(Weak::ptr_eq(
&element.borrow().enclosing_component,
&parent.borrow().enclosing_component
));
element.borrow_mut().enclosing_component = component_weak.clone();
element.borrow_mut().geometry_props = None;
}
false
});
Component {
node: parent.borrow().debug.first().map(|n| n.node.clone().into()),
id: SmolStr::default(),
root_element,
parent_element: Rc::downgrade(&parent),
..Default::default()
}
});
parent
.borrow()
.enclosing_component
.upgrade()
.unwrap()
.menu_item_tree
.borrow_mut()
.push(component.clone());
Some(component)
}
}
fn build_cases_function(
@ -450,7 +533,7 @@ fn build_cases_function(
result
}
struct GenMenuState<'a> {
struct GenMenuState {
id: usize,
/// Maps `entry.id` to the callback
activate: Vec<(SmolStr, Expression)>,
@ -458,12 +541,12 @@ struct GenMenuState<'a> {
sub_menu: Vec<(SmolStr, Expression)>,
menu_entry: Type,
diag: &'a mut BuildDiagnostics,
}
/// Recursively generate the menu entries for the given menu items
fn generate_menu_entries(
menu_items: impl Iterator<Item = ElementRc>,
state: &mut GenMenuState<'_>,
state: &mut GenMenuState,
) -> Vec<Expression> {
let mut entries = Vec::new();
@ -479,13 +562,7 @@ fn generate_menu_entries(
.borrow_mut()
.push(item.clone());
if borrow_mut.repeated.is_some() {
state.diag.push_error(
"'for' and 'if' in MenuItem is not yet implemented".into(),
&*borrow_mut,
);
continue;
}
assert!(borrow_mut.repeated.is_none());
let mut values = HashMap::<SmolStr, Expression>::new();
for prop in ["title"] {

View file

@ -28,6 +28,7 @@ pub fn move_declarations(component: &Rc<Component>) {
fn do_move_declarations(component: &Rc<Component>) {
let mut decl = Declarations::take_from_element(&mut component.root_element.borrow_mut());
component.popup_windows.borrow().iter().for_each(|f| do_move_declarations(&f.component));
component.menu_item_tree.borrow().iter().for_each(|f| do_move_declarations(f));
let mut new_root_bindings = HashMap::new();
let mut new_root_change_callbacks = HashMap::new();
@ -102,6 +103,9 @@ fn do_move_declarations(component: &Rc<Component>) {
fixup_reference(&mut t.running);
fixup_reference(&mut t.triggered);
});
component.menu_item_tree.borrow_mut().iter_mut().for_each(|c| {
visit_all_named_references(c, &mut fixup_reference);
});
component.init_code.borrow_mut().iter_mut().for_each(|expr| {
visit_named_references_in_expression(expr, &mut fixup_reference);
});

View file

@ -98,6 +98,9 @@ fn create_repeater_components(component: &Rc<Component>) {
for p in component.popup_windows.borrow().iter() {
create_repeater_components(&p.component);
}
for c in component.menu_item_tree.borrow().iter() {
create_repeater_components(c);
}
}
/// Make sure that references to property within the repeated element actually point to the reference

View file

@ -343,7 +343,13 @@ impl Snapshotter {
let root_constraints = RefCell::new(
self.snapshot_layout_constraints(&component.root_constraints.borrow()),
);
let menu_item_tree = component
.menu_item_tree
.borrow()
.iter()
.map(|it| self.create_component(it))
.collect::<Vec<_>>()
.into();
object_tree::Component {
node: component.node.clone(),
id: component.id.clone(),
@ -358,6 +364,7 @@ impl Snapshotter {
parent_element,
popup_windows,
timers,
menu_item_tree,
private_properties: RefCell::new(component.private_properties.borrow().clone()),
root_constraints,
root_element,