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