Skip to main content

cosmic/widget/menu/
menu_inner.rs

1// From iced_aw, license MIT
2
3//! Menu tree overlay
4use 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/// The condition of when to close a menu
24#[derive(Debug, Clone, Copy)]
25pub struct CloseCondition {
26    /// Close menus when the cursor moves outside the check bounds
27    pub leave: bool,
28
29    /// Close menus when the cursor clicks outside the check bounds
30    pub click_outside: bool,
31
32    /// Close menus when the cursor clicks inside the check bounds
33    pub click_inside: bool,
34}
35
36/// The width of an item
37#[derive(Debug, Clone, Copy)]
38pub enum ItemWidth {
39    /// Use uniform width
40    Uniform(u16),
41    /// Static tries to use the width value of each menu(menu tree with children),
42    /// the widths of items(menu tree with empty children) will be the same as the menu they're in,
43    /// if that value is None,
44    /// the default value will be used instead,
45    /// which is the value of the Static variant
46    Static(u16),
47}
48
49/// The height of an item
50#[derive(Debug, Clone, Copy)]
51pub enum ItemHeight {
52    /// Use uniform height.
53    Uniform(u16),
54    /// Static tries to use `MenuTree.height` as item height,
55    /// when it's `None` it'll fallback to the value of the `Static` variant.
56    Static(u16),
57    /// Dynamic tries to automatically choose the proper item height for you,
58    /// but it only works in certain cases:
59    ///
60    /// - Fixed height
61    /// - Shrink height
62    /// - Menu tree height
63    ///
64    /// If none of these is the case, it'll fallback to the value of the `Dynamic` variant.
65    Dynamic(u16),
66}
67
68/// Methods for drawing path highlight
69#[derive(Debug, Clone, Copy)]
70pub enum PathHighlight {
71    /// Draw the full path,
72    Full,
73    /// Omit the active item(the last item in the path)
74    OmitActive,
75    /// Omit the active item if it's not a menu
76    MenuActive,
77}
78
79/// X+ goes right and Y+ goes down
80#[derive(Debug, Clone, Copy)]
81pub(crate) enum Direction {
82    Positive,
83    Negative,
84}
85
86/// Adaptive open direction
87#[derive(Debug)]
88#[allow(clippy::struct_excessive_bools)]
89struct Aod {
90    // whether or not to use aod
91    horizontal: bool,
92    vertical: bool,
93
94    // whether or not to use overlap
95    horizontal_overlap: bool,
96    vertical_overlap: bool,
97
98    // default direction
99    horizontal_direction: Direction,
100    vertical_direction: Direction,
101
102    // Offset of the child in the default direction
103    horizontal_offset: f32,
104    vertical_offset: f32,
105}
106impl Aod {
107    /// Returns child position and offset position
108    #[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        /*
120        Imagine there're two sticks, parent and child
121        parent: o-----o
122        child:  o----------o
123
124        Now we align the child to the parent in one dimension
125        There are 4 possibilities:
126
127        1. to the right
128                    o-----oo----------o
129
130        2. to the right but allow overlaping
131                    o-----o
132                    o----------o
133
134        3. to the left
135        o----------oo-----o
136
137        4. to the left but allow overlaping
138                    o-----o
139               o----------o
140
141        The child goes to the default direction by default,
142        if the space on the default direction runs out it goes to the the other,
143        whether to use overlap is the caller's decision
144
145        This can be applied to any direction
146        */
147
148        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    /// Returns child position and offset position
193    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/// A part of a menu where items are displayed.
225///
226/// When the bounds of a menu exceed the viewport,
227/// only items inside the viewport will be displayed,
228/// when scrolling happens, this should be updated
229#[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)]
238/// Menu bounds in overlay space
239pub 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        // viewport space parent bounds
266        let view_parent_bounds = parent_bounds + overlay_offset;
267
268        // overlay space children position
269        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        // calc offset bounds
279        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    /// The index of the active menu item
304    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        // viewport space children bounds
327        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        // viewport space children bounds
368        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    /// returns a slice of the menu items that are inside the viewport
381    pub(super) fn slice(
382        &self,
383        viewport_size: Size,
384        overlay_offset: Vector,
385        item_height: ItemHeight,
386    ) -> MenuSlice {
387        // viewport space children bounds
388        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        // viewport space absolute bounds
393        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        // menu space relative bounds
397        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        // index range
401        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    // Flattened menu tree
439    pub(crate) menu_roots: Cow<'b, [MenuTree<Message>]>,
440    pub(crate) bounds_expand: u16,
441    /// Allows menu overlay items to overlap the parent
442    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    /// window id for this popup
455    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        // layout children;
467        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                                // if popup just use len 1?
527                                // only the last menu can have a None active index
528                                (
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            // overlay space viewport rectangle
541            Node::with_children(
542                limits.resolve(Length::Shrink, Length::Shrink, intrinsic_size),
543                children,
544            )
545            .translate(Point::ORIGIN - position)
546        })
547    }
548
549    /// Close every menu and, on Wayland, destroy the root popup this menu belongs to.
550    /// Used when clicking outside a menu.
551    #[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    /// Close the innermost open menu, see [`close_innermost`].
577    /// Used when pressing `esc`.
578    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                    // process close condition
707                    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                    // close all menus when clicking inside the menu bar
738                    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                        // react only to the last menu
806                        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                            // calc slice
816                            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                            // draw menu background
823                            // let bounds = pad_rectangle(children_bounds, styling.background_expand.into());
824                            // println!("cursor: {:?}", view_cursor);
825                            // println!("bg_bounds: {:?}", bounds);
826                            // println!("color: {:?}\n", styling.background);
827                            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                            // draw path hightlight
843                            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                                // draw item
879                                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                        // only the last menu can have a None active index
915                        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            // XXX we push a new active root manually instead
1082            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            // disable slide_x if it is set in the default
1142            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                // Hack to ensure menu opens properly
1287                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            // TODO how to select the tree for the popup
1348            &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        // Hack to ensure menu opens properly
1369        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            // then use menu states for each open menu
1403            &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        // get layout
1411        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        // process only the last widget
1421        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    /*
1449    if no active root || pressed:
1450        return
1451    else:
1452        remove invalid menus // overlay space
1453        update active item
1454        if active item is a menu:
1455            add menu // viewport space
1456    */
1457    let mut new_menu_root = None;
1458
1459    menu.tree.inner.with_data_mut(|state| {
1460
1461        /* When overlay is running, cursor_position in any widget method will go negative
1462        but I still want Widget::draw() to react to cursor movement */
1463        state.view_cursor = view_cursor;
1464
1465        // * remove invalid menus
1466        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        // * update active item
1512        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                // no menus left
1521                // TODO do we want to avoid this for popups?
1522                // state.active_root.remove(menu.depth);
1523
1524                // keep state.open when the cursor is still inside the menu bar
1525                // this allows the overlay to keep drawing when the cursor is
1526                // moving aroung the menu bar
1527                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        // cursor is in the parent part
1541        || menu.is_overlay && !last_children_bounds.contains(overlay_cursor)
1542        // cursor is outside
1543        {
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        // calc new index
1553        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        // set new index
1604        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        // get new active item
1611        // * add new menu if the new item is a menu
1612        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            // overlay space item bounds
1620            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            // viewport space children bounds
1688            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        // update
1697        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            // >= 2
1710            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                // scroll the last one
1715                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                // scroll the second last one
1729                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                // update the bounds of the last one
1736                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)]
1747/// Returns (children_size, child_positions, child_sizes)
1748fn 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
1819/// Close the innermost open menu level
1820pub(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    // Forget the parent's hovered item, so hovering it again reopens the submenu.
1849    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    // binary search
1867    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 = f32::from(menu_tree.children[left].height.unwrap_or(default_height));
1880    let height = sizes[left].height;
1881    if positions[left] + height > bound {
1882        index = left;
1883    }
1884    index
1885}