diff --git a/CHANGELOG.md b/CHANGELOG.md index 197b8d5..cd963c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,21 @@ All notable changes to this project will be documented in this file. -## [0.4.0] - 2024-04-23 +## [unreleased] + +## [0.6.0] - 2024-11-24 + +- Add `action::trigger` and `action::trigger_targets` convenience systems. +- Bump keyseq to 0.4.0, which changes notation to be more standard: `Ctrl-A` + instead of `ctrl-A`. + +## [0.5.0] - 2024-06-05 + +### Features +- Optimize look ups to incrementally search using O(log n) instead of O(m^2 log n). See [PR #7](https://github.com/not-elm/bevy-input-sequence/pull/7) for more details. + +### Bugs +- Fix bug where "W A S D" and "A S" sequences would match the latter pattern when "W A S P" was typed. ## [0.4.0] - 2024-04-23 @@ -12,7 +26,7 @@ All notable changes to this project will be documented in this file. - Use plugin. - Can configure schedule and system set. - Remove `GamepadEvent`; use system with input `In`. -- Add `IntoCondSystem` that adds `only_if()` conditions to `IntoSystems`. +- Add `IntoCondSystem` that adds `only_if()` conditi:ons to `IntoSystems`. - Add `only_if` example. - Add `prelude` module for glob imports. diff --git a/Cargo.toml b/Cargo.toml index 16515a0..55ec8ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy-input-sequence" -description = "Recognizes input sequences and send events" -version = "0.5.0" +description = "Recognizes and acts on input sequences" +version = "0.6.0" edition = "2021" authors = ["elm", "Shane Celis "] keywords = [ @@ -18,26 +18,22 @@ readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/elm-register/bevy-input-sequence" - [[example]] name = "keycode" path = "examples/keycode.rs" - [[example]] name = "gamepad_button" path = "examples/gamepad_button.rs" - [[example]] name = "multiple_input" path = "examples/multiple_input.rs" - [dependencies] bevy = { version = "0.14", default-features = false, features = [] } trie-rs = { version = "0.4" } -keyseq = { version = "0.3.0", features = [ "bevy" ] } +keyseq = { version = "0.4.1", features = [ "bevy" ] } [dev-dependencies] bevy = "0.14" diff --git a/README.md b/README.md index 5da2b3e..fe3da51 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # bevy-input-sequence -Detect input sequences from the keyboard or a gamepad. +Recognizes and acts on input sequences from the keyboard or a gamepad. # Use Cases @@ -42,6 +42,7 @@ fn main() { } fn setup(mut commands: Commands) { + // Add key sequence. commands.add( KeySequence::new(say_hello, keyseq! { H I }) @@ -63,11 +64,11 @@ with `action::send_event()`. use bevy::prelude::*; use bevy_input_sequence::prelude::*; -// Define an event +/// Define an event. #[derive(Event, Clone, Debug)] struct MyEvent; -// Add event as an key sequence +/// Add event as an key sequence. fn main() { App::new() .add_plugins(MinimalPlugins) @@ -80,7 +81,7 @@ fn main() { fn setup(mut commands: Commands) { commands.add( KeySequence::new(action::send_event(MyEvent), - keyseq! { ctrl-E L M }) + keyseq! { Ctrl-E L M }) ); } @@ -101,11 +102,11 @@ take an input of `Gamepad`. use bevy::prelude::*; use bevy_input_sequence::prelude::*; -// Define an event +/// Define an event. #[derive(Event, Clone, Debug)] struct MyEvent(Gamepad); -// Add event as an key sequence +/// Add event as an key sequence. fn main() { App::new() .add_plugins(MinimalPlugins) @@ -132,6 +133,41 @@ fn check_events(mut events: EventReader) { } ``` +## Trigger an Event on Key Sequence + +You can also trigger an event with `action::trigger()` or `action::trigger_targets()`. + +```rust +use bevy::prelude::*; +use bevy_input_sequence::prelude::*; + +/// Define an event. +#[derive(Event, Clone, Debug)] +struct MyEvent; + +/// Add event as an key sequence. +fn main() { + App::new() + .add_plugins(MinimalPlugins) + .add_plugins(InputSequencePlugin::default()) + .add_event::() + .add_systems(Startup, setup) + .observe(check_trigger) + .update(); // Normally you'd run it here. +} + +fn setup(mut commands: Commands) { + commands.add( + KeySequence::new(action::trigger(MyEvent), + keyseq! { Ctrl-E L M }) + ); +} + +fn check_trigger(mut trigger: Trigger) { + info!("got event {:?}", trigger.event()); +} +``` + ## KeySequence Creation Patterns `KeySequence::new` now returns `KeySequenceBuilder`, which implements `Command`. @@ -147,20 +183,20 @@ struct MyEvent; fn create_key_sequence(mut commands: Commands) { commands.add(KeySequence::new( action::send_event(bevy::app::AppExit::default()), - keyseq! { ctrl-E L M } + keyseq! { Ctrl-E L M } )); } fn create_key_sequence_and_add_it_to_an_entity(mut commands: Commands) { - let parent = commands.spawn_empty().id(); - commands.entity(parent).add(KeySequence::new( + let id = commands.spawn_empty().id(); + commands.entity(id).add(KeySequence::new( action::send_event(MyEvent), - keyseq! { ctrl-E L M } + keyseq! { Ctrl-E L M } )); // OR commands.spawn_empty().add(KeySequence::new( action::send_event(MyEvent), - keyseq! { ctrl-E L M } + keyseq! { Ctrl-E L M } )); } ``` @@ -178,7 +214,7 @@ fn create_key_sequence_within_command(mut commands: Commands) { commands.add(|world: &mut World| { let builder = KeySequence::new( move || { info!("got it"); }, - keyseq! { ctrl-E L M } + keyseq! { Ctrl-E L M } ); let key_sequence: KeySequence = builder.build(world); // And then put it somewhere? It ought to go as a component. @@ -199,7 +235,7 @@ cargo run --example keycode ## keymod -The `keymod` example recognizes `ctrl-W ctrl-D ctrl-S ctrl-A` and fires an event. +The `keymod` example recognizes `Ctrl-W Ctrl-D Ctrl-S Ctrl-A` and fires an event. ``` sh cargo run --example keymod @@ -254,8 +290,8 @@ cargo run --example run_if | bevy-input-sequence | bevy | |---------------------|------| -| 0.5 | 0.14 | -| 0.3 ~ 0.4.0 | 0.13 | +| 0.5 ~ 0.6 | 0.14 | +| 0.3 ~ 0.4 | 0.13 | | 0.2 | 0.12 | | 0.1 | 0.11 | diff --git a/examples/keycode.rs b/examples/keycode.rs index 76a3d87..34ed33b 100644 --- a/examples/keycode.rs +++ b/examples/keycode.rs @@ -39,7 +39,7 @@ fn setup(mut commands: Commands) { commands.add( KeySequence::new( action::send_event(MyEvent(Direction::CounterClockwise)), - keyseq!(W A S D), + keyseq!{ W A S D }, ) .time_limit(Duration::from_secs(1)), ); diff --git a/examples/keymod.rs b/examples/keymod.rs index fcc8a45..07f3e11 100644 --- a/examples/keymod.rs +++ b/examples/keymod.rs @@ -18,11 +18,11 @@ fn setup(mut commands: Commands) { commands.add( KeySequence::new( action::send_event(MyEvent), - keyseq!(ctrl-W ctrl-D ctrl-S ctrl-A), + keyseq! { Ctrl-W Ctrl-D Ctrl-S Ctrl-A }, ) .time_limit(Duration::from_secs(1)), ); - println!("Press ctrl-W ctrl-D ctrl-S ctrl-A to emit event."); + println!("Press Ctrl-W Ctrl-D Ctrl-S Ctrl-A to emit event."); } fn input_sequence_event_system(mut er: EventReader) { diff --git a/examples/multiple_input.rs b/examples/multiple_input.rs index c88b65b..1e03974 100644 --- a/examples/multiple_input.rs +++ b/examples/multiple_input.rs @@ -17,7 +17,7 @@ fn main() { fn setup(mut commands: Commands) { commands.add( - KeySequence::new(action::send_event(MyEvent(1, None)), keyseq!(W D S A)) + KeySequence::new(action::send_event(MyEvent(1, None)), keyseq! { W D S A }) .time_limit(Duration::from_secs(5)), ); @@ -35,7 +35,7 @@ fn setup(mut commands: Commands) { ); commands.add( - KeySequence::new(action::send_event(MyEvent(3, None)), keyseq!(W A S D)) + KeySequence::new(action::send_event(MyEvent(3, None)), keyseq! { W A S D }) .time_limit(Duration::from_secs(5)), ); diff --git a/examples/only_if.rs b/examples/only_if.rs index 54884da..9a3370f 100644 --- a/examples/only_if.rs +++ b/examples/only_if.rs @@ -30,12 +30,12 @@ fn main() { fn setup(mut commands: Commands) { commands.add(KeySequence::new( action::send_event(GlobalEvent), - keyseq!(Escape), + keyseq! { Escape }, )); commands.add( KeySequence::new( action::send_event(MyEvent).only_if(in_state(AppState::Game)), - keyseq!(Space), + keyseq! { Space }, ) .time_limit(Duration::from_secs(1)), ); diff --git a/examples/run_if.rs b/examples/run_if.rs index e27641b..ef94af3 100644 --- a/examples/run_if.rs +++ b/examples/run_if.rs @@ -31,7 +31,7 @@ fn setup(mut commands: Commands) { commands.add( KeySequence::new( action::send_event(MyEvent).only_if(in_state(AppState::Game)), - keyseq!(Space), + keyseq! { Space }, ) .time_limit(Duration::from_secs(1)), ); diff --git a/src/action.rs b/src/action.rs index 7dfc22b..6da5e91 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,6 +1,8 @@ //! Common actions to do on key sequence matches use bevy::ecs::{ event::{Event, EventWriter}, + observer::TriggerTargets, + prelude::Commands, system::In, }; @@ -25,6 +27,23 @@ pub fn send_event(event: E) -> impl FnMut(EventWriter) { } } +/// Trigger an event. +pub fn trigger(event: E) -> impl FnMut(Commands) { + move |mut commands: Commands| { + commands.trigger(event.clone()); + } +} + +/// Trigger an event with targets. +pub fn trigger_targets( + event: E, + targets: T, +) -> impl FnMut(Commands) { + move |mut commands: Commands| { + commands.trigger_targets(event.clone(), targets.clone()); + } +} + /// Sends an event with input, .e.g, [ButtonSequence](crate::input_sequence::ButtonSequence) provides a [Gamepad](bevy::input::gamepad::Gamepad) identifier. pub fn send_event_with_input E>( mut f: F, diff --git a/src/chord.rs b/src/chord.rs index e1c4428..e72f76a 100644 --- a/src/chord.rs +++ b/src/chord.rs @@ -1,9 +1,10 @@ use bevy::{ input::keyboard::KeyCode, + prelude::{Deref, DerefMut, Resource}, reflect::{Enum, Reflect}, }; -use std::fmt; +use std::{collections::VecDeque, fmt}; use keyseq::Modifiers; @@ -56,3 +57,10 @@ impl From for KeyChord { pub(crate) fn is_modifier(key: KeyCode) -> bool { !Modifiers::from(key).is_empty() } + +/// Manually add key chords to be processed as through they were pressed by the +/// user. +/// +/// Normally this does not need to be manipulated. It is a kind of escape hatch. +#[derive(Resource, Debug, Deref, DerefMut, Default)] +pub struct KeyChordQueue(pub VecDeque); diff --git a/src/cond_system.rs b/src/cond_system.rs index 271858f..1f813b5 100644 --- a/src/cond_system.rs +++ b/src/cond_system.rs @@ -34,7 +34,7 @@ pub trait IntoCondSystem: IntoSystem { } } -impl IntoCondSystem for T where T: IntoSystem + ?Sized {} +impl IntoCondSystem for T where T: IntoSystem {} /// A one-shot conditional system comprised of consequent `SystemA` and /// conditional `SystemB`. diff --git a/src/input_sequence.rs b/src/input_sequence.rs index 5d444e1..8dca74d 100644 --- a/src/input_sequence.rs +++ b/src/input_sequence.rs @@ -1,5 +1,6 @@ //! Input sequences for keys and gamepad buttons use crate::{cond_system::IntoCondSystem, time_limit::TimeLimit, KeyChord}; +use std::fmt; use bevy::{ ecs::{ @@ -26,6 +27,26 @@ pub struct InputSequence { pub time_limit: Option, } +impl fmt::Debug for InputSequence { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + #[derive(Debug)] + #[allow(dead_code)] + struct InputSequence<'a, Act> { + // system_id: SystemId, + acts: &'a Vec, + time_limit: &'a Option, + } + + let Self { + acts, + time_limit, + system_id: _, + } = self; + + fmt::Debug::fmt(&InputSequence { acts, time_limit }, f) + } +} + /// An input sequence builder. pub struct InputSequenceBuilder { /// The action when to run when sequence matches diff --git a/src/lib.rs b/src/lib.rs index a157c96..e67b258 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,7 @@ -#![doc(html_root_url = "https://docs.rs/bevy-input-sequence/0.5.0")] +#![doc(html_root_url = "https://docs.rs/bevy-input-sequence/0.6.0")] #![doc = include_str!("../README.md")] #![forbid(missing_docs)] -pub use chord::KeyChord; -pub use plugin::InputSequencePlugin; -pub use time_limit::TimeLimit; - pub mod action; pub mod cache; mod chord; @@ -15,6 +11,10 @@ pub mod input_sequence; mod plugin; mod time_limit; +pub use chord::{KeyChord, KeyChordQueue}; +pub use plugin::InputSequencePlugin; +pub use time_limit::TimeLimit; + pub use keyseq::{ bevy::{pkey as key, pkeyseq as keyseq}, Modifiers, @@ -22,10 +22,9 @@ pub use keyseq::{ /// Convenient glob import pub mod prelude { + pub use super::cond_system::IntoCondSystem; pub use super::input_sequence::{ButtonSequence, InputSequence, KeySequence}; pub use super::{action, keyseq, InputSequencePlugin, Modifiers, TimeLimit}; - - pub use super::chord::KeyChord; - pub use super::cond_system::IntoCondSystem; + pub use super::{KeyChord, KeyChordQueue}; pub use std::time::Duration; } diff --git a/src/plugin.rs b/src/plugin.rs index bf5659f..7672758 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -20,7 +20,7 @@ use std::collections::{HashMap, VecDeque}; use crate::{ cache::InputSequenceCache, - chord::is_modifier, + chord::{is_modifier, KeyChordQueue}, frame_time::FrameTime, input_sequence::{ButtonSequence, InputSequence, KeySequence}, KeyChord, Modifiers, @@ -53,6 +53,7 @@ impl Plugin for InputSequencePlugin { { // Add key sequence. app.init_resource::>(); + app.init_resource::(); for (schedule, set) in &self.schedules { if let Some(set) = set { @@ -233,21 +234,25 @@ fn key_sequence_matcher( mut cache: ResMut>, frame_count: Res, mut commands: Commands, + mut keychord_queue: ResMut, ) { - let mods = Modifiers::from_input(&keys); + let mods = Modifiers::from(&keys); let now = FrameTime { frame: frame_count.0, time: time.elapsed_seconds(), }; let maybe_start = last_times.front().cloned(); - let mut input = keys - .get_just_pressed() - .filter(|k| !is_modifier(**k)) - .map(|k| { - let chord = KeyChord(mods, *k); - last_times.push_back(now.clone()); - chord - }) + let mut input = keychord_queue + .drain(..) + .chain( + keys.get_just_pressed() + .filter(|k| !is_modifier(**k)) + .map(|k| { + let chord = KeyChord(mods, *k); + last_times.push_back(now.clone()); + chord + }), + ) .peekable(); if input.peek().is_none() { return; @@ -297,11 +302,22 @@ where None => { search.reset(); // This could be the start of a new sequence. - if search.query(&k).is_none() { - // This may not be necessary. - search.reset(); + // + // Let's check it. + match search.query(&k) { + Some(Answer::Match) => { + let result = Some(search.value().unwrap()); + search.reset(); + result + } + Some(Answer::PrefixAndMatch) => Some(search.value().unwrap()), + Some(Answer::Prefix) => None, + None => { + // This may not be necessary. + search.reset(); + None + } } - None } } }) diff --git a/tests/act.rs b/tests/act.rs index fec7e27..cceb1b8 100644 --- a/tests/act.rs +++ b/tests/act.rs @@ -33,131 +33,131 @@ fn eq_if_contains_key_in_lhs() { // #[test] // fn test_shifted_key_macro() { -// assert_eq!((Modifiers::CONTROL, KeyCode::KeyB), key! { ctrl-* }); +// assert_eq!((Modifiers::CONTROL, KeyCode::KeyB), key! { Ctrl-* }); // } /// XXX: This doc test isn't working. /// /// ```compile_fail -/// assert_eq!((Modifiers::CONTROL, KeyCode::F2), key!{ ctrl-f2 }); +/// assert_eq!((Modifiers::CONTROL, KeyCode::F2), key!{ Ctrl-f2 }); /// ``` /// /// ``` -/// let _ = key! { ctrl-* }); +/// let _ = key! { Ctrl-* }); /// ``` #[allow(unused_must_use)] #[test] fn test_key_macro() { - assert_eq!((Modifiers::CONTROL, KeyCode::KeyB), key! { ctrl-B }); - assert_eq!((Modifiers::CONTROL, KeyCode::Digit1), key! { ctrl-1 }); - assert_eq!((Modifiers::CONTROL, KeyCode::Digit2), key! { ctrl-2 }); - assert_eq!((Modifiers::CONTROL, KeyCode::F2), key! { ctrl-F2 }); - // assert_eq!((Modifiers::CONTROL, KeyCode::F2), key!{ ctrl-f2 }); - assert_eq!((Modifiers::CONTROL, KeyCode::Semicolon), key! { ctrl-; }); - // assert_eq!((Modifiers::CONTROL, KeyCode::Caret), key! { ctrl-^ }); - // assert_eq!((Modifiers::CONTROL, KeyCode::Colon), key! { ctrl-: }); - assert_eq!((Modifiers::CONTROL, KeyCode::Equal), key! { ctrl-= }); - assert_eq!((Modifiers::CONTROL, KeyCode::Comma), key! { ctrl-, }); - assert_eq!((Modifiers::CONTROL, KeyCode::Period), key! { ctrl-. }); - assert_eq!((Modifiers::CONTROL, KeyCode::Slash), key! { ctrl-/ }); - assert_eq!((Modifiers::CONTROL, KeyCode::Enter), key! { ctrl-Enter }); - assert_eq!((Modifiers::CONTROL, KeyCode::Space), key! { ctrl-Space }); - assert_eq!((Modifiers::CONTROL, KeyCode::Tab), key! { ctrl-Tab }); - assert_eq!((Modifiers::CONTROL, KeyCode::Delete), key! { ctrl-Delete }); - assert_eq!((Modifiers::CONTROL, KeyCode::Minus), key! { ctrl-- }); + assert_eq!((Modifiers::CONTROL, KeyCode::KeyB), key! { Ctrl-B }); + assert_eq!((Modifiers::CONTROL, KeyCode::Digit1), key! { Ctrl-1 }); + assert_eq!((Modifiers::CONTROL, KeyCode::Digit2), key! { Ctrl-2 }); + assert_eq!((Modifiers::CONTROL, KeyCode::F2), key! { Ctrl-F2 }); + // assert_eq!((Modifiers::CONTROL, KeyCode::F2), key!{ Ctrl-f2 }); + assert_eq!((Modifiers::CONTROL, KeyCode::Semicolon), key! { Ctrl-; }); + // assert_eq!((Modifiers::CONTROL, KeyCode::Caret), key! { Ctrl-^ }); + // assert_eq!((Modifiers::CONTROL, KeyCode::Colon), key! { Ctrl-: }); + assert_eq!((Modifiers::CONTROL, KeyCode::Equal), key! { Ctrl-= }); + assert_eq!((Modifiers::CONTROL, KeyCode::Comma), key! { Ctrl-, }); + assert_eq!((Modifiers::CONTROL, KeyCode::Period), key! { Ctrl-. }); + assert_eq!((Modifiers::CONTROL, KeyCode::Slash), key! { Ctrl-/ }); + assert_eq!((Modifiers::CONTROL, KeyCode::Enter), key! { Ctrl-Enter }); + assert_eq!((Modifiers::CONTROL, KeyCode::Space), key! { Ctrl-Space }); + assert_eq!((Modifiers::CONTROL, KeyCode::Tab), key! { Ctrl-Tab }); + assert_eq!((Modifiers::CONTROL, KeyCode::Delete), key! { Ctrl-Delete }); + assert_eq!((Modifiers::CONTROL, KeyCode::Minus), key! { Ctrl-- }); assert_eq!( (Modifiers::CONTROL | Modifiers::SHIFT, KeyCode::Minus), - key! { ctrl-shift-- } + key! { Ctrl-Shift-- } ); - // assert_eq!((Modifiers::CONTROL, KeyCode::Underline), key! { ctrl-_ }); + // assert_eq!((Modifiers::CONTROL, KeyCode::Underline), key! { Ctrl-_ }); // No colon key. - // assert_eq!((Modifiers::CONTROL, KeyCode::Colon), key! { ctrl-: }); + // assert_eq!((Modifiers::CONTROL, KeyCode::Colon), key! { Ctrl-: }); assert_eq!( (Modifiers::CONTROL | Modifiers::SHIFT, KeyCode::Semicolon), - key! { ctrl-shift-; } + key! { Ctrl-Shift-; } ); - assert_eq!((Modifiers::CONTROL, KeyCode::Quote), key! { ctrl-'\'' }); + assert_eq!((Modifiers::CONTROL, KeyCode::Quote), key! { Ctrl-'\'' }); assert_eq!( (Modifiers::CONTROL | Modifiers::SHIFT, KeyCode::KeyA), - key! { ctrl-shift-A } + key! { Ctrl-Shift-A } ); - // assert_eq!((Modifiers::CONTROL, KeyCode::KeyA), key!{ ctrl-A }); - assert_eq!((Modifiers::SUPER, KeyCode::KeyA), key! { super-A }); - assert_eq!((Modifiers::CONTROL, KeyCode::KeyA), key! { ctrl-A }); // Allow lowercase or demand lowercase? + // assert_eq!((Modifiers::CONTROL, KeyCode::KeyA), key!{ Ctrl-A }); + assert_eq!((Modifiers::SUPER, KeyCode::KeyA), key! { Super-A }); + assert_eq!((Modifiers::CONTROL, KeyCode::KeyA), key! { Ctrl-A }); // Allow lowercase or demand lowercase? assert_eq!((Modifiers::empty(), KeyCode::KeyA), key! { A }); let k = (Modifiers::empty(), KeyCode::KeyA); assert_eq!(k, key! { A }); // assert_eq!( // (Modifiers::CONTROL, KeyCode::Asterisk), - // key! { ctrl-Asterisk } + // key! { Ctrl-Asterisk } // ); assert_eq!( (Modifiers::CONTROL | Modifiers::SHIFT, KeyCode::Digit8), - key! { ctrl-shift-8 } + key! { Ctrl-Shift-8 } ); assert_eq!( (Modifiers::CONTROL | Modifiers::SHIFT, KeyCode::Digit8), - key! { ctrl-shift-Digit8 } + key! { Ctrl-Shift-Digit8 } ); // All bevy KeyCode names work. - // assert_eq!((Modifiers::CONTROL, KeyCode::Asterisk), key! { ctrl-* }); // with some short hand. + // assert_eq!((Modifiers::CONTROL, KeyCode::Asterisk), key! { Ctrl-* }); // with some short hand. - // assert_eq!((Modifiers::CONTROL, KeyCode::Plus), key! { ctrl-+ }); + // assert_eq!((Modifiers::CONTROL, KeyCode::Plus), key! { Ctrl-+ }); assert_eq!( (Modifiers::CONTROL | Modifiers::SHIFT, KeyCode::Equal), - key! { ctrl-shift-= } + key! { Ctrl-Shift-= } ); - // assert_eq!((Modifiers::CONTROL, KeyCode::At), key! { ctrl-@ }); + // assert_eq!((Modifiers::CONTROL, KeyCode::At), key! { Ctrl-@ }); assert_eq!( (Modifiers::CONTROL, KeyCode::BracketLeft), - key! { ctrl-'[' } + key! { Ctrl-'[' } ); assert_eq!( (Modifiers::CONTROL, KeyCode::BracketRight), - key! { ctrl-']' } + key! { Ctrl-']' } ); assert_eq!( (Modifiers::CONTROL, KeyCode::BracketRight), - key! { ctrl-']' } + key! { Ctrl-']' } ); - assert_eq!((Modifiers::CONTROL, KeyCode::Backquote), key! { ctrl-'`' }); - assert_eq!((Modifiers::CONTROL, KeyCode::Backslash), key! { ctrl-'\\' }); - assert_eq!((Modifiers::CONTROL, KeyCode::Escape), key! { ctrl-Escape }); - // assert_eq!((Modifiers::CONTROL, KeyCode::Escape), key!{ ctrl-Esc }); + assert_eq!((Modifiers::CONTROL, KeyCode::Backquote), key! { Ctrl-'`' }); + assert_eq!((Modifiers::CONTROL, KeyCode::Backslash), key! { Ctrl-'\\' }); + assert_eq!((Modifiers::CONTROL, KeyCode::Escape), key! { Ctrl-Escape }); + // assert_eq!((Modifiers::CONTROL, KeyCode::Escape), key!{ Ctrl-Esc }); assert_eq!( (Modifiers::CONTROL | Modifiers::ALT, KeyCode::KeyA), - key! { ctrl-alt-A } + key! { Ctrl-Alt-A } ); assert_eq!((Modifiers::empty(), KeyCode::KeyA), key! { A }); assert_eq!( (Modifiers::CONTROL | Modifiers::ALT, KeyCode::KeyA), - key! { ctrl-alt-A } + key! { Ctrl-Alt-A } ); assert_eq!( (Modifiers::CONTROL | Modifiers::ALT, KeyCode::KeyA), - key! { ctrl-alt-A } + key! { Ctrl-Alt-A } ); assert_eq!( (Modifiers::CONTROL | Modifiers::ALT, KeyCode::Semicolon), - key! { ctrl-alt-Semicolon } + key! { Ctrl-Alt-Semicolon } ); assert_eq!( (Modifiers::CONTROL | Modifiers::ALT, KeyCode::Semicolon), - key! { ctrl-alt-; } + key! { Ctrl-Alt-; } ); assert_eq!( ( Modifiers::CONTROL | Modifiers::ALT | Modifiers::SHIFT, KeyCode::Semicolon ), - key! { ctrl-alt-shift-; } // ctrl-alt-: + key! { Ctrl-Alt-Shift-; } // Ctrl-Alt-: ); assert_eq!( (Modifiers::CONTROL | Modifiers::ALT, KeyCode::Slash), - key! { ctrl-alt-/ } + key! { Ctrl-Alt-/ } ); } @@ -166,18 +166,18 @@ fn test_key_macro() { fn test_keyseq() { assert_eq!( vec![(Modifiers::CONTROL, KeyCode::KeyA)], - keyseq! { ctrl-A } + keyseq! { Ctrl-A } ); assert_eq!( vec![(Modifiers::CONTROL, KeyCode::KeyA)], - keyseq! { ctrl-ctrl-A } + keyseq! { Ctrl-Ctrl-A } ); assert_eq!( vec![ (Modifiers::CONTROL, KeyCode::KeyA), (Modifiers::ALT, KeyCode::KeyB) ], - keyseq! { ctrl-A alt-B } + keyseq! { Ctrl-A Alt-B } ); assert_eq!( diff --git a/tests/macros.rs b/tests/macros.rs index 8af3c13..80d49a6 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -1,6 +1,62 @@ use bevy::prelude::*; use bevy_input_sequence::*; +#[rustfmt::skip] +#[test] +fn before_cargo_format() { + assert_eq!( + [key![Ctrl-A], + key! [Ctrl-A], + key! [ Ctrl-A ], + key!{Ctrl-A}, + key! {Ctrl-A}, + key! { Ctrl-A }, + key!(Ctrl-A), + key! (Ctrl-A), + key! ( Ctrl-A ), + ], + [ + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + ] + ); +} + +#[test] +fn after_cargo_format() { + assert_eq!( + [ + key![Ctrl - A], + key![Ctrl - A], + key![Ctrl - A], + key! {Ctrl-A}, + key! {Ctrl-A}, + key! { Ctrl-A }, + key!(Ctrl - A), + key!(Ctrl - A), + key!(Ctrl - A), + ], + [ + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + (Modifiers::CONTROL, KeyCode::KeyA), + ] + ); +} + #[test] fn test_keyseq_doc() { assert_eq!( @@ -11,25 +67,25 @@ fn test_keyseq_doc() { ] ); assert_eq!( - keyseq! { ctrl-A B }, + keyseq! { Ctrl-A B }, [ (Modifiers::CONTROL, KeyCode::KeyA), (Modifiers::empty(), KeyCode::KeyB) ] ); assert_eq!( - keyseq! { ctrl-alt-A Escape }, + keyseq! { Ctrl-Alt-A Escape }, [ (Modifiers::ALT | Modifiers::CONTROL, KeyCode::KeyA), (Modifiers::empty(), KeyCode::Escape) ] ); assert_eq!( - keyseq! { ctrl-; }, + keyseq! { Ctrl-; }, [(Modifiers::CONTROL, KeyCode::Semicolon)] ); assert_eq!( - keyseq! { ctrl-Semicolon }, + keyseq! { Ctrl-Semicolon }, [(Modifiers::CONTROL, KeyCode::Semicolon)] ); } diff --git a/tests/simulated.rs b/tests/simulated.rs index 06529ba..c32f321 100644 --- a/tests/simulated.rs +++ b/tests/simulated.rs @@ -2,10 +2,10 @@ use bevy_input_sequence::{key, KeyChord}; #[test] fn keychord_display() { - let keychord = KeyChord::from(key!(ctrl - A)); - assert_eq!(format!("{}", keychord), "ctrl-A"); - let keychord = KeyChord::from(key!(ctrl - 1)); - assert_eq!(format!("{}", keychord), "ctrl-1"); + let keychord = KeyChord::from(key!(Ctrl - A)); + assert_eq!(format!("{}", keychord), "Ctrl-A"); + let keychord = KeyChord::from(key!(Ctrl - 1)); + assert_eq!(format!("{}", keychord), "Ctrl-1"); let keychord = KeyChord::from(key!(1)); assert_eq!(format!("{}", keychord), "1"); } @@ -30,7 +30,7 @@ mod simulate_app { keyboard::KeyCode, Axis, ButtonInput as Input, }, - prelude::Commands, + prelude::{Commands, ResMut, Resource}, MinimalPlugins, }; use bevy_input_sequence::prelude::*; @@ -51,6 +51,19 @@ mod simulate_app { } } + #[derive(Resource, Default)] + struct R(u8); + + fn set(x: u8) -> impl Fn(ResMut) { + move |mut r: ResMut| { + r.0 = x; + } + } + + fn get(world: &World) -> u8 { + world.resource::().0 + } + #[test] fn one_key() { let mut app = new_app(); @@ -64,7 +77,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_some()); } @@ -86,7 +99,7 @@ mod simulate_app { assert_eq!( app.world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .count(), 1 ); @@ -110,7 +123,7 @@ mod simulate_app { assert_eq!( app.world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .count(), 2 ); @@ -134,7 +147,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -144,7 +157,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_some()); } @@ -167,7 +180,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -177,7 +190,7 @@ mod simulate_app { assert_eq!( app.world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .map(|x| x.0) .unwrap(), @@ -190,7 +203,7 @@ mod simulate_app { assert_eq!( app.world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .map(|x| x.0) .unwrap(), @@ -203,7 +216,7 @@ mod simulate_app { assert_eq!( app.world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .map(|x| x.0) .unwrap(), @@ -229,7 +242,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -239,11 +252,31 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_some()); } + #[test] + fn match_a_and_c() { + let mut app = new_app(); + + app.world_mut() + .add(KeySequence::new(set(1), [KeyCode::KeyA])); + app.world_mut() + .add(KeySequence::new(set(2), [KeyCode::KeyA, KeyCode::KeyB])); + app.world_mut() + .add(KeySequence::new(set(3), [KeyCode::KeyC])); + assert_eq!(get(app.world()), 0); + press_key(&mut app, KeyCode::KeyA); + app.update(); + assert_eq!(get(app.world()), 1); + clear_just_pressed(&mut app, KeyCode::KeyA); + press_key(&mut app, KeyCode::KeyC); + app.update(); + assert_eq!(get(app.world()), 3); + } + #[test] fn two_any_patterns() { let mut app = new_app(); @@ -265,7 +298,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -275,7 +308,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_some()); } @@ -301,7 +334,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -311,7 +344,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_some()); } @@ -330,7 +363,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -340,7 +373,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_some()); } @@ -359,7 +392,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -369,7 +402,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -379,7 +412,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); } @@ -409,7 +442,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -419,7 +452,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -429,7 +462,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_some()); } @@ -470,7 +503,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -480,7 +513,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -490,7 +523,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -500,7 +533,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_some()); } @@ -520,7 +553,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -534,7 +567,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); } @@ -553,7 +586,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -565,7 +598,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); release(&mut app, KeyCode::ControlLeft); @@ -576,7 +609,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_some()); } @@ -595,7 +628,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -607,7 +640,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_some()); } @@ -629,7 +662,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -641,7 +674,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -650,7 +683,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); @@ -658,7 +691,7 @@ mod simulate_app { assert!(app .world_mut() .query::<&EventSent>() - .iter(&app.world_mut()) + .iter(app.world_mut()) .next() .is_none()); } @@ -719,6 +752,7 @@ mod simulate_app { ); app.add_systems(PostUpdate, read); app.add_event::(); + app.init_resource::(); app.init_resource::(); app.init_resource::>(); app.init_resource::>();