feat: return logical keys that does not works on every layout
This commit is contained in:
@@ -3,8 +3,9 @@ name = "bevy_controls"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
[features]
|
||||
default = []
|
||||
logical-keyboard = []
|
||||
serialize = ["bevy_input/serialize", "serde"]
|
||||
reflect = ["dep:bevy_reflect"]
|
||||
inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
|
||||
|
||||
Regular → Executable
+3
-1
@@ -22,7 +22,9 @@ bevy = { version = "0.16.1", default-features = false, features = [
|
||||
#"dynamic_linking",
|
||||
"default_font",
|
||||
]}
|
||||
bevy_controls = { path = "./../../" }
|
||||
bevy_controls = { path = "./../../", features = [
|
||||
"logical-keyboard",
|
||||
]}
|
||||
bevy_controls_derive = { path = "./../../../bevy_controls_derive" }
|
||||
strum_macros = "0.26.2"
|
||||
strum = "0.26.2"
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
// TODO strum redefine in bevy_controls
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::input::keyboard::Key;
|
||||
use bevy_controls::{
|
||||
contract::InputsContainer,
|
||||
plugin::ControlsPlugin,
|
||||
resource::{
|
||||
ActivationMode, ActivationOptions, AxisName, Binding, BindingCondition, BindingConfig,
|
||||
Bindings, ButtonCombination, Controls, InputType, InputValue, MouseInput, OptionsMode,
|
||||
Bindings, ButtonCombination, Controls, InputType, InputValue, Keyboard, MouseInput, OptionsMode,
|
||||
PlayerActions,
|
||||
},
|
||||
};
|
||||
@@ -81,13 +82,14 @@ fn main() {
|
||||
.with(
|
||||
MyAction::Left,
|
||||
BindingConfig::new(Bindings::new(vec![
|
||||
// this way input will work only on latin layout only on `A`, because it uses `Keyboard::KeyCode`
|
||||
// `KeyCode` binds physical key location, so this is the US-layout A position.
|
||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
||||
KeyCode::KeyA,
|
||||
Keyboard::KeyCode(KeyCode::KeyA),
|
||||
)))
|
||||
.with_condition(BindingCondition::InGameState(MyGameState::InGame)),
|
||||
// `Key` binds logical text, so this activates when the current layout produces h.
|
||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
||||
KeyCode::KeyH,
|
||||
Keyboard::Key(Key::Character("h".into())),
|
||||
)))
|
||||
.with_condition(BindingCondition::InGameState(MyGameState::InGame)),
|
||||
]))
|
||||
@@ -99,27 +101,29 @@ fn main() {
|
||||
.with(
|
||||
MyAction::Back,
|
||||
BindingConfig::from_vec(vec![
|
||||
Binding::from_single(InputType::Keyboard(KeyCode::KeyS)),
|
||||
Binding::from_single(InputType::Keyboard(KeyCode::KeyJ)),
|
||||
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyS))),
|
||||
Binding::from_single(InputType::Keyboard(Keyboard::Key(Key::Character("j".into())))),
|
||||
]),
|
||||
)
|
||||
.with(
|
||||
MyAction::Forward,
|
||||
BindingConfig::from_vec(vec![
|
||||
Binding::from_single(InputType::Keyboard(KeyCode::KeyW)),
|
||||
Binding::from_single(InputType::Keyboard(KeyCode::KeyK)),
|
||||
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyW))),
|
||||
Binding::from_single(InputType::Keyboard(Keyboard::Key(Key::Character("k".into())))),
|
||||
]),
|
||||
)
|
||||
.with(
|
||||
MyAction::Right,
|
||||
BindingConfig::from_vec(vec![
|
||||
Binding::from_single(InputType::Keyboard(KeyCode::KeyD)),
|
||||
Binding::from_single(InputType::Keyboard(KeyCode::KeyL)),
|
||||
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyD))),
|
||||
Binding::from_single(InputType::Keyboard(Keyboard::Key(Key::Character("l".into())))),
|
||||
]),
|
||||
)
|
||||
.with(
|
||||
MyAction::Up,
|
||||
BindingConfig::from_bind(Binding::from_single(InputType::Keyboard(KeyCode::Space))),
|
||||
BindingConfig::from_bind(Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(
|
||||
KeyCode::Space,
|
||||
)))),
|
||||
)
|
||||
.with(
|
||||
MyAction::MouseHorizontal,
|
||||
@@ -136,8 +140,8 @@ fn main() {
|
||||
.with(
|
||||
MyAction::Down,
|
||||
BindingConfig::from_bind(Binding::from_single(
|
||||
InputType::Keyboard(KeyCode::ShiftLeft),
|
||||
//InputType::Keyboard(KeyCode::Space),
|
||||
InputType::Keyboard(Keyboard::KeyCode(KeyCode::ShiftLeft)),
|
||||
//InputType::Keyboard(Keyboard::KeyCode(KeyCode::Space)),
|
||||
)),
|
||||
)
|
||||
.build(),
|
||||
@@ -181,7 +185,7 @@ fn setup(mut commands: Commands) {
|
||||
commands
|
||||
.spawn((
|
||||
Text::new(
|
||||
"Info: this example is intended to show basic crate capabilities and limitations\nso WASD work only on US layout, but HJKL may work on any layout\nAction: ",
|
||||
"Info: Important that WASD use physical key positions and HJKL use logical characters from current layout\nAction: ",
|
||||
),
|
||||
TextFont {
|
||||
font_size: TEXT_SIZE,
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
use bevy_app::{App, Plugin, PreUpdate};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::system::{In, IntoSystem};
|
||||
use bevy_ecs::event::EventReader;
|
||||
use bevy_ecs::{
|
||||
event::EventReader,
|
||||
prelude::Resource,
|
||||
system::{Res, ResMut},
|
||||
system::{In, IntoSystem, Res, ResMut},
|
||||
};
|
||||
use bevy_input::{
|
||||
keyboard::KeyCode,
|
||||
mouse::{MouseButton, MouseMotion, MouseScrollUnit, MouseWheel},
|
||||
ButtonInput,
|
||||
};
|
||||
#[cfg(feature = "logical-keyboard")]
|
||||
use bevy_input::{
|
||||
keyboard::{Key, KeyboardInput},
|
||||
ButtonState,
|
||||
};
|
||||
use bevy_state::app::AppExtStates;
|
||||
use bevy_state::state::State;
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "logical-keyboard")]
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
contract::{Action, GameState, InputsContainer},
|
||||
@@ -52,6 +58,10 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
|
||||
#[derive(Resource, Default, Debug, Deref, DerefMut)]
|
||||
struct TrigeredInputs(HashMap<InputType, InputValue>);
|
||||
|
||||
#[cfg(feature = "logical-keyboard")]
|
||||
#[derive(Resource, Default, Debug, Deref, DerefMut)]
|
||||
struct PressedLogicalKeys(HashSet<Key>);
|
||||
|
||||
#[cfg(not(feature = "reflect"))]
|
||||
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin<A, Ic, Gs> {
|
||||
fn build(&self, app: &mut App) {
|
||||
@@ -59,11 +69,15 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin
|
||||
.insert_resource(self.controls.clone())
|
||||
.init_resource::<Ic>()
|
||||
.init_resource::<TrigeredInputs>()
|
||||
.insert_state::<Gs>(Gs::default())
|
||||
.add_systems(
|
||||
PreUpdate,
|
||||
Self::collect_inputs.pipe(Self::fill_players_inputs),
|
||||
);
|
||||
.insert_state::<Gs>(Gs::default());
|
||||
|
||||
#[cfg(feature = "logical-keyboard")]
|
||||
app.init_resource::<PressedLogicalKeys>();
|
||||
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
Self::collect_inputs.pipe(Self::fill_players_inputs),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,11 +94,15 @@ where
|
||||
.insert_resource(self.controls.clone())
|
||||
.init_resource::<Ic>()
|
||||
.init_resource::<TrigeredInputs>()
|
||||
.insert_state::<Gs>(Gs::default())
|
||||
.add_systems(
|
||||
PreUpdate,
|
||||
Self::collect_inputs.pipe(Self::fill_players_inputs),
|
||||
);
|
||||
.insert_state::<Gs>(Gs::default());
|
||||
|
||||
#[cfg(feature = "logical-keyboard")]
|
||||
app.init_resource::<PressedLogicalKeys>();
|
||||
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
Self::collect_inputs.pipe(Self::fill_players_inputs),
|
||||
);
|
||||
|
||||
#[cfg(feature = "reflect")]
|
||||
app.register_type::<Controls<A, Gs>>();
|
||||
@@ -101,6 +119,8 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
|
||||
///
|
||||
fn collect_inputs(
|
||||
keyboard_button: Res<ButtonInput<KeyCode>>,
|
||||
#[cfg(feature = "logical-keyboard")] mut keyboard_evr: EventReader<KeyboardInput>,
|
||||
#[cfg(feature = "logical-keyboard")] mut pressed_logical_keys: ResMut<PressedLogicalKeys>,
|
||||
mouse_buttons: Res<ButtonInput<MouseButton>>,
|
||||
mut scroll_evr: EventReader<MouseWheel>,
|
||||
mut motion_evr: EventReader<MouseMotion>,
|
||||
@@ -108,9 +128,32 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
|
||||
) -> TrigeredInputs {
|
||||
let mut collected_inputs = TrigeredInputs(HashMap::new());
|
||||
|
||||
#[cfg(feature = "logical-keyboard")]
|
||||
for event in keyboard_evr.read() {
|
||||
match event.state {
|
||||
ButtonState::Pressed => {
|
||||
pressed_logical_keys.insert(event.logical_key.clone());
|
||||
}
|
||||
ButtonState::Released => {
|
||||
pressed_logical_keys.remove(&event.logical_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect pressed keyboard buttons
|
||||
for key in keyboard_button.get_pressed() {
|
||||
collected_inputs.insert(InputType::Keyboard(*key), InputValue::Boolean(true));
|
||||
collected_inputs.insert(
|
||||
InputType::Keyboard(Keyboard::KeyCode(*key)),
|
||||
InputValue::Boolean(true),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "logical-keyboard")]
|
||||
for key in pressed_logical_keys.iter() {
|
||||
collected_inputs.insert(
|
||||
InputType::Keyboard(Keyboard::Key(key.clone())),
|
||||
InputValue::Boolean(true),
|
||||
);
|
||||
}
|
||||
|
||||
// buttons.get_just_released()
|
||||
@@ -178,7 +221,6 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
|
||||
|
||||
// Iterate over the control bindings and collect all action-binding pairs
|
||||
for binding in config.bindings.iter() {
|
||||
|
||||
// Skip any binding with mismatched condition
|
||||
if binding
|
||||
.conditions
|
||||
|
||||
Regular → Executable
+28
-5
@@ -2,15 +2,17 @@ use std::error;
|
||||
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::prelude::Resource;
|
||||
#[cfg(feature = "logical-keyboard")]
|
||||
use bevy_input::keyboard::Key;
|
||||
use bevy_input::{keyboard::KeyCode, mouse::MouseButton};
|
||||
#[cfg(feature = "inspector-egui")]
|
||||
use bevy_inspector_egui::prelude::*;
|
||||
use bevy_platform::collections::HashMap;
|
||||
#[cfg(all(feature = "reflect", feature = "serialize"))]
|
||||
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
use log::warn;
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use std::mem::discriminant;
|
||||
use strum::IntoEnumIterator;
|
||||
#[cfg(feature = "reflect")]
|
||||
@@ -194,10 +196,18 @@ impl<A: Action> PlayerActions<A> {
|
||||
|
||||
// TODO: add option on exlude or not exclude if used
|
||||
common_traits_conditions! {
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum InputType {
|
||||
Keyboard(KeyCode),
|
||||
Mouse(MouseInput),
|
||||
/// Keyboard input identified by physical location or logical meaning.
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum Keyboard {
|
||||
KeyCode(KeyCode),
|
||||
#[cfg(feature = "logical-keyboard")]
|
||||
Key(Key),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum InputType {
|
||||
Keyboard(Keyboard),
|
||||
Mouse(MouseInput),
|
||||
// TODO: Gamepad(GamepadButtonType),
|
||||
// TODO: Touch screen
|
||||
// TODO: TouchPad inputs
|
||||
@@ -694,3 +704,16 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "logical-keyboard"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn keyboard_variants_are_distinct() {
|
||||
let physical = InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyH));
|
||||
let logical = InputType::Keyboard(Keyboard::Key(Key::Character("h".into())));
|
||||
|
||||
assert_ne!(physical, logical);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user