1use std::borrow::Cow;
5use std::sync::Arc;
6
7use super::menu_bar::MenuBarState;
8use super::menu_tree::MenuTree;
9#[cfg(wayland_platform)]
10use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
11use crate::style::menu_bar::StyleSheet;
12
13use iced::{Alignment, window};
14use iced_core::{Border, Renderer as IcedRenderer, Shadow, Widget};
15use iced_widget::core::layout::{Limits, Node};
16use iced_widget::core::mouse::{self, Cursor};
17use iced_widget::core::widget::Tree;
18use iced_widget::core::{
19 Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, overlay,
20 renderer, touch,
21};
22
23#[derive(Debug, Clone, Copy)]
25pub struct CloseCondition {
26 pub leave: bool,
28
29 pub click_outside: bool,
31
32 pub click_inside: bool,
34}
35
36#[derive(Debug, Clone, Copy)]
38pub enum ItemWidth {
39 Uniform(u16),
41 Static(u16),
47}
48
49#[derive(Debug, Clone, Copy)]
51pub enum ItemHeight {
52 Uniform(u16),
54 Static(u16),
57 Dynamic(u16),
66}
67
68#[derive(Debug, Clone, Copy)]
70pub enum PathHighlight {
71 Full,
73 OmitActive,
75 MenuActive,
77}
78
79#[derive(Debug, Clone, Copy)]
81pub(crate) enum Direction {
82 Positive,
83 Negative,
84}
85
86#[derive(Debug)]
88#[allow(clippy::struct_excessive_bools)]
89struct Aod {
90 horizontal: bool,
92 vertical: bool,
93
94 horizontal_overlap: bool,
96 vertical_overlap: bool,
97
98 horizontal_direction: Direction,
100 vertical_direction: Direction,
101
102 horizontal_offset: f32,
104 vertical_offset: f32,
105}
106impl Aod {
107 #[allow(clippy::too_many_arguments)]
109 fn adaptive(
110 parent_pos: f32,
111 parent_size: f32,
112 child_size: f32,
113 max_size: f32,
114 offset: f32,
115 on: bool,
116 overlap: bool,
117 direction: Direction,
118 ) -> (f32, f32) {
119 match direction {
149 Direction::Positive => {
150 let space_negative = parent_pos;
151 let space_positive = max_size - parent_pos - parent_size;
152
153 if overlap {
154 let overshoot = child_size - parent_size;
155 if on && space_negative > space_positive && overshoot > space_positive {
156 (parent_pos - overshoot, parent_pos - overshoot)
157 } else {
158 (parent_pos, parent_pos)
159 }
160 } else {
161 let overshoot = child_size + offset;
162 if on && space_negative > space_positive && overshoot > space_positive {
163 (parent_pos - overshoot, parent_pos - offset)
164 } else {
165 (parent_pos + parent_size + offset, parent_pos + parent_size)
166 }
167 }
168 }
169 Direction::Negative => {
170 let space_positive = parent_pos;
171 let space_negative = max_size - parent_pos - parent_size;
172
173 if overlap {
174 let overshoot = child_size - parent_size;
175 if on && space_negative > space_positive && overshoot > space_positive {
176 (parent_pos, parent_pos)
177 } else {
178 (parent_pos - overshoot, parent_pos - overshoot)
179 }
180 } else {
181 let overshoot = child_size + offset;
182 if on && space_negative > space_positive && overshoot > space_positive {
183 (parent_pos + parent_size + offset, parent_pos + parent_size)
184 } else {
185 (parent_pos - overshoot, parent_pos - offset)
186 }
187 }
188 }
189 }
190 }
191
192 fn resolve(
194 &self,
195 parent_bounds: Rectangle,
196 children_size: Size,
197 viewport_size: Size,
198 ) -> (Point, Point) {
199 let (x, ox) = Self::adaptive(
200 parent_bounds.x,
201 parent_bounds.width,
202 children_size.width,
203 viewport_size.width,
204 self.horizontal_offset,
205 self.horizontal,
206 self.horizontal_overlap,
207 self.horizontal_direction,
208 );
209 let (y, oy) = Self::adaptive(
210 parent_bounds.y,
211 parent_bounds.height,
212 children_size.height,
213 viewport_size.height,
214 self.vertical_offset,
215 self.vertical,
216 self.vertical_overlap,
217 self.vertical_direction,
218 );
219
220 ([x, y].into(), [ox, oy].into())
221 }
222}
223
224#[derive(Debug, Clone, Copy)]
230pub(super) struct MenuSlice {
231 pub(super) start_index: usize,
232 pub(super) end_index: usize,
233 pub(super) lower_bound_rel: f32,
234 pub(super) upper_bound_rel: f32,
235}
236
237#[derive(Debug, Clone)]
238pub struct MenuBounds {
240 child_positions: Vec<f32>,
241 child_sizes: Vec<Size>,
242 children_bounds: Rectangle,
243 pub parent_bounds: Rectangle,
244 check_bounds: Rectangle,
245 offset_bounds: Rectangle,
246}
247impl MenuBounds {
248 #[allow(clippy::too_many_arguments)]
249 fn new<Message>(
250 menu_tree: &MenuTree<Message>,
251 renderer: &crate::Renderer,
252 item_width: ItemWidth,
253 item_height: ItemHeight,
254 viewport_size: Size,
255 overlay_offset: Vector,
256 aod: &Aod,
257 bounds_expand: u16,
258 parent_bounds: Rectangle,
259 tree: &mut [Tree],
260 is_overlay: bool,
261 ) -> Self {
262 let (children_size, child_positions, child_sizes) =
263 get_children_layout(menu_tree, renderer, item_width, item_height, tree);
264
265 let view_parent_bounds = parent_bounds + overlay_offset;
267
268 let (children_position, offset_position) = {
270 let (cp, op) = aod.resolve(view_parent_bounds, children_size, viewport_size);
271 if is_overlay {
272 (cp - overlay_offset, op - overlay_offset)
273 } else {
274 (Point::ORIGIN, op - overlay_offset)
275 }
276 };
277
278 let delta = children_position - offset_position;
280 let offset_size = if delta.x.abs() > delta.y.abs() {
281 Size::new(delta.x, children_size.height)
282 } else {
283 Size::new(children_size.width, delta.y)
284 };
285 let offset_bounds = Rectangle::new(offset_position, offset_size);
286
287 let children_bounds = Rectangle::new(children_position, children_size);
288 let check_bounds = pad_rectangle(children_bounds, bounds_expand.into());
289
290 Self {
291 child_positions,
292 child_sizes,
293 children_bounds,
294 parent_bounds,
295 check_bounds,
296 offset_bounds,
297 }
298 }
299}
300
301#[derive(Clone)]
302pub(crate) struct MenuState {
303 pub(crate) index: Option<usize>,
305 scroll_offset: f32,
306 pub menu_bounds: MenuBounds,
307}
308impl MenuState {
309 pub(super) fn layout<Message>(
310 &mut self,
311 overlay_offset: Vector,
312 slice: MenuSlice,
313 renderer: &crate::Renderer,
314 menu_tree: &[MenuTree<Message>],
315 tree: &mut [Tree],
316 ) -> Node {
317 let MenuSlice {
318 start_index,
319 end_index,
320 lower_bound_rel,
321 upper_bound_rel,
322 } = slice;
323
324 debug_assert_eq!(menu_tree.len(), self.menu_bounds.child_positions.len());
325
326 let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
328 let child_nodes = self.menu_bounds.child_positions[start_index..=end_index]
329 .iter_mut()
330 .zip(self.menu_bounds.child_sizes[start_index..=end_index].iter_mut())
331 .zip(menu_tree[start_index..=end_index].iter())
332 .map(|((cp, size), mt)| {
333 let mut position = *cp;
334 let mut size = *size;
335
336 if position < lower_bound_rel && (position + size.height) > lower_bound_rel {
337 size.height = position + size.height - lower_bound_rel;
338 position = lower_bound_rel;
339 } else if position <= upper_bound_rel && (position + size.height) > upper_bound_rel
340 {
341 size.height = upper_bound_rel - position;
342 }
343
344 let limits = Limits::new(size, size);
345
346 mt.item
347 .element
348 .with_data_mut(|e| {
349 e.as_widget_mut()
350 .layout(&mut tree[mt.index], renderer, &limits)
351 })
352 .move_to(Point::new(0.0, position + self.scroll_offset))
353 })
354 .collect::<Vec<_>>();
355
356 Node::with_children(children_bounds.size(), child_nodes).move_to(children_bounds.position())
357 }
358
359 fn layout_single<Message>(
360 &self,
361 overlay_offset: Vector,
362 index: usize,
363 renderer: &crate::Renderer,
364 menu_tree: &mut MenuTree<Message>,
365 tree: &mut Tree,
366 ) -> Node {
367 let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
369
370 let position = self.menu_bounds.child_positions[index];
371 let limits = Limits::new(Size::ZERO, self.menu_bounds.child_sizes[index]);
372 let parent_offset = children_bounds.position() - Point::ORIGIN;
373 let node = menu_tree.item.layout(tree, renderer, &limits);
374 node.move_to(Point::new(
375 parent_offset.x,
376 parent_offset.y + position + self.scroll_offset,
377 ))
378 }
379
380 pub(super) fn slice(
382 &self,
383 viewport_size: Size,
384 overlay_offset: Vector,
385 item_height: ItemHeight,
386 ) -> MenuSlice {
387 let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
389
390 let max_index = self.menu_bounds.child_positions.len().saturating_sub(1);
391
392 let lower_bound = children_bounds.y.max(0.0);
394 let upper_bound = (children_bounds.y + children_bounds.height).min(viewport_size.height);
395
396 let lower_bound_rel = lower_bound - (children_bounds.y + self.scroll_offset);
398 let upper_bound_rel = upper_bound - (children_bounds.y + self.scroll_offset);
399
400 let (start_index, end_index) = match item_height {
402 ItemHeight::Uniform(u) => {
403 let start_index = (lower_bound_rel / f32::from(u)).floor() as usize;
404 let end_index = ((upper_bound_rel / f32::from(u)).floor() as usize).min(max_index);
405 (start_index, end_index)
406 }
407 ItemHeight::Static(_) | ItemHeight::Dynamic(_) => {
408 let positions = &self.menu_bounds.child_positions;
409 let sizes = &self.menu_bounds.child_sizes;
410
411 let start_index = search_bound(0, 0, max_index, lower_bound_rel, positions, sizes);
412 let end_index = search_bound(
413 max_index,
414 start_index,
415 max_index,
416 upper_bound_rel,
417 positions,
418 sizes,
419 )
420 .min(max_index);
421
422 (start_index, end_index)
423 }
424 };
425
426 MenuSlice {
427 start_index,
428 end_index,
429 lower_bound_rel,
430 upper_bound_rel,
431 }
432 }
433}
434
435#[derive(Clone)]
436pub(crate) struct Menu<'b, Message: std::clone::Clone> {
437 pub(crate) tree: MenuBarState,
438 pub(crate) menu_roots: Cow<'b, [MenuTree<Message>]>,
440 pub(crate) bounds_expand: u16,
441 pub(crate) menu_overlays_parent: bool,
443 pub(crate) close_condition: CloseCondition,
444 pub(crate) item_width: ItemWidth,
445 pub(crate) item_height: ItemHeight,
446 pub(crate) bar_bounds: Rectangle,
447 pub(crate) main_offset: i32,
448 pub(crate) cross_offset: i32,
449 pub(crate) root_bounds_list: Vec<Rectangle>,
450 pub(crate) path_highlight: Option<PathHighlight>,
451 pub(crate) style: Cow<'b, <crate::Theme as StyleSheet>::Style>,
452 pub(crate) position: Point,
453 pub(crate) is_overlay: bool,
454 pub(crate) window_id: window::Id,
456 pub(crate) depth: usize,
457 pub(crate) on_surface_action:
458 Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
459}
460impl<'b, Message: Clone + 'static> Menu<'b, Message> {
461 pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, crate::Renderer> {
462 overlay::Element::new(Box::new(self))
463 }
464
465 pub(crate) fn layout(&self, renderer: &crate::Renderer, limits: Limits) -> Node {
466 let position = self.position;
468 let mut intrinsic_size = Size::ZERO;
469
470 let empty = Vec::new();
471 self.tree.inner.with_data_mut(|data| {
472 if data.active_root.len() < self.depth + 1 || data.menu_states.len() < self.depth + 1 {
473 return Node::new(limits.min());
474 }
475
476 let overlay_offset = Point::ORIGIN - position;
477 let tree_children: &mut Vec<Tree> = &mut data.tree.children;
478
479 let children = (if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay {
480 data.active_root.len() - 1
481 } else {
482 self.depth
483 })
484 .map(|active_root| {
485 if self.menu_roots.is_empty() {
486 return (&empty, vec![]);
487 }
488 let (active_tree, roots) =
489 data.active_root[..=active_root].iter().skip(1).fold(
490 (
491 &mut tree_children[data.active_root[0]].children,
492 &self.menu_roots[data.active_root[0]].children,
493 ),
494 |(tree, mt), next_active_root| (tree, &mt[*next_active_root].children),
495 );
496
497 data.menu_states[if self.is_overlay { 0 } else { self.depth }
498 ..=if self.is_overlay {
499 data.active_root.len() - 1
500 } else {
501 self.depth
502 }]
503 .iter_mut()
504 .enumerate()
505 .filter(|ms| self.is_overlay || ms.0 < 1)
506 .fold(
507 (roots, Vec::new()),
508 |(menu_root, mut nodes), (_i, ms)| {
509 let slice =
510 ms.slice(limits.max(), overlay_offset, self.item_height);
511 let _start_index = slice.start_index;
512 let _end_index = slice.end_index;
513 let children_node = ms.layout(
514 overlay_offset,
515 slice,
516 renderer,
517 menu_root,
518 active_tree,
519 );
520 let node_size = children_node.size();
521 intrinsic_size.height += node_size.height;
522
523 intrinsic_size.width = intrinsic_size.width.max(node_size.width);
524
525 nodes.push(children_node);
526 (
529 ms.index
530 .map_or(menu_root, |active| &menu_root[active].children),
531 nodes,
532 )
533 },
534 )
535 })
536 .map(|(_, l)| l)
537 .next()
538 .unwrap_or_default();
539
540 Node::with_children(
542 limits.resolve(Length::Shrink, Length::Shrink, intrinsic_size),
543 children,
544 )
545 .translate(Point::ORIGIN - position)
546 })
547 }
548
549 #[allow(clippy::too_many_lines)]
550 fn update(
551 &mut self,
552 event: &event::Event,
553 layout: Layout<'_>,
554 view_cursor: Cursor,
555 renderer: &crate::Renderer,
556 clipboard: &mut dyn Clipboard,
557 shell: &mut Shell<'_, Message>,
558 ) -> Option<(usize, MenuState)> {
559 use event::Event::{Mouse, Touch};
560 use event::Status::{Captured, Ignored};
561 use mouse::Button::Left;
562 use mouse::Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled};
563 use touch::Event::{FingerLifted, FingerMoved, FingerPressed};
564
565 if !self
566 .tree
567 .inner
568 .with_data(|data| data.open || data.active_root.len() <= self.depth)
569 {
570 return None;
571 }
572
573 let viewport = layout.bounds();
574
575 let viewport_size = viewport.size();
576 let overlay_offset = Point::ORIGIN - viewport.position();
577 let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
578 let menu_roots = match &mut self.menu_roots {
579 Cow::Borrowed(_) => panic!(),
580 Cow::Owned(o) => o.as_mut_slice(),
581 };
582 process_menu_events(
583 self,
584 event,
585 view_cursor,
586 renderer,
587 clipboard,
588 shell,
589 overlay_offset,
590 );
591
592 init_root_menu(
593 self,
594 renderer,
595 shell,
596 overlay_cursor,
597 viewport_size,
598 overlay_offset,
599 self.bar_bounds,
600 self.main_offset as f32,
601 );
602
603 match event {
604 Mouse(WheelScrolled { delta }) => process_scroll_events(
605 self,
606 shell,
607 *delta,
608 overlay_cursor,
609 viewport_size,
610 overlay_offset,
611 ),
612
613 Mouse(ButtonPressed(Left)) | Touch(FingerPressed { .. }) => {
614 self.tree.inner.with_data_mut(|data| {
615 data.pressed = true;
616 data.view_cursor = view_cursor;
617 });
618 }
619
620 Mouse(CursorMoved { position }) | Touch(FingerMoved { position, .. }) => {
621 let view_cursor = Cursor::Available(*position);
622 let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
623 if !self.is_overlay && !view_cursor.is_over(viewport) {
624 return None;
625 }
626 let new_root = process_overlay_events(
627 self,
628 renderer,
629 viewport_size,
630 overlay_offset,
631 view_cursor,
632 overlay_cursor,
633 self.cross_offset as f32,
634 shell,
635 );
636
637 if self.is_overlay && view_cursor.is_over(viewport) {
638 shell.capture_event();
639 }
640
641 return new_root;
642 }
643
644 Mouse(ButtonReleased(_)) | Touch(FingerLifted { .. }) => {
645 self.tree.inner.with_data_mut(|state| {
646 state.pressed = false;
647
648 if state
650 .view_cursor
651 .position()
652 .unwrap_or_default()
653 .distance(view_cursor.position().unwrap_or_default())
654 < 2.0
655 {
656 let is_inside = state.menu_states[..=if self.is_overlay {
657 state.active_root.len().saturating_sub(1)
658 } else {
659 self.depth
660 }]
661 .iter()
662 .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor));
663 let mut needs_reset = false;
664 needs_reset |= self.close_condition.click_inside
665 && is_inside
666 && matches!(
667 event,
668 Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. })
669 );
670
671 needs_reset |= self.close_condition.click_outside && !is_inside;
672
673 if needs_reset {
674 #[cfg(wayland_platform)]
675 if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
676 && let Some(handler) = self.on_surface_action.as_ref()
677 {
678 let mut root = self.window_id;
679 let mut depth = self.depth;
680 while let Some(parent) =
681 state.popup_id.iter().find(|(_, v)| **v == root)
682 {
683 if depth == 0 {
685 break;
686 }
687 root = *parent.0;
688 depth = depth.saturating_sub(1);
689 }
690 shell
691 .publish((handler)(crate::surface::Action::DestroyPopup(root)));
692 }
693
694 state.reset();
695 }
696 }
697
698 if self.bar_bounds.contains(overlay_cursor) {
700 state.reset();
701 }
702 });
703 }
704
705 _ => {}
706 };
707 None
708 }
709
710 #[allow(unused_results, clippy::too_many_lines)]
711 fn draw(
712 &self,
713 renderer: &mut crate::Renderer,
714 theme: &crate::Theme,
715 style: &renderer::Style,
716 layout: Layout<'_>,
717 view_cursor: Cursor,
718 ) {
719 self.tree.inner.with_data(|state| {
720 if !state.open || state.active_root.len() <= self.depth {
721 return;
722 }
723 let active_root = &state.active_root[..=if self.is_overlay { 0 } else { self.depth }];
724 let viewport = layout.bounds();
725 let viewport_size = viewport.size();
726 let overlay_offset = Point::ORIGIN - viewport.position();
727
728 let render_bounds = if self.is_overlay {
729 Rectangle::new(Point::ORIGIN, viewport.size())
730 } else {
731 Rectangle::new(Point::ORIGIN, Size::INFINITE)
732 };
733
734 let styling = theme.appearance(&self.style, self.is_overlay);
735 let roots = active_root.iter().skip(1).fold(
736 &self.menu_roots[active_root[0]].children,
737 |mt, next_active_root| &mt[*next_active_root].children,
738 );
739 let indices = state.get_trimmed_indices(self.depth).collect::<Vec<_>>();
740 state.menu_states[if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay {
741 state.menu_states.len() - 1
742 } else {
743 self.depth
744 }]
745 .iter()
746 .zip(layout.children())
747 .enumerate()
748 .filter(|ms: &(usize, (&MenuState, Layout<'_>))| self.is_overlay || ms.0 < 1)
749 .fold(
750 roots,
751 |menu_roots: &Vec<MenuTree<Message>>, (i, (ms, children_layout))| {
752 let draw_path = self.path_highlight.as_ref().is_some_and(|ph| match ph {
753 PathHighlight::Full => true,
754 PathHighlight::OmitActive => {
755 !indices.is_empty() && i < indices.len() - 1
756 }
757 PathHighlight::MenuActive => {
758 !indices.is_empty()
759 && i < indices.len()
760 && menu_roots.len() > indices[i]
761 && (i < indices.len() - 1
762 || !menu_roots[indices[i]].children.is_empty())
763 }
764 });
765
766 let view_cursor = if self.depth == state.active_root.len() - 1
768 || i == state.menu_states.len() - 1
769 {
770 view_cursor
771 } else {
772 Cursor::Available([-1.0; 2].into())
773 };
774
775 let draw_menu = |r: &mut crate::Renderer| {
776 let slice = ms.slice(viewport_size, overlay_offset, self.item_height);
778 let start_index = slice.start_index;
779 let end_index = slice.end_index;
780
781 let children_bounds = children_layout.bounds();
782
783 let menu_quad = renderer::Quad {
789 bounds: pad_rectangle(
790 children_bounds.intersection(&viewport).unwrap_or_default(),
791 styling.background_expand.into(),
792 ),
793 border: Border {
794 radius: styling.menu_border_radius.into(),
795 width: styling.border_width,
796 color: styling.border_color,
797 },
798 shadow: Shadow::default(),
799 snap: true,
800 };
801 let menu_color = styling.background;
802 r.fill_quad(menu_quad, menu_color);
803 if let (true, Some(active)) = (draw_path, ms.index)
805 && let Some(active_layout) = children_layout
806 .children()
807 .nth(active.saturating_sub(start_index))
808 {
809 let i = active.saturating_sub(start_index);
810 let mut rad = styling.menu_border_radius;
811 let rad_0 = theme.cosmic().radius_0();
812 if start_index != end_index {
813 if 0 == i {
814 rad[0] = rad_0[0];
815 rad[1] = rad_0[1];
816 } else if i == end_index - start_index {
817 rad[2] = rad_0[2];
818 rad[3] = rad_0[3];
819 } else {
820 rad = rad_0;
821 }
822 }
823 let path_quad = renderer::Quad {
824 bounds: active_layout
825 .bounds()
826 .intersection(&viewport)
827 .unwrap_or_default(),
828 border: Border {
829 radius: rad.into(),
830 ..Default::default()
831 },
832 shadow: Shadow::default(),
833 snap: true,
834 };
835
836 r.fill_quad(path_quad, styling.path);
837 }
838 if start_index < menu_roots.len() {
839 menu_roots[start_index..=end_index]
841 .iter()
842 .enumerate()
843 .zip(children_layout.children())
844 .for_each(|((i, mt), clo)| {
845 let t = theme.with_list_item_position(
846 if start_index == end_index {
847 Some((Alignment::Center, i))
848 } else if 0 == i {
849 Some((Alignment::Start, i))
850 } else if i == end_index - start_index {
851 Some((Alignment::End, i))
852 } else {
853 None
854 },
855 );
856
857 mt.item.draw(
858 &state.tree.children[active_root[0]].children[mt.index],
859 r,
860 &t,
861 style,
862 clo,
863 view_cursor,
864 &children_layout
865 .bounds()
866 .intersection(&viewport)
867 .unwrap_or_default(),
868 );
869 });
870 }
871 };
872
873 renderer.with_layer(render_bounds, draw_menu);
874
875 ms.index
877 .map_or(menu_roots, |active| &menu_roots[active].children)
878 },
879 );
880 });
881 }
882}
883impl<Message: Clone + 'static> overlay::Overlay<Message, crate::Theme, crate::Renderer>
884 for Menu<'_, Message>
885{
886 fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> iced_core::layout::Node {
887 Menu::layout(
888 self,
889 renderer,
890 Limits::NONE
891 .min_width(bounds.width)
892 .max_width(bounds.width)
893 .min_height(bounds.height)
894 .max_height(bounds.height),
895 )
896 }
897
898 fn update(
899 &mut self,
900 event: &iced::Event,
901 layout: Layout<'_>,
902 cursor: mouse::Cursor,
903 renderer: &crate::Renderer,
904 clipboard: &mut dyn Clipboard,
905 shell: &mut Shell<'_, Message>,
906 ) {
907 self.update(event, layout, cursor, renderer, clipboard, shell);
908 }
909
910 fn draw(
911 &self,
912 renderer: &mut crate::Renderer,
913 theme: &crate::Theme,
914 style: &renderer::Style,
915 layout: Layout<'_>,
916 cursor: mouse::Cursor,
917 ) {
918 self.draw(renderer, theme, style, layout, cursor);
919 }
920
921 fn mouse_interaction(
922 &self,
923 layout: Layout<'_>,
924 cursor: mouse::Cursor,
925 _renderer: &crate::Renderer,
926 ) -> mouse::Interaction {
927 if cursor.is_over(layout.bounds()) {
928 mouse::Interaction::Idle
929 } else {
930 mouse::Interaction::None
931 }
932 }
933}
934
935impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
936 for Menu<'_, Message>
937{
938 fn size(&self) -> Size<Length> {
939 Size {
940 width: Length::Shrink,
941 height: Length::Shrink,
942 }
943 }
944
945 fn layout(
946 &mut self,
947 _tree: &mut Tree,
948 renderer: &crate::Renderer,
949 limits: &iced_core::layout::Limits,
950 ) -> iced_core::layout::Node {
951 Menu::layout(self, renderer, *limits)
952 }
953
954 fn draw(
955 &self,
956 _tree: &Tree,
957 renderer: &mut crate::Renderer,
958 theme: &crate::Theme,
959 style: &renderer::Style,
960 layout: Layout<'_>,
961 cursor: mouse::Cursor,
962 _viewport: &Rectangle,
963 ) {
964 Menu::draw(self, renderer, theme, style, layout, cursor);
965 }
966
967 #[allow(clippy::too_many_lines)]
968 fn update(
969 &mut self,
970 tree: &mut Tree,
971 event: &iced::Event,
972 layout: Layout<'_>,
973 cursor: mouse::Cursor,
974 renderer: &crate::Renderer,
975 clipboard: &mut dyn Clipboard,
976 shell: &mut Shell<'_, Message>,
977 viewport: &Rectangle,
978 ) {
979 let new_root = self.update(event, layout, cursor, renderer, clipboard, shell);
980
981 #[cfg(wayland_platform)]
982 if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
983 && let Some((new_root, new_ms)) = new_root
984 {
985 use iced_runtime::platform_specific::wayland::CornerRadius;
986 use iced_runtime::platform_specific::wayland::popup::{
987 SctkPopupSettings, SctkPositioner,
988 };
989
990 use crate::surface::action::LiveSettings;
991 use crate::theme::THEME;
992 let overlay_offset = Point::ORIGIN - viewport.position();
993
994 let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
995
996 let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
997 let popup_id = *state
998 .popup_id
999 .entry(self.window_id)
1000 .or_insert_with(window::Id::unique);
1001 let active_roots = state
1002 .active_root
1003 .get(self.depth)
1004 .cloned()
1005 .unwrap_or_default();
1006
1007 let root_bounds_list = layout
1008 .children()
1009 .next()
1010 .unwrap()
1011 .children()
1012 .map(|lo| lo.bounds())
1013 .collect();
1014
1015 let mut popup_menu = Menu {
1016 tree: self.tree.clone(),
1017 menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
1018 bounds_expand: self.bounds_expand,
1019 menu_overlays_parent: false,
1020 close_condition: self.close_condition,
1021 item_width: self.item_width,
1022 item_height: self.item_height,
1023 bar_bounds: layout.bounds(),
1024 main_offset: self.main_offset,
1025 cross_offset: self.cross_offset,
1026 root_bounds_list,
1027 path_highlight: self.path_highlight,
1028 style: Cow::Owned(Cow::into_owned(self.style.clone())),
1029 position: Point::new(0., 0.),
1030 is_overlay: false,
1031 window_id: popup_id,
1032 depth: self.depth + 1,
1033 on_surface_action: self.on_surface_action.clone(),
1034 };
1035
1036 state.active_root.push(new_root);
1037
1038 Some((popup_menu, popup_id))
1039 }) else {
1040 return;
1041 };
1042 init_root_popup_menu(
1044 &mut menu,
1045 renderer,
1046 shell,
1047 cursor.position().unwrap_or_default(),
1048 layout.bounds().size(),
1049 Vector::new(0., 0.),
1050 layout.bounds(),
1051 self.main_offset as f32,
1052 );
1053 let (anchor_rect, gravity) = self.tree.inner.with_data_mut(|state| {
1054 (state
1055 .menu_states
1056 .get(self.depth + 1)
1057 .map(|s| s.menu_bounds.parent_bounds)
1058 .map_or_else(
1059 || {
1060 let bounds = layout.bounds();
1061 Rectangle {
1062 x: bounds.x as i32,
1063 y: bounds.y as i32,
1064 width: bounds.width as i32,
1065 height: bounds.height as i32,
1066 }
1067 },
1068 |r| Rectangle {
1069 x: r.x as i32,
1070 y: r.y as i32,
1071 width: r.width as i32,
1072 height: r.height as i32,
1073 },
1074 ), match (state.horizontal_direction, state.vertical_direction) {
1075 (Direction::Positive, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
1076 (Direction::Positive, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight,
1077 (Direction::Negative, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft,
1078 (Direction::Negative, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft,
1079 })
1080 });
1081
1082 let menu_node = Widget::layout(
1083 &mut menu,
1084 &mut Tree::empty(),
1085 renderer,
1086 &Limits::NONE.min_width(1.).min_height(1.),
1087 );
1088
1089 let popup_size = menu_node.size();
1090 let mut positioner = SctkPositioner {
1091 size: Some((
1092 popup_size.width.ceil() as u32 + 2,
1093 popup_size.height.ceil() as u32 + 2,
1094 )),
1095 anchor_rect,
1096 anchor:
1097 cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight,
1098 gravity,
1099 reactive: true,
1100 ..Default::default()
1101 };
1102 positioner.constraint_adjustment &= !(1 << 0);
1104 let parent = self.window_id;
1105
1106 let t = THEME.lock().unwrap();
1107 let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
1108 drop(t);
1109 let rad = styling.menu_border_radius;
1110
1111 shell.publish((self.on_surface_action.as_ref().unwrap())(
1112 crate::surface::action::simple_popup(
1113 move || LiveSettings {
1114 corners: Some(CornerRadius {
1115 top_left: rad[0] as u32,
1116 top_right: rad[1] as u32,
1117 bottom_left: rad[2] as u32,
1118 bottom_right: rad[3] as u32,
1119 }),
1120 ..Default::default()
1121 },
1122 move || SctkPopupSettings {
1123 parent,
1124 id: popup_id,
1125 positioner: positioner.clone(),
1126 parent_size: None,
1127 grab: true,
1128 close_with_children: false,
1129 input_zone: None,
1130 },
1131 Some(move || {
1132 crate::Element::from(
1133 crate::widget::container(menu.clone()).center(Length::Fill),
1134 )
1135 .map(crate::action::app)
1136 }),
1137 ),
1138 ));
1139 }
1140 }
1141
1142 fn mouse_interaction(
1143 &self,
1144 _tree: &Tree,
1145 layout: Layout<'_>,
1146 cursor: mouse::Cursor,
1147 _viewport: &Rectangle,
1148 _renderer: &crate::Renderer,
1149 ) -> mouse::Interaction {
1150 if cursor.is_over(layout.bounds()) {
1151 mouse::Interaction::Idle
1152 } else {
1153 mouse::Interaction::None
1154 }
1155 }
1156}
1157
1158impl<'a, Message> From<Menu<'a, Message>>
1159 for iced::Element<'a, Message, crate::Theme, crate::Renderer>
1160where
1161 Message: std::clone::Clone + 'static,
1162{
1163 fn from(value: Menu<'a, Message>) -> Self {
1164 Self::new(value)
1165 }
1166}
1167
1168fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle {
1169 Rectangle {
1170 x: rect.x - padding.left,
1171 y: rect.y - padding.top,
1172 width: rect.width + padding.x(),
1173 height: rect.height + padding.y(),
1174 }
1175}
1176
1177#[allow(clippy::too_many_arguments)]
1178pub(crate) fn init_root_menu<Message: Clone>(
1179 menu: &mut Menu<'_, Message>,
1180 renderer: &crate::Renderer,
1181 shell: &mut Shell<'_, Message>,
1182 overlay_cursor: Point,
1183 viewport_size: Size,
1184 overlay_offset: Vector,
1185 bar_bounds: Rectangle,
1186 main_offset: f32,
1187) {
1188 menu.tree.inner.with_data_mut(|state| {
1189 if !(state.menu_states.get(menu.depth).is_none()
1190 && (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
1191 || menu.depth > 0
1192 || !state.open
1193 {
1194 return;
1195 }
1196
1197 for (i, (&root_bounds, mt)) in menu
1198 .root_bounds_list
1199 .iter()
1200 .zip(menu.menu_roots.iter())
1201 .enumerate()
1202 {
1203 if mt.children.is_empty() {
1204 continue;
1205 }
1206
1207 if root_bounds.contains(overlay_cursor) {
1208 let view_center = viewport_size.width * 0.5;
1209 let rb_center = root_bounds.center_x();
1210
1211 state.horizontal_direction = if menu.is_overlay && rb_center > view_center {
1212 Direction::Negative
1213 } else {
1214 Direction::Positive
1215 };
1216
1217 let aod = Aod {
1218 horizontal: true,
1219 vertical: true,
1220 horizontal_overlap: true,
1221 vertical_overlap: false,
1222 horizontal_direction: state.horizontal_direction,
1223 vertical_direction: state.vertical_direction,
1224 horizontal_offset: 0.0,
1225 vertical_offset: main_offset,
1226 };
1227 let menu_bounds = MenuBounds::new(
1228 mt,
1229 renderer,
1230 menu.item_width,
1231 menu.item_height,
1232 viewport_size,
1233 overlay_offset,
1234 &aod,
1235 menu.bounds_expand,
1236 root_bounds,
1237 &mut state.tree.children[0].children,
1238 menu.is_overlay,
1239 );
1240 state.active_root.push(i);
1241 let ms = MenuState {
1242 index: None,
1243 scroll_offset: 0.0,
1244 menu_bounds,
1245 };
1246 state.menu_states.push(ms);
1247 shell.invalidate_layout();
1249
1250 break;
1251 }
1252 }
1253 });
1254}
1255
1256#[cfg(wayland_platform)]
1257pub(super) fn init_root_popup_menu<Message>(
1258 menu: &mut Menu<'_, Message>,
1259 renderer: &crate::Renderer,
1260 shell: &mut Shell<'_, Message>,
1261 overlay_cursor: Point,
1262 viewport_size: Size,
1263 overlay_offset: Vector,
1264 bar_bounds: Rectangle,
1265 main_offset: f32,
1266) where
1267 Message: std::clone::Clone,
1268{
1269 menu.tree.inner.with_data_mut(|state| {
1270 if !(state.menu_states.get(menu.depth).is_none()
1271 && (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
1272 {
1273 return;
1274 }
1275
1276 let active_roots = &state.active_root[..=menu.depth];
1277
1278 let mt = active_roots
1279 .iter()
1280 .skip(1)
1281 .fold(&menu.menu_roots[active_roots[0]], |mt, next_active_root| {
1282 &mt.children[*next_active_root]
1283 });
1284 let i = active_roots.last().unwrap();
1285 let root_bounds = menu.root_bounds_list[*i];
1286
1287 assert!(!mt.children.is_empty(), "skipping menu with no children");
1288 let aod = Aod {
1289 horizontal: true,
1290 vertical: true,
1291 horizontal_overlap: true,
1292 vertical_overlap: false,
1293 horizontal_direction: state.horizontal_direction,
1294 vertical_direction: state.vertical_direction,
1295 horizontal_offset: 0.0,
1296 vertical_offset: main_offset,
1297 };
1298 let menu_bounds = MenuBounds::new(
1299 mt,
1300 renderer,
1301 menu.item_width,
1302 menu.item_height,
1303 viewport_size,
1304 overlay_offset,
1305 &aod,
1306 menu.bounds_expand,
1307 root_bounds,
1308 &mut state.tree.children[0].children,
1310 menu.is_overlay,
1311 );
1312
1313 let view_center = viewport_size.width * 0.5;
1314 let rb_center = root_bounds.center_x();
1315
1316 state.horizontal_direction = if rb_center > view_center {
1317 Direction::Negative
1318 } else {
1319 Direction::Positive
1320 };
1321
1322 let ms = MenuState {
1323 index: None,
1324 scroll_offset: 0.0,
1325 menu_bounds,
1326 };
1327 state.menu_states.push(ms);
1328
1329 shell.invalidate_layout();
1331 });
1332}
1333
1334#[allow(clippy::too_many_arguments)]
1335fn process_menu_events<Message: std::clone::Clone>(
1336 menu: &mut Menu<Message>,
1337 event: &event::Event,
1338 view_cursor: Cursor,
1339 renderer: &crate::Renderer,
1340 clipboard: &mut dyn Clipboard,
1341 shell: &mut Shell<'_, Message>,
1342 overlay_offset: Vector,
1343) {
1344 let my_state = &mut menu.tree;
1345 let menu_roots = match &mut menu.menu_roots {
1346 Cow::Borrowed(_) => panic!(),
1347 Cow::Owned(o) => o.as_mut_slice(),
1348 };
1349 my_state.inner.with_data_mut(|state| {
1350 if state.active_root.len() <= menu.depth {
1351 return;
1352 }
1353
1354 let Some(hover) = state.menu_states.last_mut() else {
1355 return;
1356 };
1357
1358 let Some(hover_index) = hover.index else {
1359 return;
1360 };
1361
1362 let mt = state.active_root.iter().skip(1).fold(
1363 &mut menu_roots[state.active_root[0]],
1365 |mt, next_active_root| &mut mt.children[*next_active_root],
1366 );
1367
1368 let mt = &mut mt.children[hover_index];
1369 let tree = &mut state.tree.children[state.active_root[0]].children[mt.index];
1370
1371 let child_node = hover.layout_single(
1373 overlay_offset,
1374 hover.index.expect("missing index within menu state."),
1375 renderer,
1376 mt,
1377 tree,
1378 );
1379 let child_layout = Layout::new(&child_node);
1380
1381 mt.item.update(
1383 tree,
1384 event,
1385 child_layout,
1386 view_cursor,
1387 renderer,
1388 clipboard,
1389 shell,
1390 &Rectangle::default(),
1391 );
1392 });
1393}
1394
1395#[allow(unused_results, clippy::too_many_lines, clippy::too_many_arguments)]
1396fn process_overlay_events<Message>(
1397 menu: &mut Menu<Message>,
1398 renderer: &crate::Renderer,
1399 viewport_size: Size,
1400 overlay_offset: Vector,
1401 view_cursor: Cursor,
1402 overlay_cursor: Point,
1403 cross_offset: f32,
1404 shell: &mut Shell<'_, Message>,
1405) -> Option<(usize, MenuState)>
1406where
1407 Message: std::clone::Clone,
1408{
1409 let mut new_menu_root = None;
1419
1420 menu.tree.inner.with_data_mut(|state| {
1421
1422 state.view_cursor = view_cursor;
1425
1426 let mut prev_bounds = std::iter::once(menu.bar_bounds)
1429 .chain(
1430 if menu.is_overlay {
1431 state.menu_states[..state.menu_states.len().saturating_sub(1)].iter()
1432 } else {
1433 state.menu_states[..menu.depth].iter()
1434 }
1435 .map(|s| s.menu_bounds.children_bounds),
1436 )
1437 .collect::<Vec<_>>();
1438
1439 if menu.is_overlay && menu.close_condition.leave {
1440 for i in (0..state.menu_states.len()).rev() {
1441 let mb = &state.menu_states[i].menu_bounds;
1442
1443 if mb.parent_bounds.contains(overlay_cursor)
1444 || menu.is_overlay && mb.children_bounds.contains(overlay_cursor)
1445 || mb.offset_bounds.contains(overlay_cursor)
1446 || (mb.check_bounds.contains(overlay_cursor)
1447 && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor)))
1448 {
1449 break;
1450 }
1451 prev_bounds.pop();
1452 state.active_root.pop();
1453 state.menu_states.pop();
1454 }
1455 } else if menu.is_overlay {
1456 for i in (0..state.menu_states.len()).rev() {
1457 let mb = &state.menu_states[i].menu_bounds;
1458
1459 if mb.parent_bounds.contains(overlay_cursor)
1460 || mb.children_bounds.contains(overlay_cursor)
1461 || prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))
1462 {
1463 break;
1464 }
1465 prev_bounds.pop();
1466 state.active_root.pop();
1467 state.menu_states.pop();
1468 }
1469 }
1470
1471 let menu_states_len = state.menu_states.len();
1473
1474 let Some(last_menu_state) = state.menu_states.get_mut(if menu.is_overlay {
1475 menu_states_len.saturating_sub(1)
1476 } else {
1477 menu.depth
1478 }) else {
1479 if menu.is_overlay {
1480 if !menu.bar_bounds.contains(overlay_cursor) {
1488 state.open = false;
1489 }
1490 }
1491 shell.capture_event();
1492 return new_menu_root;
1493 };
1494
1495 let last_menu_bounds = &last_menu_state.menu_bounds;
1496 let last_parent_bounds = last_menu_bounds.parent_bounds;
1497 let last_children_bounds = last_menu_bounds.children_bounds;
1498
1499 if (menu.is_overlay && !menu.menu_overlays_parent && last_parent_bounds.contains(overlay_cursor))
1500 || menu.is_overlay && !last_children_bounds.contains(overlay_cursor)
1502 {
1504
1505 last_menu_state.index = None;
1506 shell.capture_event();
1507 return new_menu_root;
1508 }
1509
1510 let height_diff = (overlay_cursor.y
1512 - (last_children_bounds.y + last_menu_state.scroll_offset))
1513 .clamp(0.0, last_children_bounds.height - 0.001);
1514
1515 let active_root = if menu.is_overlay {
1516 &state.active_root
1517 } else {
1518 &state.active_root[..=menu.depth]
1519 };
1520
1521 if state.pressed {
1522 return new_menu_root;
1523 }
1524 let roots = active_root.iter().skip(1).fold(
1525 &menu.menu_roots[active_root[0]].children,
1526 |mt, next_active_root| &mt[*next_active_root].children,
1527 );
1528 let tree = &mut state.tree.children[active_root[0]].children;
1529
1530 let active_menu: &Vec<MenuTree<Message>> = roots;
1531 let new_index = match menu.item_height {
1532 ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize,
1533 ItemHeight::Static(_) | ItemHeight::Dynamic(_) => {
1534 let max_index = active_menu.len() - 1;
1535 search_bound(
1536 0,
1537 0,
1538 max_index,
1539 height_diff,
1540 &last_menu_bounds.child_positions,
1541 &last_menu_bounds.child_sizes,
1542 )
1543 }
1544 };
1545
1546 let remove = last_menu_state
1547 .index
1548 .as_ref()
1549 .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty());
1550
1551 #[cfg(wayland_platform)]
1552 if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove {
1553 if let Some(id) = state.popup_id.remove(&menu.window_id) {
1554 state.active_root.truncate(menu.depth + 1);
1555 shell.publish((menu.on_surface_action.as_ref().unwrap())({
1556 crate::surface::action::destroy_popup(id)
1557 }));
1558 }
1559 }
1560 let item = &active_menu[new_index];
1561 let old_index = last_menu_state.index.replace(new_index);
1563
1564 if !item.children.is_empty() && old_index.is_none_or(|i| i != new_index) {
1567 let item_position = Point::new(
1568 0.0,
1569 last_menu_bounds.child_positions[new_index] + last_menu_state.scroll_offset,
1570 );
1571 let item_size = last_menu_bounds.child_sizes[new_index];
1572
1573 let item_bounds = Rectangle::new(item_position, item_size)
1575 + (last_menu_bounds.children_bounds.position() - Point::ORIGIN);
1576
1577 let aod = Aod {
1578 horizontal: true,
1579 vertical: true,
1580 horizontal_overlap: false,
1581 vertical_overlap: true,
1582 horizontal_direction: state.horizontal_direction,
1583 vertical_direction: state.vertical_direction,
1584 horizontal_offset: cross_offset,
1585 vertical_offset: 0.0,
1586 };
1587 let ms = MenuState {
1588 index: None,
1589 scroll_offset: 0.0,
1590 menu_bounds: MenuBounds::new(
1591 item,
1592 renderer,
1593 menu.item_width,
1594 menu.item_height,
1595 viewport_size,
1596 overlay_offset,
1597 &aod,
1598 menu.bounds_expand,
1599 item_bounds,
1600 tree,
1601 menu.is_overlay,
1602 ),
1603 };
1604
1605 new_menu_root = Some((new_index, ms.clone()));
1606 if menu.is_overlay {
1607 state.active_root.push(new_index);
1608 } else {
1609 state.menu_states.truncate(menu.depth + 1);
1610 }
1611 state.menu_states.push(ms);
1612 } else if !menu.is_overlay && remove {
1613 state.menu_states.truncate(menu.depth + 1);
1614 }
1615
1616 shell.capture_event();
1617 new_menu_root
1618 })
1619}
1620
1621fn process_scroll_events<Message>(
1622 menu: &mut Menu<'_, Message>,
1623 shell: &mut Shell<'_, Message>,
1624 delta: mouse::ScrollDelta,
1625 overlay_cursor: Point,
1626 viewport_size: Size,
1627 overlay_offset: Vector,
1628) where
1629 Message: Clone,
1630{
1631 use event::Status::{Captured, Ignored};
1632 use mouse::ScrollDelta;
1633
1634 menu.tree.inner.with_data_mut(|state| {
1635 let delta_y = match delta {
1636 ScrollDelta::Lines { y, .. } => y * 60.0,
1637 ScrollDelta::Pixels { y, .. } => y,
1638 };
1639
1640 let calc_offset_bounds = |menu_state: &MenuState, viewport_size: Size| -> (f32, f32) {
1641 let children_bounds = menu_state.menu_bounds.children_bounds + overlay_offset;
1643
1644 let max_offset = (0.0 - children_bounds.y).max(0.0);
1645 let min_offset =
1646 (viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0);
1647 (max_offset, min_offset)
1648 };
1649
1650 if state.menu_states.is_empty() {
1652 return;
1653 } else if state.menu_states.len() == 1 {
1654 let last_ms = &mut state.menu_states[0];
1655
1656 if last_ms.index.is_none() {
1657 return;
1658 }
1659
1660 let (max_offset, min_offset) = calc_offset_bounds(last_ms, viewport_size);
1661 last_ms.scroll_offset = (last_ms.scroll_offset + delta_y).clamp(min_offset, max_offset);
1662 } else {
1663 let max_index = state.menu_states.len() - 1;
1665 let last_two = &mut state.menu_states[max_index - 1..=max_index];
1666
1667 if last_two[1].index.is_some() {
1668 let (max_offset, min_offset) = calc_offset_bounds(&last_two[1], viewport_size);
1670 last_two[1].scroll_offset =
1671 (last_two[1].scroll_offset + delta_y).clamp(min_offset, max_offset);
1672 } else {
1673 if !last_two[0]
1674 .menu_bounds
1675 .children_bounds
1676 .contains(overlay_cursor)
1677 {
1678 shell.capture_event();
1679 return;
1680 }
1681
1682 let (max_offset, min_offset) = calc_offset_bounds(&last_two[0], viewport_size);
1684 let scroll_offset =
1685 (last_two[0].scroll_offset + delta_y).clamp(min_offset, max_offset);
1686 let clamped_delta_y = scroll_offset - last_two[0].scroll_offset;
1687 last_two[0].scroll_offset = scroll_offset;
1688
1689 last_two[1].menu_bounds.parent_bounds.y += clamped_delta_y;
1691 last_two[1].menu_bounds.children_bounds.y += clamped_delta_y;
1692 last_two[1].menu_bounds.check_bounds.y += clamped_delta_y;
1693 }
1694 }
1695 shell.capture_event();
1696 });
1697}
1698
1699#[allow(clippy::pedantic)]
1700fn get_children_layout<Message>(
1702 menu_tree: &MenuTree<Message>,
1703 renderer: &crate::Renderer,
1704 item_width: ItemWidth,
1705 item_height: ItemHeight,
1706 tree: &mut [Tree],
1707) -> (Size, Vec<f32>, Vec<Size>) {
1708 let width = match item_width {
1709 ItemWidth::Uniform(u) => f32::from(u),
1710 ItemWidth::Static(s) => f32::from(menu_tree.width.unwrap_or(s)),
1711 };
1712
1713 let child_sizes: Vec<Size> = match item_height {
1714 ItemHeight::Uniform(u) => {
1715 let count = menu_tree.children.len();
1716 vec![Size::new(width, f32::from(u)); count]
1717 }
1718 ItemHeight::Static(s) => menu_tree
1719 .children
1720 .iter()
1721 .map(|mt| Size::new(width, f32::from(mt.height.unwrap_or(s))))
1722 .collect(),
1723 ItemHeight::Dynamic(d) => menu_tree
1724 .children
1725 .iter()
1726 .map(|mt| {
1727 mt.item
1728 .element
1729 .with_data_mut(|w| match w.as_widget_mut().size().height {
1730 Length::Fixed(f) => Size::new(width, f),
1731 Length::Shrink => {
1732 let l_height = w
1733 .as_widget_mut()
1734 .layout(
1735 &mut tree[mt.index],
1736 renderer,
1737 &Limits::new(Size::ZERO, Size::new(width, f32::MAX)),
1738 )
1739 .size()
1740 .height;
1741
1742 let height = if (f32::MAX - l_height) < 0.001 {
1743 f32::from(d)
1744 } else {
1745 l_height
1746 };
1747
1748 Size::new(width, height)
1749 }
1750 _ => mt.height.map_or_else(
1751 || Size::new(width, f32::from(d)),
1752 |h| Size::new(width, f32::from(h)),
1753 ),
1754 })
1755 })
1756 .collect(),
1757 };
1758
1759 let max_index = menu_tree.children.len().saturating_sub(1);
1760 let child_positions: Vec<f32> = std::iter::once(0.0)
1761 .chain(child_sizes[0..max_index].iter().scan(0.0, |acc, x| {
1762 *acc += x.height;
1763 Some(*acc)
1764 }))
1765 .collect();
1766
1767 let height = child_sizes.iter().fold(0.0, |acc, x| acc + x.height);
1768
1769 (Size::new(width, height), child_positions, child_sizes)
1770}
1771
1772fn search_bound(
1773 default: usize,
1774 default_left: usize,
1775 default_right: usize,
1776 bound: f32,
1777 positions: &[f32],
1778 sizes: &[Size],
1779) -> usize {
1780 let mut left = default_left;
1782 let mut right = default_right;
1783
1784 let mut index = default;
1785 while left != right {
1786 let m = ((left + right) / 2) + 1;
1787 if positions[m] > bound {
1788 right = m - 1;
1789 } else {
1790 left = m;
1791 }
1792 }
1793 let height = sizes[left].height;
1795 if positions[left] + height > bound {
1796 index = left;
1797 }
1798 index
1799}