Skip to main content

i_slint_compiler/passes/
lower_menus.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore menulayout
5//! This pass lowers the `MenuBar` and `ContextMenuArea` as well as all their contents
6//!
7//! We can't have properties of type Model because that is not binary compatible with C++,
8//! so all the code that handle model of MenuEntry need to be handle by code in the generated code
9//! and transformed into a `SharedVector<MenuEntry>` that is passed to Slint runtime.
10//!
11//! ## MenuBar
12//!
13//! ```slint
14//! Window {
15//!      menu-bar := MenuBar {
16//!        Menu {
17//!           title: "File";
18//!           if cond1 : MenuItem {
19//!             title: "A";
20//!             activated => { debug("A") }
21//!           }
22//!           Menu {
23//!               title: "B";
24//!               for x in 42 : MenuItem { title: "C" + x; }
25//!           }
26//!        }
27//!      }
28//!      content := ...
29//! }
30//! ```
31//! Is transformed to
32//! ```slint
33//! Window {
34//!     menu-bar := VerticalLayout {
35//!        // these callbacks are connected by the setup_native_menu_bar call to an adapter from the menu tree
36//!        callback sub-menu(entry: MenuEntry);
37//!        callback activated();
38//!        if !Builtin.supports_native_menu_bar() : MenuBarImpl {
39//!           entries: parent.entries
40//!           sub-menu(..) => { parent.sub-menu(..) }
41//!           activated(..) => { parent.activated(..) }
42//!        }
43//!        Empty {
44//!           content := ...
45//!        }
46//!    }
47//!    init => {
48//!        // ... rest of init ...
49//!        // that function will always be called even for non-native.
50//!        // the menu-index is the index of the `Menu` element moved in the `object_tree::Component::menu_item_trees`
51//!        Builtin.setup_native_menu_bar(menu-bar.entries, menu-bar.sub-menu, menu-bar.activated, menu-index, no_native_menu)
52//!    }
53//! }
54//! ```
55//!
56//! ## ContextMenuInternal
57//!
58//! ```slint
59//! menu := ContextMenuInternal {
60//!     entries: [...]
61//!     sub-menu => ...
62//!     activated => ...
63//! }
64//! Button { clicked => {menu.show({x: 0, y: 0;})} }
65//! ```
66//! Is transformed to
67//!
68//! ```slint
69//! menu := ContextMenu {
70//!    property <[MenuEntry]> entries : ...
71//!    sub-menu => { ... }
72//!    activated => { ... }
73//!
74//!    // show is actually a callback called by the native code when right clicking
75//!    show(point) => { Builtin.show_popup_menu(self, self.entries, &self.sub-menu, &self.activated, point) }
76//! }
77//! ```
78//!
79//! ## ContextMenuArea
80//!
81//! This is the same as ContextMenuInternal, but entries, sub-menu, and activated are generated
82//! from the MenuItem similar to MenuBar
83//!
84//! We get a extra item tree in [`Component::menu_item_trees`]
85//! and the call to `show_popup_menu` will be responsible to set the callback handler to the
86//! `ContextMenu` item callbacks.
87//!
88//! ```slint
89//! // A `ContextMenuArea` with a complex Menu with `if` and `for` will be lowered to:
90//! menu := ContextMenu {
91//!    show(point) => {
92//!       // menu-index is an index in `Component::menu_item_trees`
93//!       // that function will set the handler to self.sub-menu and self.activated
94//!       Builtin.show_popup_menu(self, menu-index, &self.sub-menu, &self.activated, point)
95//!    }
96//! }
97//! ```
98//!
99
100use crate::diagnostics::{BuildDiagnostics, Spanned};
101use crate::expression_tree::{BuiltinFunction, Callable, Expression, NamedReference};
102use crate::langtype::{ElementType, Type};
103use crate::object_tree::*;
104use core::cell::RefCell;
105use i_slint_common::MENU_SEPARATOR_PLACEHOLDER_TITLE;
106use smol_str::{SmolStr, format_smolstr};
107use std::rc::{Rc, Weak};
108
109const HEIGHT: &str = "height";
110const ENTRIES: &str = "entries";
111const SUB_MENU: &str = "sub-menu";
112const ACTIVATED: &str = "activated";
113const SHOW: &str = "show";
114
115struct UsefulMenuComponents {
116    menubar_impl: ElementType,
117    vertical_layout: ElementType,
118    context_menu_internal: ElementType,
119    empty: ElementType,
120    menu_entry: Type,
121    menu_item_element: ElementType,
122}
123
124pub async fn lower_menus(
125    doc: &mut Document,
126    type_loader: &mut crate::typeloader::TypeLoader,
127    diag: &mut BuildDiagnostics,
128) {
129    // First check if any MenuBar, ContextMenuArea, or SystemTrayIcon is used - avoid loading std-widgets.slint if not needed
130    let mut has_menubar_or_context_menu = false;
131    doc.visit_all_used_components(|component| {
132        recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
133            if matches!(&elem.borrow().builtin_type(), Some(b) if matches!(b.name.as_str(), "MenuBar" | "ContextMenuArea" | "ContextMenuInternal" | "SystemTrayIcon")) {
134                has_menubar_or_context_menu = true;
135            }
136        })
137    });
138
139    if !has_menubar_or_context_menu {
140        return;
141    }
142
143    // Ignore import errors
144    let mut build_diags_to_ignore = BuildDiagnostics::default();
145
146    let menubar_impl = type_loader
147        .import_component("std-widgets-impl.slint", "MenuBarImpl", &mut build_diags_to_ignore)
148        .await
149        .expect("MenuBarImpl should be in std-widgets-impl.slint");
150
151    let menu_item_element = type_loader
152        .global_type_registry
153        .borrow()
154        .lookup_builtin_element("ContextMenuArea")
155        .unwrap()
156        .as_builtin()
157        .additional_accepted_child_types
158        .get("Menu")
159        .expect("ContextMenuArea should accept Menu")
160        .additional_accepted_child_types
161        .get("MenuItem")
162        .expect("Menu should accept MenuItem")
163        .clone()
164        .into();
165
166    let useful_menu_component = UsefulMenuComponents {
167        menubar_impl: menubar_impl.clone().into(),
168        context_menu_internal: type_loader
169            .global_type_registry
170            .borrow()
171            .lookup_builtin_element("ContextMenuInternal")
172            .expect("ContextMenuInternal is a builtin type"),
173        vertical_layout: type_loader
174            .global_type_registry
175            .borrow()
176            .lookup_builtin_element("VerticalLayout")
177            .expect("VerticalLayout is a builtin type"),
178        empty: type_loader.global_type_registry.borrow().empty_type(),
179        menu_entry: type_loader.global_type_registry.borrow().lookup("MenuEntry"),
180        menu_item_element,
181    };
182    assert!(matches!(&useful_menu_component.menu_entry, Type::Struct(..)));
183
184    let mut has_menu = false;
185    let mut has_menubar = false;
186
187    doc.visit_all_used_components(|component| {
188        recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
189            if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "Window") {
190                has_menubar |= process_window(elem, &useful_menu_component, type_loader.compiler_config.no_native_menu, diag);
191            }
192            if matches!(&elem.borrow().builtin_type(), Some(b) if matches!(b.name.as_str(), "ContextMenuArea" | "ContextMenuInternal")) {
193                has_menu |= process_context_menu(elem, &useful_menu_component, diag);
194            }
195            if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "SystemTrayIcon")
196                && matches!(&elem.borrow().base_type, ElementType::Builtin(b) if b.name == "SystemTrayIcon")
197            {
198                // Only the directly-Builtin SystemTrayIcon is processed here. A
199                // SystemTrayIcon-derived component as a child element (e.g.
200                // `MyTray {}` inside a Window) is rejected by
201                // `warn_about_child_windows`; calling `process_system_tray_icon`
202                // on it would `as_builtin()`-panic on the still-Component
203                // base_type (lower_menus runs before inlining). The
204                // legitimate root case is reached via the parent
205                // `visit_all_used_components` entering the user component
206                // directly, whose root_element IS the SystemTrayIcon builtin.
207                process_system_tray_icon(elem, &useful_menu_component, diag);
208            }
209        })
210    });
211
212    if has_menubar {
213        recurse_elem_including_sub_components_no_borrow(&menubar_impl, &(), &mut |elem, _| {
214            if matches!(&elem.borrow().builtin_type(), Some(b) if matches!(b.name.as_str(), "ContextMenuArea" | "ContextMenuInternal"))
215            {
216                has_menu |= process_context_menu(elem, &useful_menu_component, diag);
217            }
218        });
219    }
220    if has_menu {
221        let popup_menu_impl = type_loader
222            .import_component("std-widgets-impl.slint", "PopupMenuImpl", &mut build_diags_to_ignore)
223            .await
224            .expect("PopupMenuImpl should be in std-widgets-impl.slint");
225        {
226            let mut root = popup_menu_impl.root_element.borrow_mut();
227
228            for prop in [ENTRIES, SUB_MENU, ACTIVATED] {
229                match root.property_declarations.get_mut(prop) {
230                    Some(d) => d.expose_in_public_api = true,
231                    None => diag.push_error(format!("PopupMenuImpl doesn't have {prop}"), &*root),
232                }
233            }
234            root.property_analysis
235                .borrow_mut()
236                .entry(SmolStr::new_static(ENTRIES))
237                .or_default()
238                .is_set = true;
239        }
240
241        recurse_elem_including_sub_components_no_borrow(&popup_menu_impl, &(), &mut |elem, _| {
242            if matches!(&elem.borrow().builtin_type(), Some(b) if matches!(b.name.as_str(), "ContextMenuArea" | "ContextMenuInternal"))
243            {
244                process_context_menu(elem, &useful_menu_component, diag);
245            }
246        });
247        doc.popup_menu_impl = popup_menu_impl.into();
248    }
249}
250
251fn process_context_menu(
252    context_menu_elem: &ElementRc,
253    components: &UsefulMenuComponents,
254    diag: &mut BuildDiagnostics,
255) -> bool {
256    let is_internal = matches!(&context_menu_elem.borrow().base_type, ElementType::Builtin(b) if b.name == "ContextMenuInternal");
257
258    if is_internal && context_menu_elem.borrow().property_declarations.contains_key(ENTRIES) {
259        // Already processed;
260        return false;
261    }
262
263    // generate the show callback
264    let source_location = Some(context_menu_elem.borrow().to_source_location());
265    let position = Expression::FunctionParameterReference {
266        index: 0,
267        ty: crate::typeregister::logical_point_type().into(),
268    };
269    let expr = if !is_internal {
270        let menu_element_type = context_menu_elem
271            .borrow()
272            .base_type
273            .as_builtin()
274            .additional_accepted_child_types
275            .get("Menu")
276            .expect("ContextMenu should accept Menu")
277            .clone()
278            .into();
279
280        let mut menu_elem: Option<Rc<RefCell<Element>>> = None;
281        context_menu_elem.borrow_mut().children.retain(|x| {
282            if x.borrow().base_type == menu_element_type {
283                if let Some(ref existing) = menu_elem {
284                    diag.push_error(
285                        "Only one Menu is allowed in a ContextMenu".into(),
286                        &*x.borrow(),
287                    );
288                    diag.push_note("First Menu defined here".into(), &*existing.borrow());
289                } else {
290                    menu_elem = Some(x.clone());
291                }
292                false
293            } else {
294                true
295            }
296        });
297
298        let Some(menu_elem) = menu_elem else {
299            diag.push_error(
300                "ContextMenuArea should have a Menu".into(),
301                &*context_menu_elem.borrow(),
302            );
303            return false;
304        };
305        if menu_elem.borrow().repeated.is_some() {
306            diag.push_error(
307                "ContextMenuArea's root Menu cannot be in a conditional or repeated element".into(),
308                &*menu_elem.borrow(),
309            );
310        }
311
312        let children = std::mem::take(&mut menu_elem.borrow_mut().children);
313        let c = lower_menu_items(context_menu_elem, children, components, diag);
314        let item_tree_root = Expression::ElementReference(Rc::downgrade(&c.root_element));
315
316        context_menu_elem.borrow_mut().base_type = components.context_menu_internal.clone();
317        for (name, _) in &components.context_menu_internal.property_list() {
318            if let Some(decl) = context_menu_elem.borrow().property_declarations.get(name) {
319                diag.push_error(format!("Cannot re-define internal property '{name}'"), &decl.node);
320            }
321        }
322
323        Expression::FunctionCall {
324            function: BuiltinFunction::ShowPopupMenu.into(),
325            arguments: vec![
326                Expression::ElementReference(Rc::downgrade(context_menu_elem)),
327                item_tree_root,
328                position,
329            ],
330            source_location,
331        }
332    } else {
333        // `ContextMenuInternal`
334
335        // Materialize the entries property
336        context_menu_elem.borrow_mut().property_declarations.insert(
337            SmolStr::new_static(ENTRIES),
338            Type::Array(components.menu_entry.clone().into()).into(),
339        );
340        let entries = Expression::PropertyReference(NamedReference::new(
341            context_menu_elem,
342            SmolStr::new_static(ENTRIES),
343        ));
344
345        Expression::FunctionCall {
346            function: BuiltinFunction::ShowPopupMenuInternal.into(),
347            arguments: vec![
348                Expression::ElementReference(Rc::downgrade(context_menu_elem)),
349                entries,
350                position,
351            ],
352            source_location,
353        }
354    };
355
356    let old = context_menu_elem
357        .borrow_mut()
358        .bindings
359        .insert(SmolStr::new_static(SHOW), RefCell::new(expr.into()));
360    if let Some(old) = old {
361        diag.push_error("'show' is not a callback in ContextMenuArea".into(), &old.borrow().span);
362    }
363
364    true
365}
366
367fn process_system_tray_icon(
368    system_tray_elem: &ElementRc,
369    components: &UsefulMenuComponents,
370    diag: &mut BuildDiagnostics,
371) {
372    // A Menu child is optional; without it, no SetupSystemTrayIcon call is emitted.
373    let menu_element_type: ElementType = system_tray_elem
374        .borrow()
375        .base_type
376        .as_builtin()
377        .additional_accepted_child_types
378        .get("Menu")
379        .expect("SystemTrayIcon should accept Menu")
380        .clone()
381        .into();
382
383    let mut menu_elem: Option<Rc<RefCell<Element>>> = None;
384    system_tray_elem.borrow_mut().children.retain(|x| {
385        if x.borrow().base_type == menu_element_type {
386            if let Some(ref existing) = menu_elem {
387                diag.push_error(
388                    "Only one Menu is allowed in a SystemTrayIcon".into(),
389                    &*x.borrow(),
390                );
391                diag.push_note("First Menu defined here".into(), &*existing.borrow());
392            } else {
393                menu_elem = Some(x.clone());
394            }
395            false
396        } else {
397            true
398        }
399    });
400
401    let Some(menu_elem) = menu_elem else {
402        // No menu is a valid configuration; nothing to lower.
403        return;
404    };
405
406    // `if cond : Menu { ... }` is allowed (the wrapper switches the menu's
407    // shadow tree on/off based on the condition); `for ... : Menu { ... }`
408    // is not.
409    let repeated = menu_elem.borrow_mut().repeated.take();
410    let condition = repeated.map(|repeated| {
411        if !repeated.is_conditional_element {
412            diag.push_error(
413                "SystemTrayIcon's Menu cannot be in a repeated element".into(),
414                &*menu_elem.borrow(),
415            );
416        }
417        repeated.model
418    });
419
420    let source_location = Some(system_tray_elem.borrow().to_source_location());
421    let children = std::mem::take(&mut menu_elem.borrow_mut().children);
422    let c = lower_menu_items(system_tray_elem, children, components, diag);
423    let item_tree_root = Expression::ElementReference(Rc::downgrade(&c.root_element));
424
425    let mut arguments =
426        vec![Expression::ElementReference(Rc::downgrade(system_tray_elem)), item_tree_root];
427    if let Some(condition) = condition {
428        arguments.push(condition);
429    }
430
431    let setup = Expression::FunctionCall {
432        function: BuiltinFunction::SetupSystemTrayIcon.into(),
433        arguments,
434        source_location,
435    };
436
437    let component = system_tray_elem.borrow().enclosing_component.upgrade().unwrap();
438    component.init_code.borrow_mut().constructor_code.push(setup);
439}
440
441fn process_window(
442    win: &ElementRc,
443    components: &UsefulMenuComponents,
444    no_native_menu: bool,
445    diag: &mut BuildDiagnostics,
446) -> bool {
447    let mut menu_bar: Option<Rc<RefCell<Element>>> = None;
448    win.borrow_mut().children.retain(|x| {
449        if matches!(&x.borrow().base_type, ElementType::Builtin(b) if b.name == "MenuBar") {
450            if let Some(ref menu_bar) = menu_bar {
451                diag.push_error("Only one MenuBar is allowed in a Window".into(), &*x.borrow());
452                diag.push_note("First MenuBar defined here".into(), &*menu_bar.borrow());
453            } else {
454                menu_bar = Some(x.clone());
455            }
456            false
457        } else {
458            true
459        }
460    });
461
462    let Some(menu_bar) = menu_bar else {
463        return false;
464    };
465    let repeated = menu_bar.borrow_mut().repeated.take();
466    let mut condition = repeated.map(|repeated| {
467        if !repeated.is_conditional_element {
468            diag.push_error("MenuBar cannot be in a repeated element".into(), &*menu_bar.borrow());
469        }
470        repeated.model
471    });
472    let original_cond = condition.clone();
473
474    // Lower MenuItem's into a tree root
475    let children = std::mem::take(&mut menu_bar.borrow_mut().children);
476    let c = lower_menu_items(&menu_bar, children, components, diag);
477    let item_tree_root = Expression::ElementReference(Rc::downgrade(&c.root_element));
478
479    if !no_native_menu {
480        let supports_native_menu_bar = Expression::UnaryOp {
481            op: '!',
482            sub: Expression::FunctionCall {
483                function: BuiltinFunction::SupportsNativeMenuBar.into(),
484                arguments: Vec::new(),
485                source_location: None,
486            }
487            .into(),
488        };
489        condition = match condition {
490            Some(condition) => Some(Expression::BinaryExpression {
491                lhs: condition.into(),
492                rhs: supports_native_menu_bar.into(),
493                op: '&',
494            }),
495            None => Some(supports_native_menu_bar),
496        };
497    }
498
499    let mut window = win.borrow_mut();
500    let menubar_impl = Element {
501        id: format_smolstr!("{}-menulayout", window.id),
502        base_type: components.menubar_impl.clone(),
503        enclosing_component: window.enclosing_component.clone(),
504        repeated: condition.clone().map(|condition| crate::object_tree::RepeatedElementInfo {
505            model: condition,
506            model_data_id: SmolStr::default(),
507            index_id: SmolStr::default(),
508            is_conditional_element: true,
509            is_listview: None,
510        }),
511        ..Default::default()
512    }
513    .make_rc();
514
515    // Create a child that contains all the children of the window but the menubar
516    let child = Element {
517        id: format_smolstr!("{}-child", window.id),
518        base_type: components.empty.clone(),
519        enclosing_component: window.enclosing_component.clone(),
520        children: std::mem::take(&mut window.children),
521        ..Default::default()
522    }
523    .make_rc();
524
525    let child_height = NamedReference::new(&child, SmolStr::new_static(HEIGHT));
526
527    let source_location = Some(menu_bar.borrow().to_source_location());
528
529    for prop in [ENTRIES, SUB_MENU, ACTIVATED] {
530        // materialize the properties and callbacks
531        let ty = components.menubar_impl.lookup_property(prop).property_type;
532        assert_ne!(ty, Type::Invalid, "Can't lookup type for {prop}");
533        let nr = NamedReference::new(&menu_bar, SmolStr::new_static(prop));
534        let forward_expr = if let Type::Callback(cb) = &ty {
535            Expression::FunctionCall {
536                function: Callable::Callback(nr),
537                arguments: cb
538                    .args
539                    .iter()
540                    .enumerate()
541                    .map(|(index, ty)| Expression::FunctionParameterReference {
542                        index,
543                        ty: ty.clone(),
544                    })
545                    .collect(),
546                source_location: source_location.clone(),
547            }
548        } else {
549            Expression::PropertyReference(nr)
550        };
551        menubar_impl.borrow_mut().bindings.insert(prop.into(), RefCell::new(forward_expr.into()));
552        let old = menu_bar
553            .borrow_mut()
554            .property_declarations
555            .insert(prop.into(), PropertyDeclaration { property_type: ty, ..Default::default() });
556        if let Some(old) = old {
557            diag.push_error(format!("Cannot re-define internal property '{prop}'"), &old.node);
558        }
559    }
560
561    // Transfer the visible binding from MenuBar to MenuBarImpl
562    let visible_binding = menu_bar.borrow_mut().bindings.remove("visible");
563    if let Some(visible_binding) = &visible_binding {
564        menubar_impl
565            .borrow_mut()
566            .bindings
567            .insert(SmolStr::new_static("menubar-visible"), visible_binding.clone());
568    }
569
570    // Transform the MenuBar in a layout
571    menu_bar.borrow_mut().base_type = components.vertical_layout.clone();
572    menu_bar.borrow_mut().children = vec![menubar_impl, child];
573
574    for prop in [ENTRIES, SUB_MENU, ACTIVATED] {
575        menu_bar
576            .borrow()
577            .property_analysis
578            .borrow_mut()
579            .entry(SmolStr::new_static(prop))
580            .or_default()
581            .is_set = true;
582    }
583
584    window.children.push(menu_bar.clone());
585    let component = window.enclosing_component.upgrade().unwrap();
586    drop(window);
587
588    // Rename every access to `root.height` into `child.height`
589    let win_height = NamedReference::new(win, SmolStr::new_static(HEIGHT));
590    crate::object_tree::visit_all_named_references(&component, &mut |nr| {
591        if nr == &win_height {
592            *nr = child_height.clone()
593        }
594    });
595    // except for the actual geometry
596    win.borrow_mut().geometry_props.as_mut().unwrap().height = win_height;
597
598    let mut arguments = vec![
599        Expression::PropertyReference(NamedReference::new(&menu_bar, SmolStr::new_static(ENTRIES))),
600        Expression::PropertyReference(NamedReference::new(
601            &menu_bar,
602            SmolStr::new_static(SUB_MENU),
603        )),
604        Expression::PropertyReference(NamedReference::new(
605            &menu_bar,
606            SmolStr::new_static(ACTIVATED),
607        )),
608        item_tree_root,
609        Expression::BoolLiteral(no_native_menu),
610    ];
611
612    if let Some(condition) = original_cond {
613        arguments.push(condition);
614    } else {
615        arguments.push(Expression::BoolLiteral(true));
616    }
617
618    if let Some(visible_binding) = visible_binding {
619        arguments.push(visible_binding.borrow().expression.clone());
620    } else {
621        arguments.push(Expression::BoolLiteral(true));
622    }
623
624    let setup_menubar = Expression::FunctionCall {
625        function: BuiltinFunction::SetupMenuBar.into(),
626        arguments,
627        source_location,
628    };
629    component.init_code.borrow_mut().constructor_code.push(setup_menubar);
630
631    true
632}
633
634/// Lower the MenuItem's and Menu's to either
635///  - `entries` and `activated` and `sub-menu` properties/callback, in which cases it returns None
636///  - 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
637fn lower_menu_items(
638    parent: &ElementRc,
639    children: Vec<ElementRc>,
640    components: &UsefulMenuComponents,
641    diag: &mut BuildDiagnostics,
642) -> Rc<Component> {
643    let in_menubar = parent.borrow().base_type.type_name() == Some("MenuBar");
644    let component = Rc::new_cyclic(|component_weak| {
645        let root_element = Rc::new(RefCell::new(Element {
646            base_type: components.empty.clone(),
647            children,
648            enclosing_component: component_weak.clone(),
649            ..Default::default()
650        }));
651        recurse_elem(&root_element, &true, &mut |element: &ElementRc, is_root| {
652            if !is_root {
653                debug_assert!(Weak::ptr_eq(
654                    &element.borrow().enclosing_component,
655                    &parent.borrow().enclosing_component
656                ));
657                element.borrow_mut().enclosing_component = component_weak.clone();
658                element.borrow_mut().geometry_props = None;
659
660                if !in_menubar && let Some(binding) = element.borrow().bindings.get("shortcut") {
661                    diag.push_error(
662                        "MenuItem shortcuts are currently only supported in the MenuBar".into(),
663                        &*binding.borrow(),
664                    );
665                }
666
667                if element.borrow().base_type.type_name() == Some("MenuSeparator") {
668                    element.borrow_mut().bindings.insert(
669                        "title".into(),
670                        RefCell::new(
671                            Expression::StringLiteral(SmolStr::new_static(
672                                MENU_SEPARATOR_PLACEHOLDER_TITLE,
673                            ))
674                            .into(),
675                        ),
676                    );
677                }
678                // Menu/MenuSeparator -> MenuItem
679                element.borrow_mut().base_type = components.menu_item_element.clone();
680            }
681            false
682        });
683        Component {
684            id: SmolStr::default(),
685            root_element,
686            parent_element: RefCell::new(Rc::downgrade(parent)),
687            ..Default::default()
688        }
689    });
690    let enclosing = parent.borrow().enclosing_component.upgrade().unwrap();
691
692    super::lower_popups::check_no_reference_to_popup(
693        parent,
694        &enclosing,
695        &Rc::downgrade(&component),
696        &NamedReference::new(parent, SmolStr::new_static("x")),
697        diag,
698    );
699
700    enclosing.menu_item_tree.borrow_mut().push(component.clone());
701
702    component
703}