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