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, PropertyLookupMode, 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    // This pass runs before inlining, so a component inheriting ContextMenuArea is lowered
257    // through its own root element, the only one whose base_type is the builtin. Skip the
258    // elements instantiating such a component, however deep the inheritance chain.
259    if !matches!(&context_menu_elem.borrow().base_type, ElementType::Builtin(_)) {
260        // A Menu declared here would be dropped silently.
261        for c in &context_menu_elem.borrow().children {
262            if matches!(&c.borrow().base_type, ElementType::Builtin(b) if b.name == "Menu") {
263                diag.push_error(
264                    "Menu must be declared inside the component inheriting ContextMenuArea".into(),
265                    &*c.borrow(),
266                );
267            }
268        }
269        return false;
270    }
271
272    let is_internal = matches!(&context_menu_elem.borrow().base_type, ElementType::Builtin(b) if b.name == "ContextMenuInternal");
273
274    if is_internal && context_menu_elem.borrow().property_declarations.contains_key(ENTRIES) {
275        // Already processed;
276        return false;
277    }
278
279    // generate the show callback
280    let source_location = Some(context_menu_elem.borrow().to_source_location());
281    let position = Expression::FunctionParameterReference {
282        index: 0,
283        ty: crate::typeregister::logical_point_type().into(),
284    };
285    let expr = if !is_internal {
286        let menu_element_type = context_menu_elem
287            .borrow()
288            .base_type
289            .as_builtin()
290            .additional_accepted_child_types
291            .get("Menu")
292            .expect("ContextMenu should accept Menu")
293            .clone()
294            .into();
295
296        let mut menu_elem: Option<Rc<RefCell<Element>>> = None;
297        context_menu_elem.borrow_mut().children.retain(|x| {
298            if x.borrow().base_type == menu_element_type {
299                if let Some(ref existing) = menu_elem {
300                    diag.push_error(
301                        "Only one Menu is allowed in a ContextMenu".into(),
302                        &*x.borrow(),
303                    );
304                    diag.push_note("First Menu defined here".into(), &*existing.borrow());
305                } else {
306                    menu_elem = Some(x.clone());
307                }
308                false
309            } else {
310                true
311            }
312        });
313
314        let Some(menu_elem) = menu_elem else {
315            diag.push_error(
316                "ContextMenuArea should have a Menu".into(),
317                &*context_menu_elem.borrow(),
318            );
319            return false;
320        };
321        if menu_elem.borrow().repeated.is_some() {
322            diag.push_error(
323                "ContextMenuArea's root Menu cannot be in a conditional or repeated element".into(),
324                &*menu_elem.borrow(),
325            );
326        }
327
328        let children = std::mem::take(&mut menu_elem.borrow_mut().children);
329        let c = lower_menu_items(context_menu_elem, children, components, diag);
330        let item_tree_root = Expression::ElementReference(Rc::downgrade(&c.root_element));
331
332        context_menu_elem.borrow_mut().base_type = components.context_menu_internal.clone();
333        for (name, _) in &components.context_menu_internal.property_list() {
334            if let Some(decl) = context_menu_elem.borrow().property_declarations.get(name) {
335                diag.push_error(format!("Cannot re-define internal property '{name}'"), &decl.node);
336            }
337        }
338
339        Expression::FunctionCall {
340            function: BuiltinFunction::ShowPopupMenu.into(),
341            arguments: vec![
342                Expression::ElementReference(Rc::downgrade(context_menu_elem)),
343                item_tree_root,
344                position,
345            ],
346            source_location,
347        }
348    } else {
349        // `ContextMenuInternal`
350
351        // Materialize the entries property
352        context_menu_elem.borrow_mut().property_declarations.insert(
353            SmolStr::new_static(ENTRIES),
354            Type::Array(components.menu_entry.clone().into()).into(),
355        );
356        let entries = Expression::PropertyReference(NamedReference::new(
357            context_menu_elem,
358            SmolStr::new_static(ENTRIES),
359        ));
360
361        Expression::FunctionCall {
362            function: BuiltinFunction::ShowPopupMenuInternal.into(),
363            arguments: vec![
364                Expression::ElementReference(Rc::downgrade(context_menu_elem)),
365                entries,
366                position,
367            ],
368            source_location,
369        }
370    };
371
372    let old = context_menu_elem.borrow_mut().set_binding(SmolStr::new_static(SHOW), expr.into());
373    if let Some(old) = old {
374        diag.push_error("'show' is not a callback in ContextMenuArea".into(), &old.span);
375    }
376
377    true
378}
379
380fn process_system_tray_icon(
381    system_tray_elem: &ElementRc,
382    components: &UsefulMenuComponents,
383    diag: &mut BuildDiagnostics,
384) {
385    // A Menu child is optional; without it, no SetupSystemTrayIcon call is emitted.
386    let menu_element_type: ElementType = system_tray_elem
387        .borrow()
388        .base_type
389        .as_builtin()
390        .additional_accepted_child_types
391        .get("Menu")
392        .expect("SystemTrayIcon should accept Menu")
393        .clone()
394        .into();
395
396    let mut menu_elem: Option<Rc<RefCell<Element>>> = None;
397    system_tray_elem.borrow_mut().children.retain(|x| {
398        if x.borrow().base_type == menu_element_type {
399            if let Some(ref existing) = menu_elem {
400                diag.push_error(
401                    "Only one Menu is allowed in a SystemTrayIcon".into(),
402                    &*x.borrow(),
403                );
404                diag.push_note("First Menu defined here".into(), &*existing.borrow());
405            } else {
406                menu_elem = Some(x.clone());
407            }
408            false
409        } else {
410            true
411        }
412    });
413
414    let Some(menu_elem) = menu_elem else {
415        // No menu is a valid configuration; nothing to lower.
416        return;
417    };
418
419    // `if cond : Menu { ... }` is allowed (the wrapper switches the menu's
420    // shadow tree on/off based on the condition); `for ... : Menu { ... }`
421    // is not.
422    let repeated = menu_elem.borrow_mut().repeated.take();
423    let condition = repeated.map(|repeated| {
424        if !repeated.is_conditional_element {
425            diag.push_error(
426                "SystemTrayIcon's Menu cannot be in a repeated element".into(),
427                &*menu_elem.borrow(),
428            );
429        }
430        repeated.model
431    });
432
433    let source_location = Some(system_tray_elem.borrow().to_source_location());
434    let children = std::mem::take(&mut menu_elem.borrow_mut().children);
435    let c = lower_menu_items(system_tray_elem, children, components, diag);
436    let item_tree_root = Expression::ElementReference(Rc::downgrade(&c.root_element));
437
438    let mut arguments =
439        vec![Expression::ElementReference(Rc::downgrade(system_tray_elem)), item_tree_root];
440    if let Some(condition) = condition {
441        arguments.push(condition);
442    }
443
444    let setup = Expression::FunctionCall {
445        function: BuiltinFunction::SetupSystemTrayIcon.into(),
446        arguments,
447        source_location,
448    };
449
450    let component = system_tray_elem.borrow().enclosing_component.upgrade().unwrap();
451    component.init_code.borrow_mut().constructor_code.push(setup);
452}
453
454fn process_window(
455    win: &ElementRc,
456    components: &UsefulMenuComponents,
457    no_native_menu: bool,
458    diag: &mut BuildDiagnostics,
459) -> bool {
460    let mut menu_bar: Option<Rc<RefCell<Element>>> = None;
461    win.borrow_mut().children.retain(|x| {
462        if matches!(&x.borrow().base_type, ElementType::Builtin(b) if b.name == "MenuBar") {
463            if let Some(ref menu_bar) = menu_bar {
464                diag.push_error("Only one MenuBar is allowed in a Window".into(), &*x.borrow());
465                diag.push_note("First MenuBar defined here".into(), &*menu_bar.borrow());
466            } else {
467                menu_bar = Some(x.clone());
468            }
469            false
470        } else {
471            true
472        }
473    });
474
475    let Some(menu_bar) = menu_bar else {
476        return false;
477    };
478    let repeated = menu_bar.borrow_mut().repeated.take();
479    let mut condition = repeated.map(|repeated| {
480        if !repeated.is_conditional_element {
481            diag.push_error("MenuBar cannot be in a repeated element".into(), &*menu_bar.borrow());
482        }
483        repeated.model
484    });
485    let original_cond = condition.clone();
486
487    // Lower MenuItem's into a tree root
488    let children = std::mem::take(&mut menu_bar.borrow_mut().children);
489    let c = lower_menu_items(&menu_bar, children, components, diag);
490    let item_tree_root = Expression::ElementReference(Rc::downgrade(&c.root_element));
491
492    if !no_native_menu {
493        let supports_native_menu_bar = Expression::UnaryOp {
494            op: '!',
495            sub: Expression::FunctionCall {
496                function: BuiltinFunction::SupportsNativeMenuBar.into(),
497                arguments: Vec::new(),
498                source_location: None,
499            }
500            .into(),
501        };
502        condition = match condition {
503            Some(condition) => Some(Expression::BinaryExpression {
504                lhs: condition.into(),
505                rhs: supports_native_menu_bar.into(),
506                op: '&',
507                source_location: None,
508            }),
509            None => Some(supports_native_menu_bar),
510        };
511    }
512
513    let mut window = win.borrow_mut();
514    let menubar_impl = Element {
515        id: format_smolstr!("{}-menulayout", window.id),
516        base_type: components.menubar_impl.clone(),
517        enclosing_component: window.enclosing_component.clone(),
518        repeated: condition.clone().map(|condition| crate::object_tree::RepeatedElementInfo {
519            model: condition,
520            model_data_id: SmolStr::default(),
521            index_id: SmolStr::default(),
522            is_conditional_element: true,
523            is_listview: None,
524        }),
525        ..Default::default()
526    }
527    .make_rc();
528
529    // Create a child that contains all the children of the window but the menubar
530    let child = Element {
531        id: format_smolstr!("{}-child", window.id),
532        base_type: components.empty.clone(),
533        enclosing_component: window.enclosing_component.clone(),
534        children: std::mem::take(&mut window.children),
535        ..Default::default()
536    }
537    .make_rc();
538
539    let child_height = NamedReference::new(&child, SmolStr::new_static(HEIGHT));
540
541    let source_location = Some(menu_bar.borrow().to_source_location());
542
543    for prop in [ENTRIES, SUB_MENU, ACTIVATED] {
544        // materialize the properties and callbacks
545        let ty = components
546            .menubar_impl
547            .lookup_property(prop, PropertyLookupMode::ComponentLocal)
548            .property_type;
549        assert_ne!(ty, Type::Invalid, "Can't lookup type for {prop}");
550        let nr = NamedReference::new(&menu_bar, SmolStr::new_static(prop));
551        let forward_expr = if let Type::Callback(cb) = &ty {
552            Expression::FunctionCall {
553                function: Callable::Callback(nr),
554                arguments: cb
555                    .args
556                    .iter()
557                    .enumerate()
558                    .map(|(index, ty)| Expression::FunctionParameterReference {
559                        index,
560                        ty: ty.clone(),
561                    })
562                    .collect(),
563                source_location: source_location.clone(),
564            }
565        } else {
566            Expression::PropertyReference(nr)
567        };
568        menubar_impl.borrow_mut().set_binding(prop.into(), forward_expr.into());
569        let old = menu_bar
570            .borrow_mut()
571            .property_declarations
572            .insert(prop.into(), PropertyDeclaration { property_type: ty, ..Default::default() });
573        if let Some(old) = old {
574            diag.push_error(format!("Cannot re-define internal property '{prop}'"), &old.node);
575        }
576    }
577
578    // Transfer the visible binding from MenuBar to MenuBarImpl
579    let visible_binding = menu_bar.borrow_mut().take_binding("visible");
580    if let Some(visible_binding) = &visible_binding {
581        menubar_impl
582            .borrow_mut()
583            .set_binding(SmolStr::new_static("menubar-visible"), visible_binding.clone());
584    }
585
586    // Transform the MenuBar in a layout
587    menu_bar.borrow_mut().base_type = components.vertical_layout.clone();
588    menu_bar.borrow_mut().children = vec![menubar_impl, child];
589
590    for prop in [ENTRIES, SUB_MENU, ACTIVATED] {
591        menu_bar
592            .borrow()
593            .property_analysis
594            .borrow_mut()
595            .entry(SmolStr::new_static(prop))
596            .or_default()
597            .is_set = true;
598    }
599
600    window.children.push(menu_bar.clone());
601    let component = window.enclosing_component.upgrade().unwrap();
602    drop(window);
603
604    // Rename every access to `root.height` into `child.height`
605    let win_height = NamedReference::new(win, SmolStr::new_static(HEIGHT));
606    crate::object_tree::visit_all_named_references(&component, &mut |nr| {
607        if nr == &win_height {
608            *nr = child_height.clone()
609        }
610    });
611    // except for the actual geometry
612    win.borrow_mut().geometry_props.as_mut().unwrap().height = win_height;
613
614    let mut arguments = vec![
615        Expression::PropertyReference(NamedReference::new(&menu_bar, SmolStr::new_static(ENTRIES))),
616        Expression::PropertyReference(NamedReference::new(
617            &menu_bar,
618            SmolStr::new_static(SUB_MENU),
619        )),
620        Expression::PropertyReference(NamedReference::new(
621            &menu_bar,
622            SmolStr::new_static(ACTIVATED),
623        )),
624        item_tree_root,
625        Expression::BoolLiteral(no_native_menu),
626    ];
627
628    if let Some(condition) = original_cond {
629        arguments.push(condition);
630    } else {
631        arguments.push(Expression::BoolLiteral(true));
632    }
633
634    if let Some(visible_binding) = visible_binding {
635        arguments.push(visible_binding.expression.clone());
636    } else {
637        arguments.push(Expression::BoolLiteral(true));
638    }
639
640    let setup_menubar = Expression::FunctionCall {
641        function: BuiltinFunction::SetupMenuBar.into(),
642        arguments,
643        source_location,
644    };
645    component.init_code.borrow_mut().constructor_code.push(setup_menubar);
646
647    true
648}
649
650/// Lower the MenuItem's and Menu's to either
651///  - `entries` and `activated` and `sub-menu` properties/callback, in which cases it returns None
652///  - 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
653fn lower_menu_items(
654    parent: &ElementRc,
655    children: Vec<ElementRc>,
656    components: &UsefulMenuComponents,
657    diag: &mut BuildDiagnostics,
658) -> Rc<Component> {
659    let in_menubar = parent.borrow().base_type.type_name() == Some("MenuBar");
660    let component = Rc::new_cyclic(|component_weak| {
661        let root_element = Rc::new(RefCell::new(Element {
662            base_type: components.empty.clone(),
663            children,
664            enclosing_component: component_weak.clone(),
665            ..Default::default()
666        }));
667        recurse_elem(&root_element, &true, &mut |element: &ElementRc, is_root| {
668            if !is_root {
669                debug_assert!(Weak::ptr_eq(
670                    &element.borrow().enclosing_component,
671                    &parent.borrow().enclosing_component
672                ));
673                element.borrow_mut().enclosing_component = component_weak.clone();
674                element.borrow_mut().geometry_props = None;
675
676                if !in_menubar && let Some(binding) = element.borrow().binding("shortcut") {
677                    diag.push_error(
678                        "MenuItem shortcuts are currently only supported in the MenuBar".into(),
679                        &*binding,
680                    );
681                }
682
683                if element.borrow().base_type.type_name() == Some("MenuSeparator") {
684                    element.borrow_mut().set_binding(
685                        "title".into(),
686                        Expression::StringLiteral(SmolStr::new_static(
687                            MENU_SEPARATOR_PLACEHOLDER_TITLE,
688                        ))
689                        .into(),
690                    );
691                }
692                // Menu/MenuSeparator -> MenuItem
693                element.borrow_mut().base_type = components.menu_item_element.clone();
694            }
695            false
696        });
697        Component {
698            id: SmolStr::default(),
699            root_element,
700            parent_element: RefCell::new(Rc::downgrade(parent)),
701            ..Default::default()
702        }
703    });
704    let enclosing = parent.borrow().enclosing_component.upgrade().unwrap();
705
706    super::lower_popups::check_no_reference_to_popup(
707        parent,
708        &enclosing,
709        &Rc::downgrade(&component),
710        &NamedReference::new(parent, SmolStr::new_static("x")),
711        diag,
712    );
713
714    enclosing.menu_item_tree.borrow_mut().push(component.clone());
715
716    component
717}