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