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#[derive(Default, Clone, PartialEq)]
48pub struct Menu {
49 children: Vec<Element>,
50 on_close: Option<EventHandler<()>>,
51 key: DiffKey,
52}
53
54impl ChildrenExt for Menu {
55 fn get_children(&mut self) -> &mut Vec<Element> {
56 &mut self.children
57 }
58}
59
60impl KeyExt for Menu {
61 fn write_key(&mut self) -> &mut DiffKey {
62 &mut self.key
63 }
64}
65
66impl Menu {
67 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub fn on_close<F>(mut self, f: F) -> Self
72 where
73 F: Into<EventHandler<()>>,
74 {
75 self.on_close = Some(f.into());
76 self
77 }
78}
79
80impl ComponentOwned for Menu {
81 fn render(self) -> impl IntoElement {
82 use_provide_context(|| State::create(ROOT_MENU.0));
84 use_provide_context::<State<Vec<MenuId>>>(|| State::create(vec![ROOT_MENU]));
86 use_provide_context(|| ROOT_MENU);
88
89 rect()
90 .layer(Layer::Overlay)
91 .corner_radius(8.0)
92 .on_press(move |ev: Event<PressEventData>| {
93 ev.stop_propagation();
94 })
95 .on_global_mouse_up(move |_| {
96 if let Some(on_close) = &self.on_close {
97 on_close.call(());
98 }
99 })
100 .child(MenuContainer::new().children(self.children))
101 }
102 fn render_key(&self) -> DiffKey {
103 self.key.clone().or(self.default_key())
104 }
105}
106
107#[derive(Default, Clone, PartialEq)]
120pub struct MenuContainer {
121 pub(crate) theme: Option<MenuContainerThemePartial>,
122 children: Vec<Element>,
123 key: DiffKey,
124}
125
126impl KeyExt for MenuContainer {
127 fn write_key(&mut self) -> &mut DiffKey {
128 &mut self.key
129 }
130}
131
132impl ChildrenExt for MenuContainer {
133 fn get_children(&mut self) -> &mut Vec<Element> {
134 &mut self.children
135 }
136}
137
138impl MenuContainer {
139 pub fn new() -> Self {
140 Self::default()
141 }
142}
143
144impl ComponentOwned for MenuContainer {
145 fn render(self) -> impl IntoElement {
146 let focus = use_focus();
147 let theme = get_theme!(self.theme, menu_container);
148
149 use_provide_context(move || MenuGroup {
150 group_id: focus.a11y_id(),
151 });
152
153 rect()
154 .a11y_id(focus.a11y_id())
155 .a11y_member_of(focus.a11y_id())
156 .a11y_focusable(true)
157 .a11y_role(AccessibilityRole::Menu)
158 .position(Position::new_absolute())
159 .shadow((0.0, 4.0, 10.0, 0., theme.shadow))
160 .background(theme.background)
161 .corner_radius(theme.corner_radius)
162 .padding(theme.padding)
163 .border(Border::new().width(1.).fill(theme.border_fill))
164 .content(Content::fit())
165 .children(self.children)
166 }
167
168 fn render_key(&self) -> DiffKey {
169 self.key.clone().or(self.default_key())
170 }
171}
172
173#[derive(Clone)]
174pub struct MenuGroup {
175 pub group_id: AccessibilityId,
176}
177
178#[derive(Default, Clone, PartialEq)]
193pub struct MenuItem {
194 pub(crate) theme: Option<MenuItemThemePartial>,
195 children: Vec<Element>,
196 on_press: Option<EventHandler<Event<PressEventData>>>,
197 on_pointer_enter: Option<EventHandler<Event<PointerEventData>>>,
198 selected: bool,
199 key: DiffKey,
200}
201
202impl KeyExt for MenuItem {
203 fn write_key(&mut self) -> &mut DiffKey {
204 &mut self.key
205 }
206}
207
208impl MenuItem {
209 pub fn new() -> Self {
210 Self::default()
211 }
212
213 pub fn on_press<F>(mut self, f: F) -> Self
214 where
215 F: Into<EventHandler<Event<PressEventData>>>,
216 {
217 self.on_press = Some(f.into());
218 self
219 }
220
221 pub fn on_pointer_enter<F>(mut self, f: F) -> Self
222 where
223 F: Into<EventHandler<Event<PointerEventData>>>,
224 {
225 self.on_pointer_enter = Some(f.into());
226 self
227 }
228
229 pub fn selected(mut self, selected: bool) -> Self {
230 self.selected = selected;
231 self
232 }
233}
234
235impl ChildrenExt for MenuItem {
236 fn get_children(&mut self) -> &mut Vec<Element> {
237 &mut self.children
238 }
239}
240
241impl ComponentOwned for MenuItem {
242 fn render(self) -> impl IntoElement {
243 let theme = get_theme!(self.theme, menu_item);
244 let mut hovering = use_state(|| false);
245 let focus = use_focus();
246 let focus_status = use_focus_status(focus);
247 let MenuGroup { group_id } = use_consume::<MenuGroup>();
248
249 let background = if self.selected {
250 theme.select_background
251 } else if hovering() {
252 theme.hover_background
253 } else {
254 theme.background
255 };
256
257 let border = if focus_status() == FocusStatus::Keyboard {
258 Border::new()
259 .fill(theme.select_border_fill)
260 .width(2.)
261 .alignment(BorderAlignment::Inner)
262 } else {
263 Border::new()
264 .fill(theme.border_fill)
265 .width(1.)
266 .alignment(BorderAlignment::Inner)
267 };
268
269 let on_pointer_enter = move |e| {
270 hovering.set(true);
271 if let Some(on_pointer_enter) = &self.on_pointer_enter {
272 on_pointer_enter.call(e);
273 }
274 };
275
276 let on_pointer_leave = move |_| {
277 hovering.set(false);
278 };
279
280 let on_press = move |e: Event<PressEventData>| {
281 focus.request_focus();
282 if let Some(on_press) = &self.on_press {
283 on_press.call(e);
284 }
285 };
286
287 rect()
288 .a11y_role(AccessibilityRole::MenuItem)
289 .a11y_id(focus.a11y_id())
290 .a11y_focusable(true)
291 .a11y_member_of(group_id)
292 .min_width(Size::px(105.))
293 .width(Size::fill_minimum())
294 .padding((4.0, 10.0))
295 .corner_radius(theme.corner_radius)
296 .background(background)
297 .border(border)
298 .color(theme.color)
299 .text_align(TextAlign::Start)
300 .main_align(Alignment::Center)
301 .on_pointer_enter(on_pointer_enter)
302 .on_pointer_leave(on_pointer_leave)
303 .on_press(on_press)
304 .children(self.children)
305 }
306
307 fn render_key(&self) -> DiffKey {
308 self.key.clone().or(self.default_key())
309 }
310}
311
312#[derive(Default, Clone, PartialEq)]
325pub struct MenuButton {
326 children: Vec<Element>,
327 on_press: Option<EventHandler<Event<PressEventData>>>,
328 key: DiffKey,
329}
330
331impl ChildrenExt for MenuButton {
332 fn get_children(&mut self) -> &mut Vec<Element> {
333 &mut self.children
334 }
335}
336
337impl KeyExt for MenuButton {
338 fn write_key(&mut self) -> &mut DiffKey {
339 &mut self.key
340 }
341}
342
343impl MenuButton {
344 pub fn new() -> Self {
345 Self::default()
346 }
347
348 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
349 self.on_press = Some(on_press.into());
350 self
351 }
352}
353
354impl ComponentOwned for MenuButton {
355 fn render(self) -> impl IntoElement {
356 let mut menus = use_consume::<State<Vec<MenuId>>>();
357 let parent_menu_id = use_consume::<MenuId>();
358
359 MenuItem::new()
360 .on_pointer_enter(move |_| close_menus_until(&mut menus, parent_menu_id))
361 .map(self.on_press.clone(), |el, on_press| el.on_press(on_press))
362 .children(self.children)
363 }
364
365 fn render_key(&self) -> DiffKey {
366 self.key.clone().or(self.default_key())
367 }
368}
369
370#[derive(Default, Clone, PartialEq)]
383pub struct SubMenu {
384 label: Option<Element>,
385 items: Vec<Element>,
386 key: DiffKey,
387}
388
389impl KeyExt for SubMenu {
390 fn write_key(&mut self) -> &mut DiffKey {
391 &mut self.key
392 }
393}
394
395impl SubMenu {
396 pub fn new() -> Self {
397 Self::default()
398 }
399
400 pub fn label(mut self, label: impl IntoElement) -> Self {
401 self.label = Some(label.into_element());
402 self
403 }
404}
405
406impl ChildrenExt for SubMenu {
407 fn get_children(&mut self) -> &mut Vec<Element> {
408 &mut self.items
409 }
410}
411
412impl ComponentOwned for SubMenu {
413 fn render(self) -> impl IntoElement {
414 let parent_menu_id = use_consume::<MenuId>();
415 let mut menus = use_consume::<State<Vec<MenuId>>>();
416 let mut menus_ids_generator = use_consume::<State<usize>>();
417
418 let submenu_id = use_hook(|| {
419 *menus_ids_generator.write() += 1;
420 let menu_id = MenuId(*menus_ids_generator.peek());
421 provide_context(menu_id);
422 menu_id
423 });
424
425 let show_submenu = menus.read().contains(&submenu_id);
426
427 let on_pointer_enter = move |_| {
428 close_menus_until(&mut menus, parent_menu_id);
429 push_menu(&mut menus, submenu_id);
430 };
431
432 let on_press = move |_| {
433 close_menus_until(&mut menus, parent_menu_id);
434 push_menu(&mut menus, submenu_id);
435 };
436
437 MenuItem::new()
438 .on_pointer_enter(on_pointer_enter)
439 .on_press(on_press)
440 .child(rect().horizontal().maybe_child(self.label.clone()))
441 .maybe_child(show_submenu.then(|| {
442 rect()
443 .position(Position::new_absolute().top(-8.).right(-10.))
444 .width(Size::px(0.))
445 .height(Size::px(0.))
446 .child(
447 rect()
448 .width(Size::window_percent(100.))
449 .child(MenuContainer::new().children(self.items)),
450 )
451 }))
452 }
453
454 fn render_key(&self) -> DiffKey {
455 self.key.clone().or(self.default_key())
456 }
457}
458
459static ROOT_MENU: MenuId = MenuId(0);
460
461#[derive(Clone, Copy, PartialEq, Eq)]
462struct MenuId(usize);
463
464fn close_menus_until(menus: &mut State<Vec<MenuId>>, until: MenuId) {
465 menus.write().retain(|&id| id.0 <= until.0);
466}
467
468fn push_menu(menus: &mut State<Vec<MenuId>>, id: MenuId) {
469 if !menus.read().contains(&id) {
470 menus.write().push(id);
471 }
472}