cosmic/widget/menu/
menu_inner.rs

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