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