1use freya_core::prelude::*;
2use torin::{
3 content::Content,
4 gaps::Gaps,
5 prelude::{
6 Alignment,
7 Area,
8 Position,
9 },
10 size::Size,
11};
12
13use crate::{
14 get_theme,
15 theming::component_themes::{
16 MenuContainerThemePartial,
17 MenuItemThemePartial,
18 },
19};
20
21#[cfg_attr(feature = "docs",
71 doc = embed_doc_image::embed_image!("menu", "images/gallery_menu.png"),
72)]
73#[derive(Default, Clone, PartialEq)]
74pub struct Menu {
75 children: Vec<Element>,
76 on_close: Option<EventHandler<()>>,
77 key: DiffKey,
78}
79
80impl ChildrenExt for Menu {
81 fn get_children(&mut self) -> &mut Vec<Element> {
82 &mut self.children
83 }
84}
85
86impl KeyExt for Menu {
87 fn write_key(&mut self) -> &mut DiffKey {
88 &mut self.key
89 }
90}
91
92impl Menu {
93 pub fn new() -> Self {
94 Self::default()
95 }
96
97 pub fn on_close<F>(mut self, f: F) -> Self
98 where
99 F: Into<EventHandler<()>>,
100 {
101 self.on_close = Some(f.into());
102 self
103 }
104}
105
106impl ComponentOwned for Menu {
107 fn render(self) -> impl IntoElement {
108 use_provide_context(|| State::create(ROOT_MENU.0));
110 use_provide_context::<State<Vec<MenuId>>>(|| State::create(vec![ROOT_MENU]));
112 use_provide_context(|| ROOT_MENU);
114
115 rect()
116 .layer(Layer::Overlay)
117 .corner_radius(8.0)
118 .on_press(move |ev: Event<PressEventData>| {
119 ev.stop_propagation();
120 })
121 .on_global_pointer_press(move |_: Event<PointerEventData>| {
122 if let Some(on_close) = &self.on_close {
123 on_close.call(());
124 }
125 })
126 .child(MenuContainer::new().children(self.children))
127 }
128 fn render_key(&self) -> DiffKey {
129 self.key.clone().or(self.default_key())
130 }
131}
132
133#[derive(Default, Clone, PartialEq)]
146pub struct MenuContainer {
147 pub(crate) theme: Option<MenuContainerThemePartial>,
148 children: Vec<Element>,
149 key: DiffKey,
150}
151
152impl KeyExt for MenuContainer {
153 fn write_key(&mut self) -> &mut DiffKey {
154 &mut self.key
155 }
156}
157
158impl ChildrenExt for MenuContainer {
159 fn get_children(&mut self) -> &mut Vec<Element> {
160 &mut self.children
161 }
162}
163
164impl MenuContainer {
165 pub fn new() -> Self {
166 Self::default()
167 }
168}
169
170impl ComponentOwned for MenuContainer {
171 fn render(self) -> impl IntoElement {
172 let focus = use_focus();
173 let theme = get_theme!(self.theme, menu_container);
174 let mut measured = use_state(|| None::<(Area, f32, f32)>);
175
176 use_provide_context(move || MenuGroup {
177 group_id: focus.a11y_id(),
178 });
179
180 let (offset_x, offset_y, opacity) = match *measured.read() {
181 None => (0.0, 0.0, 0.0),
182 Some((area, win_w, win_h)) => (
183 overflow_offset(area.origin.x, area.size.width, win_w),
184 overflow_offset(area.origin.y, area.size.height, win_h),
185 1.0,
186 ),
187 };
188
189 rect()
190 .layer(Layer::Overlay)
191 .content(Content::fit())
192 .opacity(opacity)
193 .offset_x(offset_x)
194 .offset_y(offset_y)
195 .on_sized(move |e: Event<SizedEventData>| {
196 if measured.peek().is_none() {
197 let window = Platform::get().root_size.peek();
198 measured.set(Some((e.area, window.width, window.height)));
199 }
200 })
201 .child(
202 rect()
203 .a11y_id(focus.a11y_id())
204 .a11y_member_of(focus.a11y_id())
205 .a11y_focusable(true)
206 .a11y_role(AccessibilityRole::Menu)
207 .shadow((0.0, 4.0, 10.0, 0., theme.shadow))
208 .background(theme.background)
209 .corner_radius(theme.corner_radius)
210 .padding(theme.padding)
211 .border(Border::new().width(1.).fill(theme.border_fill))
212 .content(Content::fit())
213 .children(self.children),
214 )
215 }
216
217 fn render_key(&self) -> DiffKey {
218 self.key.clone().or(self.default_key())
219 }
220}
221
222#[derive(Clone)]
223pub struct MenuGroup {
224 pub group_id: AccessibilityId,
225}
226
227#[derive(Clone, PartialEq)]
242pub struct MenuItem {
243 pub(crate) theme: Option<MenuItemThemePartial>,
244 children: Vec<Element>,
245 on_press: Option<EventHandler<Event<PressEventData>>>,
246 on_pointer_enter: Option<EventHandler<Event<PointerEventData>>>,
247 selected: bool,
248 padding: Gaps,
249 key: DiffKey,
250}
251
252impl Default for MenuItem {
253 fn default() -> Self {
254 Self {
255 theme: None,
256 children: Vec::new(),
257 on_press: None,
258 on_pointer_enter: None,
259 selected: false,
260 padding: (4.0, 10.0).into(),
261 key: DiffKey::None,
262 }
263 }
264}
265
266impl KeyExt for MenuItem {
267 fn write_key(&mut self) -> &mut DiffKey {
268 &mut self.key
269 }
270}
271
272impl MenuItem {
273 pub fn new() -> Self {
274 Self::default()
275 }
276
277 pub fn on_press<F>(mut self, f: F) -> Self
278 where
279 F: Into<EventHandler<Event<PressEventData>>>,
280 {
281 self.on_press = Some(f.into());
282 self
283 }
284
285 pub fn on_pointer_enter<F>(mut self, f: F) -> Self
286 where
287 F: Into<EventHandler<Event<PointerEventData>>>,
288 {
289 self.on_pointer_enter = Some(f.into());
290 self
291 }
292
293 pub fn selected(mut self, selected: bool) -> Self {
294 self.selected = selected;
295 self
296 }
297
298 pub fn padding(mut self, padding: impl Into<Gaps>) -> Self {
300 self.padding = padding.into();
301 self
302 }
303
304 pub fn get_padding(&self) -> Gaps {
306 self.padding
307 }
308
309 pub fn get_theme(&self) -> Option<&MenuItemThemePartial> {
311 self.theme.as_ref()
312 }
313
314 pub fn theme(mut self, theme: MenuItemThemePartial) -> Self {
316 self.theme = Some(theme);
317 self
318 }
319}
320
321impl ChildrenExt for MenuItem {
322 fn get_children(&mut self) -> &mut Vec<Element> {
323 &mut self.children
324 }
325}
326
327impl ComponentOwned for MenuItem {
328 fn render(self) -> impl IntoElement {
329 let theme = get_theme!(self.theme, menu_item);
330 let mut hovering = use_state(|| false);
331 let focus = use_focus();
332 let focus_status = use_focus_status(focus);
333 let MenuGroup { group_id } = use_consume::<MenuGroup>();
334
335 let background = if self.selected {
336 theme.select_background
337 } else if hovering() {
338 theme.hover_background
339 } else {
340 theme.background
341 };
342
343 let border = if focus_status() == FocusStatus::Keyboard {
344 Border::new()
345 .fill(theme.select_border_fill)
346 .width(2.)
347 .alignment(BorderAlignment::Inner)
348 } else {
349 Border::new()
350 .fill(theme.border_fill)
351 .width(1.)
352 .alignment(BorderAlignment::Inner)
353 };
354
355 let on_pointer_enter = move |e: Event<PointerEventData>| {
356 hovering.set(true);
357 if let Some(on_pointer_enter) = &self.on_pointer_enter {
358 on_pointer_enter.call(e);
359 }
360 };
361
362 let on_pointer_leave = move |_| {
363 hovering.set(false);
364 };
365
366 let on_press = move |e: Event<PressEventData>| {
367 let prevent_default = e.get_prevent_default();
368 if let Some(on_press) = &self.on_press {
369 on_press.call(e);
370 }
371 if *prevent_default.borrow() {
372 focus.request_focus();
373 }
374 };
375
376 rect()
377 .a11y_role(AccessibilityRole::MenuItem)
378 .a11y_id(focus.a11y_id())
379 .a11y_focusable(true)
380 .a11y_member_of(group_id)
381 .min_width(Size::px(105.))
382 .width(Size::fill_minimum())
383 .content(Content::fit())
384 .padding(self.padding)
385 .corner_radius(theme.corner_radius)
386 .background(background)
387 .border(border)
388 .color(theme.color)
389 .text_align(TextAlign::Start)
390 .main_align(Alignment::Center)
391 .overflow(Overflow::Clip)
392 .on_pointer_enter(on_pointer_enter)
393 .on_pointer_leave(on_pointer_leave)
394 .on_press(on_press)
395 .children(self.children)
396 }
397
398 fn render_key(&self) -> DiffKey {
399 self.key.clone().or(self.default_key())
400 }
401}
402
403#[derive(Default, Clone, PartialEq)]
416pub struct MenuButton {
417 children: Vec<Element>,
418 on_press: Option<EventHandler<Event<PressEventData>>>,
419 key: DiffKey,
420}
421
422impl ChildrenExt for MenuButton {
423 fn get_children(&mut self) -> &mut Vec<Element> {
424 &mut self.children
425 }
426}
427
428impl KeyExt for MenuButton {
429 fn write_key(&mut self) -> &mut DiffKey {
430 &mut self.key
431 }
432}
433
434impl MenuButton {
435 pub fn new() -> Self {
436 Self::default()
437 }
438
439 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
440 self.on_press = Some(on_press.into());
441 self
442 }
443}
444
445impl ComponentOwned for MenuButton {
446 fn render(self) -> impl IntoElement {
447 let mut menus = use_consume::<State<Vec<MenuId>>>();
448 let parent_menu_id = use_consume::<MenuId>();
449
450 MenuItem::new()
451 .on_pointer_enter(move |_| close_menus_until(&mut menus, parent_menu_id))
452 .map(self.on_press.clone(), |el, on_press| el.on_press(on_press))
453 .children(self.children)
454 }
455
456 fn render_key(&self) -> DiffKey {
457 self.key.clone().or(self.default_key())
458 }
459}
460
461#[derive(Default, Clone, PartialEq)]
474pub struct SubMenu {
475 label: Option<Element>,
476 items: Vec<Element>,
477 key: DiffKey,
478}
479
480impl KeyExt for SubMenu {
481 fn write_key(&mut self) -> &mut DiffKey {
482 &mut self.key
483 }
484}
485
486impl SubMenu {
487 pub fn new() -> Self {
488 Self::default()
489 }
490
491 pub fn label(mut self, label: impl IntoElement) -> Self {
492 self.label = Some(label.into_element());
493 self
494 }
495}
496
497impl ChildrenExt for SubMenu {
498 fn get_children(&mut self) -> &mut Vec<Element> {
499 &mut self.items
500 }
501}
502
503impl ComponentOwned for SubMenu {
504 fn render(self) -> impl IntoElement {
505 let parent_menu_id = use_consume::<MenuId>();
506 let mut menus = use_consume::<State<Vec<MenuId>>>();
507 let mut menus_ids_generator = use_consume::<State<usize>>();
508
509 let submenu_id = use_hook(|| {
510 *menus_ids_generator.write() += 1;
511 let menu_id = MenuId(*menus_ids_generator.peek());
512 provide_context(menu_id);
513 menu_id
514 });
515
516 let show_submenu = menus.read().contains(&submenu_id);
517
518 let on_pointer_enter = move |_| {
519 close_menus_until(&mut menus, parent_menu_id);
520 push_menu(&mut menus, submenu_id);
521 };
522
523 let on_press = move |_| {
524 close_menus_until(&mut menus, parent_menu_id);
525 push_menu(&mut menus, submenu_id);
526 };
527
528 MenuItem::new()
529 .on_pointer_enter(on_pointer_enter)
530 .on_press(on_press)
531 .child(rect().horizontal().maybe_child(self.label.clone()))
532 .maybe_child(show_submenu.then(|| {
533 rect()
534 .position(Position::new_absolute().top(-8.).right(-10.))
535 .width(Size::px(0.))
536 .height(Size::px(0.))
537 .child(
538 rect()
539 .width(Size::window_percent(100.))
540 .child(MenuContainer::new().children(self.items)),
541 )
542 }))
543 }
544
545 fn render_key(&self) -> DiffKey {
546 self.key.clone().or(self.default_key())
547 }
548}
549
550fn overflow_offset(origin: f32, size: f32, window: f32) -> f32 {
553 let overflow = origin + size - window;
554 if overflow > 0.0 {
555 -overflow.min(origin)
556 } else {
557 0.0
558 }
559}
560
561static ROOT_MENU: MenuId = MenuId(0);
562
563#[derive(Clone, Copy, PartialEq, Eq)]
564struct MenuId(usize);
565
566fn close_menus_until(menus: &mut State<Vec<MenuId>>, until: MenuId) {
567 menus.write().retain(|&id| id.0 <= until.0);
568}
569
570fn push_menu(menus: &mut State<Vec<MenuId>>, id: MenuId) {
571 if !menus.read().contains(&id) {
572 menus.write().push(id);
573 }
574}