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 || focus.a11y_id());
149
150 rect()
151 .a11y_id(focus.a11y_id())
152 .a11y_member_of(focus.a11y_id())
153 .a11y_focusable(true)
154 .a11y_role(AccessibilityRole::Menu)
155 .position(Position::new_absolute())
156 .shadow((0.0, 4.0, 10.0, 0., theme.shadow))
157 .background(theme.background)
158 .corner_radius(theme.corner_radius)
159 .padding(theme.padding)
160 .border(Border::new().width(1.).fill(theme.border_fill))
161 .content(Content::fit())
162 .children(self.children)
163 }
164
165 fn render_key(&self) -> DiffKey {
166 self.key.clone().or(self.default_key())
167 }
168}
169
170#[derive(Default, Clone, PartialEq)]
185pub struct MenuItem {
186 pub(crate) theme: Option<MenuItemThemePartial>,
187 children: Vec<Element>,
188 on_press: Option<EventHandler<Event<PressEventData>>>,
189 on_pointer_enter: Option<EventHandler<Event<PointerEventData>>>,
190 key: DiffKey,
191}
192
193impl KeyExt for MenuItem {
194 fn write_key(&mut self) -> &mut DiffKey {
195 &mut self.key
196 }
197}
198
199impl MenuItem {
200 pub fn new() -> Self {
201 Self::default()
202 }
203
204 pub fn on_press<F>(mut self, f: F) -> Self
205 where
206 F: Into<EventHandler<Event<PressEventData>>>,
207 {
208 self.on_press = Some(f.into());
209 self
210 }
211
212 pub fn on_pointer_enter<F>(mut self, f: F) -> Self
213 where
214 F: Into<EventHandler<Event<PointerEventData>>>,
215 {
216 self.on_pointer_enter = Some(f.into());
217 self
218 }
219}
220
221impl ChildrenExt for MenuItem {
222 fn get_children(&mut self) -> &mut Vec<Element> {
223 &mut self.children
224 }
225}
226
227impl RenderOwned for MenuItem {
228 fn render(self) -> impl IntoElement {
229 let theme = get_theme!(self.theme, menu_item);
230 let mut hovering = use_state(|| false);
231 let focus = use_focus();
232 let focus_status = use_focus_status(focus);
233 let menu_group = use_consume::<AccessibilityId>();
234
235 let background = if focus_status() == FocusStatus::Keyboard || *hovering.read() {
236 theme.hover_background
237 } else {
238 Color::TRANSPARENT
239 };
240
241 let on_pointer_enter = move |e| {
242 hovering.set(true);
243 if let Some(on_pointer_enter) = &self.on_pointer_enter {
244 on_pointer_enter.call(e);
245 }
246 };
247
248 let on_pointer_leave = move |_| {
249 hovering.set(false);
250 };
251
252 let on_press = move |e: Event<PressEventData>| {
253 e.stop_propagation();
254 e.prevent_default();
255 focus.request_focus();
256 if let Some(on_press) = &self.on_press {
257 on_press.call(e);
258 }
259 };
260
261 rect()
262 .a11y_role(AccessibilityRole::MenuItem)
263 .a11y_id(focus.a11y_id())
264 .a11y_focusable(true)
265 .a11y_member_of(menu_group)
266 .min_width(Size::px(105.))
267 .width(Size::fill_minimum())
268 .padding((4.0, 10.0))
269 .corner_radius(theme.corner_radius)
270 .background(background)
271 .color(theme.color)
272 .text_align(TextAlign::Start)
273 .main_align(Alignment::Center)
274 .on_pointer_enter(on_pointer_enter)
275 .on_pointer_leave(on_pointer_leave)
276 .on_press(on_press)
277 .children(self.children)
278 }
279
280 fn render_key(&self) -> DiffKey {
281 self.key.clone().or(self.default_key())
282 }
283}
284
285#[derive(Default, Clone, PartialEq)]
298pub struct MenuButton {
299 children: Vec<Element>,
300 on_press: Option<EventHandler<()>>,
301 key: DiffKey,
302}
303
304impl ChildrenExt for MenuButton {
305 fn get_children(&mut self) -> &mut Vec<Element> {
306 &mut self.children
307 }
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 on_press(mut self, on_press: impl Into<EventHandler<()>>) -> Self {
322 self.on_press = Some(on_press.into());
323 self
324 }
325}
326
327impl RenderOwned for MenuButton {
328 fn render(self) -> impl IntoElement {
329 let mut menus = use_consume::<State<Vec<MenuId>>>();
330 let parent_menu_id = use_consume::<MenuId>();
331
332 MenuItem::new()
333 .on_pointer_enter(move |_| close_menus_until(&mut menus, parent_menu_id))
334 .on_press(move |_| {
335 if let Some(on_press) = &self.on_press {
336 on_press.call(());
337 }
338 })
339 .children(self.children)
340 }
341
342 fn render_key(&self) -> DiffKey {
343 self.key.clone().or(self.default_key())
344 }
345}
346
347#[derive(Default, Clone, PartialEq)]
360pub struct SubMenu {
361 label: Option<Element>,
362 items: Vec<Element>,
363 key: DiffKey,
364}
365
366impl KeyExt for SubMenu {
367 fn write_key(&mut self) -> &mut DiffKey {
368 &mut self.key
369 }
370}
371
372impl SubMenu {
373 pub fn new() -> Self {
374 Self::default()
375 }
376
377 pub fn label(mut self, label: impl IntoElement) -> Self {
378 self.label = Some(label.into_element());
379 self
380 }
381}
382
383impl ChildrenExt for SubMenu {
384 fn get_children(&mut self) -> &mut Vec<Element> {
385 &mut self.items
386 }
387}
388
389impl RenderOwned for SubMenu {
390 fn render(self) -> impl IntoElement {
391 let parent_menu_id = use_consume::<MenuId>();
392 let mut menus = use_consume::<State<Vec<MenuId>>>();
393 let mut menus_ids_generator = use_consume::<State<usize>>();
394
395 let submenu_id = use_hook(|| {
396 *menus_ids_generator.write() += 1;
397 let menu_id = MenuId(*menus_ids_generator.peek());
398 provide_context(menu_id);
399 menu_id
400 });
401
402 let show_submenu = menus.read().contains(&submenu_id);
403
404 let onmouseenter = move |_| {
405 close_menus_until(&mut menus, parent_menu_id);
406 push_menu(&mut menus, submenu_id);
407 };
408
409 let onpress = move |_| {
410 close_menus_until(&mut menus, parent_menu_id);
411 push_menu(&mut menus, submenu_id);
412 };
413
414 MenuItem::new()
415 .on_pointer_enter(onmouseenter)
416 .on_press(onpress)
417 .child(rect().horizontal().maybe_child(self.label.clone()))
418 .maybe_child(show_submenu.then(|| {
419 rect()
420 .position(Position::new_absolute().top(-8.).right(-10.))
421 .width(Size::px(0.))
422 .height(Size::px(0.))
423 .child(
424 rect()
425 .width(Size::window_percent(100.))
426 .child(MenuContainer::new().children(self.items)),
427 )
428 }))
429 }
430
431 fn render_key(&self) -> DiffKey {
432 self.key.clone().or(self.default_key())
433 }
434}
435
436static ROOT_MENU: MenuId = MenuId(0);
437
438#[derive(Clone, Copy, PartialEq, Eq)]
439struct MenuId(usize);
440
441fn close_menus_until(menus: &mut State<Vec<MenuId>>, until: MenuId) {
442 menus.write().retain(|&id| id.0 <= until.0);
443}
444
445fn push_menu(menus: &mut State<Vec<MenuId>>, id: MenuId) {
446 if !menus.read().contains(&id) {
447 menus.write().push(id);
448 }
449}