Skip to main content

ferroui_controls/
menu.rs

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/// A top-level menu control.
20#[repr(C)]
21pub struct Menu {
22    base: MenuBase,
23    access_key_handler: RefCell<Option<Rc<dyn IAccessKeyHandler>>>,
24    /// The handle given to the access key handler as its main menu.
25    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(&current, &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        // Child menu items should not inherit the menu's ItemContainerTheme
73        // as that is specific for top-level menu items.
74        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
109/// The menu viewed as the main menu of a window.
110struct 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    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
154    pub fn construct() -> Self {
155        Self { base: MenuBase::construct(), access_key_handler: RefCell::new(None), main_menu: RefCell::new(None) }
156    }
157
158    /// Creates the class data of a menu that uses `interaction_handler`.
159    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    /// Initializes a new instance of the [`Menu`] class.
164    pub fn new() -> Ref<Self> {
165        instantiate(Self::construct())
166    }
167
168    /// Initializes a new instance of the [`Menu`] class that uses
169    /// `interaction_handler`.
170    pub fn with_interaction_handler(interaction_handler: Rc<dyn IMenuInteractionHandler>) -> Ref<Self> {
171        instantiate(Self::construct_with(interaction_handler))
172    }
173
174    /// The menu viewed as [`IMainMenu`].
175    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}