Skip to main content

kbd_egui/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! Egui key event conversions for `kbd`.
4//!
5//! This crate converts egui's key events into `kbd`'s unified types so
6//! that GUI key events (from egui) and global hotkey events (from
7//! [`kbd-global`](https://docs.rs/kbd-global)) can feed into the same
8//! [`Dispatcher`](kbd::dispatcher::Dispatcher). This is useful in egui
9//! apps that want both in-window shortcuts and system-wide hotkeys
10//! handled through a single hotkey registry.
11//!
12//! Egui has a smaller, custom key enum that is not 1:1 with the W3C
13//! specification — some physical keys have no egui equivalent, some egui
14//! keys are logical/shifted characters without a single physical key
15//! equivalent (e.g., `Colon`, `Pipe`, `Plus`), and egui combines some
16//! concepts differently.
17//!
18//! # Extension traits
19//!
20//! - [`EguiKeyExt`] — converts an [`egui::Key`] to a [`kbd::key::Key`].
21//! - [`EguiModifiersExt`] — converts [`egui::Modifiers`] to a
22//!   `Vec<Modifier>`.
23//! - [`EguiEventExt`] — converts a full [`egui::Event`] keyboard event
24//!   to a [`kbd::hotkey::Hotkey`].
25//!
26//! # Key mapping
27//!
28//! | egui | kbd | Notes |
29//! |---|---|---|
30//! | `Key::A` – `Key::Z` | [`Key::A`] – [`Key::Z`] | Letters |
31//! | `Key::Num0` – `Key::Num9` | [`Key::DIGIT0`] – [`Key::DIGIT9`] | Digits |
32//! | `Key::F1` – `Key::F35` | [`Key::F1`] – [`Key::F35`] | Function keys |
33//! | `Key::Minus`, `Key::Period`, … | [`Key::MINUS`], [`Key::PERIOD`], … | Physical-position punctuation |
34//! | `Key::ArrowDown`, `Key::Enter`, … | [`Key::ARROW_DOWN`], [`Key::ENTER`], … | Navigation / editing |
35//! | `Key::Copy`, `Key::Cut`, `Key::Paste` | [`Key::COPY`], [`Key::CUT`], [`Key::PASTE`] | Clipboard |
36//! | `Key::Colon`, `Key::Pipe`, `Key::Plus`, … | `None` | Logical/shifted — no single physical key |
37//!
38//! # Modifier mapping
39//!
40//! | egui | kbd | Notes |
41//! |---|---|---|
42//! | `ctrl` | [`Modifier::Ctrl`] | |
43//! | `shift` | [`Modifier::Shift`] | |
44//! | `alt` | [`Modifier::Alt`] | |
45//! | `mac_cmd` | [`Modifier::Super`] | Avoids double-counting with `command` on macOS |
46//!
47//! # Usage
48//!
49//! ```
50//! use egui::{Key as EguiKey, Modifiers};
51//! use kbd::prelude::*;
52//! use kbd_egui::{EguiEventExt, EguiKeyExt, EguiModifiersExt};
53//!
54//! // Single key conversion
55//! let key = EguiKey::A.to_key();
56//! assert_eq!(key, Some(Key::A));
57//!
58//! // Modifier conversion
59//! let mods = Modifiers::CTRL.to_modifiers();
60//! assert_eq!(mods, vec![Modifier::Ctrl]);
61//!
62//! // Full event conversion
63//! let event = egui::Event::Key {
64//!     key: EguiKey::C,
65//!     physical_key: None,
66//!     pressed: true,
67//!     repeat: false,
68//!     modifiers: Modifiers::CTRL,
69//! };
70//! let hotkey = event.to_hotkey();
71//! assert_eq!(hotkey, Some(Hotkey::new(Key::C).modifier(Modifier::Ctrl)));
72//! ```
73
74use egui::Key as EguiKey;
75use egui::Modifiers;
76use kbd::hotkey::Hotkey;
77use kbd::hotkey::Modifier;
78use kbd::key::Key;
79
80mod private {
81    pub trait Sealed {}
82    impl Sealed for egui::Key {}
83    impl Sealed for egui::Modifiers {}
84    impl Sealed for egui::Event {}
85}
86
87/// Convert an [`egui::Key`] to a `kbd` [`Key`].
88///
89/// Returns `None` for egui keys that represent logical/shifted characters
90/// without a single physical key equivalent (e.g., `Colon`, `Pipe`,
91/// `Plus`, `Questionmark`). Most egui keys map directly to a physical
92/// key position.
93///
94/// This trait is sealed and cannot be implemented outside this crate.
95pub trait EguiKeyExt: private::Sealed {
96    /// Convert this egui key to a `kbd` [`Key`], or `None` if unmappable.
97    ///
98    /// # Examples
99    ///
100    /// ```
101    /// use egui::Key as EguiKey;
102    /// use kbd::prelude::*;
103    /// use kbd_egui::EguiKeyExt;
104    ///
105    /// assert_eq!(EguiKey::A.to_key(), Some(Key::A));
106    /// assert_eq!(EguiKey::F5.to_key(), Some(Key::F5));
107    /// // Shifted characters have no physical key equivalent
108    /// assert_eq!(EguiKey::Colon.to_key(), None);
109    /// ```
110    #[must_use]
111    fn to_key(&self) -> Option<Key>;
112}
113
114impl EguiKeyExt for EguiKey {
115    #[allow(clippy::too_many_lines)]
116    fn to_key(&self) -> Option<Key> {
117        match self {
118            // Commands
119            EguiKey::ArrowDown => Some(Key::ARROW_DOWN),
120            EguiKey::ArrowLeft => Some(Key::ARROW_LEFT),
121            EguiKey::ArrowRight => Some(Key::ARROW_RIGHT),
122            EguiKey::ArrowUp => Some(Key::ARROW_UP),
123            EguiKey::Escape => Some(Key::ESCAPE),
124            EguiKey::Tab => Some(Key::TAB),
125            EguiKey::Backspace => Some(Key::BACKSPACE),
126            EguiKey::Enter => Some(Key::ENTER),
127            EguiKey::Space => Some(Key::SPACE),
128            EguiKey::Insert => Some(Key::INSERT),
129            EguiKey::Delete => Some(Key::DELETE),
130            EguiKey::Home => Some(Key::HOME),
131            EguiKey::End => Some(Key::END),
132            EguiKey::PageUp => Some(Key::PAGE_UP),
133            EguiKey::PageDown => Some(Key::PAGE_DOWN),
134            EguiKey::Copy => Some(Key::COPY),
135            EguiKey::Cut => Some(Key::CUT),
136            EguiKey::Paste => Some(Key::PASTE),
137
138            // Punctuation — physical key positions
139            EguiKey::Comma => Some(Key::COMMA),
140            EguiKey::Backslash => Some(Key::BACKSLASH),
141            EguiKey::Slash => Some(Key::SLASH),
142            EguiKey::OpenBracket => Some(Key::BRACKET_LEFT),
143            EguiKey::CloseBracket => Some(Key::BRACKET_RIGHT),
144            EguiKey::Backtick => Some(Key::BACKQUOTE),
145            EguiKey::Minus => Some(Key::MINUS),
146            EguiKey::Period => Some(Key::PERIOD),
147            EguiKey::Equals => Some(Key::EQUAL),
148            EguiKey::Semicolon => Some(Key::SEMICOLON),
149            EguiKey::Quote => Some(Key::QUOTE),
150
151            // Punctuation — logical/shifted characters with no physical key equivalent.
152            // These are produced by Shift+<physical key> and don't correspond to a
153            // single physical key position.
154            EguiKey::Colon
155            | EguiKey::Pipe
156            | EguiKey::Questionmark
157            | EguiKey::Exclamationmark
158            | EguiKey::Plus
159            | EguiKey::OpenCurlyBracket
160            | EguiKey::CloseCurlyBracket => None,
161
162            // Digits
163            EguiKey::Num0 => Some(Key::DIGIT0),
164            EguiKey::Num1 => Some(Key::DIGIT1),
165            EguiKey::Num2 => Some(Key::DIGIT2),
166            EguiKey::Num3 => Some(Key::DIGIT3),
167            EguiKey::Num4 => Some(Key::DIGIT4),
168            EguiKey::Num5 => Some(Key::DIGIT5),
169            EguiKey::Num6 => Some(Key::DIGIT6),
170            EguiKey::Num7 => Some(Key::DIGIT7),
171            EguiKey::Num8 => Some(Key::DIGIT8),
172            EguiKey::Num9 => Some(Key::DIGIT9),
173
174            // Letters
175            EguiKey::A => Some(Key::A),
176            EguiKey::B => Some(Key::B),
177            EguiKey::C => Some(Key::C),
178            EguiKey::D => Some(Key::D),
179            EguiKey::E => Some(Key::E),
180            EguiKey::F => Some(Key::F),
181            EguiKey::G => Some(Key::G),
182            EguiKey::H => Some(Key::H),
183            EguiKey::I => Some(Key::I),
184            EguiKey::J => Some(Key::J),
185            EguiKey::K => Some(Key::K),
186            EguiKey::L => Some(Key::L),
187            EguiKey::M => Some(Key::M),
188            EguiKey::N => Some(Key::N),
189            EguiKey::O => Some(Key::O),
190            EguiKey::P => Some(Key::P),
191            EguiKey::Q => Some(Key::Q),
192            EguiKey::R => Some(Key::R),
193            EguiKey::S => Some(Key::S),
194            EguiKey::T => Some(Key::T),
195            EguiKey::U => Some(Key::U),
196            EguiKey::V => Some(Key::V),
197            EguiKey::W => Some(Key::W),
198            EguiKey::X => Some(Key::X),
199            EguiKey::Y => Some(Key::Y),
200            EguiKey::Z => Some(Key::Z),
201
202            // Function keys
203            EguiKey::F1 => Some(Key::F1),
204            EguiKey::F2 => Some(Key::F2),
205            EguiKey::F3 => Some(Key::F3),
206            EguiKey::F4 => Some(Key::F4),
207            EguiKey::F5 => Some(Key::F5),
208            EguiKey::F6 => Some(Key::F6),
209            EguiKey::F7 => Some(Key::F7),
210            EguiKey::F8 => Some(Key::F8),
211            EguiKey::F9 => Some(Key::F9),
212            EguiKey::F10 => Some(Key::F10),
213            EguiKey::F11 => Some(Key::F11),
214            EguiKey::F12 => Some(Key::F12),
215            EguiKey::F13 => Some(Key::F13),
216            EguiKey::F14 => Some(Key::F14),
217            EguiKey::F15 => Some(Key::F15),
218            EguiKey::F16 => Some(Key::F16),
219            EguiKey::F17 => Some(Key::F17),
220            EguiKey::F18 => Some(Key::F18),
221            EguiKey::F19 => Some(Key::F19),
222            EguiKey::F20 => Some(Key::F20),
223            EguiKey::F21 => Some(Key::F21),
224            EguiKey::F22 => Some(Key::F22),
225            EguiKey::F23 => Some(Key::F23),
226            EguiKey::F24 => Some(Key::F24),
227            EguiKey::F25 => Some(Key::F25),
228            EguiKey::F26 => Some(Key::F26),
229            EguiKey::F27 => Some(Key::F27),
230            EguiKey::F28 => Some(Key::F28),
231            EguiKey::F29 => Some(Key::F29),
232            EguiKey::F30 => Some(Key::F30),
233            EguiKey::F31 => Some(Key::F31),
234            EguiKey::F32 => Some(Key::F32),
235            EguiKey::F33 => Some(Key::F33),
236            EguiKey::F34 => Some(Key::F34),
237            EguiKey::F35 => Some(Key::F35),
238
239            // Browser keys
240            EguiKey::BrowserBack => Some(Key::BROWSER_BACK),
241        }
242    }
243}
244
245/// Convert [`egui::Modifiers`] to a sorted `Vec<Modifier>`.
246///
247/// Egui's `mac_cmd` and `command` fields are platform-dependent
248/// abstractions. On non-macOS platforms, `command` mirrors `ctrl`.
249/// This implementation maps `ctrl`, `shift`, `alt`, and either
250/// `mac_cmd` or `command` (whichever represents the platform's
251/// command key) to `kbd` modifiers.
252///
253/// To avoid double-counting on macOS (where `command` == `mac_cmd`),
254/// we use `ctrl` and `mac_cmd` as the canonical sources:
255/// - `ctrl` → `Modifier::Ctrl`
256/// - `shift` → `Modifier::Shift`
257/// - `alt` → `Modifier::Alt`
258/// - `mac_cmd` → `Modifier::Super`
259///
260/// This trait is sealed and cannot be implemented outside this crate.
261pub trait EguiModifiersExt: private::Sealed {
262    /// Convert these egui modifiers to a `Vec<Modifier>`.
263    ///
264    /// # Examples
265    ///
266    /// ```
267    /// use egui::Modifiers;
268    /// use kbd::prelude::*;
269    /// use kbd_egui::EguiModifiersExt;
270    ///
271    /// let mods = Modifiers {
272    ///     alt: false, ctrl: true, shift: true,
273    ///     mac_cmd: false, command: false,
274    /// };
275    /// assert_eq!(mods.to_modifiers(), vec![Modifier::Ctrl, Modifier::Shift]);
276    /// ```
277    #[must_use]
278    fn to_modifiers(&self) -> Vec<Modifier>;
279}
280
281impl EguiModifiersExt for Modifiers {
282    fn to_modifiers(&self) -> Vec<Modifier> {
283        Modifier::collect_active([
284            (self.ctrl, Modifier::Ctrl),
285            (self.shift, Modifier::Shift),
286            (self.alt, Modifier::Alt),
287            (self.mac_cmd, Modifier::Super),
288        ])
289    }
290}
291
292/// Convert an [`egui::Event`] keyboard event to a `kbd` [`Hotkey`].
293///
294/// Returns `None` if the event is not a keyboard event, or if the key
295/// has no `kbd` equivalent.
296///
297/// Only `Event::Key { .. }` variants produce a hotkey. All other event
298/// variants return `None`.
299///
300/// This trait is sealed and cannot be implemented outside this crate.
301pub trait EguiEventExt: private::Sealed {
302    /// Convert this event to a [`Hotkey`], or `None` if not a keyboard event.
303    ///
304    /// # Examples
305    ///
306    /// ```
307    /// use egui::{Key as EguiKey, Modifiers};
308    /// use kbd::prelude::*;
309    /// use kbd_egui::EguiEventExt;
310    ///
311    /// let event = egui::Event::Key {
312    ///     key: EguiKey::S,
313    ///     physical_key: None,
314    ///     pressed: true,
315    ///     repeat: false,
316    ///     modifiers: Modifiers::CTRL,
317    /// };
318    /// assert_eq!(
319    ///     event.to_hotkey(),
320    ///     Some(Hotkey::new(Key::S).modifier(Modifier::Ctrl)),
321    /// );
322    /// ```
323    #[must_use]
324    fn to_hotkey(&self) -> Option<Hotkey>;
325}
326
327impl EguiEventExt for egui::Event {
328    fn to_hotkey(&self) -> Option<Hotkey> {
329        if let egui::Event::Key { key, modifiers, .. } = self {
330            let kbd_key = key.to_key()?;
331            let mods = modifiers.to_modifiers();
332            Some(Hotkey::with_modifiers(kbd_key, mods))
333        } else {
334            None
335        }
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use egui::Key as EguiKey;
342    use egui::Modifiers;
343    use kbd::hotkey::Hotkey;
344    use kbd::hotkey::Modifier;
345    use kbd::key::Key;
346
347    use super::*;
348
349    // EguiKeyExt tests
350
351    #[test]
352    fn letter_keys() {
353        assert_eq!(EguiKey::A.to_key(), Some(Key::A));
354        assert_eq!(EguiKey::B.to_key(), Some(Key::B));
355        assert_eq!(EguiKey::Z.to_key(), Some(Key::Z));
356    }
357
358    #[test]
359    fn digit_keys() {
360        assert_eq!(EguiKey::Num0.to_key(), Some(Key::DIGIT0));
361        assert_eq!(EguiKey::Num1.to_key(), Some(Key::DIGIT1));
362        assert_eq!(EguiKey::Num9.to_key(), Some(Key::DIGIT9));
363    }
364
365    #[test]
366    fn function_keys() {
367        assert_eq!(EguiKey::F1.to_key(), Some(Key::F1));
368        assert_eq!(EguiKey::F12.to_key(), Some(Key::F12));
369        assert_eq!(EguiKey::F20.to_key(), Some(Key::F20));
370        assert_eq!(EguiKey::F35.to_key(), Some(Key::F35));
371    }
372
373    #[test]
374    fn navigation_keys() {
375        assert_eq!(EguiKey::ArrowDown.to_key(), Some(Key::ARROW_DOWN));
376        assert_eq!(EguiKey::ArrowUp.to_key(), Some(Key::ARROW_UP));
377        assert_eq!(EguiKey::ArrowLeft.to_key(), Some(Key::ARROW_LEFT));
378        assert_eq!(EguiKey::ArrowRight.to_key(), Some(Key::ARROW_RIGHT));
379        assert_eq!(EguiKey::Home.to_key(), Some(Key::HOME));
380        assert_eq!(EguiKey::End.to_key(), Some(Key::END));
381        assert_eq!(EguiKey::PageUp.to_key(), Some(Key::PAGE_UP));
382        assert_eq!(EguiKey::PageDown.to_key(), Some(Key::PAGE_DOWN));
383    }
384
385    #[test]
386    fn command_keys() {
387        assert_eq!(EguiKey::Escape.to_key(), Some(Key::ESCAPE));
388        assert_eq!(EguiKey::Tab.to_key(), Some(Key::TAB));
389        assert_eq!(EguiKey::Backspace.to_key(), Some(Key::BACKSPACE));
390        assert_eq!(EguiKey::Enter.to_key(), Some(Key::ENTER));
391        assert_eq!(EguiKey::Space.to_key(), Some(Key::SPACE));
392        assert_eq!(EguiKey::Insert.to_key(), Some(Key::INSERT));
393        assert_eq!(EguiKey::Delete.to_key(), Some(Key::DELETE));
394    }
395
396    #[test]
397    fn clipboard_keys() {
398        assert_eq!(EguiKey::Copy.to_key(), Some(Key::COPY));
399        assert_eq!(EguiKey::Cut.to_key(), Some(Key::CUT));
400        assert_eq!(EguiKey::Paste.to_key(), Some(Key::PASTE));
401    }
402
403    #[test]
404    fn punctuation_keys() {
405        assert_eq!(EguiKey::Minus.to_key(), Some(Key::MINUS));
406        assert_eq!(EguiKey::Period.to_key(), Some(Key::PERIOD));
407        assert_eq!(EguiKey::Comma.to_key(), Some(Key::COMMA));
408        assert_eq!(EguiKey::Semicolon.to_key(), Some(Key::SEMICOLON));
409        assert_eq!(EguiKey::Backslash.to_key(), Some(Key::BACKSLASH));
410        assert_eq!(EguiKey::Slash.to_key(), Some(Key::SLASH));
411        assert_eq!(EguiKey::Backtick.to_key(), Some(Key::BACKQUOTE));
412        assert_eq!(EguiKey::OpenBracket.to_key(), Some(Key::BRACKET_LEFT));
413        assert_eq!(EguiKey::CloseBracket.to_key(), Some(Key::BRACKET_RIGHT));
414        assert_eq!(EguiKey::Equals.to_key(), Some(Key::EQUAL));
415        assert_eq!(EguiKey::Quote.to_key(), Some(Key::QUOTE));
416    }
417
418    #[test]
419    fn browser_back_key() {
420        assert_eq!(EguiKey::BrowserBack.to_key(), Some(Key::BROWSER_BACK));
421    }
422
423    #[test]
424    fn keys_with_no_physical_equivalent_return_none() {
425        // Egui has some keys that don't map cleanly to physical positions.
426        // Colon, Pipe, Questionmark, Exclamationmark, Plus are logical/shifted
427        // characters, not physical key positions.
428        assert_eq!(EguiKey::Colon.to_key(), None);
429        assert_eq!(EguiKey::Pipe.to_key(), None);
430        assert_eq!(EguiKey::Questionmark.to_key(), None);
431        assert_eq!(EguiKey::Exclamationmark.to_key(), None);
432        assert_eq!(EguiKey::Plus.to_key(), None);
433        assert_eq!(EguiKey::OpenCurlyBracket.to_key(), None);
434        assert_eq!(EguiKey::CloseCurlyBracket.to_key(), None);
435    }
436
437    // EguiModifiersExt tests
438
439    #[test]
440    fn empty_modifiers() {
441        assert_eq!(Modifiers::NONE.to_modifiers(), Vec::<Modifier>::new());
442    }
443
444    #[test]
445    fn single_ctrl_modifier() {
446        assert_eq!(Modifiers::CTRL.to_modifiers(), vec![Modifier::Ctrl]);
447    }
448
449    #[test]
450    fn single_shift_modifier() {
451        assert_eq!(Modifiers::SHIFT.to_modifiers(), vec![Modifier::Shift]);
452    }
453
454    #[test]
455    fn single_alt_modifier() {
456        assert_eq!(Modifiers::ALT.to_modifiers(), vec![Modifier::Alt]);
457    }
458
459    #[test]
460    fn mac_cmd_maps_to_super() {
461        let mods = Modifiers {
462            alt: false,
463            ctrl: false,
464            shift: false,
465            mac_cmd: true,
466            command: true,
467        };
468        assert_eq!(mods.to_modifiers(), vec![Modifier::Super]);
469    }
470
471    #[test]
472    fn combined_modifiers() {
473        let mods = Modifiers {
474            alt: false,
475            ctrl: true,
476            shift: true,
477            mac_cmd: false,
478            command: false,
479        };
480        assert_eq!(mods.to_modifiers(), vec![Modifier::Ctrl, Modifier::Shift]);
481    }
482
483    #[test]
484    fn all_modifiers() {
485        let mods = Modifiers {
486            alt: true,
487            ctrl: true,
488            shift: true,
489            mac_cmd: true,
490            command: true,
491        };
492        assert_eq!(
493            mods.to_modifiers(),
494            vec![
495                Modifier::Ctrl,
496                Modifier::Shift,
497                Modifier::Alt,
498                Modifier::Super,
499            ]
500        );
501    }
502
503    // EguiEventExt tests
504
505    #[test]
506    fn key_event_to_hotkey() {
507        let event = egui::Event::Key {
508            key: EguiKey::C,
509            physical_key: None,
510            pressed: true,
511            repeat: false,
512            modifiers: Modifiers::NONE,
513        };
514        assert_eq!(event.to_hotkey(), Some(Hotkey::new(Key::C)));
515    }
516
517    #[test]
518    fn key_event_with_ctrl() {
519        let event = egui::Event::Key {
520            key: EguiKey::C,
521            physical_key: None,
522            pressed: true,
523            repeat: false,
524            modifiers: Modifiers::CTRL,
525        };
526        assert_eq!(
527            event.to_hotkey(),
528            Some(Hotkey::new(Key::C).modifier(Modifier::Ctrl))
529        );
530    }
531
532    #[test]
533    fn key_event_with_multiple_modifiers() {
534        let event = egui::Event::Key {
535            key: EguiKey::A,
536            physical_key: None,
537            pressed: true,
538            repeat: false,
539            modifiers: Modifiers {
540                alt: false,
541                ctrl: true,
542                shift: true,
543                mac_cmd: false,
544                command: false,
545            },
546        };
547        assert_eq!(
548            event.to_hotkey(),
549            Some(
550                Hotkey::new(Key::A)
551                    .modifier(Modifier::Ctrl)
552                    .modifier(Modifier::Shift)
553            )
554        );
555    }
556
557    #[test]
558    fn non_key_event_returns_none() {
559        let event = egui::Event::PointerMoved(egui::pos2(10.0, 20.0));
560        assert_eq!(event.to_hotkey(), None);
561    }
562
563    #[test]
564    fn unmappable_key_event_returns_none() {
565        let event = egui::Event::Key {
566            key: EguiKey::Colon,
567            physical_key: None,
568            pressed: true,
569            repeat: false,
570            modifiers: Modifiers::NONE,
571        };
572        assert_eq!(event.to_hotkey(), None);
573    }
574
575    #[test]
576    fn ctrl_shift_f5() {
577        let event = egui::Event::Key {
578            key: EguiKey::F5,
579            physical_key: None,
580            pressed: true,
581            repeat: false,
582            modifiers: Modifiers {
583                alt: false,
584                ctrl: true,
585                shift: true,
586                mac_cmd: false,
587                command: false,
588            },
589        };
590        assert_eq!(
591            event.to_hotkey(),
592            Some(
593                Hotkey::new(Key::F5)
594                    .modifier(Modifier::Ctrl)
595                    .modifier(Modifier::Shift)
596            )
597        );
598    }
599
600    #[test]
601    fn space_event() {
602        let event = egui::Event::Key {
603            key: EguiKey::Space,
604            physical_key: None,
605            pressed: true,
606            repeat: false,
607            modifiers: Modifiers::NONE,
608        };
609        assert_eq!(event.to_hotkey(), Some(Hotkey::new(Key::SPACE)));
610    }
611}