1use crate::generators::RecycleKey;
2use crate::i_menu::{register_menu, IMenu};
3use crate::i_menu_element::IMenuElement;
4use crate::i_menu_item::{as_menu_item, IMenuItem};
5use crate::platform::{DefaultMenuInteractionHandler, IMenuInteractionHandler};
6use crate::primitives::{SelectingItemsControl, SelectingItemsControlImpl, TemplatedControlImpl};
7use crate::{Control, ControlImpl, ItemsControl, ItemsControlImpl, Menu, MenuItem, Separator, TopLevel};
8use ferroui_base::input::{IFocusScope, IMainMenu, InputElement, InputElementImpl, KeyEventArgs, NavigationDirection};
9use ferroui_base::interactivity::{
10 Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
11};
12use ferroui_base::layout::LayoutableImpl;
13use ferroui_base::{
14 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, ferro_routed_event, BoxedValue,
15 DirectProperty, FerroObjectImpl, FerroProperty, Ref, StyledElement, StyledElementImpl, VisualImpl, VisualImplExt,
16 VisualTreeAttachmentEventArgs,
17};
18use std::cell::Cell;
19use std::rc::Rc;
20
21#[repr(C)]
23pub struct MenuBase {
24 base: SelectingItemsControl,
25 is_open: Cell<bool>,
26 interaction_handler: Rc<dyn IMenuInteractionHandler>,
27}
28
29ferro_class! {
30 MenuBase: SelectingItemsControl, virtuals MenuBaseImpl: SelectingItemsControlImpl {
31 fn close(this);
33 fn open(this);
35 fn on_submenu_opened(this, e: &RoutedEventArgs);
37 }
38}
39
40ferro_class_info!(MenuBase {});
41
42ferro_impl_classes!(
43 MenuBase: FerroObjectImpl,
44 StyledElementImpl,
45 LayoutableImpl,
46 InteractiveImpl,
47 ControlImpl,
48 TemplatedControlImpl,
49 SelectingItemsControlImpl
50);
51
52impl IFocusScope for MenuBase {}
53
54impl VisualImpl for MenuBase {
55 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
56 Self::parent_on_attached_to_visual_tree(this, e);
57 this.interaction_handler.attach(&this.to_ref());
58 }
59
60 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
61 Self::parent_on_detached_from_visual_tree(this, e);
62 this.interaction_handler.detach(&this.to_ref());
63 }
64}
65
66impl InputElementImpl for MenuBase {
67 fn is_focus_scope(_this: &Self) -> bool {
68 true
69 }
70
71 fn on_key_down(_this: &Self, _e: &KeyEventArgs) {
72 }
74}
75
76impl ItemsControlImpl for MenuBase {
77 fn create_container_for_item_override(
78 _this: &Self,
79 _item: &Option<BoxedValue>,
80 _index: i32,
81 _recycle_key: Option<RecycleKey>,
82 ) -> Ref<Control> {
83 MenuItem::new().upcast()
84 }
85
86 fn needs_container_override(_this: &Self, item: &Option<BoxedValue>, _index: i32) -> (bool, Option<RecycleKey>) {
87 Self::needs_menu_container(item)
88 }
89}
90
91impl MenuBaseImpl for MenuBase {
92 fn close(_this: &Self) {
93 panic!("MenuBase is abstract.")
94 }
95
96 fn open(_this: &Self) {
97 panic!("MenuBase is abstract.")
98 }
99
100 fn on_submenu_opened(this: &Self, e: &RoutedEventArgs) {
101 let menu_item = e.source().and_then(|source| source.cast::<MenuItem>());
102
103 if let Some(menu_item) = menu_item {
104 let this_element: &StyledElement = this;
105 if menu_item.parent().is_some_and(|parent| std::ptr::eq::<StyledElement>(&*parent, this_element)) {
106 let children: Vec<Ref<MenuItem>> = StyledElement::logical_children(this)
107 .to_vec()
108 .into_iter()
109 .filter_map(|child| child.cast::<MenuItem>())
110 .collect();
111
112 for child in children {
113 if child != menu_item && child.is_sub_menu_open() {
114 child.set_is_sub_menu_open(false);
115 }
116 }
117 }
118 }
119
120 this.set_is_open(true);
121 }
122}
123
124struct MenuBaseHandle(Ref<MenuBase>);
126
127impl IMenuElement for MenuBaseHandle {
128 fn element(&self) -> Ref<InputElement> {
129 self.0.clone().upcast()
130 }
131
132 fn selected_item(&self) -> Option<Rc<dyn IMenuItem>> {
133 let index = self.0.selected_index();
134 if index != -1 {
135 self.0.container_from_index(index).and_then(|container| as_menu_item(&container))
136 } else {
137 None
138 }
139 }
140
141 fn set_selected_item(&self, value: Option<Rc<dyn IMenuItem>>) {
142 let index = match value.and_then(|value| value.element().cast::<Control>()) {
143 Some(c) => self.0.index_from_container(&c),
144 None => -1,
145 };
146 self.0.set_selected_index(index);
147 }
148
149 fn sub_items(&self) -> Vec<Rc<dyn IMenuItem>> {
150 StyledElement::logical_children(&self.0).to_vec().iter().filter_map(|child| as_menu_item(child)).collect()
151 }
152
153 fn open(&self) {
154 self.0.open();
155 }
156
157 fn close(&self) {
158 self.0.close();
159 }
160
161 fn move_selection(&self, direction: NavigationDirection, wrap: bool) -> bool {
162 self.0.move_selection(direction, wrap, false)
163 }
164
165 fn as_menu(&self) -> Option<Rc<dyn IMenu>> {
166 Some(Rc::new(MenuBaseHandle(self.0.clone())))
167 }
168
169 fn as_main_menu(&self) -> Option<Rc<dyn IMainMenu>> {
170 self.0.clone().cast::<Menu>().map(|menu| menu.to_main_menu())
171 }
172}
173
174impl IMenu for MenuBaseHandle {
175 fn interaction_handler(&self) -> Rc<dyn IMenuInteractionHandler> {
176 self.0.interaction_handler()
177 }
178
179 fn is_open(&self) -> bool {
180 self.0.is_open()
181 }
182
183 fn top_level(&self) -> Option<Ref<TopLevel>> {
184 TopLevel::get_top_level(Some(&self.0))
185 }
186}
187
188ferro_properties! {
189 impl MenuBase {
190 pub fn is_open_property() -> DirectProperty<MenuBase, bool> {
192 FerroProperty::register_direct::<MenuBase, _>("IsOpen", |o| o.is_open(), None, false)
193 }
194 }
195}
196
197impl MenuBase {
198 ferro_routed_event!(
199 pub fn opened_event() -> RoutedEvent<RoutedEventArgs> {
201 RoutedEvent::register::<MenuBase, _>("Opened", RoutingStrategies::BUBBLE)
202 }
203 );
204
205 ferro_routed_event!(
206 pub fn closed_event() -> RoutedEvent<RoutedEventArgs> {
208 RoutedEvent::register::<MenuBase, _>("Closed", RoutingStrategies::BUBBLE)
209 }
210 );
211
212 fn static_constructor() {
214 register_menu::<MenuBase>(|menu| Rc::new(MenuBaseHandle(menu)));
215 MenuItem::submenu_opened_event().add_class_handler::<MenuBase>(|x, e| x.on_submenu_opened(e));
216 }
217
218 pub fn construct() -> Self {
221 Self::construct_with(DefaultMenuInteractionHandler::new(false))
222 }
223
224 pub fn construct_with(interaction_handler: Rc<dyn IMenuInteractionHandler>) -> Self {
226 Self { base: SelectingItemsControl::construct(), is_open: Cell::new(false), interaction_handler }
227 }
228
229 pub fn is_open(&self) -> bool {
231 self.is_open.get()
232 }
233
234 pub fn set_is_open(&self, value: bool) {
237 self.set_and_raise_cell(Self::is_open_property(), &self.is_open, value);
238 }
239
240 pub fn interaction_handler(&self) -> Rc<dyn IMenuInteractionHandler> {
242 self.interaction_handler.clone()
243 }
244
245 pub fn opened(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
247 self.add_handler(Self::opened_event(), handler)
248 }
249
250 pub fn closed(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
252 self.add_handler(Self::closed_event(), handler)
253 }
254
255 pub(crate) fn to_menu(&self) -> Rc<dyn IMenu> {
257 Rc::new(MenuBaseHandle(self.to_ref()))
258 }
259
260 pub(crate) fn needs_menu_container(item: &Option<BoxedValue>) -> (bool, Option<RecycleKey>) {
263 let is_container = item
264 .as_ref()
265 .and_then(Control::from_boxed)
266 .is_some_and(|control| control.is::<MenuItem>() || control.is::<Separator>());
267
268 if is_container {
269 (false, None)
270 } else {
271 (true, Some(ItemsControl::DEFAULT_RECYCLE_KEY))
272 }
273 }
274}