iced_aw/widget/menu/
menu_tree.rs

1//! [`Item`] and [`Menu`]
2//!
3#![allow(clippy::unwrap_used)]
4#![allow(clippy::doc_markdown)]
5#![allow(clippy::wildcard_imports)]
6#![allow(clippy::enum_glob_use)]
7#![allow(clippy::too_many_arguments)]
8#![allow(clippy::unused_self)]
9#![allow(clippy::return_self_not_must_use)]
10#![allow(clippy::pedantic)]
11#![allow(clippy::similar_names)]
12
13use super::common::*;
14use super::flex;
15use iced_core::{
16    Clipboard, Element, Event, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Vector,
17    alignment,
18    layout::{Layout, Limits, Node},
19    mouse, renderer,
20    time::Instant,
21    widget::Operation,
22    widget::tree::{self, Tree},
23    window,
24};
25use std::iter::once;
26
27use super::menu_bar::*;
28use crate::style::menu_bar::*;
29
30#[cfg(feature = "debug_log")]
31use log::{debug, warn};
32
33/*
34menu tree:
35Item{
36    widget
37    Menu [
38        Item{...}
39        Item{...}
40        Item{...}
41        ...
42    ]
43}
44
45state tree:
46Tree{
47    item state
48    [
49        Tree{widget state}
50        Tree{
51            menu state
52            [
53                Tree{item state [...]}
54                Tree{item state [...]}
55                Tree{item state [...]}
56                ...
57            ]
58        }
59    ]
60}
61
62*/
63
64#[derive(Debug)]
65pub(super) struct MenuState {
66    pub(super) scroll_offset: f32,
67    pub(super) active: Index,
68    pub(super) slice: MenuSlice,
69}
70impl MenuState {
71    /// item_tree: Tree{item state, [Tree{widget state}, Tree{menu state, [...]}]}
72    pub(super) fn open_new_menu<'a, Message, Theme: Catalog, Renderer: renderer::Renderer>(
73        &mut self,
74        active_index: usize,
75        item: &Item<'a, Message, Theme, Renderer>,
76        item_tree: &mut Tree,
77    ) {
78        #[cfg(feature = "debug_log")]
79        debug!(target:"menu::MenuState::open_new_menu", "");
80        let Some(menu) = item.menu.as_ref() else {
81            #[cfg(feature = "debug_log")]
82            debug!(target:"menu::MenuState::open_new_menu", "item.menu is None");
83            return;
84        };
85
86        self.active = Some(active_index);
87
88        // build the state tree for the new menu
89        let menu_tree = menu.tree();
90
91        #[cfg(feature = "debug_log")]
92        {
93            if item_tree.children.len() > 2 {
94                warn!(
95                    target:"menu::MenuState::open_new_menu",
96                    "item_tree.children.len() > 2 | len: {:?}",
97                    item_tree.children.len()
98                );
99            }
100        }
101
102        if item_tree.children.len() == 1 {
103            item_tree.children.push(menu_tree);
104        } else {
105            item_tree.children[1] = menu_tree;
106        }
107    }
108}
109impl Default for MenuState {
110    fn default() -> Self {
111        Self {
112            scroll_offset: 0.0,
113            active: None,
114            slice: MenuSlice {
115                start_index: 0,
116                end_index: usize::MAX - 1,
117                lower_bound_rel: 0.0,
118                upper_bound_rel: f32::MAX,
119            },
120        }
121    }
122}
123
124/// Menu
125#[must_use]
126pub struct Menu<'a, Message, Theme, Renderer>
127where
128    Theme: Catalog,
129    Renderer: renderer::Renderer,
130{
131    pub(super) items: Vec<Item<'a, Message, Theme, Renderer>>,
132    pub(super) spacing: Pixels,
133    pub(super) max_width: f32,
134    pub(super) width: Length,
135    pub(super) height: Length,
136    pub(super) axis: Axis,
137    pub(super) offset: f32,
138    pub(super) padding: Padding,
139    pub(super) close_on_item_click: Option<bool>,
140    pub(super) close_on_background_click: Option<bool>,
141}
142impl<'a, Message, Theme, Renderer> Menu<'a, Message, Theme, Renderer>
143where
144    Theme: Catalog,
145    Renderer: renderer::Renderer,
146{
147    /// Creates a [`Menu`] with the given items.
148    pub fn new(items: Vec<Item<'a, Message, Theme, Renderer>>) -> Self {
149        Self {
150            items,
151            spacing: Pixels::ZERO,
152            max_width: f32::MAX,
153            width: Length::Fill,
154            height: Length::Shrink,
155            axis: Axis::Horizontal,
156            offset: 0.0,
157            padding: Padding::new(5.0),
158            close_on_item_click: None,
159            close_on_background_click: None,
160        }
161    }
162
163    /// Sets the maximum width of the [`Menu`].
164    pub fn max_width(mut self, max_width: f32) -> Self {
165        self.max_width = max_width;
166        self
167    }
168
169    /// Sets the width of the [`Menu`].
170    pub fn width(mut self, width: impl Into<Length>) -> Self {
171        self.width = width.into();
172        self
173    }
174
175    /// Sets the spacing of the [`Menu`].
176    pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
177        self.spacing = spacing.into();
178        self
179    }
180
181    /// The offset from the menu's parent item.
182    pub fn offset(mut self, offset: f32) -> Self {
183        self.offset = offset;
184        self
185    }
186
187    /// Sets the padding of the [`Menu`].
188    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
189        self.padding = padding.into();
190        self
191    }
192
193    /// Sets the close on item click option of the [`Menu`].
194    pub fn close_on_item_click(mut self, value: bool) -> Self {
195        self.close_on_item_click = Some(value);
196        self
197    }
198
199    /// Sets the close on background click option of the [`Menu`].
200    pub fn close_on_background_click(mut self, value: bool) -> Self {
201        self.close_on_background_click = Some(value);
202        self
203    }
204
205    /// Rebuild state tree
206    pub(super) fn tree(&self) -> Tree {
207        Tree {
208            tag: self.tag(),
209            state: self.state(),
210            children: self.children(),
211        }
212    }
213}
214impl<Message, Theme, Renderer> Menu<'_, Message, Theme, Renderer>
215where
216    Theme: Catalog,
217    Renderer: renderer::Renderer,
218{
219    pub(super) fn tag(&self) -> tree::Tag {
220        tree::Tag::of::<MenuState>()
221    }
222
223    pub(super) fn state(&self) -> tree::State {
224        tree::State::Some(Box::<MenuState>::default())
225    }
226
227    /// out: \[item_tree...]
228    pub(super) fn children(&self) -> Vec<Tree> {
229        self.items.iter().map(Item::tree).collect()
230    }
231
232    /// tree: Tree{menu_state, \[item_tree...]}
233    pub(super) fn diff(&self, tree: &mut Tree) {
234        tree.diff_children_custom(&self.items, |tree, item| item.diff(tree), Item::tree);
235    }
236
237    /// tree: Tree{ menu_state, \[item_tree...] }
238    ///
239    /// out: Node{inf, \[ slice_node, items_bounds, offset_bounds]}
240    pub(super) fn layout(
241        &mut self,
242        tree: &mut Tree,
243        renderer: &Renderer,
244        limits: &Limits,
245        parent_bounds: Rectangle,
246        parent_direction: (Direction, Direction),
247        viewport: &Rectangle,
248    ) -> (Node, (Direction, Direction)) {
249        #[cfg(feature = "debug_log")]
250        debug!(target:"menu::Menu::layout", "");
251        let limits = limits.max_width(self.max_width);
252
253        let items_node = flex::resolve(
254            flex::Axis::Vertical,
255            renderer,
256            &limits,
257            self.width,
258            self.height,
259            Padding::ZERO,
260            self.spacing,
261            alignment::Alignment::Center,
262            &mut self
263                .items
264                .iter_mut()
265                .map(|i| &mut i.item)
266                .collect::<Vec<_>>(),
267            &mut tree
268                .children
269                .iter_mut()
270                .map(|t| &mut t.children[0])
271                .collect::<Vec<_>>(),
272        );
273
274        let aod = Aod::new(
275            self.axis,
276            viewport.size(),
277            parent_bounds,
278            parent_direction,
279            self.offset,
280        );
281
282        let children_size = items_node.bounds().size();
283        let (children_position, offset_position, child_direction) =
284            aod.resolve(parent_bounds, children_size, viewport.size());
285
286        // calc auxiliary bounds
287        let delta = children_position - offset_position;
288        let offset_size = if delta.x.abs() > delta.y.abs() {
289            Size::new(self.offset, children_size.height)
290        } else {
291            Size::new(children_size.width, self.offset)
292        };
293
294        let offset_bounds = Rectangle::new(offset_position, offset_size);
295
296        let menu_state = tree.state.downcast_mut::<MenuState>();
297
298        // calc slice
299        let (lower_bound_rel, upper_bound_rel) = cal_bounds_rel_menu(
300            &items_node,
301            children_position - Point::ORIGIN,
302            viewport.size(),
303            menu_state.scroll_offset,
304        );
305        let slice =
306            MenuSlice::from_bounds_rel(lower_bound_rel, upper_bound_rel, &items_node, |n| {
307                n.bounds().y
308            });
309        menu_state.slice = slice;
310        #[cfg(feature = "debug_log")]
311        debug!(target:"menu::Menu::layout", "slice: {:?}", slice);
312
313        let slice_node = if slice.start_index == slice.end_index {
314            let node = &items_node.children()[slice.start_index];
315            let bounds = node.bounds();
316            let start_offset = slice.lower_bound_rel - bounds.y;
317            let height = slice.upper_bound_rel - slice.lower_bound_rel;
318
319            Node::with_children(
320                Size::new(items_node.bounds().width, height),
321                once(clip_node_y(node, height, start_offset)).collect(),
322            )
323        } else {
324            let start_node = {
325                let node = &items_node.children()[slice.start_index];
326                let bounds = node.bounds();
327                let start_offset = slice.lower_bound_rel - bounds.y;
328                let height = bounds.height - start_offset;
329                clip_node_y(node, height, start_offset)
330            };
331
332            let end_node = {
333                let node = &items_node.children()[slice.end_index];
334                let bounds = node.bounds();
335                let height = slice.upper_bound_rel - bounds.y;
336                clip_node_y(node, height, 0.0)
337            };
338
339            Node::with_children(
340                Size::new(
341                    items_node.bounds().width,
342                    slice.upper_bound_rel - slice.lower_bound_rel,
343                ),
344                once(start_node)
345                    .chain(
346                        items_node.children()[slice.start_index + 1..slice.end_index]
347                            .iter()
348                            .map(Clone::clone),
349                    )
350                    .chain(once(end_node))
351                    .collect(),
352            )
353        };
354
355        (
356            Node::with_children(
357                Size::INFINITE,
358                [
359                    slice_node
360                        .move_to(children_position)
361                        .translate([0.0, menu_state.scroll_offset]), // slice_layout
362                    Node::new(children_size).move_to(children_position), // items_bounds
363                    Node::new(offset_bounds.size()).move_to(offset_bounds.position()), // offset_bounds
364                ]
365                .into(),
366            ),
367            child_direction,
368        )
369    }
370
371    /// tree: Tree{ menu_state, \[item_tree...] }
372    ///
373    /// layout: Node{inf, \[ slice_node, items_bounds, offset_bounds]}
374    pub(super) fn update(
375        &mut self,
376        global_state: &mut GlobalState,
377        global_parameters: &GlobalParameters<'_, Theme>,
378        rec_event: RecEvent,
379        tree: &mut Tree,
380        event: &Event,
381        layout: Layout<'_>,
382        cursor: mouse::Cursor,
383        renderer: &Renderer,
384        clipboard: &mut dyn Clipboard,
385        shell: &mut Shell<'_, Message>,
386        viewport: &Rectangle,
387        parent_bounds: Rectangle,
388        prev_bounds_list: &[Rectangle],
389        prev_active: &mut Index,
390    ) -> RecEvent {
391        let mut lc = layout.children();
392        let slice_layout = lc.next().unwrap();
393        let items_bounds = lc.next().unwrap().bounds();
394        let offset_bounds = lc.next().unwrap().bounds();
395        let background_bounds = pad_rectangle(items_bounds, self.padding);
396        let safe_bounds = pad_rectangle(
397            background_bounds,
398            Padding::new(global_parameters.safe_bounds_margin),
399        );
400
401        enum Op {
402            UpdateItems,
403            OpenEvent,
404            LeftPress,
405            ScrollEvent,
406            RedrawUpdate,
407        }
408
409        let mut run_op = |global_state: &mut GlobalState, tree: &mut Tree, op: &Op| {
410            let Tree {
411                state,
412                children: item_trees,
413                ..
414            } = tree;
415            let menu_state = state.downcast_mut::<MenuState>();
416
417            match op {
418                Op::UpdateItems => {
419                    itl_iter_slice!(
420                        menu_state.slice,
421                        self.items;iter_mut,
422                        item_trees;iter_mut,
423                        slice_layout.children()
424                    )
425                    .for_each(|((item, tree), layout)| {
426                        item.update(
427                            tree, event, layout, cursor, renderer, clipboard, shell, viewport,
428                        );
429                    });
430                }
431                Op::RedrawUpdate => {
432                    let cursor = if let Some(active) = menu_state.active {
433                        match &global_parameters.draw_path {
434                            DrawPath::FakeHovering => {
435                                let active_in_slice = active - menu_state.slice.start_index;
436                                let center = slice_layout
437                                    .children()
438                                    .nth(active_in_slice)
439                                    .unwrap_or_else(|| panic!(" Index {:?} (in slice space) is not within the slice layout \
440                                        | slice_layout.children().count(): {:?} \
441                                        | This should not happen, please report this issue
442                                        ",
443                                        active_in_slice,
444                                        slice_layout.children().count()))
445                                    .bounds()
446                                    .center();
447                                mouse::Cursor::Available(center)
448                            }
449                            DrawPath::Backdrop => mouse::Cursor::Unavailable,
450                        }
451                    } else {
452                        cursor
453                    };
454
455                    let mut temp_messages = vec![];
456                    let mut temp_shell = Shell::new(&mut temp_messages);
457
458                    let redraw_event =
459                        Event::Window(window::Event::RedrawRequested(Instant::now()));
460
461                    itl_iter_slice!(
462                        menu_state.slice,
463                        self.items;iter_mut,
464                        item_trees;iter_mut,
465                        slice_layout.children()
466                    )
467                    .for_each(|((item, tree), layout)| {
468                        item.update(
469                            tree,
470                            &redraw_event,
471                            layout,
472                            cursor,
473                            renderer,
474                            clipboard,
475                            &mut temp_shell,
476                            viewport,
477                        );
478                    });
479                    shell.merge(temp_shell, |message| message);
480                }
481                Op::LeftPress => {
482                    if matches!(
483                        event,
484                        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
485                    ) {
486                        schedule_close_on_click(
487                            global_state,
488                            global_parameters,
489                            menu_state.slice,
490                            &mut self.items,
491                            slice_layout.children(),
492                            cursor,
493                            self.close_on_item_click,
494                            self.close_on_background_click,
495                        );
496                    }
497                }
498                Op::ScrollEvent => {
499                    if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
500                        if cursor.is_over(background_bounds) {
501                            let delta_y = match delta {
502                                mouse::ScrollDelta::Lines { y, .. } => {
503                                    y * global_parameters.scroll_speed.line
504                                }
505                                mouse::ScrollDelta::Pixels { y, .. } => {
506                                    y * global_parameters.scroll_speed.pixel
507                                }
508                            };
509
510                            let max_offset = (0.0 - items_bounds.y).max(0.0);
511                            let min_offset = (viewport.size().height
512                                - (items_bounds.y + items_bounds.height))
513                                .min(0.0);
514                            menu_state.scroll_offset =
515                                (menu_state.scroll_offset + delta_y).clamp(min_offset, max_offset);
516                        }
517                        shell.request_redraw();
518                    }
519                }
520                Op::OpenEvent => {
521                    if !global_state.pressed {
522                        assert!(
523                            menu_state.active.is_none(),
524                            "
525                        Menu::open_event() is called only when RecEvent::Close is returned, \
526                        which means no child menu should be open (menu_state.active must be None). \
527                        If this assert fails, please report this issue.
528                    "
529                        );
530
531                        try_open_menu(
532                            &mut self.items,
533                            menu_state,
534                            item_trees,
535                            slice_layout.children(),
536                            cursor,
537                            shell,
538                        );
539                    }
540                }
541            }
542        };
543
544        let mut update = |global_state: &mut GlobalState, tree: &mut Tree, ops: &[Op]| {
545            for op in ops.iter() {
546                run_op(global_state, tree, op);
547            }
548        };
549
550        match rec_event {
551            RecEvent::Event => {
552                // menu not in focus
553                update(global_state, tree, &[Op::RedrawUpdate]);
554                shell.capture_event();
555                RecEvent::Event
556            }
557            RecEvent::Close => {
558                if cursor.is_over(background_bounds) || cursor.is_over(offset_bounds) {
559                    // menu in focus
560                    update(
561                        global_state,
562                        tree,
563                        &[
564                            Op::UpdateItems,
565                            Op::LeftPress,
566                            Op::ScrollEvent,
567                            Op::OpenEvent,
568                        ],
569                    );
570                    shell.capture_event();
571                    RecEvent::Event
572                } else if cursor.is_over(parent_bounds) {
573                    // menu not in focus
574                    update(global_state, tree, &[Op::RedrawUpdate]);
575                    // the cursor is over the parent bounds
576                    // let the parent process the event
577                    assert!(!shell.is_event_captured(), "Returning RecEvent::None");
578                    RecEvent::None
579                } else {
580                    let open = {
581                        if global_state.pressed {
582                            true
583                        } else if prev_bounds_list.iter().any(|r| cursor.is_over(*r)) {
584                            false
585                        } else {
586                            cursor.is_over(safe_bounds)
587                        }
588                    };
589
590                    if open {
591                        // the current menu is not ready to close
592                        update(global_state, tree, &[Op::UpdateItems, Op::LeftPress]);
593                        shell.capture_event();
594                        RecEvent::Event
595                    } else {
596                        // the current menu is ready to close
597                        #[cfg(feature = "debug_log")]
598                        debug!(target:"menu::Menu::update", "close menu");
599                        assert!(!shell.is_event_captured(), "Returning RecEvent::Close");
600                        *prev_active = None;
601                        if tree.children.len() == 2 {
602                            // prune the menu tree when the menu is closed
603                            let _ = tree.children.pop();
604                        }
605                        shell.invalidate_layout();
606                        shell.request_redraw();
607                        RecEvent::Close
608                    }
609                }
610            }
611            RecEvent::None => {
612                update(
613                    global_state,
614                    tree,
615                    &[Op::UpdateItems, Op::LeftPress, Op::ScrollEvent],
616                );
617                shell.capture_event();
618                RecEvent::Event
619            }
620        }
621    }
622
623    pub(super) fn operate(
624        &mut self,
625        tree: &mut Tree,
626        layout: Layout<'_>,
627        renderer: &Renderer,
628        operation: &mut dyn Operation<()>,
629    ) {
630        let mut lc = layout.children();
631        let slice_layout = lc.next().unwrap();
632
633        let menu_state = tree.state.downcast_mut::<MenuState>();
634        let slice = menu_state.slice;
635
636        operation.container(None, layout.bounds());
637        operation.traverse(&mut |operation| {
638            itl_iter_slice!(slice, self.items;iter_mut, tree.children;iter_mut, slice_layout.children())
639                .for_each(|((child, state), layout)| {
640                    child.operate(state, layout, renderer, operation);
641                });
642        });
643    }
644
645    /// tree: Tree{ menu_state, \[item_tree...] }
646    ///
647    /// layout: Node{inf, \[ slice_node, items_bounds, offset_bounds]}
648    pub(super) fn mouse_interaction(
649        &self,
650        tree: &Tree,
651        layout: Layout<'_>,
652        cursor: mouse::Cursor,
653        renderer: &Renderer,
654    ) -> mouse::Interaction {
655        let mut lc = layout.children();
656        let slice_layout = lc.next().unwrap();
657
658        let menu_state = tree.state.downcast_ref::<MenuState>();
659        let slice = menu_state.slice;
660
661        itl_iter_slice!(slice, self.items;iter, tree.children;iter, slice_layout.children())
662            .map(|((item, tree), layout)| item.mouse_interaction(tree, layout, cursor, renderer))
663            .max()
664            .unwrap_or_default()
665    }
666
667    /// tree: Tree{menu_state, \[item_tree...]}
668    ///
669    /// layout: Node{inf, \[ items_node, slice_node, items_bounds, offset_bounds]}
670    pub(super) fn draw(
671        &self,
672        draw_path: &DrawPath,
673        tree: &Tree,
674        renderer: &mut Renderer,
675        theme: &Theme,
676        style: &renderer::Style,
677        theme_style: &Style,
678        layout: Layout<'_>,
679        cursor: mouse::Cursor,
680        viewport: &Rectangle,
681    ) {
682        let mut lc = layout.children();
683        let slice_layout = lc.next().unwrap();
684        let items_bounds = lc.next().unwrap().bounds();
685
686        let menu_state = tree.state.downcast_ref::<MenuState>();
687        let slice = menu_state.slice;
688
689        // draw background
690        let pad_rectangle = pad_rectangle(items_bounds, self.padding);
691        if pad_rectangle.intersects(viewport) {
692            renderer.fill_quad(
693                renderer::Quad {
694                    bounds: pad_rectangle,
695                    border: theme_style.menu_border,
696                    shadow: theme_style.menu_shadow,
697                    ..Default::default()
698                },
699                theme_style.menu_background,
700            );
701        }
702
703        if let (DrawPath::Backdrop, Some(active)) = (draw_path, menu_state.active) {
704            let active_in_slice = active - menu_state.slice.start_index;
705            let active_bounds = slice_layout
706                .children()
707                .nth(active_in_slice)
708                .unwrap_or_else(|| {
709                    panic!(
710                        "Index {:?} (in slice space) is not within the slice layout \
711                    | slice_layout.children().count(): {:?} \
712                    | This should not happen, please report this issue
713                    ",
714                        active_in_slice,
715                        slice_layout.children().count()
716                    )
717                })
718                .bounds();
719
720            renderer.fill_quad(
721                renderer::Quad {
722                    bounds: active_bounds,
723                    border: theme_style.path_border,
724                    ..Default::default()
725                },
726                theme_style.path,
727            );
728        }
729
730        renderer.with_layer(items_bounds, |r| {
731            itl_iter_slice!(slice, self.items;iter, tree.children;iter, slice_layout.children())
732                .for_each(|((item, tree), layout)| {
733                    item.draw(tree, r, theme, style, layout, cursor, viewport);
734                });
735        });
736    }
737}
738
739/// Item inside a [`Menu`]
740#[must_use]
741pub struct Item<'a, Message, Theme, Renderer>
742where
743    Theme: Catalog,
744    Renderer: renderer::Renderer,
745{
746    pub(super) item: Element<'a, Message, Theme, Renderer>,
747    pub(super) menu: Option<Box<Menu<'a, Message, Theme, Renderer>>>,
748    pub(super) close_on_click: Option<bool>,
749}
750impl<'a, Message, Theme, Renderer> Item<'a, Message, Theme, Renderer>
751where
752    Theme: Catalog,
753    Renderer: renderer::Renderer,
754{
755    /// Creates an [`Item`] with the given element.
756    pub fn new(item: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
757        Self {
758            item: item.into(),
759            menu: None,
760            close_on_click: None,
761        }
762    }
763
764    /// Creates an [`Item`] with the given element and menu.
765    pub fn with_menu(
766        item: impl Into<Element<'a, Message, Theme, Renderer>>,
767        menu: Menu<'a, Message, Theme, Renderer>,
768    ) -> Self {
769        Self {
770            item: item.into(),
771            menu: Some(Box::new(menu)),
772            close_on_click: None,
773        }
774    }
775
776    /// Sets the close on click option of the [`Item`].
777    pub fn close_on_click(mut self, close_on_click: bool) -> Self {
778        self.close_on_click = Some(close_on_click);
779        self
780    }
781
782    /// Rebuild state tree
783    pub(super) fn tree(&self) -> Tree {
784        Tree {
785            tag: self.tag(),
786            state: self.state(),
787            children: self.children(),
788        }
789    }
790}
791impl<Message, Theme, Renderer> Item<'_, Message, Theme, Renderer>
792where
793    Theme: Catalog,
794    Renderer: renderer::Renderer,
795{
796    // pub(super) fn size(&self) -> Size<Length> {
797    //     self.item.as_widget().size()
798    // }
799
800    pub(super) fn tag(&self) -> tree::Tag {
801        tree::Tag::stateless()
802    }
803
804    pub(super) fn state(&self) -> tree::State {
805        tree::State::None
806    }
807
808    /// out: \[widget_tree, menu_tree]
809    pub(super) fn children(&self) -> Vec<Tree> {
810        // self.menu.as_ref().map_or_else(
811        //     || [Tree::new(&self.item)].into(),
812        //     |m| [Tree::new(&self.item), m.tree()].into(),
813        // )
814        vec![Tree::new(&self.item)]
815    }
816
817    /// tree: Tree{stateless, \[widget_tree, menu_tree]}
818    #[allow(clippy::option_if_let_else)]
819    pub(super) fn diff(&self, tree: &mut Tree) {
820        if let Some(t0) = tree.children.get_mut(0) {
821            t0.diff(&self.item);
822            if let Some(m) = self.menu.as_ref() {
823                if let Some(t1) = tree.children.get_mut(1) {
824                    m.diff(t1);
825                } else {
826                    *tree = self.tree();
827                }
828            }
829        } else {
830            *tree = self.tree();
831        }
832    }
833
834    /// tree: Tree{stateless, \[widget_tree, menu_tree]}
835    ///
836    pub(super) fn update(
837        &mut self,
838        tree: &mut Tree,
839        event: &Event,
840        layout: Layout<'_>,
841        cursor: mouse::Cursor,
842        renderer: &Renderer,
843        clipboard: &mut dyn Clipboard,
844        shell: &mut Shell<'_, Message>,
845        viewport: &Rectangle,
846    ) {
847        #[cfg(feature = "debug_log")]
848        debug!(target:"Item::update", "");
849        self.item.as_widget_mut().update(
850            &mut tree.children[0],
851            event,
852            layout,
853            cursor,
854            renderer,
855            clipboard,
856            shell,
857            viewport,
858        )
859    }
860
861    /// tree: Tree{stateless, \[widget_tree, menu_tree]}
862    ///
863    pub(super) fn mouse_interaction(
864        &self,
865        tree: &Tree,
866        layout: Layout<'_>,
867        cursor: mouse::Cursor,
868        renderer: &Renderer,
869    ) -> mouse::Interaction {
870        self.item.as_widget().mouse_interaction(
871            &tree.children[0],
872            layout,
873            cursor,
874            &layout.bounds(),
875            renderer,
876        )
877    }
878
879    /// tree: Tree{stateless, \[widget_tree, menu_tree]}
880    ///
881    pub(super) fn draw(
882        &self,
883        tree: &Tree,
884        renderer: &mut Renderer,
885        theme: &Theme,
886        style: &renderer::Style,
887        layout: Layout<'_>,
888        cursor: mouse::Cursor,
889        viewport: &Rectangle,
890    ) {
891        self.item.as_widget().draw(
892            &tree.children[0],
893            renderer,
894            theme,
895            style,
896            layout,
897            cursor,
898            viewport,
899        );
900    }
901
902    pub(super) fn operate(
903        &mut self,
904        tree: &mut Tree,
905        layout: Layout<'_>,
906        renderer: &Renderer,
907        operation: &mut dyn Operation<()>,
908    ) {
909        self.item
910            .as_widget_mut()
911            .operate(&mut tree.children[0], layout, renderer, operation);
912    }
913}
914
915/// Adaptive open direction
916#[derive(Debug)]
917#[allow(clippy::struct_excessive_bools)]
918struct Aod {
919    // whether or not to use overlap
920    horizontal_overlap: bool,
921    vertical_overlap: bool,
922
923    // default direction
924    horizontal_direction: Direction,
925    vertical_direction: Direction,
926
927    // Offset of the child in the default direction
928    horizontal_offset: f32,
929    vertical_offset: f32,
930}
931impl Aod {
932    /// Returns (child position, offset position, child direction)
933    fn adaptive(
934        parent_pos: f32,
935        parent_size: f32,
936        child_size: f32,
937        max_size: f32,
938        offset: f32,
939        overlap: bool,
940        direction: Direction,
941    ) -> (f32, f32, Direction) {
942        /*
943        Imagine there are two sticks, parent and child
944        parent: o-----o
945        child:  o----------o
946
947        Now we align the child to the parent in one dimension
948        There are 4 possibilities:
949
950        1. to the right
951                    o-----oo----------o
952
953        2. to the right with overlapping
954                    o-----o
955                    o----------o
956
957        3. to the left
958        o----------oo-----o
959
960        4. to the left with overlapping
961                    o-----o
962               o----------o
963
964        The child goes to the default direction by default,
965        if the space on the default direction runs out it goes to the other,
966        whether to use overlap is the caller's decision
967
968        This can be applied to any direction
969        */
970
971        match direction {
972            Direction::Positive => {
973                let space_negative = parent_pos;
974                let space_positive = max_size - parent_pos - parent_size;
975
976                if overlap {
977                    let overshoot = child_size - parent_size;
978                    if space_negative > space_positive && overshoot > space_positive {
979                        (
980                            parent_pos - overshoot,
981                            parent_pos - overshoot,
982                            direction.flip(),
983                        )
984                    } else {
985                        (parent_pos, parent_pos, direction)
986                    }
987                } else {
988                    let overshoot = child_size + offset;
989                    if space_negative > space_positive && overshoot > space_positive {
990                        (
991                            parent_pos - overshoot,
992                            parent_pos - offset,
993                            direction.flip(),
994                        )
995                    } else {
996                        (
997                            parent_pos + parent_size + offset,
998                            parent_pos + parent_size,
999                            direction,
1000                        )
1001                    }
1002                }
1003            }
1004            Direction::Negative => {
1005                let space_positive = parent_pos;
1006                let space_negative = max_size - parent_pos - parent_size;
1007
1008                if overlap {
1009                    let overshoot = child_size - parent_size;
1010                    if space_negative > space_positive && overshoot > space_positive {
1011                        (parent_pos, parent_pos, direction.flip())
1012                    } else {
1013                        (parent_pos - overshoot, parent_pos - overshoot, direction)
1014                    }
1015                } else {
1016                    let overshoot = child_size + offset;
1017                    if space_negative > space_positive && overshoot > space_positive {
1018                        (
1019                            parent_pos + parent_size + offset,
1020                            parent_pos + parent_size,
1021                            direction.flip(),
1022                        )
1023                    } else {
1024                        (parent_pos - overshoot, parent_pos - offset, direction)
1025                    }
1026                }
1027            }
1028        }
1029    }
1030
1031    /// Returns (child position, offset position, child direction)
1032    fn resolve(
1033        &self,
1034        parent_bounds: Rectangle,
1035        children_size: Size,
1036        viewport_size: Size,
1037    ) -> (Point, Point, (Direction, Direction)) {
1038        let (x, ox, dx) = Self::adaptive(
1039            parent_bounds.x,
1040            parent_bounds.width,
1041            children_size.width,
1042            viewport_size.width,
1043            self.horizontal_offset,
1044            self.horizontal_overlap,
1045            self.horizontal_direction,
1046        );
1047        let (y, oy, dy) = Self::adaptive(
1048            parent_bounds.y,
1049            parent_bounds.height,
1050            children_size.height,
1051            viewport_size.height,
1052            self.vertical_offset,
1053            self.vertical_overlap,
1054            self.vertical_direction,
1055        );
1056
1057        ([x, y].into(), [ox, oy].into(), (dx, dy))
1058    }
1059
1060    fn new(
1061        axis: Axis,
1062        viewport: Size,
1063        parent_bounds: Rectangle,
1064        parent_direction: (Direction, Direction),
1065        offset: f32,
1066    ) -> Self {
1067        let hcenter = viewport.width / 2.0;
1068        let vcenter = viewport.height / 2.0;
1069
1070        let phcenter = parent_bounds.x + parent_bounds.width / 2.0;
1071        let pvcenter = parent_bounds.y + parent_bounds.height / 2.0;
1072
1073        let (pdx, pdy) = parent_direction;
1074        match axis {
1075            Axis::Horizontal => {
1076                let horizontal_direction = pdx;
1077                let vertical_direction = if pvcenter < vcenter {
1078                    Direction::Positive
1079                } else {
1080                    Direction::Negative
1081                };
1082                Self {
1083                    horizontal_overlap: false,
1084                    vertical_overlap: true,
1085                    horizontal_direction,
1086                    vertical_direction,
1087                    horizontal_offset: offset,
1088                    vertical_offset: 0.0,
1089                }
1090            }
1091            Axis::Vertical => {
1092                let horizontal_direction = if phcenter < hcenter {
1093                    Direction::Positive
1094                } else {
1095                    Direction::Negative
1096                };
1097                let vertical_direction = pdy;
1098                Self {
1099                    horizontal_overlap: true,
1100                    vertical_overlap: false,
1101                    horizontal_direction,
1102                    vertical_direction,
1103                    horizontal_offset: 0.0,
1104                    vertical_offset: offset,
1105                }
1106            }
1107        }
1108    }
1109}
1110
1111fn cal_bounds_rel_menu(
1112    items_node: &Node,
1113    translation: Vector,
1114    viewport: Size,
1115    scroll_offset: f32,
1116) -> (f32, f32) {
1117    let items_bounds = items_node.bounds() + translation; // viewport space
1118
1119    // viewport space absolute bounds
1120    let lower_bound = items_bounds.y.max(0.0);
1121    let upper_bound = (items_bounds.y + items_bounds.height).min(viewport.height);
1122
1123    // menu space relative bounds
1124    let lower_bound_rel = lower_bound - (items_bounds.y + scroll_offset);
1125    let upper_bound_rel = upper_bound - (items_bounds.y + scroll_offset);
1126
1127    (lower_bound_rel, upper_bound_rel)
1128}