1use freya_core::prelude::*;
2use torin::{
3 content::Content,
4 prelude::{
5 Alignment,
6 Position,
7 },
8 size::Size,
9};
10
11use crate::{
12 get_theme,
13 theming::component_themes::{
14 MenuContainerThemePartial,
15 MenuItemThemePartial,
16 },
17};
18
19#[cfg_attr(feature = "docs",
69 doc = embed_doc_image::embed_image!("menu", "images/gallery_menu.png"),
70)]
71#[derive(Default, Clone, PartialEq)]
72pub struct Menu {
73 children: Vec<Element>,
74 on_close: Option<EventHandler<()>>,
75 key: DiffKey,
76}
77
78impl ChildrenExt for Menu {
79 fn get_children(&mut self) -> &mut Vec<Element> {
80 &mut self.children
81 }
82}
83
84impl KeyExt for Menu {
85 fn write_key(&mut self) -> &mut DiffKey {
86 &mut self.key
87 }
88}
89
90impl Menu {
91 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn on_close<F>(mut self, f: F) -> Self
96 where
97 F: Into<EventHandler<()>>,
98 {
99 self.on_close = Some(f.into());
100 self
101 }
102}
103
104impl ComponentOwned for Menu {
105 fn render(self) -> impl IntoElement {
106 use_provide_context(|| State::create(ROOT_MENU.0));
108 use_provide_context::<State<Vec<MenuId>>>(|| State::create(vec![ROOT_MENU]));
110 use_provide_context(|| ROOT_MENU);
112
113 rect()
114 .layer(Layer::Overlay)
115 .corner_radius(8.0)
116 .on_press(move |ev: Event<PressEventData>| {
117 ev.stop_propagation();
118 })
119 .on_global_mouse_up(move |_| {
120 if let Some(on_close) = &self.on_close {
121 on_close.call(());
122 }
123 })
124 .child(MenuContainer::new().children(self.children))
125 }
126 fn render_key(&self) -> DiffKey {
127 self.key.clone().or(self.default_key())
128 }
129}
130
131#[derive(Default, Clone, PartialEq)]
144pub struct MenuContainer {
145 pub(crate) theme: Option<MenuContainerThemePartial>,
146 children: Vec<Element>,
147 key: DiffKey,
148}
149
150impl KeyExt for MenuContainer {
151 fn write_key(&mut self) -> &mut DiffKey {
152 &mut self.key
153 }
154}
155
156impl ChildrenExt for MenuContainer {
157 fn get_children(&mut self) -> &mut Vec<Element> {
158 &mut self.children
159 }
160}
161
162impl MenuContainer {
163 pub fn new() -> Self {
164 Self::default()
165 }
166}
167
168impl ComponentOwned for MenuContainer {
169 fn render(self) -> impl IntoElement {
170 let focus = use_focus();
171 let theme = get_theme!(self.theme, menu_container);
172
173 use_provide_context(move || MenuGroup {
174 group_id: focus.a11y_id(),
175 });
176
177 rect()
178 .a11y_id(focus.a11y_id())
179 .a11y_member_of(focus.a11y_id())
180 .a11y_focusable(true)
181 .a11y_role(AccessibilityRole::Menu)
182 .position(Position::new_absolute())
183 .shadow((0.0, 4.0, 10.0, 0., theme.shadow))
184 .background(theme.background)
185 .corner_radius(theme.corner_radius)
186 .padding(theme.padding)
187 .border(Border::new().width(1.).fill(theme.border_fill))
188 .content(Content::fit())
189 .children(self.children)
190 }
191
192 fn render_key(&self) -> DiffKey {
193 self.key.clone().or(self.default_key())
194 }
195}
196
197#[derive(Clone)]
198pub struct MenuGroup {
199 pub group_id: AccessibilityId,
200}
201
202#[derive(Default, Clone, PartialEq)]
217pub struct MenuItem {
218 pub(crate) theme: Option<MenuItemThemePartial>,
219 children: Vec<Element>,
220 on_press: Option<EventHandler<Event<PressEventData>>>,
221 on_pointer_enter: Option<EventHandler<Event<PointerEventData>>>,
222 selected: bool,
223 key: DiffKey,
224}
225
226impl KeyExt for MenuItem {
227 fn write_key(&mut self) -> &mut DiffKey {
228 &mut self.key
229 }
230}
231
232impl MenuItem {
233 pub fn new() -> Self {
234 Self::default()
235 }
236
237 pub fn on_press<F>(mut self, f: F) -> Self
238 where
239 F: Into<EventHandler<Event<PressEventData>>>,
240 {
241 self.on_press = Some(f.into());
242 self
243 }
244
245 pub fn on_pointer_enter<F>(mut self, f: F) -> Self
246 where
247 F: Into<EventHandler<Event<PointerEventData>>>,
248 {
249 self.on_pointer_enter = Some(f.into());
250 self
251 }
252
253 pub fn selected(mut self, selected: bool) -> Self {
254 self.selected = selected;
255 self
256 }
257}
258
259impl ChildrenExt for MenuItem {
260 fn get_children(&mut self) -> &mut Vec<Element> {
261 &mut self.children
262 }
263}
264
265impl ComponentOwned for MenuItem {
266 fn render(self) -> impl IntoElement {
267 let theme = get_theme!(self.theme, menu_item);
268 let mut hovering = use_state(|| false);
269 let focus = use_focus();
270 let focus_status = use_focus_status(focus);
271 let MenuGroup { group_id } = use_consume::<MenuGroup>();
272
273 let background = if self.selected {
274 theme.select_background
275 } else if hovering() {
276 theme.hover_background
277 } else {
278 theme.background
279 };
280
281 let border = if focus_status() == FocusStatus::Keyboard {
282 Border::new()
283 .fill(theme.select_border_fill)
284 .width(2.)
285 .alignment(BorderAlignment::Inner)
286 } else {
287 Border::new()
288 .fill(theme.border_fill)
289 .width(1.)
290 .alignment(BorderAlignment::Inner)
291 };
292
293 let on_pointer_enter = move |e| {
294 hovering.set(true);
295 if let Some(on_pointer_enter) = &self.on_pointer_enter {
296 on_pointer_enter.call(e);
297 }
298 };
299
300 let on_pointer_leave = move |_| {
301 hovering.set(false);
302 };
303
304 let on_press = move |e: Event<PressEventData>| {
305 focus.request_focus();
306 if let Some(on_press) = &self.on_press {
307 on_press.call(e);
308 }
309 };
310
311 rect()
312 .a11y_role(AccessibilityRole::MenuItem)
313 .a11y_id(focus.a11y_id())
314 .a11y_focusable(true)
315 .a11y_member_of(group_id)
316 .min_width(Size::px(105.))
317 .width(Size::fill_minimum())
318 .padding((4.0, 10.0))
319 .corner_radius(theme.corner_radius)
320 .background(background)
321 .border(border)
322 .color(theme.color)
323 .text_align(TextAlign::Start)
324 .main_align(Alignment::Center)
325 .on_pointer_enter(on_pointer_enter)
326 .on_pointer_leave(on_pointer_leave)
327 .on_press(on_press)
328 .children(self.children)
329 }
330
331 fn render_key(&self) -> DiffKey {
332 self.key.clone().or(self.default_key())
333 }
334}
335
336#[derive(Default, Clone, PartialEq)]
349pub struct MenuButton {
350 children: Vec<Element>,
351 on_press: Option<EventHandler<Event<PressEventData>>>,
352 key: DiffKey,
353}
354
355impl ChildrenExt for MenuButton {
356 fn get_children(&mut self) -> &mut Vec<Element> {
357 &mut self.children
358 }
359}
360
361impl KeyExt for MenuButton {
362 fn write_key(&mut self) -> &mut DiffKey {
363 &mut self.key
364 }
365}
366
367impl MenuButton {
368 pub fn new() -> Self {
369 Self::default()
370 }
371
372 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
373 self.on_press = Some(on_press.into());
374 self
375 }
376}
377
378impl ComponentOwned for MenuButton {
379 fn render(self) -> impl IntoElement {
380 let mut menus = use_consume::<State<Vec<MenuId>>>();
381 let parent_menu_id = use_consume::<MenuId>();
382
383 MenuItem::new()
384 .on_pointer_enter(move |_| close_menus_until(&mut menus, parent_menu_id))
385 .map(self.on_press.clone(), |el, on_press| el.on_press(on_press))
386 .children(self.children)
387 }
388
389 fn render_key(&self) -> DiffKey {
390 self.key.clone().or(self.default_key())
391 }
392}
393
394#[derive(Default, Clone, PartialEq)]
407pub struct SubMenu {
408 label: Option<Element>,
409 items: Vec<Element>,
410 key: DiffKey,
411}
412
413impl KeyExt for SubMenu {
414 fn write_key(&mut self) -> &mut DiffKey {
415 &mut self.key
416 }
417}
418
419impl SubMenu {
420 pub fn new() -> Self {
421 Self::default()
422 }
423
424 pub fn label(mut self, label: impl IntoElement) -> Self {
425 self.label = Some(label.into_element());
426 self
427 }
428}
429
430impl ChildrenExt for SubMenu {
431 fn get_children(&mut self) -> &mut Vec<Element> {
432 &mut self.items
433 }
434}
435
436impl ComponentOwned for SubMenu {
437 fn render(self) -> impl IntoElement {
438 let parent_menu_id = use_consume::<MenuId>();
439 let mut menus = use_consume::<State<Vec<MenuId>>>();
440 let mut menus_ids_generator = use_consume::<State<usize>>();
441
442 let submenu_id = use_hook(|| {
443 *menus_ids_generator.write() += 1;
444 let menu_id = MenuId(*menus_ids_generator.peek());
445 provide_context(menu_id);
446 menu_id
447 });
448
449 let show_submenu = menus.read().contains(&submenu_id);
450
451 let on_pointer_enter = move |_| {
452 close_menus_until(&mut menus, parent_menu_id);
453 push_menu(&mut menus, submenu_id);
454 };
455
456 let on_press = move |_| {
457 close_menus_until(&mut menus, parent_menu_id);
458 push_menu(&mut menus, submenu_id);
459 };
460
461 MenuItem::new()
462 .on_pointer_enter(on_pointer_enter)
463 .on_press(on_press)
464 .child(rect().horizontal().maybe_child(self.label.clone()))
465 .maybe_child(show_submenu.then(|| {
466 rect()
467 .position(Position::new_absolute().top(-8.).right(-10.))
468 .width(Size::px(0.))
469 .height(Size::px(0.))
470 .child(
471 rect()
472 .width(Size::window_percent(100.))
473 .child(MenuContainer::new().children(self.items)),
474 )
475 }))
476 }
477
478 fn render_key(&self) -> DiffKey {
479 self.key.clone().or(self.default_key())
480 }
481}
482
483static ROOT_MENU: MenuId = MenuId(0);
484
485#[derive(Clone, Copy, PartialEq, Eq)]
486struct MenuId(usize);
487
488fn close_menus_until(menus: &mut State<Vec<MenuId>>, until: MenuId) {
489 menus.write().retain(|&id| id.0 <= until.0);
490}
491
492fn push_menu(menus: &mut State<Vec<MenuId>>, id: MenuId) {
493 if !menus.read().contains(&id) {
494 menus.write().push(id);
495 }
496}