Skip to main content

cosmic/widget/menu/
menu_bar.rs

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