1use 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 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 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 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 if !matches!(&context_menu_elem.borrow().base_type, ElementType::Builtin(_)) {
260 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 return false;
277 }
278
279 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 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 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 return;
417 };
418
419 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 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 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 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 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 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 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 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
650fn 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 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}