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    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 + 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    #[allow(clippy::too_many_lines)]
550    fn update(
551        &mut self,
552        event: &event::Event,
553        layout: Layout<'_>,
554        view_cursor: Cursor,
555        renderer: &crate::Renderer,
556        clipboard: &mut dyn Clipboard,
557        shell: &mut Shell<'_, Message>,
558    ) -> Option<(usize, MenuState)> {
559        use event::Event::{Mouse, Touch};
560        use event::Status::{Captured, Ignored};
561        use mouse::Button::Left;
562        use mouse::Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled};
563        use touch::Event::{FingerLifted, FingerMoved, FingerPressed};
564
565        if !self
566            .tree
567            .inner
568            .with_data(|data| data.open || data.active_root.len() <= self.depth)
569        {
570            return None;
571        }
572
573        let viewport = layout.bounds();
574
575        let viewport_size = viewport.size();
576        let overlay_offset = Point::ORIGIN - viewport.position();
577        let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
578        let menu_roots = match &mut self.menu_roots {
579            Cow::Borrowed(_) => panic!(),
580            Cow::Owned(o) => o.as_mut_slice(),
581        };
582        process_menu_events(
583            self,
584            event,
585            view_cursor,
586            renderer,
587            clipboard,
588            shell,
589            overlay_offset,
590        );
591
592        init_root_menu(
593            self,
594            renderer,
595            shell,
596            overlay_cursor,
597            viewport_size,
598            overlay_offset,
599            self.bar_bounds,
600            self.main_offset as f32,
601        );
602
603        match event {
604            Mouse(WheelScrolled { delta }) => process_scroll_events(
605                self,
606                shell,
607                *delta,
608                overlay_cursor,
609                viewport_size,
610                overlay_offset,
611            ),
612
613            Mouse(ButtonPressed(Left)) | Touch(FingerPressed { .. }) => {
614                self.tree.inner.with_data_mut(|data| {
615                    data.pressed = true;
616                    data.view_cursor = view_cursor;
617                });
618            }
619
620            Mouse(CursorMoved { position }) | Touch(FingerMoved { position, .. }) => {
621                let view_cursor = Cursor::Available(*position);
622                let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
623                if !self.is_overlay && !view_cursor.is_over(viewport) {
624                    return None;
625                }
626                let new_root = process_overlay_events(
627                    self,
628                    renderer,
629                    viewport_size,
630                    overlay_offset,
631                    view_cursor,
632                    overlay_cursor,
633                    self.cross_offset as f32,
634                    shell,
635                );
636
637                if self.is_overlay && view_cursor.is_over(viewport) {
638                    shell.capture_event();
639                }
640
641                return new_root;
642            }
643
644            Mouse(ButtonReleased(_)) | Touch(FingerLifted { .. }) => {
645                self.tree.inner.with_data_mut(|state| {
646                    state.pressed = false;
647
648                    // process close condition
649                    if state
650                        .view_cursor
651                        .position()
652                        .unwrap_or_default()
653                        .distance(view_cursor.position().unwrap_or_default())
654                        < 2.0
655                    {
656                        let is_inside = state.menu_states[..=if self.is_overlay {
657                            state.active_root.len().saturating_sub(1)
658                        } else {
659                            self.depth
660                        }]
661                            .iter()
662                            .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor));
663                        let mut needs_reset = false;
664                        needs_reset |= self.close_condition.click_inside
665                            && is_inside
666                            && matches!(
667                                event,
668                                Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. })
669                            );
670
671                        needs_reset |= self.close_condition.click_outside && !is_inside;
672
673                        if needs_reset {
674                            #[cfg(wayland_platform)]
675                            if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
676                                && let Some(handler) = self.on_surface_action.as_ref()
677                            {
678                                let mut root = self.window_id;
679                                let mut depth = self.depth;
680                                while let Some(parent) =
681                                    state.popup_id.iter().find(|(_, v)| **v == root)
682                                {
683                                    // parent of root popup is the window, so we stop.
684                                    if depth == 0 {
685                                        break;
686                                    }
687                                    root = *parent.0;
688                                    depth = depth.saturating_sub(1);
689                                }
690                                shell
691                                    .publish((handler)(crate::surface::Action::DestroyPopup(root)));
692                            }
693
694                            state.reset();
695                        }
696                    }
697
698                    // close all menus when clicking inside the menu bar
699                    if self.bar_bounds.contains(overlay_cursor) {
700                        state.reset();
701                    }
702                });
703            }
704
705            _ => {}
706        };
707        None
708    }
709
710    #[allow(unused_results, clippy::too_many_lines)]
711    fn draw(
712        &self,
713        renderer: &mut crate::Renderer,
714        theme: &crate::Theme,
715        style: &renderer::Style,
716        layout: Layout<'_>,
717        view_cursor: Cursor,
718    ) {
719        self.tree.inner.with_data(|state| {
720            if !state.open || state.active_root.len() <= self.depth {
721                return;
722            }
723            let active_root = &state.active_root[..=if self.is_overlay { 0 } else { self.depth }];
724            let viewport = layout.bounds();
725            let viewport_size = viewport.size();
726            let overlay_offset = Point::ORIGIN - viewport.position();
727
728            let render_bounds = if self.is_overlay {
729                Rectangle::new(Point::ORIGIN, viewport.size())
730            } else {
731                Rectangle::new(Point::ORIGIN, Size::INFINITE)
732            };
733
734            let styling = theme.appearance(&self.style, self.is_overlay);
735            let roots = active_root.iter().skip(1).fold(
736                &self.menu_roots[active_root[0]].children,
737                |mt, next_active_root| &mt[*next_active_root].children,
738            );
739            let indices = state.get_trimmed_indices(self.depth).collect::<Vec<_>>();
740            state.menu_states[if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay {
741                state.menu_states.len() - 1
742            } else {
743                self.depth
744            }]
745                .iter()
746                .zip(layout.children())
747                .enumerate()
748                .filter(|ms: &(usize, (&MenuState, Layout<'_>))| self.is_overlay || ms.0 < 1)
749                .fold(
750                    roots,
751                    |menu_roots: &Vec<MenuTree<Message>>, (i, (ms, children_layout))| {
752                        let draw_path = self.path_highlight.as_ref().is_some_and(|ph| match ph {
753                            PathHighlight::Full => true,
754                            PathHighlight::OmitActive => {
755                                !indices.is_empty() && i < indices.len() - 1
756                            }
757                            PathHighlight::MenuActive => {
758                                !indices.is_empty()
759                                    && i < indices.len()
760                                    && menu_roots.len() > indices[i]
761                                    && (i < indices.len() - 1
762                                        || !menu_roots[indices[i]].children.is_empty())
763                            }
764                        });
765
766                        // react only to the last menu
767                        let view_cursor = if self.depth == state.active_root.len() - 1
768                            || i == state.menu_states.len() - 1
769                        {
770                            view_cursor
771                        } else {
772                            Cursor::Available([-1.0; 2].into())
773                        };
774
775                        let draw_menu = |r: &mut crate::Renderer| {
776                            // calc slice
777                            let slice = ms.slice(viewport_size, overlay_offset, self.item_height);
778                            let start_index = slice.start_index;
779                            let end_index = slice.end_index;
780
781                            let children_bounds = children_layout.bounds();
782
783                            // draw menu background
784                            // let bounds = pad_rectangle(children_bounds, styling.background_expand.into());
785                            // println!("cursor: {:?}", view_cursor);
786                            // println!("bg_bounds: {:?}", bounds);
787                            // println!("color: {:?}\n", styling.background);
788                            let menu_quad = renderer::Quad {
789                                bounds: pad_rectangle(
790                                    children_bounds.intersection(&viewport).unwrap_or_default(),
791                                    styling.background_expand.into(),
792                                ),
793                                border: Border {
794                                    radius: styling.menu_border_radius.into(),
795                                    width: styling.border_width,
796                                    color: styling.border_color,
797                                },
798                                shadow: Shadow::default(),
799                                snap: true,
800                            };
801                            let menu_color = styling.background;
802                            r.fill_quad(menu_quad, menu_color);
803                            // draw path hightlight
804                            if let (true, Some(active)) = (draw_path, ms.index)
805                                && let Some(active_layout) = children_layout
806                                    .children()
807                                    .nth(active.saturating_sub(start_index))
808                            {
809                                let i = active.saturating_sub(start_index);
810                                let mut rad = styling.menu_border_radius;
811                                let rad_0 = theme.cosmic().radius_0();
812                                if start_index != end_index {
813                                    if 0 == i {
814                                        rad[0] = rad_0[0];
815                                        rad[1] = rad_0[1];
816                                    } else if i == end_index - start_index {
817                                        rad[2] = rad_0[2];
818                                        rad[3] = rad_0[3];
819                                    } else {
820                                        rad = rad_0;
821                                    }
822                                }
823                                let path_quad = renderer::Quad {
824                                    bounds: active_layout
825                                        .bounds()
826                                        .intersection(&viewport)
827                                        .unwrap_or_default(),
828                                    border: Border {
829                                        radius: rad.into(),
830                                        ..Default::default()
831                                    },
832                                    shadow: Shadow::default(),
833                                    snap: true,
834                                };
835
836                                r.fill_quad(path_quad, styling.path);
837                            }
838                            if start_index < menu_roots.len() {
839                                // draw item
840                                menu_roots[start_index..=end_index]
841                                    .iter()
842                                    .enumerate()
843                                    .zip(children_layout.children())
844                                    .for_each(|((i, mt), clo)| {
845                                        let t = theme.with_list_item_position(
846                                            if start_index == end_index {
847                                                Some((Alignment::Center, i))
848                                            } else if 0 == i {
849                                                Some((Alignment::Start, i))
850                                            } else if i == end_index - start_index {
851                                                Some((Alignment::End, i))
852                                            } else {
853                                                None
854                                            },
855                                        );
856
857                                        mt.item.draw(
858                                            &state.tree.children[active_root[0]].children[mt.index],
859                                            r,
860                                            &t,
861                                            style,
862                                            clo,
863                                            view_cursor,
864                                            &children_layout
865                                                .bounds()
866                                                .intersection(&viewport)
867                                                .unwrap_or_default(),
868                                        );
869                                    });
870                            }
871                        };
872
873                        renderer.with_layer(render_bounds, draw_menu);
874
875                        // only the last menu can have a None active index
876                        ms.index
877                            .map_or(menu_roots, |active| &menu_roots[active].children)
878                    },
879                );
880        });
881    }
882}
883impl<Message: Clone + 'static> overlay::Overlay<Message, crate::Theme, crate::Renderer>
884    for Menu<'_, Message>
885{
886    fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> iced_core::layout::Node {
887        Menu::layout(
888            self,
889            renderer,
890            Limits::NONE
891                .min_width(bounds.width)
892                .max_width(bounds.width)
893                .min_height(bounds.height)
894                .max_height(bounds.height),
895        )
896    }
897
898    fn update(
899        &mut self,
900        event: &iced::Event,
901        layout: Layout<'_>,
902        cursor: mouse::Cursor,
903        renderer: &crate::Renderer,
904        clipboard: &mut dyn Clipboard,
905        shell: &mut Shell<'_, Message>,
906    ) {
907        self.update(event, layout, cursor, renderer, clipboard, shell);
908    }
909
910    fn draw(
911        &self,
912        renderer: &mut crate::Renderer,
913        theme: &crate::Theme,
914        style: &renderer::Style,
915        layout: Layout<'_>,
916        cursor: mouse::Cursor,
917    ) {
918        self.draw(renderer, theme, style, layout, cursor);
919    }
920
921    fn mouse_interaction(
922        &self,
923        layout: Layout<'_>,
924        cursor: mouse::Cursor,
925        _renderer: &crate::Renderer,
926    ) -> mouse::Interaction {
927        if cursor.is_over(layout.bounds()) {
928            mouse::Interaction::Idle
929        } else {
930            mouse::Interaction::None
931        }
932    }
933}
934
935impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
936    for Menu<'_, Message>
937{
938    fn size(&self) -> Size<Length> {
939        Size {
940            width: Length::Shrink,
941            height: Length::Shrink,
942        }
943    }
944
945    fn layout(
946        &mut self,
947        _tree: &mut Tree,
948        renderer: &crate::Renderer,
949        limits: &iced_core::layout::Limits,
950    ) -> iced_core::layout::Node {
951        Menu::layout(self, renderer, *limits)
952    }
953
954    fn draw(
955        &self,
956        _tree: &Tree,
957        renderer: &mut crate::Renderer,
958        theme: &crate::Theme,
959        style: &renderer::Style,
960        layout: Layout<'_>,
961        cursor: mouse::Cursor,
962        _viewport: &Rectangle,
963    ) {
964        Menu::draw(self, renderer, theme, style, layout, cursor);
965    }
966
967    #[allow(clippy::too_many_lines)]
968    fn update(
969        &mut self,
970        tree: &mut Tree,
971        event: &iced::Event,
972        layout: Layout<'_>,
973        cursor: mouse::Cursor,
974        renderer: &crate::Renderer,
975        clipboard: &mut dyn Clipboard,
976        shell: &mut Shell<'_, Message>,
977        viewport: &Rectangle,
978    ) {
979        let new_root = self.update(event, layout, cursor, renderer, clipboard, shell);
980
981        #[cfg(wayland_platform)]
982        if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
983            && let Some((new_root, new_ms)) = new_root
984        {
985            use iced_runtime::platform_specific::wayland::CornerRadius;
986            use iced_runtime::platform_specific::wayland::popup::{
987                SctkPopupSettings, SctkPositioner,
988            };
989
990            use crate::surface::action::LiveSettings;
991            use crate::theme::THEME;
992            let overlay_offset = Point::ORIGIN - viewport.position();
993
994            let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
995
996            let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
997                let popup_id = *state
998                    .popup_id
999                    .entry(self.window_id)
1000                    .or_insert_with(window::Id::unique);
1001                let active_roots = state
1002                    .active_root
1003                    .get(self.depth)
1004                    .cloned()
1005                    .unwrap_or_default();
1006
1007                let root_bounds_list = layout
1008                    .children()
1009                    .next()
1010                    .unwrap()
1011                    .children()
1012                    .map(|lo| lo.bounds())
1013                    .collect();
1014
1015                let mut popup_menu = Menu {
1016                    tree: self.tree.clone(),
1017                    menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
1018                    bounds_expand: self.bounds_expand,
1019                    menu_overlays_parent: false,
1020                    close_condition: self.close_condition,
1021                    item_width: self.item_width,
1022                    item_height: self.item_height,
1023                    bar_bounds: layout.bounds(),
1024                    main_offset: self.main_offset,
1025                    cross_offset: self.cross_offset,
1026                    root_bounds_list,
1027                    path_highlight: self.path_highlight,
1028                    style: Cow::Owned(Cow::into_owned(self.style.clone())),
1029                    position: Point::new(0., 0.),
1030                    is_overlay: false,
1031                    window_id: popup_id,
1032                    depth: self.depth + 1,
1033                    on_surface_action: self.on_surface_action.clone(),
1034                };
1035
1036                state.active_root.push(new_root);
1037
1038                Some((popup_menu, popup_id))
1039            }) else {
1040                return;
1041            };
1042            // XXX we push a new active root manually instead
1043            init_root_popup_menu(
1044                &mut menu,
1045                renderer,
1046                shell,
1047                cursor.position().unwrap_or_default(),
1048                layout.bounds().size(),
1049                Vector::new(0., 0.),
1050                layout.bounds(),
1051                self.main_offset as f32,
1052            );
1053            let (anchor_rect, gravity) = self.tree.inner.with_data_mut(|state| {
1054                (state
1055                    .menu_states
1056                    .get(self.depth + 1)
1057                    .map(|s| s.menu_bounds.parent_bounds)
1058                    .map_or_else(
1059                        || {
1060                            let bounds = layout.bounds();
1061                            Rectangle {
1062                                x: bounds.x as i32,
1063                                y: bounds.y as i32,
1064                                width: bounds.width as i32,
1065                                height: bounds.height as i32,
1066                            }
1067                        },
1068                        |r| Rectangle {
1069                            x: r.x as i32,
1070                            y: r.y as i32,
1071                            width: r.width as i32,
1072                            height: r.height as i32,
1073                        },
1074                    ), match (state.horizontal_direction, state.vertical_direction) {
1075                        (Direction::Positive, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
1076                        (Direction::Positive, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight,
1077                        (Direction::Negative, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft,
1078                        (Direction::Negative, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft,
1079                    })
1080            });
1081
1082            let menu_node = Widget::layout(
1083                &mut menu,
1084                &mut Tree::empty(),
1085                renderer,
1086                &Limits::NONE.min_width(1.).min_height(1.),
1087            );
1088
1089            let popup_size = menu_node.size();
1090            let mut positioner = SctkPositioner {
1091                size: Some((
1092                    popup_size.width.ceil() as u32 + 2,
1093                    popup_size.height.ceil() as u32 + 2,
1094                )),
1095                anchor_rect,
1096                anchor:
1097                    cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight,
1098                gravity,
1099                reactive: true,
1100                ..Default::default()
1101            };
1102            // disable slide_x if it is set in the default
1103            positioner.constraint_adjustment &= !(1 << 0);
1104            let parent = self.window_id;
1105
1106            let t = THEME.lock().unwrap();
1107            let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
1108            drop(t);
1109            let rad = styling.menu_border_radius;
1110
1111            shell.publish((self.on_surface_action.as_ref().unwrap())(
1112                crate::surface::action::simple_popup(
1113                    move || LiveSettings {
1114                        corners: Some(CornerRadius {
1115                            top_left: rad[0] as u32,
1116                            top_right: rad[1] as u32,
1117                            bottom_left: rad[2] as u32,
1118                            bottom_right: rad[3] as u32,
1119                        }),
1120                        ..Default::default()
1121                    },
1122                    move || SctkPopupSettings {
1123                        parent,
1124                        id: popup_id,
1125                        positioner: positioner.clone(),
1126                        parent_size: None,
1127                        grab: true,
1128                        close_with_children: false,
1129                        input_zone: None,
1130                    },
1131                    Some(move || {
1132                        crate::Element::from(
1133                            crate::widget::container(menu.clone()).center(Length::Fill),
1134                        )
1135                        .map(crate::action::app)
1136                    }),
1137                ),
1138            ));
1139        }
1140    }
1141
1142    fn mouse_interaction(
1143        &self,
1144        _tree: &Tree,
1145        layout: Layout<'_>,
1146        cursor: mouse::Cursor,
1147        _viewport: &Rectangle,
1148        _renderer: &crate::Renderer,
1149    ) -> mouse::Interaction {
1150        if cursor.is_over(layout.bounds()) {
1151            mouse::Interaction::Idle
1152        } else {
1153            mouse::Interaction::None
1154        }
1155    }
1156}
1157
1158impl<'a, Message> From<Menu<'a, Message>>
1159    for iced::Element<'a, Message, crate::Theme, crate::Renderer>
1160where
1161    Message: std::clone::Clone + 'static,
1162{
1163    fn from(value: Menu<'a, Message>) -> Self {
1164        Self::new(value)
1165    }
1166}
1167
1168fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle {
1169    Rectangle {
1170        x: rect.x - padding.left,
1171        y: rect.y - padding.top,
1172        width: rect.width + padding.x(),
1173        height: rect.height + padding.y(),
1174    }
1175}
1176
1177#[allow(clippy::too_many_arguments)]
1178pub(crate) fn init_root_menu<Message: Clone>(
1179    menu: &mut Menu<'_, Message>,
1180    renderer: &crate::Renderer,
1181    shell: &mut Shell<'_, Message>,
1182    overlay_cursor: Point,
1183    viewport_size: Size,
1184    overlay_offset: Vector,
1185    bar_bounds: Rectangle,
1186    main_offset: f32,
1187) {
1188    menu.tree.inner.with_data_mut(|state| {
1189        if !(state.menu_states.get(menu.depth).is_none()
1190            && (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
1191            || menu.depth > 0
1192            || !state.open
1193        {
1194            return;
1195        }
1196
1197        for (i, (&root_bounds, mt)) in menu
1198            .root_bounds_list
1199            .iter()
1200            .zip(menu.menu_roots.iter())
1201            .enumerate()
1202        {
1203            if mt.children.is_empty() {
1204                continue;
1205            }
1206
1207            if root_bounds.contains(overlay_cursor) {
1208                let view_center = viewport_size.width * 0.5;
1209                let rb_center = root_bounds.center_x();
1210
1211                state.horizontal_direction = if menu.is_overlay && rb_center > view_center {
1212                    Direction::Negative
1213                } else {
1214                    Direction::Positive
1215                };
1216
1217                let aod = Aod {
1218                    horizontal: true,
1219                    vertical: true,
1220                    horizontal_overlap: true,
1221                    vertical_overlap: false,
1222                    horizontal_direction: state.horizontal_direction,
1223                    vertical_direction: state.vertical_direction,
1224                    horizontal_offset: 0.0,
1225                    vertical_offset: main_offset,
1226                };
1227                let menu_bounds = MenuBounds::new(
1228                    mt,
1229                    renderer,
1230                    menu.item_width,
1231                    menu.item_height,
1232                    viewport_size,
1233                    overlay_offset,
1234                    &aod,
1235                    menu.bounds_expand,
1236                    root_bounds,
1237                    &mut state.tree.children[0].children,
1238                    menu.is_overlay,
1239                );
1240                state.active_root.push(i);
1241                let ms = MenuState {
1242                    index: None,
1243                    scroll_offset: 0.0,
1244                    menu_bounds,
1245                };
1246                state.menu_states.push(ms);
1247                // Hack to ensure menu opens properly
1248                shell.invalidate_layout();
1249
1250                break;
1251            }
1252        }
1253    });
1254}
1255
1256#[cfg(wayland_platform)]
1257pub(super) fn init_root_popup_menu<Message>(
1258    menu: &mut Menu<'_, Message>,
1259    renderer: &crate::Renderer,
1260    shell: &mut Shell<'_, Message>,
1261    overlay_cursor: Point,
1262    viewport_size: Size,
1263    overlay_offset: Vector,
1264    bar_bounds: Rectangle,
1265    main_offset: f32,
1266) where
1267    Message: std::clone::Clone,
1268{
1269    menu.tree.inner.with_data_mut(|state| {
1270        if !(state.menu_states.get(menu.depth).is_none()
1271            && (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
1272        {
1273            return;
1274        }
1275
1276        let active_roots = &state.active_root[..=menu.depth];
1277
1278        let mt = active_roots
1279            .iter()
1280            .skip(1)
1281            .fold(&menu.menu_roots[active_roots[0]], |mt, next_active_root| {
1282                &mt.children[*next_active_root]
1283            });
1284        let i = active_roots.last().unwrap();
1285        let root_bounds = menu.root_bounds_list[*i];
1286
1287        assert!(!mt.children.is_empty(), "skipping menu with no children");
1288        let aod = Aod {
1289            horizontal: true,
1290            vertical: true,
1291            horizontal_overlap: true,
1292            vertical_overlap: false,
1293            horizontal_direction: state.horizontal_direction,
1294            vertical_direction: state.vertical_direction,
1295            horizontal_offset: 0.0,
1296            vertical_offset: main_offset,
1297        };
1298        let menu_bounds = MenuBounds::new(
1299            mt,
1300            renderer,
1301            menu.item_width,
1302            menu.item_height,
1303            viewport_size,
1304            overlay_offset,
1305            &aod,
1306            menu.bounds_expand,
1307            root_bounds,
1308            // TODO how to select the tree for the popup
1309            &mut state.tree.children[0].children,
1310            menu.is_overlay,
1311        );
1312
1313        let view_center = viewport_size.width * 0.5;
1314        let rb_center = root_bounds.center_x();
1315
1316        state.horizontal_direction = if rb_center > view_center {
1317            Direction::Negative
1318        } else {
1319            Direction::Positive
1320        };
1321
1322        let ms = MenuState {
1323            index: None,
1324            scroll_offset: 0.0,
1325            menu_bounds,
1326        };
1327        state.menu_states.push(ms);
1328
1329        // Hack to ensure menu opens properly
1330        shell.invalidate_layout();
1331    });
1332}
1333
1334#[allow(clippy::too_many_arguments)]
1335fn process_menu_events<Message: std::clone::Clone>(
1336    menu: &mut Menu<Message>,
1337    event: &event::Event,
1338    view_cursor: Cursor,
1339    renderer: &crate::Renderer,
1340    clipboard: &mut dyn Clipboard,
1341    shell: &mut Shell<'_, Message>,
1342    overlay_offset: Vector,
1343) {
1344    let my_state = &mut menu.tree;
1345    let menu_roots = match &mut menu.menu_roots {
1346        Cow::Borrowed(_) => panic!(),
1347        Cow::Owned(o) => o.as_mut_slice(),
1348    };
1349    my_state.inner.with_data_mut(|state| {
1350        if state.active_root.len() <= menu.depth {
1351            return;
1352        }
1353
1354        let Some(hover) = state.menu_states.last_mut() else {
1355            return;
1356        };
1357
1358        let Some(hover_index) = hover.index else {
1359            return;
1360        };
1361
1362        let mt = state.active_root.iter().skip(1).fold(
1363            // then use menu states for each open menu
1364            &mut menu_roots[state.active_root[0]],
1365            |mt, next_active_root| &mut mt.children[*next_active_root],
1366        );
1367
1368        let mt = &mut mt.children[hover_index];
1369        let tree = &mut state.tree.children[state.active_root[0]].children[mt.index];
1370
1371        // get layout
1372        let child_node = hover.layout_single(
1373            overlay_offset,
1374            hover.index.expect("missing index within menu state."),
1375            renderer,
1376            mt,
1377            tree,
1378        );
1379        let child_layout = Layout::new(&child_node);
1380
1381        // process only the last widget
1382        mt.item.update(
1383            tree,
1384            event,
1385            child_layout,
1386            view_cursor,
1387            renderer,
1388            clipboard,
1389            shell,
1390            &Rectangle::default(),
1391        );
1392    });
1393}
1394
1395#[allow(unused_results, clippy::too_many_lines, clippy::too_many_arguments)]
1396fn process_overlay_events<Message>(
1397    menu: &mut Menu<Message>,
1398    renderer: &crate::Renderer,
1399    viewport_size: Size,
1400    overlay_offset: Vector,
1401    view_cursor: Cursor,
1402    overlay_cursor: Point,
1403    cross_offset: f32,
1404    shell: &mut Shell<'_, Message>,
1405) -> Option<(usize, MenuState)>
1406where
1407    Message: std::clone::Clone,
1408{
1409    /*
1410    if no active root || pressed:
1411        return
1412    else:
1413        remove invalid menus // overlay space
1414        update active item
1415        if active item is a menu:
1416            add menu // viewport space
1417    */
1418    let mut new_menu_root = None;
1419
1420    menu.tree.inner.with_data_mut(|state| {
1421
1422        /* When overlay is running, cursor_position in any widget method will go negative
1423        but I still want Widget::draw() to react to cursor movement */
1424        state.view_cursor = view_cursor;
1425
1426        // * remove invalid menus
1427
1428        let mut prev_bounds = std::iter::once(menu.bar_bounds)
1429            .chain(
1430                if menu.is_overlay {
1431                    state.menu_states[..state.menu_states.len().saturating_sub(1)].iter()
1432                } else {
1433                    state.menu_states[..menu.depth].iter()
1434                }
1435                .map(|s| s.menu_bounds.children_bounds),
1436            )
1437            .collect::<Vec<_>>();
1438
1439        if menu.is_overlay && menu.close_condition.leave {
1440            for i in (0..state.menu_states.len()).rev() {
1441                let mb = &state.menu_states[i].menu_bounds;
1442
1443                if mb.parent_bounds.contains(overlay_cursor)
1444                    || menu.is_overlay && mb.children_bounds.contains(overlay_cursor)
1445                    || mb.offset_bounds.contains(overlay_cursor)
1446                    || (mb.check_bounds.contains(overlay_cursor)
1447                        && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor)))
1448                {
1449                    break;
1450                }
1451                prev_bounds.pop();
1452                state.active_root.pop();
1453                state.menu_states.pop();
1454            }
1455        } else if menu.is_overlay {
1456            for i in (0..state.menu_states.len()).rev() {
1457                let mb = &state.menu_states[i].menu_bounds;
1458
1459                if mb.parent_bounds.contains(overlay_cursor)
1460                    || mb.children_bounds.contains(overlay_cursor)
1461                    || prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))
1462                {
1463                    break;
1464                }
1465                prev_bounds.pop();
1466                state.active_root.pop();
1467                state.menu_states.pop();
1468            }
1469        }
1470
1471        // * update active item
1472        let menu_states_len = state.menu_states.len();
1473
1474        let Some(last_menu_state) = state.menu_states.get_mut(if menu.is_overlay {
1475            menu_states_len.saturating_sub(1)
1476        } else {
1477            menu.depth
1478        }) else {
1479            if menu.is_overlay {
1480                // no menus left
1481                // TODO do we want to avoid this for popups?
1482                // state.active_root.remove(menu.depth);
1483
1484                // keep state.open when the cursor is still inside the menu bar
1485                // this allows the overlay to keep drawing when the cursor is
1486                // moving aroung the menu bar
1487                if !menu.bar_bounds.contains(overlay_cursor) {
1488                    state.open = false;
1489                }
1490            }
1491            shell.capture_event();
1492            return new_menu_root;
1493        };
1494
1495        let last_menu_bounds = &last_menu_state.menu_bounds;
1496        let last_parent_bounds = last_menu_bounds.parent_bounds;
1497        let last_children_bounds = last_menu_bounds.children_bounds;
1498
1499        if (menu.is_overlay && !menu.menu_overlays_parent && last_parent_bounds.contains(overlay_cursor))
1500        // cursor is in the parent part
1501        || menu.is_overlay && !last_children_bounds.contains(overlay_cursor)
1502        // cursor is outside
1503        {
1504
1505            last_menu_state.index = None;
1506            shell.capture_event();
1507            return new_menu_root;
1508        }
1509
1510        // calc new index
1511        let height_diff = (overlay_cursor.y
1512            - (last_children_bounds.y + last_menu_state.scroll_offset))
1513            .clamp(0.0, last_children_bounds.height - 0.001);
1514
1515        let active_root = if menu.is_overlay {
1516            &state.active_root
1517        } else {
1518            &state.active_root[..=menu.depth]
1519        };
1520
1521        if state.pressed {
1522            return new_menu_root;
1523        }
1524        let roots = active_root.iter().skip(1).fold(
1525            &menu.menu_roots[active_root[0]].children,
1526            |mt, next_active_root| &mt[*next_active_root].children,
1527        );
1528        let tree = &mut state.tree.children[active_root[0]].children;
1529
1530        let active_menu: &Vec<MenuTree<Message>> = roots;
1531        let new_index = match menu.item_height {
1532            ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize,
1533            ItemHeight::Static(_) | ItemHeight::Dynamic(_) => {
1534                let max_index = active_menu.len() - 1;
1535                search_bound(
1536                    0,
1537                    0,
1538                    max_index,
1539                    height_diff,
1540                    &last_menu_bounds.child_positions,
1541                    &last_menu_bounds.child_sizes,
1542                )
1543            }
1544        };
1545
1546        let remove = last_menu_state
1547            .index
1548            .as_ref()
1549            .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty());
1550
1551        #[cfg(wayland_platform)]
1552        if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove {
1553            if let Some(id) = state.popup_id.remove(&menu.window_id) {
1554                state.active_root.truncate(menu.depth + 1);
1555                shell.publish((menu.on_surface_action.as_ref().unwrap())({
1556                    crate::surface::action::destroy_popup(id)
1557                }));
1558            }
1559        }
1560        let item = &active_menu[new_index];
1561        // set new index
1562        let old_index = last_menu_state.index.replace(new_index);
1563
1564        // get new active item
1565        // * add new menu if the new item is a menu
1566        if !item.children.is_empty() && old_index.is_none_or(|i| i != new_index) {
1567            let item_position = Point::new(
1568                0.0,
1569                last_menu_bounds.child_positions[new_index] + last_menu_state.scroll_offset,
1570            );
1571            let item_size = last_menu_bounds.child_sizes[new_index];
1572
1573            // overlay space item bounds
1574            let item_bounds = Rectangle::new(item_position, item_size)
1575                + (last_menu_bounds.children_bounds.position() - Point::ORIGIN);
1576
1577            let aod = Aod {
1578                horizontal: true,
1579                vertical: true,
1580                horizontal_overlap: false,
1581                vertical_overlap: true,
1582                horizontal_direction: state.horizontal_direction,
1583                vertical_direction: state.vertical_direction,
1584                horizontal_offset: cross_offset,
1585                vertical_offset: 0.0,
1586            };
1587            let ms = MenuState {
1588                index: None,
1589                scroll_offset: 0.0,
1590                menu_bounds: MenuBounds::new(
1591                    item,
1592                    renderer,
1593                    menu.item_width,
1594                    menu.item_height,
1595                    viewport_size,
1596                    overlay_offset,
1597                    &aod,
1598                    menu.bounds_expand,
1599                    item_bounds,
1600                    tree,
1601                    menu.is_overlay,
1602                ),
1603            };
1604
1605            new_menu_root = Some((new_index, ms.clone()));
1606            if menu.is_overlay {
1607                state.active_root.push(new_index);
1608            } else {
1609                state.menu_states.truncate(menu.depth + 1);
1610            }
1611            state.menu_states.push(ms);
1612        } else if !menu.is_overlay && remove {
1613            state.menu_states.truncate(menu.depth + 1);
1614        }
1615
1616        shell.capture_event();
1617        new_menu_root
1618    })
1619}
1620
1621fn process_scroll_events<Message>(
1622    menu: &mut Menu<'_, Message>,
1623    shell: &mut Shell<'_, Message>,
1624    delta: mouse::ScrollDelta,
1625    overlay_cursor: Point,
1626    viewport_size: Size,
1627    overlay_offset: Vector,
1628) where
1629    Message: Clone,
1630{
1631    use event::Status::{Captured, Ignored};
1632    use mouse::ScrollDelta;
1633
1634    menu.tree.inner.with_data_mut(|state| {
1635        let delta_y = match delta {
1636            ScrollDelta::Lines { y, .. } => y * 60.0,
1637            ScrollDelta::Pixels { y, .. } => y,
1638        };
1639
1640        let calc_offset_bounds = |menu_state: &MenuState, viewport_size: Size| -> (f32, f32) {
1641            // viewport space children bounds
1642            let children_bounds = menu_state.menu_bounds.children_bounds + overlay_offset;
1643
1644            let max_offset = (0.0 - children_bounds.y).max(0.0);
1645            let min_offset =
1646                (viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0);
1647            (max_offset, min_offset)
1648        };
1649
1650        // update
1651        if state.menu_states.is_empty() {
1652            return;
1653        } else if state.menu_states.len() == 1 {
1654            let last_ms = &mut state.menu_states[0];
1655
1656            if last_ms.index.is_none() {
1657                return;
1658            }
1659
1660            let (max_offset, min_offset) = calc_offset_bounds(last_ms, viewport_size);
1661            last_ms.scroll_offset = (last_ms.scroll_offset + delta_y).clamp(min_offset, max_offset);
1662        } else {
1663            // >= 2
1664            let max_index = state.menu_states.len() - 1;
1665            let last_two = &mut state.menu_states[max_index - 1..=max_index];
1666
1667            if last_two[1].index.is_some() {
1668                // scroll the last one
1669                let (max_offset, min_offset) = calc_offset_bounds(&last_two[1], viewport_size);
1670                last_two[1].scroll_offset =
1671                    (last_two[1].scroll_offset + delta_y).clamp(min_offset, max_offset);
1672            } else {
1673                if !last_two[0]
1674                    .menu_bounds
1675                    .children_bounds
1676                    .contains(overlay_cursor)
1677                {
1678                    shell.capture_event();
1679                    return;
1680                }
1681
1682                // scroll the second last one
1683                let (max_offset, min_offset) = calc_offset_bounds(&last_two[0], viewport_size);
1684                let scroll_offset =
1685                    (last_two[0].scroll_offset + delta_y).clamp(min_offset, max_offset);
1686                let clamped_delta_y = scroll_offset - last_two[0].scroll_offset;
1687                last_two[0].scroll_offset = scroll_offset;
1688
1689                // update the bounds of the last one
1690                last_two[1].menu_bounds.parent_bounds.y += clamped_delta_y;
1691                last_two[1].menu_bounds.children_bounds.y += clamped_delta_y;
1692                last_two[1].menu_bounds.check_bounds.y += clamped_delta_y;
1693            }
1694        }
1695        shell.capture_event();
1696    });
1697}
1698
1699#[allow(clippy::pedantic)]
1700/// Returns (children_size, child_positions, child_sizes)
1701fn get_children_layout<Message>(
1702    menu_tree: &MenuTree<Message>,
1703    renderer: &crate::Renderer,
1704    item_width: ItemWidth,
1705    item_height: ItemHeight,
1706    tree: &mut [Tree],
1707) -> (Size, Vec<f32>, Vec<Size>) {
1708    let width = match item_width {
1709        ItemWidth::Uniform(u) => f32::from(u),
1710        ItemWidth::Static(s) => f32::from(menu_tree.width.unwrap_or(s)),
1711    };
1712
1713    let child_sizes: Vec<Size> = match item_height {
1714        ItemHeight::Uniform(u) => {
1715            let count = menu_tree.children.len();
1716            vec![Size::new(width, f32::from(u)); count]
1717        }
1718        ItemHeight::Static(s) => menu_tree
1719            .children
1720            .iter()
1721            .map(|mt| Size::new(width, f32::from(mt.height.unwrap_or(s))))
1722            .collect(),
1723        ItemHeight::Dynamic(d) => menu_tree
1724            .children
1725            .iter()
1726            .map(|mt| {
1727                mt.item
1728                    .element
1729                    .with_data_mut(|w| match w.as_widget_mut().size().height {
1730                        Length::Fixed(f) => Size::new(width, f),
1731                        Length::Shrink => {
1732                            let l_height = w
1733                                .as_widget_mut()
1734                                .layout(
1735                                    &mut tree[mt.index],
1736                                    renderer,
1737                                    &Limits::new(Size::ZERO, Size::new(width, f32::MAX)),
1738                                )
1739                                .size()
1740                                .height;
1741
1742                            let height = if (f32::MAX - l_height) < 0.001 {
1743                                f32::from(d)
1744                            } else {
1745                                l_height
1746                            };
1747
1748                            Size::new(width, height)
1749                        }
1750                        _ => mt.height.map_or_else(
1751                            || Size::new(width, f32::from(d)),
1752                            |h| Size::new(width, f32::from(h)),
1753                        ),
1754                    })
1755            })
1756            .collect(),
1757    };
1758
1759    let max_index = menu_tree.children.len().saturating_sub(1);
1760    let child_positions: Vec<f32> = std::iter::once(0.0)
1761        .chain(child_sizes[0..max_index].iter().scan(0.0, |acc, x| {
1762            *acc += x.height;
1763            Some(*acc)
1764        }))
1765        .collect();
1766
1767    let height = child_sizes.iter().fold(0.0, |acc, x| acc + x.height);
1768
1769    (Size::new(width, height), child_positions, child_sizes)
1770}
1771
1772fn search_bound(
1773    default: usize,
1774    default_left: usize,
1775    default_right: usize,
1776    bound: f32,
1777    positions: &[f32],
1778    sizes: &[Size],
1779) -> usize {
1780    // binary search
1781    let mut left = default_left;
1782    let mut right = default_right;
1783
1784    let mut index = default;
1785    while left != right {
1786        let m = ((left + right) / 2) + 1;
1787        if positions[m] > bound {
1788            right = m - 1;
1789        } else {
1790            left = m;
1791        }
1792    }
1793    // let height = f32::from(menu_tree.children[left].height.unwrap_or(default_height));
1794    let height = sizes[left].height;
1795    if positions[left] + height > bound {
1796        index = left;
1797    }
1798    index
1799}