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