1use std::fmt;
58use std::hash::Hash;
59use std::str::FromStr;
60
61use iced::Subscription;
62use iced::advanced::subscription::{self as recipe, EventStream, Hasher, Recipe};
63use iced::event::Status;
64use iced::futures::{StreamExt, future};
65use iced::keyboard::{self, Key, Modifiers, key::Named};
66use iced::{mouse, window};
67
68#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Event {
76 pub key: Key,
77 pub modifiers: Modifiers,
78}
79
80pub fn subscription() -> Subscription<Event> {
83 recipe::from_recipe(Listen)
84}
85
86struct Listen;
87
88#[cfg(not(target_arch = "wasm32"))]
89type Stream = iced::futures::stream::BoxStream<'static, Event>;
90#[cfg(target_arch = "wasm32")]
91type Stream = iced::futures::stream::LocalBoxStream<'static, Event>;
92
93impl Recipe for Listen {
94 type Output = Event;
95
96 fn hash(&self, state: &mut Hasher) {
97 std::any::TypeId::of::<Self>().hash(state);
98 }
99
100 fn stream(self: Box<Self>, input: EventStream) -> Stream {
101 let mut tracker = Tracker::default();
102 Box::pin(input.filter_map(move |event| {
103 let recipe::Event::Interaction { event, status, .. } = event else {
104 return future::ready(None);
105 };
106 future::ready(tracker.update(&event, status == Status::Captured))
107 }))
108 }
109}
110
111#[derive(Debug, Clone, Default, PartialEq, Eq)]
115pub struct Tracker {
116 held: Option<Named>,
117}
118
119impl Tracker {
120 pub fn new() -> Self {
121 Self::default()
122 }
123
124 pub fn update(&mut self, event: &iced::Event, captured: bool) -> Option<Event> {
128 match event {
129 iced::Event::Keyboard(keyboard::Event::KeyPressed {
130 key,
131 modifiers,
132 repeat,
133 ..
134 }) => self.pressed(key, *modifiers, *repeat, captured),
135 iced::Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
136 self.released(key, captured)
137 }
138 iced::Event::Mouse(
139 mouse::Event::ButtonPressed(_) | mouse::Event::WheelScrolled { .. },
140 )
141 | iced::Event::Window(window::Event::Unfocused) => {
142 self.held = None;
143 None
144 }
145 _ => None,
146 }
147 }
148
149 fn pressed(
150 &mut self,
151 key: &Key,
152 modifiers: Modifiers,
153 repeat: bool,
154 captured: bool,
155 ) -> Option<Event> {
156 let Some(modifier) = modifier_key(key) else {
157 self.held = None;
158 return (!captured).then(|| Event {
159 key: key.clone(),
160 modifiers,
161 });
162 };
163 if repeat && self.held == Some(modifier) {
164 return None;
165 }
166 let others = modifiers.difference(modifier_flag(modifier));
167 self.held = (self.held.is_none() && others.is_empty()).then_some(modifier);
168 None
169 }
170
171 fn released(&mut self, key: &Key, captured: bool) -> Option<Event> {
172 let modifier = modifier_key(key)?;
173 let lone = self.held.take() == Some(modifier);
174 (lone && !captured).then(|| Event {
175 key: Key::Named(modifier),
176 modifiers: Modifiers::empty(),
177 })
178 }
179}
180
181fn modifier_key(key: &Key) -> Option<Named> {
182 match key {
183 Key::Named(
184 named @ (Named::Alt
185 | Named::AltGraph
186 | Named::Control
187 | Named::Shift
188 | Named::Super
189 | Named::Meta),
190 ) => Some(*named),
191 _ => None,
192 }
193}
194
195fn modifier_flag(key: Named) -> Modifiers {
196 match key {
197 Named::Alt | Named::AltGraph => Modifiers::ALT,
198 Named::Control => Modifiers::CTRL,
199 Named::Shift => Modifiers::SHIFT,
200 Named::Super | Named::Meta => Modifiers::LOGO,
201 _ => Modifiers::empty(),
202 }
203}
204
205const NAMED: &[(Named, &[&str])] = &[
207 (Named::Enter, &["Enter", "Return"]),
208 (Named::Tab, &["Tab"]),
209 (Named::Space, &["Space", "Spacebar"]),
210 (Named::Escape, &["Escape", "Esc"]),
211 (Named::Backspace, &["Backspace"]),
212 (Named::Delete, &["Delete", "Del"]),
213 (Named::Insert, &["Insert", "Ins"]),
214 (Named::ArrowUp, &["ArrowUp", "Up"]),
215 (Named::ArrowDown, &["ArrowDown", "Down"]),
216 (Named::ArrowLeft, &["ArrowLeft", "Left"]),
217 (Named::ArrowRight, &["ArrowRight", "Right"]),
218 (Named::Home, &["Home"]),
219 (Named::End, &["End"]),
220 (Named::PageUp, &["PageUp", "PgUp"]),
221 (Named::PageDown, &["PageDown", "PgDn"]),
222 (Named::ContextMenu, &["Menu", "ContextMenu", "Apps"]),
223 (Named::F1, &["F1"]),
224 (Named::F2, &["F2"]),
225 (Named::F3, &["F3"]),
226 (Named::F4, &["F4"]),
227 (Named::F5, &["F5"]),
228 (Named::F6, &["F6"]),
229 (Named::F7, &["F7"]),
230 (Named::F8, &["F8"]),
231 (Named::F9, &["F9"]),
232 (Named::F10, &["F10"]),
233 (Named::F11, &["F11"]),
234 (Named::F12, &["F12"]),
235];
236
237const LOGO: &str = if cfg!(target_os = "macos") {
239 "Cmd"
240} else {
241 "Super"
242};
243
244#[derive(Debug, Clone, PartialEq, Eq, Hash)]
262pub struct Chord {
263 key: Key,
264 modifiers: Modifiers,
265}
266
267impl Chord {
268 pub fn new(key: Key, modifiers: Modifiers) -> Self {
270 Self {
271 key: normalise(key),
272 modifiers,
273 }
274 }
275
276 pub fn named(key: Named) -> Self {
278 Self::new(Key::Named(key), Modifiers::empty())
279 }
280
281 pub fn character(key: char) -> Self {
283 Self::new(Key::Character(key.to_string().into()), Modifiers::empty())
284 }
285
286 pub fn ctrl(self) -> Self {
287 self.with(Modifiers::CTRL)
288 }
289
290 pub fn shift(self) -> Self {
291 self.with(Modifiers::SHIFT)
292 }
293
294 pub fn alt(self) -> Self {
295 self.with(Modifiers::ALT)
296 }
297
298 pub fn logo(self) -> Self {
300 self.with(Modifiers::LOGO)
301 }
302
303 pub fn command(self) -> Self {
305 self.with(Modifiers::COMMAND)
306 }
307
308 fn with(mut self, modifiers: Modifiers) -> Self {
309 self.modifiers |= modifiers;
310 self
311 }
312
313 pub fn key(&self) -> &Key {
314 &self.key
315 }
316
317 pub fn modifiers(&self) -> Modifiers {
318 self.modifiers
319 }
320
321 pub fn matches(&self, key: &Key, modifiers: Modifiers) -> bool {
323 self.modifiers == modifiers && same_key(&self.key, key)
324 }
325}
326
327fn normalise(key: Key) -> Key {
328 match key {
329 Key::Character(text) => Key::Character(text.to_lowercase().into()),
330 key => key,
331 }
332}
333
334fn same_key(chord: &Key, pressed: &Key) -> bool {
335 match (chord, pressed) {
336 (Key::Character(a), Key::Character(b)) => a.as_str() == b.to_lowercase(),
337 (a, b) => a == b,
338 }
339}
340
341#[derive(Debug, Clone, PartialEq, Eq)]
343pub enum ParseChordError {
344 Empty,
345 MissingKey,
347 UnknownModifier(String),
348 UnknownKey(String),
349}
350
351impl fmt::Display for ParseChordError {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353 match self {
354 Self::Empty => write!(f, "empty key chord"),
355 Self::MissingKey => write!(f, "key chord has an empty part"),
356 Self::UnknownModifier(name) => write!(f, "unknown modifier \"{name}\""),
357 Self::UnknownKey(name) => write!(f, "unknown key \"{name}\""),
358 }
359 }
360}
361
362impl std::error::Error for ParseChordError {}
363
364impl FromStr for Chord {
365 type Err = ParseChordError;
366
367 fn from_str(value: &str) -> Result<Self, Self::Err> {
368 let value = value.trim();
369 if value.is_empty() {
370 return Err(ParseChordError::Empty);
371 }
372
373 let (modifiers, key) = match value.strip_suffix("++") {
375 Some(rest) => (Some(rest), "+"),
376 None if value == "+" => (None, "+"),
377 None => match value.rsplit_once('+') {
378 Some((rest, key)) => (Some(rest), key),
379 None => (None, value),
380 },
381 };
382
383 let mut chord = Chord::new(parse_key(key.trim())?, Modifiers::empty());
384 let Some(modifiers) = modifiers else {
385 return Ok(chord);
386 };
387 for name in modifiers.split('+') {
388 chord = chord.with(parse_modifier(name.trim())?);
389 }
390 Ok(chord)
391 }
392}
393
394impl TryFrom<&str> for Chord {
395 type Error = ParseChordError;
396
397 fn try_from(value: &str) -> Result<Self, Self::Error> {
398 value.parse()
399 }
400}
401
402fn parse_key(name: &str) -> Result<Key, ParseChordError> {
403 if name.is_empty() {
404 return Err(ParseChordError::MissingKey);
405 }
406 if let Some((named, _)) = NAMED
407 .iter()
408 .find(|(_, names)| names.iter().any(|alias| alias.eq_ignore_ascii_case(name)))
409 {
410 return Ok(Key::Named(*named));
411 }
412 let mut chars = name.chars();
413 match (chars.next(), chars.next()) {
414 (Some(_), None) => Ok(Key::Character(name.into())),
415 _ => Err(ParseChordError::UnknownKey(name.to_owned())),
416 }
417}
418
419fn parse_modifier(name: &str) -> Result<Modifiers, ParseChordError> {
420 if name.is_empty() {
421 return Err(ParseChordError::MissingKey);
422 }
423 let modifiers = match name.to_ascii_lowercase().as_str() {
424 "ctrl" | "control" => Modifiers::CTRL,
425 "shift" => Modifiers::SHIFT,
426 "alt" | "option" | "opt" => Modifiers::ALT,
427 "cmd" | "command" | "super" | "meta" | "logo" | "win" => Modifiers::LOGO,
428 "mod" => Modifiers::COMMAND,
429 _ => return Err(ParseChordError::UnknownModifier(name.to_owned())),
430 };
431 Ok(modifiers)
432}
433
434impl fmt::Display for Chord {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 for (modifier, name) in [
437 (Modifiers::CTRL, "Ctrl"),
438 (Modifiers::ALT, "Alt"),
439 (Modifiers::SHIFT, "Shift"),
440 (Modifiers::LOGO, LOGO),
441 ] {
442 if self.modifiers.contains(modifier) {
443 write!(f, "{name}+")?;
444 }
445 }
446 match &self.key {
447 Key::Named(named) => match NAMED.iter().find(|(key, _)| key == named) {
448 Some((_, names)) => write!(f, "{}", names.first().copied().unwrap_or_default()),
449 None => write!(f, "{named:?}"),
450 },
451 Key::Character(text) => write!(f, "{}", text.to_uppercase()),
452 Key::Unidentified => write!(f, "Unidentified"),
453 }
454 }
455}
456
457pub trait Action: Copy + PartialEq + fmt::Debug + 'static {
459 const ALL: &'static [Self];
461
462 fn defaults() -> Keymap<Self>;
464
465 fn name(self) -> &'static str;
467
468 fn description(self) -> &'static str;
470}
471
472#[derive(Debug, Clone, PartialEq)]
475pub struct Keymap<A> {
476 bindings: Vec<(Chord, A)>,
477}
478
479impl<A> Default for Keymap<A> {
480 fn default() -> Self {
481 Self {
482 bindings: Vec::new(),
483 }
484 }
485}
486
487impl<A> Keymap<A> {
488 pub fn new() -> Self {
490 Self::default()
491 }
492
493 pub fn bind(mut self, chord: Chord, action: A) -> Self {
495 match self.bindings.iter_mut().find(|(bound, _)| *bound == chord) {
496 Some(binding) => binding.1 = action,
497 None => self.bindings.push((chord, action)),
498 }
499 self
500 }
501
502 pub fn unbind(mut self, chord: &Chord) -> Self {
504 self.bindings.retain(|(bound, _)| bound != chord);
505 self
506 }
507
508 pub fn clear(mut self) -> Self {
510 self.bindings.clear();
511 self
512 }
513
514 pub fn bindings(&self) -> impl Iterator<Item = (&Chord, &A)> {
516 self.bindings.iter().map(|(chord, action)| (chord, action))
517 }
518
519 pub fn len(&self) -> usize {
520 self.bindings.len()
521 }
522
523 pub fn is_empty(&self) -> bool {
524 self.bindings.is_empty()
525 }
526}
527
528impl<A: Clone> Keymap<A> {
529 pub fn resolve(&self, key: &Key, modifiers: Modifiers) -> Option<A> {
531 self.bindings
532 .iter()
533 .find(|(chord, _)| chord.matches(key, modifiers))
534 .map(|(_, action)| action.clone())
535 }
536
537 pub fn resolve_event(&self, event: &Event) -> Option<A> {
539 self.resolve(&event.key, event.modifiers)
540 }
541}
542
543impl<A: Clone + PartialEq> Keymap<A> {
544 pub fn chords(&self, action: &A) -> Vec<&Chord> {
546 self.bindings
547 .iter()
548 .filter(|(_, bound)| bound == action)
549 .map(|(chord, _)| chord)
550 .collect()
551 }
552
553 pub fn unbind_action(mut self, action: &A) -> Self {
555 self.bindings.retain(|(_, bound)| bound != action);
556 self
557 }
558
559 pub fn grouped(&self) -> Vec<(A, Vec<Chord>)> {
561 let mut groups: Vec<(A, Vec<Chord>)> = Vec::new();
562 for (chord, action) in &self.bindings {
563 match groups.iter_mut().find(|(bound, _)| bound == action) {
564 Some((_, chords)) => chords.push(chord.clone()),
565 None => groups.push((action.clone(), vec![chord.clone()])),
566 }
567 }
568 groups
569 }
570}
571
572impl<A: Action> Keymap<A> {
573 pub fn defaults() -> Self {
575 A::defaults()
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582
583 fn chord(value: &str) -> Chord {
584 value.parse().unwrap()
585 }
586
587 fn character(value: &str) -> Key {
588 Key::Character(value.into())
589 }
590
591 #[test]
592 fn parses_named_keys_with_modifiers() {
593 let parsed = chord("Ctrl+Shift+Tab");
594 assert_eq!(parsed.key(), &Key::Named(Named::Tab));
595 assert_eq!(parsed.modifiers(), Modifiers::CTRL | Modifiers::SHIFT);
596 assert_eq!(chord("Escape"), Chord::named(Named::Escape));
597 assert_eq!(chord("ArrowRight"), Chord::named(Named::ArrowRight));
598 }
599
600 #[test]
601 fn parsing_ignores_case_and_accepts_aliases() {
602 assert_eq!(chord("ctrl+k"), chord("Ctrl+K"));
603 assert_eq!(chord("CONTROL + K"), chord("Ctrl+K"));
604 assert_eq!(chord("esc"), Chord::named(Named::Escape));
605 assert_eq!(chord("right"), Chord::named(Named::ArrowRight));
606 assert_eq!(chord("Option+PgDn"), Chord::named(Named::PageDown).alt());
607 assert_eq!(chord("Meta+S"), Chord::character('s').logo());
608 assert_eq!(chord("ContextMenu"), Chord::named(Named::ContextMenu));
609 assert_eq!(chord("Menu").to_string(), "Menu");
610 }
611
612 #[test]
613 fn mod_is_the_platform_command_key() {
614 let parsed = chord("Mod+K");
615 assert_eq!(parsed.modifiers(), Modifiers::COMMAND);
616 if cfg!(target_os = "macos") {
617 assert_eq!(parsed, chord("Cmd+K"));
618 } else {
619 assert_eq!(parsed, chord("Ctrl+K"));
620 }
621 }
622
623 #[test]
624 fn plus_can_be_the_key() {
625 assert_eq!(chord("+").key(), &character("+"));
626 let zoom = chord("Ctrl++");
627 assert_eq!(zoom.key(), &character("+"));
628 assert_eq!(zoom.modifiers(), Modifiers::CTRL);
629 }
630
631 #[test]
632 fn rejects_invalid_chords() {
633 assert_eq!("".parse::<Chord>(), Err(ParseChordError::Empty));
634 assert_eq!(" ".parse::<Chord>(), Err(ParseChordError::Empty));
635 assert_eq!("Ctrl+".parse::<Chord>(), Err(ParseChordError::MissingKey));
636 assert_eq!("Ctrl++K".parse::<Chord>(), Err(ParseChordError::MissingKey));
637 assert_eq!(
638 "Hyper+K".parse::<Chord>(),
639 Err(ParseChordError::UnknownModifier("Hyper".into()))
640 );
641 assert_eq!(
642 "Ctrl+Banana".parse::<Chord>(),
643 Err(ParseChordError::UnknownKey("Banana".into()))
644 );
645 }
646
647 #[test]
648 fn displays_in_canonical_form() {
649 assert_eq!(chord("shift+ctrl+tab").to_string(), "Ctrl+Shift+Tab");
650 assert_eq!(chord("ctrl+k").to_string(), "Ctrl+K");
651 assert_eq!(chord("esc").to_string(), "Escape");
652 assert_eq!(chord("alt+up").to_string(), "Alt+ArrowUp");
653 assert_eq!(chord("Ctrl++").to_string(), "Ctrl++");
654 }
655
656 #[test]
657 fn display_round_trips() {
658 for value in ["Ctrl+Alt+Shift+Cmd+F5", "Space", "Mod+/", "Shift+End", "+"] {
659 let parsed = chord(value);
660 assert_eq!(chord(&parsed.to_string()), parsed, "{value}");
661 }
662 }
663
664 #[test]
665 fn matches_exact_modifiers_and_any_case() {
666 let save = chord("Ctrl+S");
667 assert!(save.matches(&character("s"), Modifiers::CTRL));
668 assert!(save.matches(&character("S"), Modifiers::CTRL));
669 assert!(!save.matches(&character("s"), Modifiers::empty()));
670 assert!(!save.matches(&character("s"), Modifiers::CTRL | Modifiers::SHIFT));
671 assert!(!save.matches(&Key::Named(Named::Tab), Modifiers::CTRL));
672 assert!(chord("Tab").matches(&Key::Named(Named::Tab), Modifiers::empty()));
673 }
674
675 #[derive(Debug, Clone, Copy, PartialEq)]
676 enum Test {
677 Open,
678 Close,
679 }
680
681 fn keymap() -> Keymap<Test> {
682 Keymap::new()
683 .bind(chord("Ctrl+O"), Test::Open)
684 .bind(chord("Enter"), Test::Open)
685 .bind(chord("Escape"), Test::Close)
686 }
687
688 #[test]
689 fn resolves_bound_chords_only() {
690 let keymap = keymap();
691 assert_eq!(
692 keymap.resolve(&character("o"), Modifiers::CTRL),
693 Some(Test::Open)
694 );
695 assert_eq!(
696 keymap.resolve(&Key::Named(Named::Escape), Modifiers::empty()),
697 Some(Test::Close)
698 );
699 assert_eq!(keymap.resolve(&character("o"), Modifiers::empty()), None);
700 let event = Event {
701 key: Key::Named(Named::Enter),
702 modifiers: Modifiers::empty(),
703 };
704 assert_eq!(keymap.resolve_event(&event), Some(Test::Open));
705 }
706
707 #[test]
708 fn bind_replaces_an_existing_chord() {
709 let keymap = keymap().bind(chord("Enter"), Test::Close);
710 assert_eq!(keymap.len(), 3);
711 assert_eq!(
712 keymap.resolve(&Key::Named(Named::Enter), Modifiers::empty()),
713 Some(Test::Close)
714 );
715 }
716
717 #[test]
718 fn unbind_and_clear() {
719 let keymap = keymap().unbind(&chord("Escape"));
720 assert_eq!(
721 keymap.resolve(&Key::Named(Named::Escape), Modifiers::empty()),
722 None
723 );
724 assert_eq!(keymap.len(), 2);
725 let unchanged = keymap.clone().unbind(&chord("F1"));
726 assert_eq!(unchanged, keymap);
727 assert!(keymap.clear().is_empty());
728 }
729
730 #[test]
731 fn chords_grouped_and_unbound_by_action() {
732 let keymap = keymap();
733 assert_eq!(keymap.chords(&Test::Open).len(), 2);
734 let grouped = keymap.grouped();
735 assert_eq!(grouped.len(), 2);
736 assert_eq!(grouped[0].0, Test::Open);
737 assert_eq!(grouped[0].1, vec![chord("Ctrl+O"), chord("Enter")]);
738 assert_eq!(grouped[1], (Test::Close, vec![chord("Escape")]));
739 let closed = keymap.unbind_action(&Test::Open);
740 assert_eq!(closed.len(), 1);
741 }
742
743 fn press(key: Key, modifiers: Modifiers) -> iced::Event {
744 press_repeat(key, modifiers, false)
745 }
746
747 fn press_repeat(key: Key, modifiers: Modifiers, repeat: bool) -> iced::Event {
748 iced::Event::Keyboard(keyboard::Event::KeyPressed {
749 modified_key: key.clone(),
750 key,
751 physical_key: keyboard::key::Physical::Unidentified(
752 keyboard::key::NativeCode::Unidentified,
753 ),
754 location: keyboard::Location::Standard,
755 modifiers,
756 text: None,
757 repeat,
758 })
759 }
760
761 fn release(key: Key, modifiers: Modifiers) -> iced::Event {
762 iced::Event::Keyboard(keyboard::Event::KeyReleased {
763 modified_key: key.clone(),
764 key,
765 physical_key: keyboard::key::Physical::Unidentified(
766 keyboard::key::NativeCode::Unidentified,
767 ),
768 location: keyboard::Location::Standard,
769 modifiers,
770 })
771 }
772
773 fn feed(events: &[iced::Event]) -> Vec<Event> {
774 let mut tracker = Tracker::new();
775 events
776 .iter()
777 .filter_map(|event| tracker.update(event, false))
778 .collect()
779 }
780
781 fn alt() -> Key {
782 Key::Named(Named::Alt)
783 }
784
785 fn lone(named: Named) -> Event {
786 Event {
787 key: Key::Named(named),
788 modifiers: Modifiers::empty(),
789 }
790 }
791
792 #[test]
793 fn a_lone_modifier_arrives_on_release() {
794 let events = feed(&[
795 press(alt(), Modifiers::ALT),
796 release(alt(), Modifiers::empty()),
797 ]);
798 assert_eq!(events, vec![lone(Named::Alt)]);
799 }
800
801 #[test]
802 fn a_modifier_used_in_a_chord_is_not_lone() {
803 let up = Key::Named(Named::ArrowUp);
804 let events = feed(&[
805 press(alt(), Modifiers::ALT),
806 press(up.clone(), Modifiers::ALT),
807 release(up.clone(), Modifiers::ALT),
808 release(alt(), Modifiers::empty()),
809 ]);
810 assert_eq!(
811 events,
812 vec![Event {
813 key: up,
814 modifiers: Modifiers::ALT
815 }]
816 );
817 }
818
819 #[test]
820 fn key_repeat_keeps_a_modifier_lone() {
821 let events = feed(&[
822 press(alt(), Modifiers::ALT),
823 press_repeat(alt(), Modifiers::ALT, true),
824 release(alt(), Modifiers::empty()),
825 ]);
826 assert_eq!(events, vec![lone(Named::Alt)]);
827 }
828
829 #[test]
830 fn two_modifiers_together_are_not_lone() {
831 let ctrl = Key::Named(Named::Control);
832 let events = feed(&[
833 press(ctrl.clone(), Modifiers::CTRL),
834 press(alt(), Modifiers::CTRL | Modifiers::ALT),
835 release(alt(), Modifiers::CTRL),
836 release(ctrl, Modifiers::empty()),
837 ]);
838 assert!(events.is_empty());
839 }
840
841 #[test]
842 fn a_click_while_held_stops_a_modifier_being_lone() {
843 let events = feed(&[
844 press(alt(), Modifiers::ALT),
845 iced::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
846 release(alt(), Modifiers::empty()),
847 ]);
848 assert!(events.is_empty());
849 }
850
851 #[test]
852 fn captured_keys_are_not_delivered_but_still_break_a_lone_modifier() {
853 let mut tracker = Tracker::new();
854 let up = Key::Named(Named::ArrowUp);
855 assert_eq!(tracker.update(&press(alt(), Modifiers::ALT), false), None);
856 assert_eq!(tracker.update(&press(up, Modifiers::ALT), true), None);
857 assert_eq!(
858 tracker.update(&release(alt(), Modifiers::empty()), false),
859 None
860 );
861 }
862
863 #[test]
864 fn ordinary_presses_pass_through_unchanged() {
865 let events = feed(&[press(character("k"), Modifiers::CTRL)]);
866 assert_eq!(
867 events,
868 vec![Event {
869 key: character("k"),
870 modifiers: Modifiers::CTRL
871 }]
872 );
873 }
874}