Skip to main content

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}