1use crate::platform::{DefaultMenuInteractionHandler, IMenuInteractionHandler};
2use crate::primitives::{SelectingItemsControlImpl, TemplatedControlImpl};
3use crate::templates::{FuncTemplate, ITemplateOf};
4use crate::{Control, ControlImpl, ItemsControl, ItemsControlImpl, ItemsControlImplExt, MenuBase, MenuBaseImpl, MenuItem, Panel, StackPanel, TopLevel};
5use ferroui_base::input::{
6 AccessKeyHandler, AccessKeyPressedEventArgs, IAccessKeyHandler, IMainMenu, InputElementImpl, KeyboardNavigation,
7 KeyboardNavigationMode,
8};
9use ferroui_base::interactivity::{InteractiveImpl, RoutedEventArgs};
10use ferroui_base::layout::{LayoutableImpl, Orientation};
11use ferroui_base::reactive::{Disposable, IDisposable};
12use ferroui_base::{
13 ferro_class, ferro_class_info, ferro_impl_classes, instantiate, BoxedValue, FerroObjectImpl, Ref, StyledElement,
14 StyledElementImpl, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
15};
16use std::cell::RefCell;
17use std::rc::Rc;
18
19#[repr(C)]
21pub struct Menu {
22 base: MenuBase,
23 access_key_handler: RefCell<Option<Rc<dyn IAccessKeyHandler>>>,
24 main_menu: RefCell<Option<Rc<dyn IMainMenu>>>,
26}
27
28ferro_class!(Menu: MenuBase);
29ferro_class_info!(Menu { new: Menu::new });
30
31ferro_impl_classes!(
32 Menu: FerroObjectImpl,
33 StyledElementImpl,
34 LayoutableImpl,
35 InteractiveImpl,
36 InputElementImpl,
37 ControlImpl,
38 TemplatedControlImpl,
39 SelectingItemsControlImpl
40);
41
42impl VisualImpl for Menu {
43 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
44 Self::parent_on_attached_to_visual_tree(this, e);
45
46 let access_key_handler = TopLevel::get_top_level(Some(this)).and_then(|top_level| top_level.access_key_handler());
47 *this.access_key_handler.borrow_mut() = access_key_handler.clone();
48 if let Some(access_key_handler) = access_key_handler {
49 let main_menu = this.to_main_menu();
50 *this.main_menu.borrow_mut() = Some(main_menu.clone());
51 access_key_handler.set_main_menu(Some(main_menu));
52 }
53 }
54
55 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
56 let access_key_handler = this.access_key_handler.borrow_mut().take();
57 let main_menu = this.main_menu.borrow_mut().take();
58 if let (Some(access_key_handler), Some(main_menu)) = (access_key_handler, main_menu) {
59 if access_key_handler.main_menu().is_some_and(|current| Rc::ptr_eq(¤t, &main_menu)) {
60 access_key_handler.set_main_menu(None);
61 }
62 }
63
64 Self::parent_on_detached_from_visual_tree(this, e);
65 }
66}
67
68impl ItemsControlImpl for Menu {
69 fn prepare_container_for_item_override(this: &Self, element: &Ref<Control>, item: &Option<BoxedValue>, index: i32) {
70 Self::parent_prepare_container_for_item_override(this, element, item, index);
71
72 if let Some(menu_item) = element.downcast_ref::<MenuItem>() {
75 if menu_item.item_container_theme() == this.item_container_theme() {
76 element.clear_value(ItemsControl::item_container_theme_property());
77 }
78 }
79 }
80}
81
82impl MenuBaseImpl for Menu {
83 fn close(this: &Self) {
84 if !this.is_open() {
85 return;
86 }
87
88 for i in this.to_menu().sub_items() {
89 i.close();
90 }
91
92 this.set_is_open(false);
93 this.set_selected_index(-1);
94
95 this.raise_event(&RoutedEventArgs::with_event_and_source(MenuBase::closed_event(), this.to_ref()));
96 }
97
98 fn open(this: &Self) {
99 if this.is_open() {
100 return;
101 }
102
103 this.set_is_open(true);
104
105 this.raise_event(&RoutedEventArgs::with_event_and_source(MenuBase::opened_event(), this.to_ref()));
106 }
107}
108
109struct MainMenuHandle(Ref<Menu>);
111
112impl IMainMenu for MainMenuHandle {
113 fn is_open(&self) -> bool {
114 self.0.is_open()
115 }
116
117 fn close(&self) {
118 self.0.close();
119 }
120
121 fn open(&self) {
122 self.0.open();
123 }
124
125 fn closed(&self, handler: Rc<dyn Fn(&RoutedEventArgs)>) -> Rc<dyn IDisposable> {
126 let token = self.0.closed(move |_, e| handler(e));
127 let menu = self.0.downgrade();
128 Disposable::create(move || {
129 if let Some(menu) = menu.upgrade() {
130 menu.remove_handler(MenuBase::closed_event(), token);
131 }
132 })
133 }
134}
135
136impl Menu {
137 fn static_constructor() {
138 let default_panel: Rc<dyn ITemplateOf<Option<Ref<Panel>>>> = FuncTemplate::new(|| {
139 let panel = StackPanel::new();
140 panel.set_orientation(Orientation::Horizontal);
141 Some(panel.upcast::<Panel>())
142 });
143
144 ItemsControl::items_panel_property().override_default_value::<Menu>(default_panel);
145 KeyboardNavigation::tab_navigation_property().override_default_value::<Menu>(KeyboardNavigationMode::Once);
146 crate::automation::AutomationProperties::accessibility_view_property()
147 .override_default_value::<Menu>(crate::automation::AccessibilityView::Control);
148 crate::automation::AutomationProperties::control_type_override_property()
149 .override_default_value::<Menu>(Some(crate::automation::peers::AutomationControlType::Menu));
150 AccessKeyHandler::access_key_pressed_event().add_class_handler::<Menu>(Self::on_access_key_pressed);
151 }
152
153 pub fn construct() -> Self {
155 Self { base: MenuBase::construct(), access_key_handler: RefCell::new(None), main_menu: RefCell::new(None) }
156 }
157
158 pub fn construct_with(interaction_handler: Rc<dyn IMenuInteractionHandler>) -> Self {
160 Self { base: MenuBase::construct_with(interaction_handler), access_key_handler: RefCell::new(None), main_menu: RefCell::new(None) }
161 }
162
163 pub fn new() -> Ref<Self> {
165 instantiate(Self::construct())
166 }
167
168 pub fn with_interaction_handler(interaction_handler: Rc<dyn IMenuInteractionHandler>) -> Ref<Self> {
171 instantiate(Self::construct_with(interaction_handler))
172 }
173
174 pub(crate) fn to_main_menu(&self) -> Rc<dyn IMainMenu> {
176 Rc::new(MainMenuHandle(self.to_ref()))
177 }
178
179 fn on_access_key_pressed(_sender: &Menu, e: &AccessKeyPressedEventArgs) {
180 if e.handled() {
181 return;
182 }
183 let Some(target) = e.source().and_then(|source| source.cast::<StyledElement>()) else { return };
184
185 e.set_target(DefaultMenuInteractionHandler::get_menu_item_core(Some(target)).map(|item| item.element()));
186 e.set_handled(true);
187 }
188}