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