1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use 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
87pub trait EguiKeyExt: private::Sealed {
96 #[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 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 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 EguiKey::Colon
155 | EguiKey::Pipe
156 | EguiKey::Questionmark
157 | EguiKey::Exclamationmark
158 | EguiKey::Plus
159 | EguiKey::OpenCurlyBracket
160 | EguiKey::CloseCurlyBracket => None,
161
162 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 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 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 EguiKey::BrowserBack => Some(Key::BROWSER_BACK),
241 }
242 }
243}
244
245pub trait EguiModifiersExt: private::Sealed {
262 #[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
292pub trait EguiEventExt: private::Sealed {
302 #[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 #[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 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 #[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 #[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}