1use super::{menu_bar::MenuBarState, menu_tree::MenuTree};
5use crate::style::menu_bar::StyleSheet;
6
7use iced_core::{Border, Shadow};
8use iced_widget::core::{
9 Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event,
10 layout::{Limits, Node},
11 mouse::{self, Cursor},
12 overlay, renderer, touch,
13 widget::Tree,
14};
15
16#[derive(Debug, Clone, Copy)]
18pub struct CloseCondition {
19 pub leave: bool,
21
22 pub click_outside: bool,
24
25 pub click_inside: bool,
27}
28
29#[derive(Debug, Clone, Copy)]
31pub enum ItemWidth {
32 Uniform(u16),
34 Static(u16),
40}
41
42#[derive(Debug, Clone, Copy)]
44pub enum ItemHeight {
45 Uniform(u16),
47 Static(u16),
50 Dynamic(u16),
59}
60
61#[derive(Debug, Clone, Copy)]
63pub enum PathHighlight {
64 Full,
66 OmitActive,
68 MenuActive,
70}
71
72#[derive(Debug, Clone, Copy)]
74pub(crate) enum Direction {
75 Positive,
76 Negative,
77}
78
79#[derive(Debug)]
81#[allow(clippy::struct_excessive_bools)]
82struct Aod {
83 horizontal: bool,
85 vertical: bool,
86
87 horizontal_overlap: bool,
89 vertical_overlap: bool,
90
91 horizontal_direction: Direction,
93 vertical_direction: Direction,
94
95 horizontal_offset: f32,
97 vertical_offset: f32,
98}
99impl Aod {
100 #[allow(clippy::too_many_arguments)]
102 fn adaptive(
103 parent_pos: f32,
104 parent_size: f32,
105 child_size: f32,
106 max_size: f32,
107 offset: f32,
108 on: bool,
109 overlap: bool,
110 direction: Direction,
111 ) -> (f32, f32) {
112 match direction {
142 Direction::Positive => {
143 let space_negative = parent_pos;
144 let space_positive = max_size - parent_pos - parent_size;
145
146 if overlap {
147 let overshoot = child_size - parent_size;
148 if on && space_negative > space_positive && overshoot > space_positive {
149 (parent_pos - overshoot, parent_pos - overshoot)
150 } else {
151 (parent_pos, parent_pos)
152 }
153 } else {
154 let overshoot = child_size + offset;
155 if on && space_negative > space_positive && overshoot > space_positive {
156 (parent_pos - overshoot, parent_pos - offset)
157 } else {
158 (parent_pos + parent_size + offset, parent_pos + parent_size)
159 }
160 }
161 }
162 Direction::Negative => {
163 let space_positive = parent_pos;
164 let space_negative = max_size - parent_pos - parent_size;
165
166 if overlap {
167 let overshoot = child_size - parent_size;
168 if on && space_negative > space_positive && overshoot > space_positive {
169 (parent_pos, parent_pos)
170 } else {
171 (parent_pos - overshoot, parent_pos - overshoot)
172 }
173 } else {
174 let overshoot = child_size + offset;
175 if on && space_negative > space_positive && overshoot > space_positive {
176 (parent_pos + parent_size + offset, parent_pos + parent_size)
177 } else {
178 (parent_pos - overshoot, parent_pos - offset)
179 }
180 }
181 }
182 }
183 }
184
185 fn resolve(
187 &self,
188 parent_bounds: Rectangle,
189 children_size: Size,
190 viewport_size: Size,
191 ) -> (Point, Point) {
192 let (x, ox) = Self::adaptive(
193 parent_bounds.x,
194 parent_bounds.width,
195 children_size.width,
196 viewport_size.width,
197 self.horizontal_offset,
198 self.horizontal,
199 self.horizontal_overlap,
200 self.horizontal_direction,
201 );
202 let (y, oy) = Self::adaptive(
203 parent_bounds.y,
204 parent_bounds.height,
205 children_size.height,
206 viewport_size.height,
207 self.vertical_offset,
208 self.vertical,
209 self.vertical_overlap,
210 self.vertical_direction,
211 );
212
213 ([x, y].into(), [ox, oy].into())
214 }
215}
216
217#[derive(Debug, Clone, Copy)]
223pub(super) struct MenuSlice {
224 pub(super) start_index: usize,
225 pub(super) end_index: usize,
226 pub(super) lower_bound_rel: f32,
227 pub(super) upper_bound_rel: f32,
228}
229
230struct MenuBounds {
232 child_positions: Vec<f32>,
233 child_sizes: Vec<Size>,
234 children_bounds: Rectangle,
235 parent_bounds: Rectangle,
236 check_bounds: Rectangle,
237 offset_bounds: Rectangle,
238}
239impl MenuBounds {
240 #[allow(clippy::too_many_arguments)]
241 fn new<Message, Renderer>(
242 menu_tree: &MenuTree<'_, Message, Renderer>,
243 renderer: &Renderer,
244 item_width: ItemWidth,
245 item_height: ItemHeight,
246 viewport_size: Size,
247 overlay_offset: Vector,
248 aod: &Aod,
249 bounds_expand: u16,
250 parent_bounds: Rectangle,
251 tree: &mut [Tree],
252 ) -> Self
253 where
254 Renderer: renderer::Renderer,
255 {
256 let (children_size, child_positions, child_sizes) =
257 get_children_layout(menu_tree, renderer, item_width, item_height, tree);
258
259 let view_parent_bounds = parent_bounds + overlay_offset;
261
262 let (children_position, offset_position) = {
264 let (cp, op) = aod.resolve(view_parent_bounds, children_size, viewport_size);
265 (cp - overlay_offset, op - overlay_offset)
266 };
267
268 let delta = children_position - offset_position;
270 let offset_size = if delta.x.abs() > delta.y.abs() {
271 Size::new(delta.x, children_size.height)
272 } else {
273 Size::new(children_size.width, delta.y)
274 };
275 let offset_bounds = Rectangle::new(offset_position, offset_size);
276
277 let children_bounds = Rectangle::new(children_position, children_size);
278 let check_bounds = pad_rectangle(children_bounds, bounds_expand.into());
279
280 Self {
281 child_positions,
282 child_sizes,
283 children_bounds,
284 parent_bounds,
285 check_bounds,
286 offset_bounds,
287 }
288 }
289}
290
291pub(crate) struct MenuState {
292 pub(super) index: Option<usize>,
293 scroll_offset: f32,
294 menu_bounds: MenuBounds,
295}
296impl MenuState {
297 pub(super) fn layout<Message, Renderer>(
298 &self,
299 overlay_offset: Vector,
300 slice: MenuSlice,
301 renderer: &Renderer,
302 menu_tree: &MenuTree<'_, Message, Renderer>,
303 tree: &mut [Tree],
304 ) -> Node
305 where
306 Renderer: renderer::Renderer,
307 {
308 let MenuSlice {
309 start_index,
310 end_index,
311 lower_bound_rel,
312 upper_bound_rel,
313 } = slice;
314
315 assert_eq!(
316 menu_tree.children.len(),
317 self.menu_bounds.child_positions.len()
318 );
319
320 let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
322
323 let child_nodes = self.menu_bounds.child_positions[start_index..=end_index]
324 .iter()
325 .zip(self.menu_bounds.child_sizes[start_index..=end_index].iter())
326 .zip(menu_tree.children[start_index..=end_index].iter())
327 .map(|((cp, size), mt)| {
328 let mut position = *cp;
329 let mut size = *size;
330
331 if position < lower_bound_rel && (position + size.height) > lower_bound_rel {
332 size.height = position + size.height - lower_bound_rel;
333 position = lower_bound_rel;
334 } else if position <= upper_bound_rel && (position + size.height) > upper_bound_rel
335 {
336 size.height = upper_bound_rel - position;
337 }
338
339 let limits = Limits::new(Size::ZERO, size);
340
341 mt.item
342 .as_widget()
343 .layout(&mut tree[mt.index], renderer, &limits)
344 .move_to(Point::new(0.0, position + self.scroll_offset))
345 })
346 .collect::<Vec<_>>();
347
348 Node::with_children(children_bounds.size(), child_nodes).move_to(children_bounds.position())
349 }
350
351 fn layout_single<Message, Renderer>(
352 &self,
353 overlay_offset: Vector,
354 index: usize,
355 renderer: &Renderer,
356 menu_tree: &MenuTree<'_, Message, Renderer>,
357 tree: &mut Tree,
358 ) -> Node
359 where
360 Renderer: renderer::Renderer,
361 {
362 let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
364
365 let position = self.menu_bounds.child_positions[index];
366 let limits = Limits::new(Size::ZERO, self.menu_bounds.child_sizes[index]);
367 let parent_offset = children_bounds.position() - Point::ORIGIN;
368 let node = menu_tree.item.as_widget().layout(tree, renderer, &limits);
369 node.clone().move_to(Point::new(
370 parent_offset.x,
371 parent_offset.y + position + self.scroll_offset,
372 ))
373 }
374
375 pub(super) fn slice(
376 &self,
377 viewport_size: Size,
378 overlay_offset: Vector,
379 item_height: ItemHeight,
380 ) -> MenuSlice {
381 let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
383
384 let max_index = self.menu_bounds.child_positions.len().saturating_sub(1);
385
386 let lower_bound = children_bounds.y.max(0.0);
388 let upper_bound = (children_bounds.y + children_bounds.height).min(viewport_size.height);
389
390 let lower_bound_rel = lower_bound - (children_bounds.y + self.scroll_offset);
392 let upper_bound_rel = upper_bound - (children_bounds.y + self.scroll_offset);
393
394 let (start_index, end_index) = match item_height {
396 ItemHeight::Uniform(u) => {
397 let start_index = (lower_bound_rel / f32::from(u)).floor() as usize;
398 let end_index = ((upper_bound_rel / f32::from(u)).floor() as usize).min(max_index);
399 (start_index, end_index)
400 }
401 ItemHeight::Static(_) | ItemHeight::Dynamic(_) => {
402 let positions = &self.menu_bounds.child_positions;
403 let sizes = &self.menu_bounds.child_sizes;
404
405 let start_index = search_bound(0, 0, max_index, lower_bound_rel, positions, sizes);
406 let end_index = search_bound(
407 max_index,
408 start_index,
409 max_index,
410 upper_bound_rel,
411 positions,
412 sizes,
413 )
414 .min(max_index);
415
416 (start_index, end_index)
417 }
418 };
419
420 MenuSlice {
421 start_index,
422 end_index,
423 lower_bound_rel,
424 upper_bound_rel,
425 }
426 }
427}
428
429pub(crate) struct Menu<'a, 'b, Message, Renderer>
430where
431 Renderer: renderer::Renderer,
432{
433 pub(crate) tree: &'b mut Tree,
434 pub(crate) menu_roots: &'b mut Vec<MenuTree<'a, Message, Renderer>>,
435 pub(crate) bounds_expand: u16,
436 pub(crate) menu_overlays_parent: bool,
438 pub(crate) close_condition: CloseCondition,
439 pub(crate) item_width: ItemWidth,
440 pub(crate) item_height: ItemHeight,
441 pub(crate) bar_bounds: Rectangle,
442 pub(crate) main_offset: i32,
443 pub(crate) cross_offset: i32,
444 pub(crate) root_bounds_list: Vec<Rectangle>,
445 pub(crate) path_highlight: Option<PathHighlight>,
446 pub(crate) style: &'b <crate::Theme as StyleSheet>::Style,
447 pub(crate) position: Point,
448}
449impl<'b, Message, Renderer> Menu<'_, 'b, Message, Renderer>
450where
451 Renderer: renderer::Renderer,
452{
453 pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, Renderer> {
454 overlay::Element::new(Box::new(self))
455 }
456}
457impl<Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer>
458 for Menu<'_, '_, Message, Renderer>
459where
460 Renderer: renderer::Renderer,
461{
462 fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
463 let position = self.position;
465 let state = self.tree.state.downcast_mut::<MenuBarState>();
466 let overlay_offset = Point::ORIGIN - position;
467 let tree_children = &mut self.tree.children;
468 let children = state
469 .active_root
470 .map(|active_root| {
471 let root = &self.menu_roots[active_root];
472 let active_tree = &mut tree_children[active_root];
473 state.menu_states.iter().enumerate().fold(
474 (root, Vec::new()),
475 |(menu_root, mut nodes), (_i, ms)| {
476 let slice = ms.slice(bounds, overlay_offset, self.item_height);
477 let _start_index = slice.start_index;
478 let _end_index = slice.end_index;
479 let children_node = ms.layout(
480 overlay_offset,
481 slice,
482 renderer,
483 menu_root,
484 &mut active_tree.children,
485 );
486 nodes.push(children_node);
487 (
489 ms.index
490 .map_or(menu_root, |active| &menu_root.children[active]),
491 nodes,
492 )
493 },
494 )
495 })
496 .map(|(_, l)| l)
497 .unwrap_or_default();
498
499 Node::with_children(bounds, children).translate(Point::ORIGIN - position)
501 }
502
503 fn on_event(
504 &mut self,
505 event: event::Event,
506 layout: Layout<'_>,
507 view_cursor: Cursor,
508 renderer: &Renderer,
509 clipboard: &mut dyn Clipboard,
510 shell: &mut Shell<'_, Message>,
511 ) -> event::Status {
512 use event::{
513 Event::{Mouse, Touch},
514 Status::{Captured, Ignored},
515 };
516 use mouse::{
517 Button::Left,
518 Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled},
519 };
520 use touch::Event::{FingerLifted, FingerMoved, FingerPressed};
521
522 if !self.tree.state.downcast_ref::<MenuBarState>().open {
523 return Ignored;
524 };
525
526 let viewport = layout.bounds();
527 let viewport_size = viewport.size();
528 let overlay_offset = Point::ORIGIN - viewport.position();
529 let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
530
531 let menu_status = process_menu_events(
532 self.tree,
533 self.menu_roots,
534 event.clone(),
535 view_cursor,
536 renderer,
537 clipboard,
538 shell,
539 overlay_offset,
540 );
541
542 init_root_menu(
543 self,
544 renderer,
545 shell,
546 overlay_cursor,
547 viewport_size,
548 overlay_offset,
549 self.bar_bounds,
550 self.main_offset as f32,
551 );
552
553 match event {
554 Mouse(WheelScrolled { delta }) => {
555 process_scroll_events(self, delta, overlay_cursor, viewport_size, overlay_offset)
556 .merge(menu_status)
557 }
558
559 Mouse(ButtonPressed(Left)) | Touch(FingerPressed { .. }) => {
560 let state = self.tree.state.downcast_mut::<MenuBarState>();
561 state.pressed = true;
562 state.view_cursor = view_cursor;
563 Captured
564 }
565
566 Mouse(CursorMoved { position }) | Touch(FingerMoved { position, .. }) => {
567 let view_cursor = Cursor::Available(position);
568 let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
569 process_overlay_events(
570 self,
571 renderer,
572 viewport_size,
573 overlay_offset,
574 view_cursor,
575 overlay_cursor,
576 self.cross_offset as f32,
577 )
578 .merge(menu_status)
579 }
580
581 Mouse(ButtonReleased(_)) | Touch(FingerLifted { .. }) => {
582 let state = self.tree.state.downcast_mut::<MenuBarState>();
583 state.pressed = false;
584
585 if state
587 .view_cursor
588 .position()
589 .unwrap_or_default()
590 .distance(view_cursor.position().unwrap_or_default())
591 < 2.0
592 {
593 let is_inside = state
594 .menu_states
595 .iter()
596 .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor));
597
598 if self.close_condition.click_inside
599 && is_inside
600 && matches!(
601 event,
602 Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. })
603 )
604 {
605 state.reset();
606 return Captured;
607 }
608
609 if self.close_condition.click_outside && !is_inside {
610 state.reset();
611 return Captured;
612 }
613 }
614
615 if self.bar_bounds.contains(overlay_cursor) {
617 state.reset();
618 Captured
619 } else {
620 menu_status
621 }
622 }
623
624 _ => menu_status,
625 }
626 }
627
628 #[allow(unused_results)]
629 fn draw(
630 &self,
631 renderer: &mut Renderer,
632 theme: &crate::Theme,
633 style: &renderer::Style,
634 layout: Layout<'_>,
635 view_cursor: Cursor,
636 ) {
637 let state = self.tree.state.downcast_ref::<MenuBarState>();
638 let Some(active_root) = state.active_root else {
639 return;
640 };
641
642 let viewport = layout.bounds();
643 let viewport_size = viewport.size();
644 let overlay_offset = Point::ORIGIN - viewport.position();
645 let render_bounds = Rectangle::new(Point::ORIGIN, viewport.size());
646
647 let styling = theme.appearance(self.style);
648
649 let tree = &self.tree.children[active_root].children;
650 let root = &self.menu_roots[active_root];
651
652 let indices = state.get_trimmed_indices().collect::<Vec<_>>();
653
654 state
655 .menu_states
656 .iter()
657 .zip(layout.children())
658 .enumerate()
659 .fold(root, |menu_root, (i, (ms, children_layout))| {
660 let draw_path = self.path_highlight.as_ref().map_or(false, |ph| match ph {
661 PathHighlight::Full => true,
662 PathHighlight::OmitActive => !indices.is_empty() && i < indices.len() - 1,
663 PathHighlight::MenuActive => i < state.menu_states.len() - 1,
664 });
665
666 let view_cursor = if i == state.menu_states.len() - 1 {
668 view_cursor
669 } else {
670 Cursor::Available([-1.0; 2].into())
671 };
672
673 let draw_menu = |r: &mut Renderer| {
674 let slice = ms.slice(viewport_size, overlay_offset, self.item_height);
676 let start_index = slice.start_index;
677 let end_index = slice.end_index;
678
679 let children_bounds = children_layout.bounds();
680
681 let menu_quad = renderer::Quad {
687 bounds: pad_rectangle(children_bounds, styling.background_expand.into()),
688 border: Border {
689 radius: styling.menu_border_radius.into(),
690 width: styling.border_width,
691 color: styling.border_color,
692 },
693 shadow: Shadow::default(),
694 };
695 let menu_color = styling.background;
696 r.fill_quad(menu_quad, menu_color);
697
698 if let (true, Some(active)) = (draw_path, ms.index) {
700 let active_bounds = children_layout
701 .children()
702 .nth(active.saturating_sub(start_index))
703 .expect("No active children were found in menu?")
704 .bounds();
705 let path_quad = renderer::Quad {
706 bounds: active_bounds,
707 border: Border {
708 radius: styling.menu_border_radius.into(),
709 ..Default::default()
710 },
711 shadow: Shadow::default(),
712 };
713
714 r.fill_quad(path_quad, styling.path);
715 }
716
717 menu_root.children[start_index..=end_index]
719 .iter()
720 .zip(children_layout.children())
721 .for_each(|(mt, clo)| {
722 mt.item.as_widget().draw(
723 &tree[mt.index],
724 r,
725 theme,
726 style,
727 clo,
728 view_cursor,
729 &children_layout.bounds(),
730 );
731 });
732 };
733
734 renderer.with_layer(render_bounds, draw_menu);
735
736 ms.index
738 .map_or(menu_root, |active| &menu_root.children[active])
739 });
740 }
741}
742
743fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle {
744 Rectangle {
745 x: rect.x - padding.left,
746 y: rect.y - padding.top,
747 width: rect.width + padding.horizontal(),
748 height: rect.height + padding.vertical(),
749 }
750}
751
752pub(super) fn init_root_menu<Message, Renderer>(
753 menu: &mut Menu<'_, '_, Message, Renderer>,
754 renderer: &Renderer,
755 shell: &mut Shell<'_, Message>,
756 overlay_cursor: Point,
757 viewport_size: Size,
758 overlay_offset: Vector,
759 bar_bounds: Rectangle,
760 main_offset: f32,
761) where
762 Renderer: renderer::Renderer,
763{
764 let state = menu.tree.state.downcast_mut::<MenuBarState>();
765 if !(state.menu_states.is_empty() && bar_bounds.contains(overlay_cursor)) {
766 return;
767 }
768
769 for (i, (&root_bounds, mt)) in menu
770 .root_bounds_list
771 .iter()
772 .zip(menu.menu_roots.iter())
773 .enumerate()
774 {
775 if mt.children.is_empty() {
776 continue;
777 }
778
779 if root_bounds.contains(overlay_cursor) {
780 let view_center = viewport_size.width * 0.5;
781 let rb_center = root_bounds.center_x();
782
783 state.horizontal_direction = if rb_center > view_center {
784 Direction::Negative
785 } else {
786 Direction::Positive
787 };
788
789 let aod = Aod {
790 horizontal: true,
791 vertical: true,
792 horizontal_overlap: true,
793 vertical_overlap: false,
794 horizontal_direction: state.horizontal_direction,
795 vertical_direction: state.vertical_direction,
796 horizontal_offset: 0.0,
797 vertical_offset: main_offset,
798 };
799
800 let menu_bounds = MenuBounds::new(
801 mt,
802 renderer,
803 menu.item_width,
804 menu.item_height,
805 viewport_size,
806 overlay_offset,
807 &aod,
808 menu.bounds_expand,
809 root_bounds,
810 &mut menu.tree.children[i].children,
811 );
812
813 state.active_root = Some(i);
814 state.menu_states.push(MenuState {
815 index: None,
816 scroll_offset: 0.0,
817 menu_bounds,
818 });
819
820 shell.invalidate_layout();
822
823 break;
824 }
825 }
826}
827
828#[allow(clippy::too_many_arguments)]
829fn process_menu_events<'b, Message, Renderer>(
830 tree: &'b mut Tree,
831 menu_roots: &'b mut [MenuTree<'_, Message, Renderer>],
832 event: event::Event,
833 view_cursor: Cursor,
834 renderer: &Renderer,
835 clipboard: &mut dyn Clipboard,
836 shell: &mut Shell<'_, Message>,
837 overlay_offset: Vector,
838) -> event::Status
839where
840 Renderer: renderer::Renderer,
841{
842 use event::Status;
843
844 let state = tree.state.downcast_mut::<MenuBarState>();
845 let Some(active_root) = state.active_root else {
846 return Status::Ignored;
847 };
848
849 let indices = state.get_trimmed_indices().collect::<Vec<_>>();
850
851 if indices.is_empty() {
852 return Status::Ignored;
853 }
854
855 let mt = indices
857 .iter()
858 .fold(&mut menu_roots[active_root], |mt, &i| &mut mt.children[i]);
859
860 let tree = &mut tree.children[active_root].children[mt.index];
862
863 let last_ms = &state.menu_states[indices.len() - 1];
865 let child_node = last_ms.layout_single(
866 overlay_offset,
867 last_ms.index.expect("missing index within menu state."),
868 renderer,
869 mt,
870 tree,
871 );
872 let child_layout = Layout::new(&child_node);
873
874 mt.item.as_widget_mut().on_event(
876 tree,
877 event,
878 child_layout,
879 view_cursor,
880 renderer,
881 clipboard,
882 shell,
883 &Rectangle::default(),
884 )
885}
886
887#[allow(unused_results)]
888fn process_overlay_events<Message, Renderer>(
889 menu: &mut Menu<'_, '_, Message, Renderer>,
890 renderer: &Renderer,
891 viewport_size: Size,
892 overlay_offset: Vector,
893 view_cursor: Cursor,
894 overlay_cursor: Point,
895 cross_offset: f32,
896) -> event::Status
897where
898 Renderer: renderer::Renderer,
899{
900 use event::Status::{Captured, Ignored};
901 let state = menu.tree.state.downcast_mut::<MenuBarState>();
912
913 let Some(active_root) = state.active_root else {
914 if !menu.bar_bounds.contains(overlay_cursor) {
915 state.reset();
916 }
917 return Ignored;
918 };
919
920 if state.pressed {
921 return Ignored;
922 }
923
924 state.view_cursor = view_cursor;
927
928 let mut prev_bounds = std::iter::once(menu.bar_bounds)
930 .chain(
931 state.menu_states[..state.menu_states.len().saturating_sub(1)]
932 .iter()
933 .map(|ms| ms.menu_bounds.children_bounds),
934 )
935 .collect::<Vec<_>>();
936
937 if menu.close_condition.leave {
938 for i in (0..state.menu_states.len()).rev() {
939 let mb = &state.menu_states[i].menu_bounds;
940
941 if mb.parent_bounds.contains(overlay_cursor)
942 || mb.children_bounds.contains(overlay_cursor)
943 || mb.offset_bounds.contains(overlay_cursor)
944 || (mb.check_bounds.contains(overlay_cursor)
945 && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor)))
946 {
947 break;
948 }
949 prev_bounds.pop();
950 state.menu_states.pop();
951 }
952 } else {
953 for i in (0..state.menu_states.len()).rev() {
954 let mb = &state.menu_states[i].menu_bounds;
955
956 if mb.parent_bounds.contains(overlay_cursor)
957 || mb.children_bounds.contains(overlay_cursor)
958 || prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))
959 {
960 break;
961 }
962 prev_bounds.pop();
963 state.menu_states.pop();
964 }
965 }
966
967 let indices = state
969 .menu_states
970 .iter()
971 .map(|ms| ms.index)
972 .collect::<Vec<_>>();
973
974 let Some(last_menu_state) = state.menu_states.last_mut() else {
976 state.active_root = None;
978
979 if !menu.bar_bounds.contains(overlay_cursor) {
983 state.open = false;
984 }
985 return Captured;
986 };
987
988 let last_menu_bounds = &last_menu_state.menu_bounds;
989 let last_parent_bounds = last_menu_bounds.parent_bounds;
990 let last_children_bounds = last_menu_bounds.children_bounds;
991
992 if (!menu.menu_overlays_parent && last_parent_bounds.contains(overlay_cursor))
993 || !last_children_bounds.contains(overlay_cursor)
995 {
997 last_menu_state.index = None;
998 return Captured;
999 }
1000 let height_diff = (overlay_cursor.y - (last_children_bounds.y + last_menu_state.scroll_offset))
1004 .clamp(0.0, last_children_bounds.height - 0.001);
1005
1006 let active_menu_root = &menu.menu_roots[active_root];
1007
1008 let active_menu = indices[0..indices.len().saturating_sub(1)]
1009 .iter()
1010 .fold(active_menu_root, |mt, i| {
1011 &mt.children[i.expect("missing active child index in menu")]
1012 });
1013
1014 let new_index = match menu.item_height {
1015 ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize,
1016 ItemHeight::Static(_) | ItemHeight::Dynamic(_) => {
1017 let max_index = active_menu.children.len() - 1;
1018 search_bound(
1019 0,
1020 0,
1021 max_index,
1022 height_diff,
1023 &last_menu_bounds.child_positions,
1024 &last_menu_bounds.child_sizes,
1025 )
1026 }
1027 };
1028
1029 last_menu_state.index = Some(new_index);
1031
1032 let item = &active_menu.children[new_index];
1034
1035 if !item.children.is_empty() {
1037 let item_position = Point::new(
1038 0.0,
1039 last_menu_bounds.child_positions[new_index] + last_menu_state.scroll_offset,
1040 );
1041 let item_size = last_menu_bounds.child_sizes[new_index];
1042
1043 let item_bounds = Rectangle::new(item_position, item_size)
1045 + (last_menu_bounds.children_bounds.position() - Point::ORIGIN);
1046
1047 let aod = Aod {
1048 horizontal: true,
1049 vertical: true,
1050 horizontal_overlap: false,
1051 vertical_overlap: true,
1052 horizontal_direction: state.horizontal_direction,
1053 vertical_direction: state.vertical_direction,
1054 horizontal_offset: cross_offset,
1055 vertical_offset: 0.0,
1056 };
1057
1058 state.menu_states.push(MenuState {
1059 index: None,
1060 scroll_offset: 0.0,
1061 menu_bounds: MenuBounds::new(
1062 item,
1063 renderer,
1064 menu.item_width,
1065 menu.item_height,
1066 viewport_size,
1067 overlay_offset,
1068 &aod,
1069 menu.bounds_expand,
1070 item_bounds,
1071 &mut menu.tree.children[active_root].children,
1072 ),
1073 });
1074 }
1075
1076 Captured
1077}
1078
1079fn process_scroll_events<Message, Renderer>(
1080 menu: &mut Menu<'_, '_, Message, Renderer>,
1081 delta: mouse::ScrollDelta,
1082 overlay_cursor: Point,
1083 viewport_size: Size,
1084 overlay_offset: Vector,
1085) -> event::Status
1086where
1087 Renderer: renderer::Renderer,
1088{
1089 use event::Status::{Captured, Ignored};
1090 use mouse::ScrollDelta;
1091
1092 let state = menu.tree.state.downcast_mut::<MenuBarState>();
1093
1094 let delta_y = match delta {
1095 ScrollDelta::Lines { y, .. } => y * 60.0,
1096 ScrollDelta::Pixels { y, .. } => y,
1097 };
1098
1099 let calc_offset_bounds = |menu_state: &MenuState, viewport_size: Size| -> (f32, f32) {
1100 let children_bounds = menu_state.menu_bounds.children_bounds + overlay_offset;
1102
1103 let max_offset = (0.0 - children_bounds.y).max(0.0);
1104 let min_offset =
1105 (viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0);
1106 (max_offset, min_offset)
1107 };
1108
1109 if state.menu_states.is_empty() {
1111 return Ignored;
1112 } else if state.menu_states.len() == 1 {
1113 let last_ms = &mut state.menu_states[0];
1114
1115 if last_ms.index.is_none() {
1116 return Captured;
1117 }
1118
1119 let (max_offset, min_offset) = calc_offset_bounds(last_ms, viewport_size);
1120 last_ms.scroll_offset = (last_ms.scroll_offset + delta_y).clamp(min_offset, max_offset);
1121 } else {
1122 let max_index = state.menu_states.len() - 1;
1124 let last_two = &mut state.menu_states[max_index - 1..=max_index];
1125
1126 if last_two[1].index.is_some() {
1127 let (max_offset, min_offset) = calc_offset_bounds(&last_two[1], viewport_size);
1129 last_two[1].scroll_offset =
1130 (last_two[1].scroll_offset + delta_y).clamp(min_offset, max_offset);
1131 } else {
1132 if !last_two[0]
1133 .menu_bounds
1134 .children_bounds
1135 .contains(overlay_cursor)
1136 {
1137 return Captured;
1138 }
1139
1140 let (max_offset, min_offset) = calc_offset_bounds(&last_two[0], viewport_size);
1142 let scroll_offset = (last_two[0].scroll_offset + delta_y).clamp(min_offset, max_offset);
1143 let clamped_delta_y = scroll_offset - last_two[0].scroll_offset;
1144 last_two[0].scroll_offset = scroll_offset;
1145
1146 last_two[1].menu_bounds.parent_bounds.y += clamped_delta_y;
1148 last_two[1].menu_bounds.children_bounds.y += clamped_delta_y;
1149 last_two[1].menu_bounds.check_bounds.y += clamped_delta_y;
1150 }
1151 }
1152 Captured
1153}
1154
1155#[allow(clippy::pedantic)]
1156fn get_children_layout<Message, Renderer>(
1158 menu_tree: &MenuTree<'_, Message, Renderer>,
1159 renderer: &Renderer,
1160 item_width: ItemWidth,
1161 item_height: ItemHeight,
1162 tree: &mut [Tree],
1163) -> (Size, Vec<f32>, Vec<Size>)
1164where
1165 Renderer: renderer::Renderer,
1166{
1167 let width = match item_width {
1168 ItemWidth::Uniform(u) => f32::from(u),
1169 ItemWidth::Static(s) => f32::from(menu_tree.width.unwrap_or(s)),
1170 };
1171
1172 let child_sizes: Vec<Size> = match item_height {
1173 ItemHeight::Uniform(u) => {
1174 let count = menu_tree.children.len();
1175 (0..count).map(|_| Size::new(width, f32::from(u))).collect()
1176 }
1177 ItemHeight::Static(s) => menu_tree
1178 .children
1179 .iter()
1180 .map(|mt| Size::new(width, f32::from(mt.height.unwrap_or(s))))
1181 .collect(),
1182 ItemHeight::Dynamic(d) => menu_tree
1183 .children
1184 .iter()
1185 .map(|mt| {
1186 let w = mt.item.as_widget();
1187 match w.size().height {
1188 Length::Fixed(f) => Size::new(width, f),
1189 Length::Shrink => {
1190 let l_height = w
1191 .layout(
1192 &mut tree[mt.index],
1193 renderer,
1194 &Limits::new(Size::ZERO, Size::new(width, f32::MAX)),
1195 )
1196 .size()
1197 .height;
1198
1199 let height = if (f32::MAX - l_height) < 0.001 {
1200 f32::from(d)
1201 } else {
1202 l_height
1203 };
1204
1205 Size::new(width, height)
1206 }
1207 _ => mt.height.map_or_else(
1208 || Size::new(width, f32::from(d)),
1209 |h| Size::new(width, f32::from(h)),
1210 ),
1211 }
1212 })
1213 .collect(),
1214 };
1215
1216 let max_index = menu_tree.children.len() - 1;
1217 let child_positions: Vec<f32> = std::iter::once(0.0)
1218 .chain(child_sizes[0..max_index].iter().scan(0.0, |acc, x| {
1219 *acc += x.height;
1220 Some(*acc)
1221 }))
1222 .collect();
1223
1224 let height = child_sizes.iter().fold(0.0, |acc, x| acc + x.height);
1225
1226 (Size::new(width, height), child_positions, child_sizes)
1227}
1228
1229fn search_bound(
1230 default: usize,
1231 default_left: usize,
1232 default_right: usize,
1233 bound: f32,
1234 positions: &[f32],
1235 sizes: &[Size],
1236) -> usize {
1237 let mut left = default_left;
1239 let mut right = default_right;
1240
1241 let mut index = default;
1242 while left != right {
1243 let m = ((left + right) / 2) + 1;
1244 if positions[m] > bound {
1245 right = m - 1;
1246 } else {
1247 left = m;
1248 }
1249 }
1250 let height = sizes[left].height;
1252 if positions[left] + height > bound {
1253 index = left;
1254 }
1255 index
1256}