egui/
menu.rs

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