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