1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use freya_core::{
integration::ScopeId,
prelude::*,
};
use torin::prelude::CursorPoint;
use crate::menu::Menu;
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum ContextMenuCloseRequest {
None,
Pending,
}
/// Context for managing a global context menu.
///
/// # Example
///
/// ```rust
/// # use freya::prelude::*;
/// fn app() -> impl IntoElement {
/// rect()
/// .on_secondary_down(move |e: Event<PressEventData>| {
/// ContextMenu::open_from_event(
/// &e,
/// Menu::new().child(MenuButton::new().child("Option 1")),
/// );
/// })
/// .child("Right click to open menu")
/// }
/// ```
#[derive(Clone, Copy, PartialEq)]
pub struct ContextMenu {
pub(crate) location: State<CursorPoint>,
pub(crate) menu: State<Option<(CursorPoint, Menu)>>,
pub(crate) close_request: State<ContextMenuCloseRequest>,
}
impl ContextMenu {
pub fn get() -> Self {
match try_consume_root_context() {
Some(rt) => rt,
None => {
let context_menu_state = ContextMenu {
location: State::create_in_scope(CursorPoint::default(), ScopeId::ROOT),
menu: State::create_in_scope(None, ScopeId::ROOT),
close_request: State::create_in_scope(
ContextMenuCloseRequest::None,
ScopeId::ROOT,
),
};
provide_context_for_scope_id(context_menu_state, ScopeId::ROOT);
context_menu_state
}
}
}
pub fn is_open() -> bool {
Self::get().menu.read().is_some()
}
/// Open the context menu with the given menu.
/// Prefer using [`ContextMenu::open_from_event`] instead as it correctly handles
/// the close behavior based on the source event.
pub fn open(menu: Menu) {
let mut this = Self::get();
this.menu.set(Some(((this.location)(), menu)));
this.close_request.set(ContextMenuCloseRequest::None);
}
/// Open the context menu with the given menu, using the source event to determine
/// the close behavior. When opened from a primary button (left click) press event,
/// the first close request is consumed to prevent the menu from closing immediately.
/// When opened from a secondary button (right click) down event, the menu can be
/// closed with a single click.
pub fn open_from_event(event: &Event<PressEventData>, menu: Menu) {
let mut this = Self::get();
this.menu.set(Some(((this.location)(), menu)));
let close_request = match event.data() {
PressEventData::Mouse(mouse) if mouse.button == Some(MouseButton::Left) => {
ContextMenuCloseRequest::Pending
}
_ => ContextMenuCloseRequest::None,
};
this.close_request.set(close_request);
}
pub fn close() {
Self::get().menu.set(None);
}
}