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