cosmic/widget/menu/
menu_bar.rs

1// From iced_aw, license MIT
2
3//! A widget that handles menu trees
4use std::{collections::HashMap, sync::Arc};
5
6use super::{
7    menu_inner::{
8        CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight,
9    },
10    menu_tree::MenuTree,
11};
12#[cfg(all(
13    feature = "multi-window",
14    feature = "wayland",
15    feature = "winit",
16    feature = "surface-message"
17))]
18use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
19use crate::{
20    Renderer,
21    style::menu_bar::StyleSheet,
22    widget::{
23        RcWrapper,
24        dropdown::menu::{self, State},
25        menu::menu_inner::init_root_menu,
26    },
27};
28
29use iced::{Point, Shadow, Vector, window};
30use iced_core::Border;
31use iced_widget::core::{
32    Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event,
33    layout::{Limits, Node},
34    mouse::{self, Cursor},
35    overlay,
36    renderer::{self, Renderer as IcedRenderer},
37    touch,
38    widget::{Tree, tree},
39};
40
41/// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing.
42pub fn menu_bar<Message>(menu_roots: Vec<MenuTree<Message>>) -> MenuBar<Message>
43where
44    Message: Clone + 'static,
45{
46    MenuBar::new(menu_roots)
47}
48
49#[derive(Clone, Default)]
50pub(crate) struct MenuBarState {
51    pub(crate) inner: RcWrapper<MenuBarStateInner>,
52}
53
54pub(crate) struct MenuBarStateInner {
55    pub(crate) tree: Tree,
56    pub(crate) popup_id: HashMap<window::Id, window::Id>,
57    pub(crate) pressed: bool,
58    pub(crate) bar_pressed: bool,
59    pub(crate) view_cursor: Cursor,
60    pub(crate) open: bool,
61    pub(crate) active_root: Vec<usize>,
62    pub(crate) horizontal_direction: Direction,
63    pub(crate) vertical_direction: Direction,
64    /// List of all menu states
65    pub(crate) menu_states: Vec<MenuState>,
66}
67impl MenuBarStateInner {
68    /// get the list of indices hovered for the menu
69    pub(super) fn get_trimmed_indices(&self, index: usize) -> impl Iterator<Item = usize> + '_ {
70        self.menu_states
71            .iter()
72            .skip(index)
73            .take_while(|ms| ms.index.is_some())
74            .map(|ms| ms.index.expect("No indices were found in the menu state."))
75    }
76
77    pub(crate) fn reset(&mut self) {
78        self.open = false;
79        self.active_root = Vec::new();
80        self.menu_states.clear();
81    }
82}
83impl Default for MenuBarStateInner {
84    fn default() -> Self {
85        Self {
86            tree: Tree::empty(),
87            pressed: false,
88            view_cursor: Cursor::Available([-0.5, -0.5].into()),
89            open: false,
90            active_root: Vec::new(),
91            horizontal_direction: Direction::Positive,
92            vertical_direction: Direction::Positive,
93            menu_states: Vec::new(),
94            popup_id: HashMap::new(),
95            bar_pressed: false,
96        }
97    }
98}
99
100pub(crate) fn menu_roots_children<Message>(menu_roots: &Vec<MenuTree<Message>>) -> Vec<Tree>
101where
102    Message: Clone + 'static,
103{
104    /*
105    menu bar
106        menu root 1 (stateless)
107            flat tree
108        menu root 2 (stateless)
109            flat tree
110        ...
111    */
112
113    menu_roots
114        .iter()
115        .map(|root| {
116            let mut tree = Tree::empty();
117            let flat = root
118                .flattern()
119                .iter()
120                .map(|mt| Tree::new(mt.item.clone()))
121                .collect();
122            tree.children = flat;
123            tree
124        })
125        .collect()
126}
127
128#[allow(invalid_reference_casting)]
129pub(crate) fn menu_roots_diff<Message>(menu_roots: &mut Vec<MenuTree<Message>>, tree: &mut Tree)
130where
131    Message: Clone + 'static,
132{
133    if tree.children.len() > menu_roots.len() {
134        tree.children.truncate(menu_roots.len());
135    }
136
137    tree.children
138        .iter_mut()
139        .zip(menu_roots.iter())
140        .for_each(|(t, root)| {
141            let mut flat = root
142                .flattern()
143                .iter()
144                .map(|mt| {
145                    let widget = &mt.item;
146                    let widget_ptr = widget as *const dyn Widget<Message, crate::Theme, Renderer>;
147                    let widget_ptr_mut =
148                        widget_ptr as *mut dyn Widget<Message, crate::Theme, Renderer>;
149                    //TODO: find a way to diff_children without unsafe code
150                    unsafe { &mut *widget_ptr_mut }
151                })
152                .collect::<Vec<_>>();
153
154            t.diff_children(flat.as_mut_slice());
155        });
156
157    if tree.children.len() < menu_roots.len() {
158        let extended = menu_roots[tree.children.len()..].iter().map(|root| {
159            let mut tree = Tree::empty();
160            let flat = root
161                .flattern()
162                .iter()
163                .map(|mt| Tree::new(mt.item.clone()))
164                .collect();
165            tree.children = flat;
166            tree
167        });
168        tree.children.extend(extended);
169    }
170}
171
172pub fn get_mut_or_default<T: Default>(vec: &mut Vec<T>, index: usize) -> &mut T {
173    if index < vec.len() {
174        &mut vec[index]
175    } else {
176        vec.resize_with(index + 1, T::default);
177        &mut vec[index]
178    }
179}
180
181/// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing.
182#[allow(missing_debug_implementations)]
183pub struct MenuBar<Message> {
184    width: Length,
185    height: Length,
186    spacing: f32,
187    padding: Padding,
188    bounds_expand: u16,
189    main_offset: i32,
190    cross_offset: i32,
191    close_condition: CloseCondition,
192    item_width: ItemWidth,
193    item_height: ItemHeight,
194    path_highlight: Option<PathHighlight>,
195    menu_roots: Vec<MenuTree<Message>>,
196    style: <crate::Theme as StyleSheet>::Style,
197    window_id: window::Id,
198    #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))]
199    positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
200    pub(crate) on_surface_action:
201        Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
202}
203
204impl<Message> MenuBar<Message>
205where
206    Message: Clone + 'static,
207{
208    /// Creates a new [`MenuBar`] with the given menu roots
209    #[must_use]
210    pub fn new(menu_roots: Vec<MenuTree<Message>>) -> Self {
211        let mut menu_roots = menu_roots;
212        menu_roots.iter_mut().for_each(MenuTree::set_index);
213
214        Self {
215            width: Length::Shrink,
216            height: Length::Shrink,
217            spacing: 0.0,
218            padding: Padding::ZERO,
219            bounds_expand: 16,
220            main_offset: 0,
221            cross_offset: 0,
222            close_condition: CloseCondition {
223                leave: false,
224                click_outside: true,
225                click_inside: true,
226            },
227            item_width: ItemWidth::Uniform(150),
228            item_height: ItemHeight::Uniform(30),
229            path_highlight: Some(PathHighlight::MenuActive),
230            menu_roots,
231            style: <crate::Theme as StyleSheet>::Style::default(),
232            window_id: window::Id::NONE,
233            #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))]
234            positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
235            on_surface_action: None,
236        }
237    }
238
239    /// Sets the expand value for each menu's check bounds
240    ///
241    /// When the cursor goes outside of a menu's check bounds,
242    /// the menu will be closed automatically, this value expands
243    /// the check bounds
244    #[must_use]
245    pub fn bounds_expand(mut self, value: u16) -> Self {
246        self.bounds_expand = value;
247        self
248    }
249
250    /// [`CloseCondition`]
251    #[must_use]
252    pub fn close_condition(mut self, close_condition: CloseCondition) -> Self {
253        self.close_condition = close_condition;
254        self
255    }
256
257    /// Moves each menu in the horizontal open direction
258    #[must_use]
259    pub fn cross_offset(mut self, value: i32) -> Self {
260        self.cross_offset = value;
261        self
262    }
263
264    /// Sets the height of the [`MenuBar`]
265    #[must_use]
266    pub fn height(mut self, height: Length) -> Self {
267        self.height = height;
268        self
269    }
270
271    /// [`ItemHeight`]
272    #[must_use]
273    pub fn item_height(mut self, item_height: ItemHeight) -> Self {
274        self.item_height = item_height;
275        self
276    }
277
278    /// [`ItemWidth`]
279    #[must_use]
280    pub fn item_width(mut self, item_width: ItemWidth) -> Self {
281        self.item_width = item_width;
282        self
283    }
284
285    /// Moves all the menus in the vertical open direction
286    #[must_use]
287    pub fn main_offset(mut self, value: i32) -> Self {
288        self.main_offset = value;
289        self
290    }
291
292    /// Sets the [`Padding`] of the [`MenuBar`]
293    #[must_use]
294    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
295        self.padding = padding.into();
296        self
297    }
298
299    /// Sets the method for drawing path highlight
300    #[must_use]
301    pub fn path_highlight(mut self, path_highlight: Option<PathHighlight>) -> Self {
302        self.path_highlight = path_highlight;
303        self
304    }
305
306    /// Sets the spacing between menu roots
307    #[must_use]
308    pub fn spacing(mut self, units: f32) -> Self {
309        self.spacing = units;
310        self
311    }
312
313    /// Sets the style of the menu bar and its menus
314    #[must_use]
315    pub fn style(mut self, style: impl Into<<crate::Theme as StyleSheet>::Style>) -> Self {
316        self.style = style.into();
317        self
318    }
319
320    /// Sets the width of the [`MenuBar`]
321    #[must_use]
322    pub fn width(mut self, width: Length) -> Self {
323        self.width = width;
324        self
325    }
326
327    #[cfg(all(feature = "multi-window", feature = "wayland", feature = "winit"))]
328    pub fn with_positioner(
329        mut self,
330        positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
331    ) -> Self {
332        self.positioner = positioner;
333        self
334    }
335
336    #[must_use]
337    pub fn window_id(mut self, id: window::Id) -> Self {
338        self.window_id = id;
339        self
340    }
341
342    #[must_use]
343    pub fn window_id_maybe(mut self, id: Option<window::Id>) -> Self {
344        if let Some(id) = id {
345            self.window_id = id;
346        }
347        self
348    }
349
350    #[must_use]
351    pub fn on_surface_action(
352        mut self,
353        handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
354    ) -> Self {
355        self.on_surface_action = Some(Arc::new(handler));
356        self
357    }
358
359    #[cfg(all(
360        feature = "multi-window",
361        feature = "wayland",
362        feature = "winit",
363        feature = "surface-message"
364    ))]
365    #[allow(clippy::too_many_lines)]
366    fn create_popup(
367        &mut self,
368        layout: Layout<'_>,
369        view_cursor: Cursor,
370        renderer: &Renderer,
371        shell: &mut Shell<'_, Message>,
372        viewport: &Rectangle,
373        my_state: &mut MenuBarState,
374    ) {
375        if self.window_id != window::Id::NONE && self.on_surface_action.is_some() {
376            use crate::surface::action::destroy_popup;
377            use iced_runtime::platform_specific::wayland::popup::{
378                SctkPopupSettings, SctkPositioner,
379            };
380
381            let surface_action = self.on_surface_action.as_ref().unwrap();
382            let old_active_root = my_state
383                .inner
384                .with_data(|state| state.active_root.get(0).copied());
385
386            // if position is not on menu bar button skip.
387            let hovered_root = layout
388                .children()
389                .position(|lo| view_cursor.is_over(lo.bounds()));
390            if hovered_root.is_none()
391                || old_active_root
392                    .zip(hovered_root)
393                    .is_some_and(|r| r.0 == r.1)
394            {
395                return;
396            }
397
398            let (id, root_list) = my_state.inner.with_data_mut(|state| {
399                if let Some(id) = state.popup_id.get(&self.window_id).copied() {
400                    // close existing popups
401                    state.menu_states.clear();
402                    state.active_root.clear();
403                    shell.publish(surface_action(destroy_popup(id)));
404                    state.view_cursor = view_cursor;
405                    (id, layout.children().map(|lo| lo.bounds()).collect())
406                } else {
407                    (
408                        window::Id::unique(),
409                        layout.children().map(|lo| lo.bounds()).collect(),
410                    )
411                }
412            });
413
414            let mut popup_menu: Menu<'static, _> = Menu {
415                tree: my_state.clone(),
416                menu_roots: std::borrow::Cow::Owned(self.menu_roots.clone()),
417                bounds_expand: self.bounds_expand,
418                menu_overlays_parent: false,
419                close_condition: self.close_condition,
420                item_width: self.item_width,
421                item_height: self.item_height,
422                bar_bounds: layout.bounds(),
423                main_offset: self.main_offset,
424                cross_offset: self.cross_offset,
425                root_bounds_list: root_list,
426                path_highlight: self.path_highlight,
427                style: std::borrow::Cow::Owned(self.style.clone()),
428                position: Point::new(0., 0.),
429                is_overlay: false,
430                window_id: id,
431                depth: 0,
432                on_surface_action: self.on_surface_action.clone(),
433            };
434
435            init_root_menu(
436                &mut popup_menu,
437                renderer,
438                shell,
439                view_cursor.position().unwrap(),
440                viewport.size(),
441                Vector::new(0., 0.),
442                layout.bounds(),
443                self.main_offset as f32,
444            );
445            let (anchor_rect, gravity) = my_state.inner.with_data_mut(|state| {
446                state.popup_id.insert(self.window_id, id);
447                (state
448                    .menu_states
449                    .iter()
450                    .find(|s| s.index.is_none())
451                    .map(|s| s.menu_bounds.parent_bounds)
452                    .map_or_else(
453                        || {
454                            let bounds = layout.bounds();
455                            Rectangle {
456                                x: bounds.x as i32,
457                                y: bounds.y as i32,
458                                width: bounds.width as i32,
459                                height: bounds.height as i32,
460                            }
461                        },
462                        |r| Rectangle {
463                            x: r.x as i32,
464                            y: r.y as i32,
465                            width: r.width as i32,
466                            height: r.height as i32,
467                        },
468                    ), match (state.horizontal_direction, state.vertical_direction) {
469                        (Direction::Positive, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
470                        (Direction::Positive, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight,
471                        (Direction::Negative, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft,
472                        (Direction::Negative, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft,
473                    })
474            });
475
476            let menu_node = popup_menu.layout(renderer, Limits::NONE.min_width(1.).min_height(1.));
477            let popup_size = menu_node.size();
478            let positioner = SctkPositioner {
479                size: Some((
480                    popup_size.width.ceil() as u32 + 2,
481                    popup_size.height.ceil() as u32 + 2,
482                )),
483                anchor_rect,
484                anchor:
485                    cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft,
486                gravity,
487                reactive: true,
488                ..Default::default()
489            };
490            let parent = self.window_id;
491            shell.publish((surface_action)(crate::surface::action::simple_popup(
492                move || SctkPopupSettings {
493                    parent,
494                    id,
495                    positioner: positioner.clone(),
496                    parent_size: None,
497                    grab: true,
498                    close_with_children: false,
499                    input_zone: None,
500                },
501                Some(move || {
502                    Element::from(crate::widget::container(popup_menu.clone()).center(Length::Fill))
503                        .map(crate::action::app)
504                }),
505            )));
506        }
507    }
508}
509impl<Message> Widget<Message, crate::Theme, Renderer> for MenuBar<Message>
510where
511    Message: Clone + 'static,
512{
513    fn size(&self) -> iced_core::Size<Length> {
514        iced_core::Size::new(self.width, self.height)
515    }
516
517    fn diff(&mut self, tree: &mut Tree) {
518        let state = tree.state.downcast_mut::<MenuBarState>();
519        state
520            .inner
521            .with_data_mut(|inner| menu_roots_diff(&mut self.menu_roots, &mut inner.tree));
522    }
523
524    fn tag(&self) -> tree::Tag {
525        tree::Tag::of::<MenuBarState>()
526    }
527
528    fn state(&self) -> tree::State {
529        tree::State::new(MenuBarState::default())
530    }
531
532    fn children(&self) -> Vec<Tree> {
533        menu_roots_children(&self.menu_roots)
534    }
535
536    fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
537        use super::flex;
538
539        let limits = limits.width(self.width).height(self.height);
540        let children = self
541            .menu_roots
542            .iter()
543            .map(|root| &root.item)
544            .collect::<Vec<_>>();
545        // the first children of the tree are the menu roots items
546        let mut tree_children = tree
547            .children
548            .iter_mut()
549            .map(|t| &mut t.children[0])
550            .collect::<Vec<_>>();
551        flex::resolve_wrapper(
552            &flex::Axis::Horizontal,
553            renderer,
554            &limits,
555            self.padding,
556            self.spacing,
557            Alignment::Center,
558            &children,
559            &mut tree_children,
560        )
561    }
562
563    #[allow(clippy::too_many_lines)]
564    fn on_event(
565        &mut self,
566        tree: &mut Tree,
567        event: event::Event,
568        layout: Layout<'_>,
569        view_cursor: Cursor,
570        renderer: &Renderer,
571        clipboard: &mut dyn Clipboard,
572        shell: &mut Shell<'_, Message>,
573        viewport: &Rectangle,
574    ) -> event::Status {
575        use event::Event::{Mouse, Touch};
576        use mouse::{Button::Left, Event::ButtonReleased};
577        use touch::Event::{FingerLifted, FingerLost};
578
579        let root_status = process_root_events(
580            &mut self.menu_roots,
581            view_cursor,
582            tree,
583            &event,
584            layout,
585            renderer,
586            clipboard,
587            shell,
588            viewport,
589        );
590
591        let my_state = tree.state.downcast_mut::<MenuBarState>();
592
593        // XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open.
594        let reset = self.window_id != window::Id::NONE
595            && my_state
596                .inner
597                .with_data(|d| !d.open && !d.active_root.is_empty());
598
599        let open = my_state.inner.with_data_mut(|state| {
600            if reset {
601                if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() {
602                    if let Some(handler) = self.on_surface_action.as_ref() {
603                        shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id)));
604                        state.reset();
605                    }
606                }
607            }
608            state.open
609        });
610
611        match event {
612            Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => {
613                let create_popup = my_state.inner.with_data_mut(|state| {
614                    let mut create_popup = false;
615                    if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) {
616                        state.view_cursor = view_cursor;
617                        state.open = true;
618                        create_popup = true;
619                    } else if let Some(_id) = state.popup_id.remove(&self.window_id) {
620                        state.menu_states.clear();
621                        state.active_root.clear();
622                        state.open = false;
623                        #[cfg(all(
624                            feature = "wayland",
625                            feature = "winit",
626                            feature = "surface-message"
627                        ))]
628                        {
629                            let surface_action = self.on_surface_action.as_ref().unwrap();
630
631                            shell.publish(surface_action(crate::surface::action::destroy_popup(
632                                _id,
633                            )));
634                        }
635                        state.view_cursor = view_cursor;
636                    }
637                    create_popup
638                });
639
640                if !create_popup {
641                    return event::Status::Ignored;
642                }
643                #[cfg(all(
644                    feature = "multi-window",
645                    feature = "wayland",
646                    feature = "winit",
647                    feature = "surface-message"
648                ))]
649                if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
650                    self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state);
651                }
652            }
653            Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
654                if open && view_cursor.is_over(layout.bounds()) =>
655            {
656                #[cfg(all(
657                    feature = "multi-window",
658                    feature = "wayland",
659                    feature = "winit",
660                    feature = "surface-message"
661                ))]
662                if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
663                    self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state);
664                }
665            }
666            _ => (),
667        }
668
669        root_status
670    }
671
672    fn draw(
673        &self,
674        tree: &Tree,
675        renderer: &mut Renderer,
676        theme: &crate::Theme,
677        style: &renderer::Style,
678        layout: Layout<'_>,
679        view_cursor: Cursor,
680        viewport: &Rectangle,
681    ) {
682        let state = tree.state.downcast_ref::<MenuBarState>();
683        let cursor_pos = view_cursor.position().unwrap_or_default();
684        state.inner.with_data_mut(|state| {
685            let position = if state.open && (cursor_pos.x < 0.0 || cursor_pos.y < 0.0) {
686                state.view_cursor
687            } else {
688                view_cursor
689            };
690
691            // draw path highlight
692            if self.path_highlight.is_some() {
693                let styling = theme.appearance(&self.style);
694                if let Some(active) = state.active_root.first() {
695                    let active_bounds = layout
696                        .children()
697                        .nth(*active)
698                        .expect("Active child not found in menu?")
699                        .bounds();
700                    let path_quad = renderer::Quad {
701                        bounds: active_bounds,
702                        border: Border {
703                            radius: styling.bar_border_radius.into(),
704                            ..Default::default()
705                        },
706                        shadow: Shadow::default(),
707                    };
708
709                    renderer.fill_quad(path_quad, styling.path);
710                }
711            }
712
713            self.menu_roots
714                .iter()
715                .zip(&tree.children)
716                .zip(layout.children())
717                .for_each(|((root, t), lo)| {
718                    root.item.draw(
719                        &t.children[root.index],
720                        renderer,
721                        theme,
722                        style,
723                        lo,
724                        position,
725                        viewport,
726                    );
727                });
728        });
729    }
730
731    fn overlay<'b>(
732        &'b mut self,
733        tree: &'b mut Tree,
734        layout: Layout<'_>,
735        _renderer: &Renderer,
736        translation: Vector,
737    ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
738        #[cfg(all(
739            feature = "multi-window",
740            feature = "wayland",
741            feature = "winit",
742            feature = "surface-message"
743        ))]
744        if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
745            && self.on_surface_action.is_some()
746            && self.window_id != window::Id::NONE
747        {
748            return None;
749        }
750
751        let state = tree.state.downcast_ref::<MenuBarState>();
752        if state.inner.with_data(|state| !state.open) {
753            return None;
754        }
755
756        Some(
757            Menu {
758                tree: state.clone(),
759                menu_roots: std::borrow::Cow::Owned(self.menu_roots.clone()),
760                bounds_expand: self.bounds_expand,
761                menu_overlays_parent: false,
762                close_condition: self.close_condition,
763                item_width: self.item_width,
764                item_height: self.item_height,
765                bar_bounds: layout.bounds(),
766                main_offset: self.main_offset,
767                cross_offset: self.cross_offset,
768                root_bounds_list: layout.children().map(|lo| lo.bounds()).collect(),
769                path_highlight: self.path_highlight,
770                style: std::borrow::Cow::Borrowed(&self.style),
771                position: Point::new(translation.x, translation.y),
772                is_overlay: true,
773                window_id: window::Id::NONE,
774                depth: 0,
775                on_surface_action: self.on_surface_action.clone(),
776            }
777            .overlay(),
778        )
779    }
780}
781
782impl<Message> From<MenuBar<Message>> for Element<'_, Message, crate::Theme, Renderer>
783where
784    Message: Clone + 'static,
785{
786    fn from(value: MenuBar<Message>) -> Self {
787        Self::new(value)
788    }
789}
790
791#[allow(unused_results, clippy::too_many_arguments)]
792fn process_root_events<Message>(
793    menu_roots: &mut [MenuTree<Message>],
794    view_cursor: Cursor,
795    tree: &mut Tree,
796    event: &event::Event,
797    layout: Layout<'_>,
798    renderer: &Renderer,
799    clipboard: &mut dyn Clipboard,
800    shell: &mut Shell<'_, Message>,
801    viewport: &Rectangle,
802) -> event::Status
803where
804{
805    menu_roots
806        .iter_mut()
807        .zip(&mut tree.children)
808        .zip(layout.children())
809        .map(|((root, t), lo)| {
810            // assert!(t.tag == tree::Tag::stateless());
811            root.item.on_event(
812                &mut t.children[root.index],
813                event.clone(),
814                lo,
815                view_cursor,
816                renderer,
817                clipboard,
818                shell,
819                viewport,
820            )
821        })
822        .fold(event::Status::Ignored, event::Status::merge)
823}