ferroui_controls/
native_menu_bar.rs1use 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#[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 pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] =
86 &[TemplatePartAttribute::new("PART_NativeMenuPresenter", <MenuBase as StaticType>::TYPE)];
87
88 fn static_constructor() {
89 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 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}