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