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