egui/
menu.rs

1//! Menu bar functionality (very basic so far).
2//!
3//! Usage:
4//! ```
5//! fn show_menu(ui: &mut egui::Ui) {
6//!     use egui::{menu, Button};
7//!
8//!     menu::bar(ui, |ui| {
9//!         ui.menu_button("File", |ui| {
10//!             if ui.button("Open").clicked() {
11//!                 // …
12//!             }
13//!         });
14//!     });
15//! }
16//! ```
17
18use super::{
19    style::WidgetVisuals, Align, Context, Id, InnerResponse, PointerState, Pos2, Rect, Response,
20    Sense, TextStyle, Ui, Vec2,
21};
22use crate::{widgets::*, *};
23use epaint::mutex::RwLock;
24use std::sync::Arc;
25
26/// What is saved between frames.
27#[derive(Clone, Default)]
28pub struct BarState {
29    open_menu: MenuRootManager,
30}
31
32impl BarState {
33    pub fn load(ctx: &Context, bar_id: Id) -> Self {
34        ctx.data_mut(|d| d.get_temp::<Self>(bar_id).unwrap_or_default())
35    }
36
37    pub fn store(self, ctx: &Context, bar_id: Id) {
38        ctx.data_mut(|d| d.insert_temp(bar_id, self));
39    }
40
41    /// Show a menu at pointer if primary-clicked response.
42    ///
43    /// Should be called from [`Context`] on a [`Response`]
44    pub fn bar_menu<R>(
45        &mut self,
46        button: &Response,
47        add_contents: impl FnOnce(&mut Ui) -> R,
48    ) -> Option<InnerResponse<R>> {
49        MenuRoot::stationary_click_interaction(button, &mut self.open_menu);
50        self.open_menu.show(button, add_contents)
51    }
52
53    pub(crate) fn has_root(&self) -> bool {
54        self.open_menu.inner.is_some()
55    }
56}
57
58impl std::ops::Deref for BarState {
59    type Target = MenuRootManager;
60
61    fn deref(&self) -> &Self::Target {
62        &self.open_menu
63    }
64}
65
66impl std::ops::DerefMut for BarState {
67    fn deref_mut(&mut self) -> &mut Self::Target {
68        &mut self.open_menu
69    }
70}
71
72fn set_menu_style(style: &mut Style) {
73    style.spacing.button_padding = vec2(2.0, 0.0);
74    style.visuals.widgets.active.bg_stroke = Stroke::NONE;
75    style.visuals.widgets.hovered.bg_stroke = Stroke::NONE;
76    style.visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
77    style.visuals.widgets.inactive.bg_stroke = Stroke::NONE;
78}
79
80/// The menu bar goes well in a [`TopBottomPanel::top`],
81/// but can also be placed in a [`Window`].
82/// In the latter case you may want to wrap it in [`Frame`].
83pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
84    ui.horizontal(|ui| {
85        set_menu_style(ui.style_mut());
86
87        // Take full width and fixed height:
88        let height = ui.spacing().interact_size.y;
89        ui.set_min_size(vec2(ui.available_width(), height));
90
91        add_contents(ui)
92    })
93}
94
95/// Construct a top level menu in a menu bar. This would be e.g. "File", "Edit" etc.
96///
97/// Responds to primary clicks.
98///
99/// Returns `None` if the menu is not open.
100pub fn menu_button<R>(
101    ui: &mut Ui,
102    title: impl Into<WidgetText>,
103    add_contents: impl FnOnce(&mut Ui) -> R,
104) -> InnerResponse<Option<R>> {
105    stationary_menu_impl(ui, title, Box::new(add_contents))
106}
107
108/// Construct a top level menu with an image in a menu bar. This would be e.g. "File", "Edit" etc.
109///
110/// Responds to primary clicks.
111///
112/// Returns `None` if the menu is not open.
113pub fn menu_image_button<R>(
114    ui: &mut Ui,
115    image_button: ImageButton<'_>,
116    add_contents: impl FnOnce(&mut Ui) -> R,
117) -> InnerResponse<Option<R>> {
118    stationary_menu_image_impl(ui, image_button, Box::new(add_contents))
119}
120
121/// Construct a nested sub menu in another menu.
122///
123/// Opens on hover.
124///
125/// Returns `None` if the menu is not open.
126pub(crate) fn submenu_button<R>(
127    ui: &mut Ui,
128    parent_state: Arc<RwLock<MenuState>>,
129    title: impl Into<WidgetText>,
130    add_contents: impl FnOnce(&mut Ui) -> R,
131) -> InnerResponse<Option<R>> {
132    SubMenu::new(parent_state, title).show(ui, add_contents)
133}
134
135/// wrapper for the contents of every menu.
136fn menu_popup<'c, R>(
137    ctx: &Context,
138    parent_layer: LayerId,
139    menu_state_arc: &Arc<RwLock<MenuState>>,
140    menu_id: Id,
141    add_contents: impl FnOnce(&mut Ui) -> R + 'c,
142) -> InnerResponse<R> {
143    let pos = {
144        let mut menu_state = menu_state_arc.write();
145        menu_state.entry_count = 0;
146        menu_state.rect.min
147    };
148
149    let area_id = menu_id.with("__menu");
150
151    ctx.frame_state_mut(|fs| {
152        fs.layers
153            .entry(parent_layer)
154            .or_default()
155            .open_popups
156            .insert(area_id)
157    });
158
159    let area = Area::new(area_id)
160        .kind(UiKind::Menu)
161        .order(Order::Foreground)
162        .fixed_pos(pos)
163        .default_width(ctx.style().spacing.menu_width)
164        .sense(Sense::hover());
165
166    let mut sizing_pass = false;
167
168    let area_response = area.show(ctx, |ui| {
169        sizing_pass = ui.is_sizing_pass();
170
171        set_menu_style(ui.style_mut());
172
173        Frame::menu(ui.style())
174            .show(ui, |ui| {
175                ui.set_menu_state(Some(menu_state_arc.clone()));
176                ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents)
177                    .inner
178            })
179            .inner
180    });
181
182    let area_rect = area_response.response.rect;
183
184    menu_state_arc.write().rect = if sizing_pass {
185        // During the sizing pass we didn't know the size yet,
186        // so we might have just constrained the position unnecessarily.
187        // Therefore keep the original=desired position until the next frame.
188        Rect::from_min_size(pos, area_rect.size())
189    } else {
190        // We knew the size, and this is where it ended up (potentially constrained to screen).
191        // Remember it for the future:
192        area_rect
193    };
194
195    area_response
196}
197
198/// Build a top level menu with a button.
199///
200/// Responds to primary clicks.
201fn stationary_menu_impl<'c, R>(
202    ui: &mut Ui,
203    title: impl Into<WidgetText>,
204    add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
205) -> InnerResponse<Option<R>> {
206    let title = title.into();
207    let bar_id = ui.id();
208    let menu_id = bar_id.with(title.text());
209
210    let mut bar_state = BarState::load(ui.ctx(), bar_id);
211
212    let mut button = Button::new(title);
213
214    if bar_state.open_menu.is_menu_open(menu_id) {
215        button = button.fill(ui.visuals().widgets.open.weak_bg_fill);
216        button = button.stroke(ui.visuals().widgets.open.bg_stroke);
217    }
218
219    let button_response = ui.add(button);
220    let inner = bar_state.bar_menu(&button_response, add_contents);
221
222    bar_state.store(ui.ctx(), bar_id);
223    InnerResponse::new(inner.map(|r| r.inner), button_response)
224}
225
226/// Build a top level menu with an image button.
227///
228/// Responds to primary clicks.
229fn stationary_menu_image_impl<'c, R>(
230    ui: &mut Ui,
231    image_button: ImageButton<'_>,
232    add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
233) -> InnerResponse<Option<R>> {
234    let bar_id = ui.id();
235
236    let mut bar_state = BarState::load(ui.ctx(), bar_id);
237    let button_response = ui.add(image_button);
238    let inner = bar_state.bar_menu(&button_response, add_contents);
239
240    bar_state.store(ui.ctx(), bar_id);
241    InnerResponse::new(inner.map(|r| r.inner), button_response)
242}
243
244pub(crate) const CONTEXT_MENU_ID_STR: &str = "__egui::context_menu";
245
246/// Response to secondary clicks (right-clicks) by showing the given menu.
247pub(crate) fn context_menu(
248    response: &Response,
249    add_contents: impl FnOnce(&mut Ui),
250) -> Option<InnerResponse<()>> {
251    let menu_id = Id::new(CONTEXT_MENU_ID_STR);
252    let mut bar_state = BarState::load(&response.ctx, menu_id);
253
254    MenuRoot::context_click_interaction(response, &mut bar_state);
255    let inner_response = bar_state.show(response, add_contents);
256
257    bar_state.store(&response.ctx, menu_id);
258    inner_response
259}
260
261/// Returns `true` if the context menu is opened for this widget.
262pub(crate) fn context_menu_opened(response: &Response) -> bool {
263    let menu_id = Id::new(CONTEXT_MENU_ID_STR);
264    let bar_state = BarState::load(&response.ctx, menu_id);
265    bar_state.is_menu_open(response.id)
266}
267
268/// Stores the state for the context menu.
269#[derive(Clone, Default)]
270pub struct MenuRootManager {
271    inner: Option<MenuRoot>,
272}
273
274impl MenuRootManager {
275    /// Show a menu at pointer if right-clicked response.
276    ///
277    /// Should be called from [`Context`] on a [`Response`]
278    pub fn show<R>(
279        &mut self,
280        button: &Response,
281        add_contents: impl FnOnce(&mut Ui) -> R,
282    ) -> Option<InnerResponse<R>> {
283        if let Some(root) = self.inner.as_mut() {
284            let (menu_response, inner_response) = root.show(button, add_contents);
285            if menu_response.is_close() {
286                self.inner = None;
287            }
288            inner_response
289        } else {
290            None
291        }
292    }
293
294    fn is_menu_open(&self, id: Id) -> bool {
295        self.inner.as_ref().map(|m| m.id) == Some(id)
296    }
297}
298
299impl std::ops::Deref for MenuRootManager {
300    type Target = Option<MenuRoot>;
301
302    fn deref(&self) -> &Self::Target {
303        &self.inner
304    }
305}
306
307impl std::ops::DerefMut for MenuRootManager {
308    fn deref_mut(&mut self) -> &mut Self::Target {
309        &mut self.inner
310    }
311}
312
313/// Menu root associated with an Id from a Response
314#[derive(Clone)]
315pub struct MenuRoot {
316    pub menu_state: Arc<RwLock<MenuState>>,
317    pub id: Id,
318}
319
320impl MenuRoot {
321    pub fn new(position: Pos2, id: Id) -> Self {
322        Self {
323            menu_state: Arc::new(RwLock::new(MenuState::new(position))),
324            id,
325        }
326    }
327
328    pub fn show<R>(
329        &mut self,
330        button: &Response,
331        add_contents: impl FnOnce(&mut Ui) -> R,
332    ) -> (MenuResponse, Option<InnerResponse<R>>) {
333        if self.id == button.id {
334            let inner_response = menu_popup(
335                &button.ctx,
336                button.layer_id,
337                &self.menu_state,
338                self.id,
339                add_contents,
340            );
341            let menu_state = self.menu_state.read();
342
343            let escape_pressed = button.ctx.input(|i| i.key_pressed(Key::Escape));
344            if menu_state.response.is_close() || escape_pressed {
345                return (MenuResponse::Close, Some(inner_response));
346            }
347        }
348        (MenuResponse::Stay, None)
349    }
350
351    /// Interaction with a stationary menu, i.e. fixed in another Ui.
352    ///
353    /// Responds to primary clicks.
354    fn stationary_interaction(button: &Response, root: &mut MenuRootManager) -> MenuResponse {
355        let id = button.id;
356
357        if (button.clicked() && root.is_menu_open(id))
358            || button.ctx.input(|i| i.key_pressed(Key::Escape))
359        {
360            // menu open and button clicked or esc pressed
361            return MenuResponse::Close;
362        } else if (button.clicked() && !root.is_menu_open(id))
363            || (button.hovered() && root.is_some())
364        {
365            // menu not open and button clicked
366            // or button hovered while other menu is open
367            let mut pos = button.rect.left_bottom();
368
369            let menu_frame = Frame::menu(&button.ctx.style());
370            pos.x -= menu_frame.total_margin().left; // Make fist button in menu align with the parent button
371            pos.y += button.ctx.style().spacing.menu_spacing;
372
373            if let Some(root) = root.inner.as_mut() {
374                let menu_rect = root.menu_state.read().rect;
375                let screen_rect = button.ctx.input(|i| i.screen_rect);
376
377                if pos.y + menu_rect.height() > screen_rect.max.y {
378                    pos.y = screen_rect.max.y - menu_rect.height() - button.rect.height();
379                }
380
381                if pos.x + menu_rect.width() > screen_rect.max.x {
382                    pos.x = screen_rect.max.x - menu_rect.width();
383                }
384            }
385
386            if let Some(transform) = button
387                .ctx
388                .memory(|m| m.layer_transforms.get(&button.layer_id).copied())
389            {
390                pos = transform * pos;
391            }
392
393            return MenuResponse::Create(pos, id);
394        } else if button
395            .ctx
396            .input(|i| i.pointer.any_pressed() && i.pointer.primary_down())
397        {
398            if let Some(pos) = button.ctx.input(|i| i.pointer.interact_pos()) {
399                if let Some(root) = root.inner.as_mut() {
400                    if root.id == id {
401                        // pressed somewhere while this menu is open
402                        let in_menu = root.menu_state.read().area_contains(pos);
403                        if !in_menu {
404                            return MenuResponse::Close;
405                        }
406                    }
407                }
408            }
409        }
410        MenuResponse::Stay
411    }
412
413    /// Interaction with a context menu (secondary click).
414    pub fn context_interaction(response: &Response, root: &mut Option<Self>) -> MenuResponse {
415        let response = response.interact(Sense::click());
416        let hovered = response.hovered();
417        let secondary_clicked = response.secondary_clicked();
418
419        response.ctx.input(|input| {
420            let pointer = &input.pointer;
421            if let Some(pos) = pointer.interact_pos() {
422                let mut in_old_menu = false;
423                let mut destroy = false;
424                if let Some(root) = root {
425                    in_old_menu = root.menu_state.read().area_contains(pos);
426                    destroy = !in_old_menu && pointer.any_pressed() && root.id == response.id;
427                }
428                if !in_old_menu {
429                    if hovered && secondary_clicked {
430                        return MenuResponse::Create(pos, response.id);
431                    } else if destroy || hovered && pointer.primary_down() {
432                        return MenuResponse::Close;
433                    }
434                }
435            }
436            MenuResponse::Stay
437        })
438    }
439
440    pub fn handle_menu_response(root: &mut MenuRootManager, menu_response: MenuResponse) {
441        match menu_response {
442            MenuResponse::Create(pos, id) => {
443                root.inner = Some(Self::new(pos, id));
444            }
445            MenuResponse::Close => root.inner = None,
446            MenuResponse::Stay => {}
447        }
448    }
449
450    /// Respond to secondary (right) clicks.
451    pub fn context_click_interaction(response: &Response, root: &mut MenuRootManager) {
452        let menu_response = Self::context_interaction(response, root);
453        Self::handle_menu_response(root, menu_response);
454    }
455
456    // Responds to primary clicks.
457    pub fn stationary_click_interaction(button: &Response, root: &mut MenuRootManager) {
458        let menu_response = Self::stationary_interaction(button, root);
459        Self::handle_menu_response(root, menu_response);
460    }
461}
462
463#[derive(Copy, Clone, PartialEq, Eq)]
464pub enum MenuResponse {
465    Close,
466    Stay,
467    Create(Pos2, Id),
468}
469
470impl MenuResponse {
471    pub fn is_close(&self) -> bool {
472        *self == Self::Close
473    }
474}
475
476pub struct SubMenuButton {
477    text: WidgetText,
478    icon: WidgetText,
479    index: usize,
480}
481
482impl SubMenuButton {
483    /// The `icon` can be an emoji (e.g. `⏵` right arrow), shown right of the label
484    fn new(text: impl Into<WidgetText>, icon: impl Into<WidgetText>, index: usize) -> Self {
485        Self {
486            text: text.into(),
487            icon: icon.into(),
488            index,
489        }
490    }
491
492    fn visuals<'a>(
493        ui: &'a Ui,
494        response: &Response,
495        menu_state: &MenuState,
496        sub_id: Id,
497    ) -> &'a WidgetVisuals {
498        if menu_state.is_open(sub_id) && !response.hovered() {
499            &ui.style().visuals.widgets.open
500        } else {
501            ui.style().interact(response)
502        }
503    }
504
505    #[inline]
506    pub fn icon(mut self, icon: impl Into<WidgetText>) -> Self {
507        self.icon = icon.into();
508        self
509    }
510
511    pub(crate) fn show(self, ui: &mut Ui, menu_state: &MenuState, sub_id: Id) -> Response {
512        let Self { text, icon, .. } = self;
513
514        let text_style = TextStyle::Button;
515        let sense = Sense::click();
516
517        let text_icon_gap = ui.spacing().item_spacing.x;
518        let button_padding = ui.spacing().button_padding;
519        let total_extra = button_padding + button_padding;
520        let text_available_width = ui.available_width() - total_extra.x;
521        let text_galley = text.into_galley(
522            ui,
523            Some(TextWrapMode::Wrap),
524            text_available_width,
525            text_style.clone(),
526        );
527
528        let icon_available_width = text_available_width - text_galley.size().x;
529        let icon_galley = icon.into_galley(
530            ui,
531            Some(TextWrapMode::Wrap),
532            icon_available_width,
533            text_style,
534        );
535        let text_and_icon_size = Vec2::new(
536            text_galley.size().x + text_icon_gap + icon_galley.size().x,
537            text_galley.size().y.max(icon_galley.size().y),
538        );
539        let mut desired_size = text_and_icon_size + 2.0 * button_padding;
540        desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
541
542        let (rect, response) = ui.allocate_at_least(desired_size, sense);
543        response.widget_info(|| {
544            crate::WidgetInfo::labeled(
545                crate::WidgetType::Button,
546                ui.is_enabled(),
547                text_galley.text(),
548            )
549        });
550
551        if ui.is_rect_visible(rect) {
552            let visuals = Self::visuals(ui, &response, menu_state, sub_id);
553            let text_pos = Align2::LEFT_CENTER
554                .align_size_within_rect(text_galley.size(), rect.shrink2(button_padding))
555                .min;
556            let icon_pos = Align2::RIGHT_CENTER
557                .align_size_within_rect(icon_galley.size(), rect.shrink2(button_padding))
558                .min;
559
560            if ui.visuals().button_frame {
561                ui.painter().rect_filled(
562                    rect.expand(visuals.expansion),
563                    visuals.rounding,
564                    visuals.weak_bg_fill,
565                );
566            }
567
568            let text_color = visuals.text_color();
569            ui.painter().galley(text_pos, text_galley, text_color);
570            ui.painter().galley(icon_pos, icon_galley, text_color);
571        }
572        response
573    }
574}
575
576pub struct SubMenu {
577    button: SubMenuButton,
578    parent_state: Arc<RwLock<MenuState>>,
579}
580
581impl SubMenu {
582    fn new(parent_state: Arc<RwLock<MenuState>>, text: impl Into<WidgetText>) -> Self {
583        let index = parent_state.write().next_entry_index();
584        Self {
585            button: SubMenuButton::new(text, "⏵", index),
586            parent_state,
587        }
588    }
589
590    pub fn show<R>(
591        self,
592        ui: &mut Ui,
593        add_contents: impl FnOnce(&mut Ui) -> R,
594    ) -> InnerResponse<Option<R>> {
595        let sub_id = ui.id().with(self.button.index);
596        let response = self.button.show(ui, &self.parent_state.read(), sub_id);
597        self.parent_state
598            .write()
599            .submenu_button_interaction(ui, sub_id, &response);
600        let inner =
601            self.parent_state
602                .write()
603                .show_submenu(ui.ctx(), ui.layer_id(), sub_id, add_contents);
604        InnerResponse::new(inner, response)
605    }
606}
607
608/// Components of menu state, public for advanced usage.
609///
610/// Usually you don't need to use it directly.
611pub struct MenuState {
612    /// The opened sub-menu and its [`Id`]
613    sub_menu: Option<(Id, Arc<RwLock<MenuState>>)>,
614
615    /// Bounding box of this menu (without the sub-menu),
616    /// including the frame and everything.
617    pub rect: Rect,
618
619    /// Used to check if any menu in the tree wants to close
620    pub response: MenuResponse,
621
622    /// Used to hash different [`Id`]s for sub-menus
623    entry_count: usize,
624}
625
626impl MenuState {
627    pub fn new(position: Pos2) -> Self {
628        Self {
629            rect: Rect::from_min_size(position, Vec2::ZERO),
630            sub_menu: None,
631            response: MenuResponse::Stay,
632            entry_count: 0,
633        }
634    }
635
636    /// Close menu hierarchy.
637    pub fn close(&mut self) {
638        self.response = MenuResponse::Close;
639    }
640
641    fn show_submenu<R>(
642        &mut self,
643        ctx: &Context,
644        parent_layer: LayerId,
645        id: Id,
646        add_contents: impl FnOnce(&mut Ui) -> R,
647    ) -> Option<R> {
648        let (sub_response, response) = self.submenu(id).map(|sub| {
649            let inner_response = menu_popup(ctx, parent_layer, sub, id, add_contents);
650            (sub.read().response, inner_response.inner)
651        })?;
652        self.cascade_close_response(sub_response);
653        Some(response)
654    }
655
656    /// Check if position is in the menu hierarchy's area.
657    pub fn area_contains(&self, pos: Pos2) -> bool {
658        self.rect.contains(pos)
659            || self
660                .sub_menu
661                .as_ref()
662                .map_or(false, |(_, sub)| sub.read().area_contains(pos))
663    }
664
665    fn next_entry_index(&mut self) -> usize {
666        self.entry_count += 1;
667        self.entry_count - 1
668    }
669
670    /// Sense button interaction opening and closing submenu.
671    fn submenu_button_interaction(&mut self, ui: &Ui, sub_id: Id, button: &Response) {
672        let pointer = ui.input(|i| i.pointer.clone());
673        let open = self.is_open(sub_id);
674        if self.moving_towards_current_submenu(&pointer) {
675            // We don't close the submenu if the pointer is on its way to hover it.
676            // ensure to repaint once even when pointer is not moving
677            ui.ctx().request_repaint();
678        } else if !open && button.hovered() {
679            // TODO(emilk): open menu to the left if there isn't enough space to the right
680            let mut pos = button.rect.right_top();
681            pos.x = self.rect.right() + ui.spacing().menu_spacing;
682            pos.y -= Frame::menu(ui.style()).total_margin().top; // align the first button in the submenu with the parent button
683
684            self.open_submenu(sub_id, pos);
685        } else if open
686            && ui.interact_bg(Sense::hover()).contains_pointer()
687            && !button.hovered()
688            && !self.hovering_current_submenu(&pointer)
689        {
690            // We are hovering something else in the menu, so close the submenu.
691            self.close_submenu();
692        }
693    }
694
695    /// Check if pointer is moving towards current submenu.
696    fn moving_towards_current_submenu(&self, pointer: &PointerState) -> bool {
697        if pointer.is_still() {
698            return false;
699        }
700
701        if let Some(sub_menu) = self.current_submenu() {
702            if let Some(pos) = pointer.hover_pos() {
703                let rect = sub_menu.read().rect;
704                return rect.intersects_ray(pos, pointer.direction().normalized());
705            }
706        }
707        false
708    }
709
710    /// Check if pointer is hovering current submenu.
711    fn hovering_current_submenu(&self, pointer: &PointerState) -> bool {
712        if let Some(sub_menu) = self.current_submenu() {
713            if let Some(pos) = pointer.hover_pos() {
714                return sub_menu.read().area_contains(pos);
715            }
716        }
717        false
718    }
719
720    /// Cascade close response to menu root.
721    fn cascade_close_response(&mut self, response: MenuResponse) {
722        if response.is_close() {
723            self.response = response;
724        }
725    }
726
727    fn is_open(&self, id: Id) -> bool {
728        self.sub_id() == Some(id)
729    }
730
731    fn sub_id(&self) -> Option<Id> {
732        self.sub_menu.as_ref().map(|(id, _)| *id)
733    }
734
735    fn current_submenu(&self) -> Option<&Arc<RwLock<Self>>> {
736        self.sub_menu.as_ref().map(|(_, sub)| sub)
737    }
738
739    fn submenu(&mut self, id: Id) -> Option<&Arc<RwLock<Self>>> {
740        self.sub_menu
741            .as_ref()
742            .and_then(|(k, sub)| if id == *k { Some(sub) } else { None })
743    }
744
745    /// Open submenu at position, if not already open.
746    fn open_submenu(&mut self, id: Id, pos: Pos2) {
747        if !self.is_open(id) {
748            self.sub_menu = Some((id, Arc::new(RwLock::new(Self::new(pos)))));
749        }
750    }
751
752    fn close_submenu(&mut self) {
753        self.sub_menu = None;
754    }
755}