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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//! A canvas's context menu, whose entries answer to their shortcuts too.
use egui::{Button, Context, KeyboardShortcut, Response};
struct Entry<A> {
label: &'static str,
action: A,
shortcut: Option<KeyboardShortcut>,
/// Whether the shortcut acts here, or is only shown beside the entry.
acts: bool,
}
/// The entries of a context menu, and keys that act without one.
pub(crate) struct ContextMenu<A> {
entries: Vec<Entry<A>>,
/// Shortcuts that act without a menu entry of their own.
keys: Vec<(KeyboardShortcut, A)>,
/// No text field has the keyboard, so shortcuts may act: typing into a
/// field must never delete or select on the canvas behind it. A clicked
/// button keeps the focus, so only a text field counts.
keyboard: bool,
}
impl<A: Clone> ContextMenu<A> {
pub(crate) fn new(ctx: &Context) -> Self {
Self {
entries: vec![],
keys: vec![],
keyboard: !ctx.text_edit_focused(),
}
}
/// An entry, whose shortcut acts too.
pub(crate) fn entry(
mut self,
label: &'static str,
action: A,
shortcut: Option<KeyboardShortcut>,
) -> Self {
self.entries.push(Entry {
label,
action,
shortcut,
acts: true,
});
self
}
/// An entry whose shortcut is only shown beside it, because something
/// else handles the key — egui turns the clipboard shortcuts into events
/// of their own.
pub(crate) fn entry_shown(
mut self,
label: &'static str,
action: A,
shortcut: KeyboardShortcut,
) -> Self {
self.entries.push(Entry {
label,
action,
shortcut: Some(shortcut),
acts: false,
});
self
}
/// A shortcut with no entry of its own: a second key for an entry's
/// action, say.
pub(crate) fn key(mut self, shortcut: KeyboardShortcut, action: A) -> Self {
self.keys.push((shortcut, action));
self
}
/// Shows the menu on `response`, and returns the action a click or a
/// shortcut asked for. Shortcuts are tried in the order they were added,
/// so a shifted one has to come before its unshifted twin.
pub(crate) fn show(self, response: &Response) -> Option<A> {
let mut clicked = None;
response.context_menu(|ui| {
for entry in &self.entries {
let mut button = Button::new(entry.label);
if let Some(shortcut) = &entry.shortcut {
button = button.shortcut_text(ui.ctx().format_shortcut(shortcut));
}
if ui.add(button).clicked() {
clicked = Some(entry.action.clone());
return;
}
}
});
if clicked.is_some() || !self.keyboard {
return clicked;
}
let mut acting = self
.entries
.iter()
.filter(|entry| entry.acts)
.filter_map(|entry| Some((entry.shortcut?, &entry.action)))
.chain(
self.keys
.iter()
.map(|(shortcut, action)| (*shortcut, action)),
);
response.ctx.input_mut(|input| {
acting
.find(|(shortcut, _)| input.consume_shortcut(shortcut))
.map(|(_, action)| action.clone())
})
}
}