Skip to main content

ferroui_controls/
native_menu_bar.rs

1use crate::items_source::ItemsSource;
2use crate::metadata::TemplatePartAttribute;
3use crate::native_menu_bar_presenter::NativeMenuBarPresenter;
4use crate::primitives::{TemplateAppliedEventArgs, TemplatedControl, TemplatedControlImpl, TemplatedControlImplExt};
5use crate::templates::{FuncControlTemplate, FuncTemplateNameScopeExtensions, IControlTemplate};
6use crate::{ControlImpl, ItemsControl, MenuBase, NativeMenu, TopLevel};
7use ferroui_base::data::{BindingPriority, TemplateBinding};
8use ferroui_base::input::InputElementImpl;
9use ferroui_base::interactivity::InteractiveImpl;
10use ferroui_base::layout::LayoutableImpl;
11use ferroui_base::reactive::{CompositeDisposable, IDisposable, ObservableExt};
12use ferroui_base::{
13    ferro_class, ferro_class_info, ferro_impl_classes, instantiate, FerroObjectExtensions, FerroObjectImpl, Ref,
14    StaticType, StyledElementImpl, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
15};
16use std::cell::RefCell;
17use std::rc::Rc;
18
19/// Shows the native menu of the top-level it is in when the platform does
20/// not export the menu.
21#[repr(C)]
22pub struct NativeMenuBar {
23    base: TemplatedControl,
24    menu: RefCell<Option<Ref<MenuBase>>>,
25    subscriptions: RefCell<Option<Rc<dyn IDisposable>>>,
26}
27
28ferro_class!(NativeMenuBar: TemplatedControl);
29ferro_class_info!(NativeMenuBar { new: NativeMenuBar::new });
30
31ferro_impl_classes!(
32    NativeMenuBar: FerroObjectImpl,
33    StyledElementImpl,
34    LayoutableImpl,
35    InteractiveImpl,
36    InputElementImpl
37);
38
39impl ControlImpl for NativeMenuBar {
40    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
41        crate::automation::peers::NativeMenuBarAutomationPeer::new(this).upcast()
42    }
43}
44
45impl TemplatedControlImpl for NativeMenuBar {
46    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
47        Self::parent_on_apply_template(this, e);
48
49        let menu = e
50            .name_scope()
51            .find_as::<MenuBase>("PART_NativeMenuPresenter")
52            .or_else(|| this.find_descendant_of_type::<MenuBase>(false))
53            .unwrap_or_else(|| panic!("NativeMenuBar requires a MenuBase#PART_NativeMenuPresenter template part."));
54        *this.menu.borrow_mut() = Some(menu.clone());
55
56        if let Some(top_level) = TopLevel::get_top_level(Some(this)) {
57            this.subscribe_to_toplevel(&top_level, &menu);
58        }
59    }
60}
61
62impl VisualImpl for NativeMenuBar {
63    fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
64        Self::parent_on_attached_to_visual_tree(this, e);
65
66        let Some(menu) = this.menu.borrow().clone() else { return };
67
68        if let Some(top_level) = TopLevel::get_top_level(Some(this)) {
69            this.subscribe_to_toplevel(&top_level, &menu);
70        }
71    }
72
73    fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
74        Self::parent_on_detached_from_visual_tree(this, e);
75
76        let subscriptions = this.subscriptions.borrow_mut().take();
77        if let Some(subscriptions) = subscriptions {
78            subscriptions.dispose();
79        }
80    }
81}
82
83impl NativeMenuBar {
84    /// The named parts expected in the control template.
85    pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] =
86        &[TemplatePartAttribute::new("PART_NativeMenuPresenter", <MenuBase as StaticType>::TYPE)];
87
88    fn static_constructor() {
89        // TODO12 Ideally we should make NativeMenuBar inherit MenuBase directly, but it would be a breaking change for 11.x.
90        // Changing default template while keeping old StyleKeyOverride => Menu isn't a breaking change.
91        let template: Rc<dyn IControlTemplate> = FuncControlTemplate::new(|_, ns| {
92            let presenter = NativeMenuBarPresenter::new();
93            presenter.set_name(Some("PART_NativeMenuPresenter".to_string()));
94            presenter.bind_binding(
95                TemplatedControl::background_property().as_property(),
96                &TemplateBinding::new(TemplatedControl::background_property().as_property()),
97            );
98            presenter.bind_binding(
99                TemplatedControl::border_brush_property().as_property(),
100                &TemplateBinding::new(TemplatedControl::border_brush_property().as_property()),
101            );
102            presenter.register_in_name_scope(&**ns).upcast()
103        });
104        TemplatedControl::template_property().override_default_value::<NativeMenuBar>(Some(template));
105    }
106
107    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
108    pub fn construct() -> Self {
109        Self { base: TemplatedControl::construct(), menu: RefCell::new(None), subscriptions: RefCell::new(None) }
110    }
111
112    pub fn new() -> Ref<Self> {
113        instantiate(Self::construct())
114    }
115
116    fn subscribe_to_toplevel(&self, top_level: &Ref<TopLevel>, menu: &Ref<MenuBase>) {
117        let old = self.subscriptions.borrow_mut().take();
118        if let Some(old) = old {
119            old.dispose();
120        }
121
122        let subscriptions: Rc<dyn IDisposable> = Rc::new(CompositeDisposable::from_disposables([
123            menu.bind(
124                Visual::is_visible_property(),
125                top_level
126                    .get_binding_observable(NativeMenu::is_native_menu_exported_property())
127                    .select(|v| !v.get_value_or_default(false)),
128                BindingPriority::LocalValue,
129            ),
130            menu.bind(
131                ItemsControl::items_source_property(),
132                top_level
133                    .get_binding_observable(NativeMenu::menu_property())
134                    .select(|v| v.get_value_or_default(None).map(|menu| ItemsSource::from(Rc::new(menu.items())))),
135                BindingPriority::LocalValue,
136            ),
137        ]));
138        *self.subscriptions.borrow_mut() = Some(subscriptions);
139    }
140}