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