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