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