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