Skip to content

Bump keyseq to 0.4.0 and add action::trigger* convenience systems, CHANGE NOTATION, and prep for bevy-input-sequence 0.6.0 release. #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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<Gamepad>`.
- 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.

Expand Down
10 changes: 3 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
keywords = [
Expand All @@ -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"
Expand Down
66 changes: 51 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -42,6 +42,7 @@ fn main() {
}

fn setup(mut commands: Commands) {
// Add key sequence.
commands.add(
KeySequence::new(say_hello,
keyseq! { H I })
Expand All @@ -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)
Expand All @@ -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 })
);
}

Expand All @@ -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)
Expand All @@ -132,6 +133,41 @@ fn check_events(mut events: EventReader<MyEvent>) {
}
```

## 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::<MyEvent>()
.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<MyEvent>) {
info!("got event {:?}", trigger.event());
}
```

## KeySequence Creation Patterns

`KeySequence::new` now returns `KeySequenceBuilder`, which implements `Command`.
Expand All @@ -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 }
));
}
```
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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 |

Expand Down
2 changes: 1 addition & 1 deletion examples/keycode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
);
Expand Down
4 changes: 2 additions & 2 deletions examples/keymod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MyEvent>) {
Expand Down
4 changes: 2 additions & 2 deletions examples/multiple_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
);

Expand All @@ -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)),
);

Expand Down
4 changes: 2 additions & 2 deletions examples/only_if.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
);
Expand Down
2 changes: 1 addition & 1 deletion examples/run_if.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
);
Expand Down
19 changes: 19 additions & 0 deletions src/action.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Common actions to do on key sequence matches
use bevy::ecs::{
event::{Event, EventWriter},
observer::TriggerTargets,
prelude::Commands,
system::In,
};

Expand All @@ -25,6 +27,23 @@ pub fn send_event<E: Event + Clone>(event: E) -> impl FnMut(EventWriter<E>) {
}
}

/// Trigger an event.
pub fn trigger<E: Event + Clone>(event: E) -> impl FnMut(Commands) {
move |mut commands: Commands| {
commands.trigger(event.clone());
}
}

/// Trigger an event with targets.
pub fn trigger_targets<E: Event + Clone, T: TriggerTargets + Clone>(
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: Event, Input: 'static, F: FnMut(Input) -> E>(
mut f: F,
Expand Down
10 changes: 9 additions & 1 deletion src/chord.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -56,3 +57,10 @@ impl From<KeyCode> 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<KeyChord>);
2 changes: 1 addition & 1 deletion src/cond_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub trait IntoCondSystem<I, O, M>: IntoSystem<I, O, M> {
}
}

impl<I, O, M, T> IntoCondSystem<I, O, M> for T where T: IntoSystem<I, O, M> + ?Sized {}
impl<I, O, M, T> IntoCondSystem<I, O, M> for T where T: IntoSystem<I, O, M> {}

/// A one-shot conditional system comprised of consequent `SystemA` and
/// conditional `SystemB`.
Expand Down
21 changes: 21 additions & 0 deletions src/input_sequence.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -26,6 +27,26 @@ pub struct InputSequence<Act, In> {
pub time_limit: Option<TimeLimit>,
}

impl<Act: fmt::Debug, In> fmt::Debug for InputSequence<Act, In> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
#[derive(Debug)]
#[allow(dead_code)]
struct InputSequence<'a, Act> {
// system_id: SystemId<In>,
acts: &'a Vec<Act>,
time_limit: &'a Option<TimeLimit>,
}

let Self {
acts,
time_limit,
system_id: _,
} = self;

fmt::Debug::fmt(&InputSequence { acts, time_limit }, f)
}
}

/// An input sequence builder.
pub struct InputSequenceBuilder<Act, S> {
/// The action when to run when sequence matches
Expand Down
Loading
Loading