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