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