egui/data/input/modifiers.rs
1use super::ModifierNames;
2
3/// State of the modifier keys. These must be fed to egui.
4///
5/// The best way to compare [`Modifiers`] is by using [`Modifiers::matches_logically`] or [`Modifiers::matches_exact`].
6///
7/// To access the [`Modifiers`] you can use the [`crate::Context::input`] function
8///
9/// ```rust
10/// # let ctx = egui::Context::default();
11/// let modifiers = ctx.input(|i| i.modifiers);
12/// ```
13///
14/// NOTE: For cross-platform uses, ALT+SHIFT is a bad combination of modifiers
15/// as on mac that is how you type special characters,
16/// so those key presses are usually not reported to egui.
17#[derive(Clone, Copy, Default, Hash, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
19pub struct Modifiers {
20 /// Either of the alt keys are down (option ⌥ on Mac).
21 pub alt: bool,
22
23 /// Either of the control keys are down.
24 /// When checking for keyboard shortcuts, consider using [`Self::command`] instead.
25 pub ctrl: bool,
26
27 /// Either of the shift keys are down.
28 pub shift: bool,
29
30 /// The Mac ⌘ Command key. Should always be set to `false` on other platforms.
31 pub mac_cmd: bool,
32
33 /// On Windows and Linux, set this to the same value as `ctrl`.
34 /// On Mac, this should be set whenever one of the ⌘ Command keys are down (same as `mac_cmd`).
35 /// This is so that egui can, for instance, select all text by checking for `command + A`
36 /// and it will work on both Mac and Windows.
37 pub command: bool,
38}
39
40impl core::fmt::Debug for Modifiers {
41 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 if self.is_none() {
43 return write!(f, "Modifiers::NONE");
44 }
45
46 let Self {
47 alt,
48 ctrl,
49 shift,
50 mac_cmd,
51 command,
52 } = *self;
53
54 let mut debug = f.debug_struct("Modifiers");
55 if alt {
56 debug.field("alt", &true);
57 }
58 if ctrl {
59 debug.field("ctrl", &true);
60 }
61 if shift {
62 debug.field("shift", &true);
63 }
64 if mac_cmd {
65 debug.field("mac_cmd", &true);
66 }
67 if command {
68 debug.field("command", &true);
69 }
70 debug.finish()
71 }
72}
73
74impl Modifiers {
75 pub const NONE: Self = Self {
76 alt: false,
77 ctrl: false,
78 shift: false,
79 mac_cmd: false,
80 command: false,
81 };
82
83 pub const ALT: Self = Self {
84 alt: true,
85 ctrl: false,
86 shift: false,
87 mac_cmd: false,
88 command: false,
89 };
90 pub const CTRL: Self = Self {
91 alt: false,
92 ctrl: true,
93 shift: false,
94 mac_cmd: false,
95 command: false,
96 };
97 pub const SHIFT: Self = Self {
98 alt: false,
99 ctrl: false,
100 shift: true,
101 mac_cmd: false,
102 command: false,
103 };
104
105 /// The Mac ⌘ Command key
106 pub const MAC_CMD: Self = Self {
107 alt: false,
108 ctrl: false,
109 shift: false,
110 mac_cmd: true,
111 command: false,
112 };
113
114 /// On Mac: ⌘ Command key, elsewhere: Ctrl key
115 pub const COMMAND: Self = Self {
116 alt: false,
117 ctrl: false,
118 shift: false,
119 mac_cmd: false,
120 command: true,
121 };
122
123 /// ```
124 /// # use egui::Modifiers;
125 /// assert_eq!(
126 /// Modifiers::CTRL | Modifiers::ALT,
127 /// Modifiers { ctrl: true, alt: true, ..Default::default() }
128 /// );
129 /// assert_eq!(
130 /// Modifiers::ALT.plus(Modifiers::CTRL),
131 /// Modifiers::CTRL.plus(Modifiers::ALT),
132 /// );
133 /// assert_eq!(
134 /// Modifiers::CTRL | Modifiers::ALT,
135 /// Modifiers::CTRL.plus(Modifiers::ALT),
136 /// );
137 /// ```
138 #[inline]
139 pub const fn plus(self, rhs: Self) -> Self {
140 Self {
141 alt: self.alt | rhs.alt,
142 ctrl: self.ctrl | rhs.ctrl,
143 shift: self.shift | rhs.shift,
144 mac_cmd: self.mac_cmd | rhs.mac_cmd,
145 command: self.command | rhs.command,
146 }
147 }
148
149 #[inline]
150 pub fn is_none(&self) -> bool {
151 self == &Self::default()
152 }
153
154 #[inline]
155 pub fn any(&self) -> bool {
156 !self.is_none()
157 }
158
159 #[inline]
160 pub fn all(&self) -> bool {
161 self.alt && self.ctrl && self.shift && self.command
162 }
163
164 /// Is shift the only pressed button?
165 #[inline]
166 pub fn shift_only(&self) -> bool {
167 self.shift && !(self.alt || self.command)
168 }
169
170 /// true if only [`Self::ctrl`] or only [`Self::mac_cmd`] is pressed.
171 #[inline]
172 pub fn command_only(&self) -> bool {
173 !self.alt && !self.shift && self.command
174 }
175
176 /// Checks that the `ctrl/cmd` matches, and that the `shift/alt` of the argument is a subset
177 /// of the pressed key (`self`).
178 ///
179 /// This means that if the pattern has not set `shift`, then `self` can have `shift` set or not.
180 ///
181 /// The reason is that many logical keys require `shift` or `alt` on some keyboard layouts.
182 /// For instance, in order to press `+` on an English keyboard, you need to press `shift` and `=`,
183 /// but a Swedish keyboard has dedicated `+` key.
184 /// So if you want to make a [`KeyboardShortcut`](crate::KeyboardShortcut) looking for `Cmd` + `+`, it makes sense
185 /// to ignore the shift key.
186 /// Similarly, the `Alt` key is sometimes used to type special characters.
187 ///
188 /// However, if the pattern (the argument) explicitly requires the `shift` or `alt` keys
189 /// to be pressed, then they must be pressed.
190 ///
191 /// # Example:
192 /// ```
193 /// # use egui::Modifiers;
194 /// # let pressed_modifiers = Modifiers::default();
195 /// if pressed_modifiers.matches_logically(Modifiers::ALT | Modifiers::SHIFT) {
196 /// // Alt and Shift are pressed, but not ctrl/command
197 /// }
198 /// ```
199 ///
200 /// ## Behavior:
201 /// ```
202 /// # use egui::Modifiers;
203 /// assert!(Modifiers::CTRL.matches_logically(Modifiers::CTRL));
204 /// assert!(!Modifiers::CTRL.matches_logically(Modifiers::CTRL | Modifiers::SHIFT));
205 /// assert!((Modifiers::CTRL | Modifiers::SHIFT).matches_logically(Modifiers::CTRL));
206 /// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches_logically(Modifiers::CTRL));
207 /// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches_logically(Modifiers::COMMAND));
208 /// assert!((Modifiers::MAC_CMD | Modifiers::COMMAND).matches_logically(Modifiers::COMMAND));
209 /// assert!(!Modifiers::COMMAND.matches_logically(Modifiers::MAC_CMD));
210 /// ```
211 pub fn matches_logically(&self, pattern: Self) -> bool {
212 if pattern.alt && !self.alt {
213 return false;
214 }
215 if pattern.shift && !self.shift {
216 return false;
217 }
218
219 self.cmd_ctrl_matches(pattern)
220 }
221
222 /// Check for equality but with proper handling of [`Self::command`].
223 ///
224 /// `self` here are the currently pressed modifiers,
225 /// and the argument the pattern we are testing for.
226 ///
227 /// Note that this will require the `shift` and `alt` keys match, even though
228 /// these modifiers are sometimes required to produce some logical keys.
229 /// For instance, to press `+` on an English keyboard, you need to press `shift` and `=`,
230 /// but on a Swedish keyboard you can press the dedicated `+` key.
231 /// Therefore, you often want to use [`Self::matches_logically`] instead.
232 ///
233 /// # Example:
234 /// ```
235 /// # use egui::Modifiers;
236 /// # let pressed_modifiers = Modifiers::default();
237 /// if pressed_modifiers.matches_exact(Modifiers::ALT | Modifiers::SHIFT) {
238 /// // Alt and Shift are pressed, and nothing else
239 /// }
240 /// ```
241 ///
242 /// ## Behavior:
243 /// ```
244 /// # use egui::Modifiers;
245 /// assert!(Modifiers::CTRL.matches_exact(Modifiers::CTRL));
246 /// assert!(!Modifiers::CTRL.matches_exact(Modifiers::CTRL | Modifiers::SHIFT));
247 /// assert!(!(Modifiers::CTRL | Modifiers::SHIFT).matches_exact(Modifiers::CTRL));
248 /// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches_exact(Modifiers::CTRL));
249 /// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches_exact(Modifiers::COMMAND));
250 /// assert!((Modifiers::MAC_CMD | Modifiers::COMMAND).matches_exact(Modifiers::COMMAND));
251 /// assert!(!Modifiers::COMMAND.matches_exact(Modifiers::MAC_CMD));
252 /// ```
253 pub fn matches_exact(&self, pattern: Self) -> bool {
254 // alt and shift must always match the pattern:
255 if pattern.alt != self.alt || pattern.shift != self.shift {
256 return false;
257 }
258
259 self.cmd_ctrl_matches(pattern)
260 }
261
262 /// Check if any of the modifiers match exactly.
263 ///
264 /// Returns true if the same modifier is pressed in `self` as in `pattern`,
265 /// for at least one modifier.
266 ///
267 /// ## Behavior:
268 /// ```
269 /// # use egui::Modifiers;
270 /// assert!(Modifiers::CTRL.matches_any(Modifiers::CTRL));
271 /// assert!(Modifiers::CTRL.matches_any(Modifiers::CTRL | Modifiers::SHIFT));
272 /// assert!((Modifiers::CTRL | Modifiers::SHIFT).matches_any(Modifiers::CTRL));
273 /// ```
274 pub fn matches_any(&self, pattern: Self) -> bool {
275 if self.alt && pattern.alt {
276 return true;
277 }
278 if self.shift && pattern.shift {
279 return true;
280 }
281 if self.ctrl && pattern.ctrl {
282 return true;
283 }
284 if self.mac_cmd && pattern.mac_cmd {
285 return true;
286 }
287 if (self.mac_cmd || self.command || self.ctrl) && pattern.command {
288 return true;
289 }
290 false
291 }
292
293 /// Checks only cmd/ctrl, not alt/shift.
294 ///
295 /// `self` here are the currently pressed modifiers,
296 /// and the argument the pattern we are testing for.
297 ///
298 /// This takes care to properly handle the difference between
299 /// [`Self::ctrl`], [`Self::command`] and [`Self::mac_cmd`].
300 pub fn cmd_ctrl_matches(&self, pattern: Self) -> bool {
301 if pattern.mac_cmd {
302 // Mac-specific match:
303 if !self.mac_cmd {
304 return false;
305 }
306 if pattern.ctrl != self.ctrl {
307 return false;
308 }
309 return true;
310 }
311
312 if !pattern.ctrl && !pattern.command {
313 // the pattern explicitly doesn't want any ctrl/command:
314 return !self.ctrl && !self.command;
315 }
316
317 // if the pattern is looking for command, then `ctrl` may or may not be set depending on platform.
318 // if the pattern is looking for `ctrl`, then `command` may or may not be set depending on platform.
319
320 if pattern.ctrl && !self.ctrl {
321 return false;
322 }
323 if pattern.command && !self.command {
324 return false;
325 }
326
327 true
328 }
329
330 /// Whether another set of modifiers is contained in this set of modifiers with proper handling of [`Self::command`].
331 ///
332 /// ```
333 /// # use egui::Modifiers;
334 /// assert!(Modifiers::default().contains(Modifiers::default()));
335 /// assert!(Modifiers::CTRL.contains(Modifiers::default()));
336 /// assert!(Modifiers::CTRL.contains(Modifiers::CTRL));
337 /// assert!(Modifiers::CTRL.contains(Modifiers::COMMAND));
338 /// assert!(Modifiers::MAC_CMD.contains(Modifiers::COMMAND));
339 /// assert!(Modifiers::COMMAND.contains(Modifiers::MAC_CMD));
340 /// assert!(Modifiers::COMMAND.contains(Modifiers::CTRL));
341 /// assert!(!(Modifiers::ALT | Modifiers::CTRL).contains(Modifiers::SHIFT));
342 /// assert!((Modifiers::CTRL | Modifiers::SHIFT).contains(Modifiers::CTRL));
343 /// assert!(!Modifiers::CTRL.contains(Modifiers::CTRL | Modifiers::SHIFT));
344 /// ```
345 pub fn contains(&self, query: Self) -> bool {
346 if query == Self::default() {
347 return true;
348 }
349
350 let Self {
351 alt,
352 ctrl,
353 shift,
354 mac_cmd,
355 command,
356 } = *self;
357
358 if alt && query.alt {
359 return self.contains(Self {
360 alt: false,
361 ..query
362 });
363 }
364 if shift && query.shift {
365 return self.contains(Self {
366 shift: false,
367 ..query
368 });
369 }
370
371 if (ctrl || command) && (query.ctrl || query.command) {
372 return self.contains(Self {
373 command: false,
374 ctrl: false,
375 ..query
376 });
377 }
378 if (mac_cmd || command) && (query.mac_cmd || query.command) {
379 return self.contains(Self {
380 mac_cmd: false,
381 command: false,
382 ..query
383 });
384 }
385
386 false
387 }
388}
389
390impl core::ops::BitOr for Modifiers {
391 type Output = Self;
392
393 #[inline]
394 fn bitor(self, rhs: Self) -> Self {
395 self.plus(rhs)
396 }
397}
398
399impl core::ops::BitOrAssign for Modifiers {
400 #[inline]
401 fn bitor_assign(&mut self, rhs: Self) {
402 *self = *self | rhs;
403 }
404}
405
406impl Modifiers {
407 pub fn ui(&self, ui: &mut crate::Ui) {
408 ui.label(ModifierNames::NAMES.format(self, ui.ctx().os().is_mac()));
409 }
410}