From a79bd9d4b63297c63f43a7e275fa71c8f7fb08ee Mon Sep 17 00:00:00 2001 From: jack crump-leys Date: Thu, 12 Oct 2023 14:39:21 +1300 Subject: [PATCH 1/6] basic keybind logic --- Cargo.lock | 13 ++++ Cargo.toml | 1 + crates/input/Cargo.toml | 18 ++++++ crates/input/src/key.rs | 137 ++++++++++++++++++++++++++++++++++++++++ crates/input/src/lib.rs | 61 ++++++++++++++++++ 5 files changed, 230 insertions(+) create mode 100644 crates/input/Cargo.toml create mode 100644 crates/input/src/key.rs create mode 100644 crates/input/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 5950c203..65a8d809 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -856,6 +856,7 @@ dependencies = [ "bevy_tasks", "bevy_utils", "bytemuck", + "serde", ] [[package]] @@ -1041,6 +1042,7 @@ dependencies = [ "bevy_math", "bevy_reflect", "bevy_utils", + "serde", "thiserror", ] @@ -1364,6 +1366,7 @@ dependencies = [ "bevy_reflect", "bevy_utils", "crossbeam-channel", + "serde", "thiserror", ] @@ -1378,6 +1381,7 @@ dependencies = [ "bevy_hierarchy", "bevy_math", "bevy_reflect", + "serde", ] [[package]] @@ -1451,6 +1455,7 @@ dependencies = [ "bevy_reflect", "bevy_utils", "raw-window-handle", + "serde", ] [[package]] @@ -2401,6 +2406,14 @@ dependencies = [ "parry3d", ] +[[package]] +name = "de_input" +version = "0.1.0-dev" +dependencies = [ + "bevy", + "serde", +] + [[package]] name = "de_loader" version = "0.1.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 9f7123e1..bbc7d0f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -191,4 +191,5 @@ features = [ "tonemapping_luts", "default_font", "webgl2", + "serialize" ] diff --git a/crates/input/Cargo.toml b/crates/input/Cargo.toml new file mode 100644 index 00000000..b94cfe2e --- /dev/null +++ b/crates/input/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "de_input" +description = "Manage input from keyboard and mouse." + +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true +keywords.workspace = true +homepage.workspace = true +license.workspace = true +categories.workspace = true + +[dependencies] + +# Other +bevy.workspace = true +serde.workspace = true \ No newline at end of file diff --git a/crates/input/src/key.rs b/crates/input/src/key.rs new file mode 100644 index 00000000..a88215f0 --- /dev/null +++ b/crates/input/src/key.rs @@ -0,0 +1,137 @@ +use std::hash::Hash; +use std::marker::PhantomData; + +use bevy::app::PreUpdate; +use bevy::input::InputSystem; +use bevy::prelude::{App, Input, IntoSystemConfigs, Plugin, Res, ResMut, Resource, SystemSet}; + +use crate::ActionTrait; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, SystemSet)] +pub struct ActionSet; + +#[derive(Resource)] +pub struct Action { + inputs: Vec, + is_currently_pressed: bool, + just_pressed: bool, + just_released: bool, + _marker: PhantomData, +} + +impl Action { + pub fn inputs(&self) -> &Vec { + &self.inputs + } +} + +pub struct KeyPlugin { + keys: Vec, + config_name: String, + _marker: PhantomData, +} + +impl KeyPlugin { + pub fn new(keys: Vec, config_name: String) -> Self { + Self { + keys, + config_name, + _marker: PhantomData, + } + } +} + +impl Plugin for KeyPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + PreUpdate, + keyboard_input::.after(InputSystem).in_set(ActionSet), + ) + .insert_resource(Action:: { + inputs: self.keys.clone(), + is_currently_pressed: false, + just_pressed: false, + just_released: false, + _marker: PhantomData, + }); + } +} + +fn keyboard_input( + input: Res>, + mut action: ResMut>, +) { + let mut just_pressed = false; + let mut just_released = false; + let mut is_currently_pressed = false; + + for key in action.inputs() { + if input.just_pressed(*key) { + just_pressed = true; + } + + if input.just_released(*key) { + just_released = true; + } + + if input.pressed(*key) { + is_currently_pressed = true; + } + } + + action.just_pressed = just_pressed; + action.just_released = just_released; + action.is_currently_pressed = is_currently_pressed; +} + +#[cfg(test)] +mod tests { + use bevy::input::InputPlugin; + use bevy::prelude::KeyCode; + use bevy::prelude::KeyCode::Key0; + + use super::*; + use crate::AppKeybinding; + + #[test] + fn test_keybinding() { + struct TestAction; + + impl ActionTrait for TestAction { + type InputType = KeyCode; + } + + let mut app = App::new(); + + app.add_plugins(InputPlugin); + + app.add_keybinding::(Key0, "test_key".to_string()); + + app.update(); + let action = app.world.get_resource::>().unwrap(); + + assert!(!action.just_pressed); + assert!(!action.just_released); + assert!(!action.is_currently_pressed); + + fn test_press_key(mut keyboard_input_events: ResMut>) { + keyboard_input_events.press(Key0); + } + + app.add_systems( + PreUpdate, + test_press_key.before(ActionSet).after(InputSystem), + ); + + app.update(); + + let action = app.world.get_resource::>().unwrap(); + let keyboard_input_events = app.world.get_resource::>().unwrap(); + + println!("{:?}", keyboard_input_events); + + assert!(action.just_pressed); + assert!(!action.just_released); + assert!(action.is_currently_pressed); + } +} diff --git a/crates/input/src/lib.rs b/crates/input/src/lib.rs new file mode 100644 index 00000000..dc07c8e6 --- /dev/null +++ b/crates/input/src/lib.rs @@ -0,0 +1,61 @@ +mod key; + +use std::hash::Hash; + +use bevy::prelude::App; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::key::KeyPlugin; + +pub trait KeyBinding: + Copy + Eq + Hash + Send + Sync + Serialize + DeserializeOwned + 'static +{ +} + +impl KeyBinding for T {} + +pub trait ActionTrait { + type InputType: KeyBinding; +} + +pub trait AppKeybinding { + /// Add a keybinding to the app. + /// + /// # Arguments + /// * `E` - The event type to be sent when the keybinding is pressed. + /// * `I` - The type of Input. + /// * `default` - The default keybinding. + /// * `config_name` - The name of the keybinding in the config file. + fn add_keybinding( + &mut self, + default_keys: impl IntoKeys, + config_name: String, + ) -> &mut Self; +} + +pub trait IntoKeys { + fn into_keys(self) -> Vec; +} + +impl IntoKeys for T { + fn into_keys(self) -> Vec { + vec![self] + } +} + +impl IntoKeys for Vec { + fn into_keys(self) -> Vec { + self + } +} + +impl AppKeybinding for App { + fn add_keybinding( + &mut self, + default_keys: impl IntoKeys, + config_name: String, + ) -> &mut Self { + self.add_plugins(KeyPlugin::::new(default_keys.into_keys(), config_name)) + } +} From 9208b7dfaf86d3dc46e14a35976e9600fa5b4aa5 Mon Sep 17 00:00:00 2001 From: jack crump-leys Date: Thu, 12 Oct 2023 14:47:45 +1300 Subject: [PATCH 2/6] allow dead code TODO: Remove this and use this field --- crates/input/src/key.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/input/src/key.rs b/crates/input/src/key.rs index a88215f0..d2854d71 100644 --- a/crates/input/src/key.rs +++ b/crates/input/src/key.rs @@ -27,6 +27,7 @@ impl Action { pub struct KeyPlugin { keys: Vec, + #[allow(dead_code)] // TODO: Remove this and use this field config_name: String, _marker: PhantomData, } From 6fe0ca171f01f74c040b728d44914fe092466fac Mon Sep 17 00:00:00 2001 From: jack crump-leys Date: Wed, 25 Oct 2023 11:15:00 +1300 Subject: [PATCH 3/6] WIP - still alot of bugs with input --- Cargo.lock | 45 ++++++ Cargo.toml | 3 + crates/controller/Cargo.toml | 4 + crates/controller/src/actions.rs | 90 +++++++++++ crates/controller/src/commands/handlers.rs | 144 +++++++++--------- crates/controller/src/commands/keyboard.rs | 51 ------- crates/controller/src/commands/mod.rs | 1 - .../controller/src/hud/minimap/interaction.rs | 51 +++---- crates/controller/src/lib.rs | 4 + crates/controller/src/mouse/input.rs | 80 +++++----- crates/core/src/schedule.rs | 7 +- crates/input/Cargo.toml | 7 +- crates/input/src/io.rs | 32 ++++ crates/input/src/key.rs | 138 ----------------- crates/input/src/lib.rs | 119 ++++++++++----- crates/input/src/plugin.rs | 108 +++++++++++++ 16 files changed, 512 insertions(+), 372 deletions(-) create mode 100644 crates/controller/src/actions.rs delete mode 100644 crates/controller/src/commands/keyboard.rs create mode 100644 crates/input/src/io.rs delete mode 100644 crates/input/src/key.rs create mode 100644 crates/input/src/plugin.rs diff --git a/Cargo.lock b/Cargo.lock index 65a8d809..2754fc62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2313,6 +2313,7 @@ dependencies = [ "de_energy", "de_gui", "de_index", + "de_input", "de_map", "de_objects", "de_pathing", @@ -2322,8 +2323,11 @@ dependencies = [ "de_types", "enum-map", "glam", + "leafwing-input-manager", "parry2d", "parry3d", + "petitset", + "serde", ] [[package]] @@ -2411,7 +2415,11 @@ name = "de_input" version = "0.1.0-dev" dependencies = [ "bevy", + "de_core", + "leafwing-input-manager", + "ron", "serde", + "serde_yaml", ] [[package]] @@ -3955,6 +3963,34 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leafwing-input-manager" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b56a6f71e8f5beefc8d3841ffa6ea9bbb1123956fa7c75fd673e69b65a8ca62f" +dependencies = [ + "bevy", + "derive_more", + "fixedbitset", + "itertools 0.11.0", + "leafwing_input_manager_macros", + "once_cell", + "petitset", + "serde", +] + +[[package]] +name = "leafwing_input_manager_macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11aefabfcc9ef0a78bc97eea053d2f1bb050d5278a4134bdc1b769064a90064a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "libc" version = "0.2.147" @@ -4833,6 +4869,15 @@ dependencies = [ "indexmap 1.9.3", ] +[[package]] +name = "petitset" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1a50d821a2526af6d5756c23c68e453532a986e361a3e12c9cc7fe61d862ac" +dependencies = [ + "serde", +] + [[package]] name = "pin-project" version = "1.1.3" diff --git a/Cargo.toml b/Cargo.toml index bbc7d0f3..f9963e15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ de_core = { path = "crates/core", version = "0.1.0-dev" } de_energy = { path = "crates/energy", version = "0.1.0-dev" } de_gui = { path = "crates/gui", version = "0.1.0-dev" } de_index = { path = "crates/index", version = "0.1.0-dev" } +de_input = { path = "crates/input", version = "0.1.0-dev" } de_loader = { path = "crates/loader", version = "0.1.0-dev" } de_lobby_client = { path = "crates/lobby_client", version = "0.1.0-dev" } de_lobby_model = { path = "crates/lobby_model", version = "0.1.0-dev" } @@ -136,6 +137,7 @@ glam = "0.24" gltf = "1.0" itertools = "0.11.0" iyes_progress = "0.9.0" +leafwing-input-manager = "0.10.0" log = "0.4.17" nalgebra = { version = "0.32.3", features = ["convert-glam024"] } nix = "0.26.2" @@ -143,6 +145,7 @@ ntest = "0.9.0" parry2d = "0.13.1" parry3d = "0.13.1" paste = "1.0.12" +petitset = "0.2.1" priority-queue = "1.3.0" proc-macro2 = "1.0.63" quote = "1.0.27" diff --git a/crates/controller/Cargo.toml b/crates/controller/Cargo.toml index d56aae72..f3952c99 100644 --- a/crates/controller/Cargo.toml +++ b/crates/controller/Cargo.toml @@ -29,11 +29,15 @@ de_signs.workspace = true de_spawner.workspace = true de_terrain.workspace = true de_types.workspace = true +de_input.workspace = true # Other ahash.workspace = true bevy.workspace = true enum-map.workspace = true glam.workspace = true +leafwing-input-manager.workspace = true parry2d.workspace = true parry3d.workspace = true +petitset.workspace = true +serde.workspace = true diff --git a/crates/controller/src/actions.rs b/crates/controller/src/actions.rs new file mode 100644 index 00000000..006db825 --- /dev/null +++ b/crates/controller/src/actions.rs @@ -0,0 +1,90 @@ +use bevy::input::keyboard::KeyCode; +use bevy::prelude::Reflect; +use bevy::prelude::{Commands, MouseButton, Res, Startup}; +use de_input::{AppKeybinding, DefaultKeybindings}; +use leafwing_input_manager::prelude::DualAxis; +use leafwing_input_manager::prelude::{ActionState, InputMap, UserInput}; +use leafwing_input_manager::Actionlike; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; + +pub struct ActionPlugin; + +impl bevy::app::Plugin for ActionPlugin { + fn build(&self, app: &mut App) { + app.add_action_set::("actions"); + } +} + +/// make actoinlike enum that has normal actions and factory actions. an action is A vareint followed by a KeyConfig +macro_rules! make_actions { + {$($action:ident, ($($keybind:expr),*)),*; $($mouse_action:ident, ($($mouse_keybind:expr),*)),*; $($building_action:ident, $building_type:ident, ($($building_key:expr),*)),*} => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, Actionlike, Reflect, PartialOrd, Ord)] + pub enum Action { + $($action,)* + $($mouse_action,)* + $($building_action,)* + } + + impl DefaultKeybindings for Action { + fn default_keybindings() -> InputMap where Self: Sized { + use Action::*; + let keybindings = InputMap::::from( + vec![ + $(($action, vec![$($keybind.into()),*])),*, + $(($mouse_action, vec![$($mouse_keybind.into()),*])),*, + $(($building_action, vec![$($building_key.into()),*])),* + ].into_iter().collect::>>() + ); + keybindings + } + } + + impl Action { + pub fn get_factory_actions() -> Vec<(Self, de_types::objects::BuildingType)> { + use Action::*; + use de_types::objects::BuildingType::*; + + vec![$(($building_action, $building_type)),*] + } + + pub fn get_mouse_actions() -> Vec { + use Action::*; + vec![$($mouse_action),*] + } + } + } +} + +use bevy::app::App; +use petitset::PetitSet; +use std::collections::HashMap; + +make_actions! { + // --- general actions --- + // keyboard actions + Exit, (KeyCode::Escape), + SelectAllVisible, (UserInput::chord(vec![KeyCode::ControlLeft, KeyCode::ShiftLeft, KeyCode::A])), + SelectAll, (UserInput::chord(vec![KeyCode::ControlLeft, KeyCode::A])), + // mouse selections + AddToSelection, ( + UserInput::Chord(PetitSet::from_iter(vec![KeyCode::ControlLeft.into(),MouseButton::Left.into()])), + UserInput::Chord(PetitSet::from_iter(vec![KeyCode::ControlRight.into(), MouseButton::Left.into()]))), + ReplaceSelection, (MouseButton::Left), + // camera controls + Up, (KeyCode::W, KeyCode::Up), + Down, (KeyCode::S, KeyCode::Down), + Left, (KeyCode::A, KeyCode::Left), + Right, (KeyCode::D, KeyCode::Right), + Pivot, (UserInput::Chord(PetitSet::from_iter(vec![KeyCode::ControlLeft.into(), MouseButton::Middle.into()]))); + // --- mouse actions (these will trigger the drag logic) --- + PrimaryClick, (MouseButton::Left), + SecondaryClick, (MouseButton::Right); + // --- building actions --- + BuildBase, Base, (KeyCode::B), + BuildPowerHub, PowerHub, (KeyCode::P) +} + +pub(crate) fn action_pressed(action: A) -> impl Fn(Res>) -> bool { + move |action_state: Res>| action_state.pressed(action.clone()) +} diff --git a/crates/controller/src/commands/handlers.rs b/crates/controller/src/commands/handlers.rs index 1026a4b9..33076768 100644 --- a/crates/controller/src/commands/handlers.rs +++ b/crates/controller/src/commands/handlers.rs @@ -2,11 +2,7 @@ //! keyboard shortcuts, mouse actions events, and so on. use bevy::{ - input::{ - keyboard::KeyboardInput, - mouse::{MouseMotion, MouseScrollUnit, MouseWheel}, - ButtonState, - }, + input::mouse::{MouseMotion, MouseScrollUnit, MouseWheel}, prelude::*, window::PrimaryWindow, }; @@ -27,18 +23,17 @@ use de_types::{ objects::{BuildingType, PLAYER_MAX_BUILDINGS}, projection::ToFlat, }; -use enum_map::enum_map; +use leafwing_input_manager::prelude::ActionState; use super::{ - executor::DeliveryLocationSelectedEvent, keyboard::KeyCondition, CommandsSet, GroupAttackEvent, - SendSelectedEvent, + executor::DeliveryLocationSelectedEvent, CommandsSet, GroupAttackEvent, SendSelectedEvent, }; +use crate::actions::{action_pressed, Action}; use crate::{ draft::{DiscardDraftsEvent, DraftSet, NewDraftEvent, SpawnDraftsEvent}, hud::{GameMenuSet, ToggleGameMenuEvent, UpdateSelectionBoxEvent}, mouse::{ - DragUpdateType, MouseClickedEvent, MouseDoubleClickedEvent, MouseDraggedEvent, MouseSet, - Pointer, PointerSet, + DragUpdateType, MouseDoubleClickedEvent, MouseDraggedEvent, MouseSet, Pointer, PointerSet, }, selection::{ AreaSelectSet, SelectEvent, SelectInRectEvent, Selected, SelectionMode, SelectionSet, @@ -53,17 +48,12 @@ pub(super) struct HandlersPlugin; impl HandlersPlugin { fn add_place_draft_systems(app: &mut App) { - let key_map = enum_map! { - BuildingType::Base => KeyCode::B, - BuildingType::PowerHub => KeyCode::P, - }; - - for (building_type, &key) in key_map.iter() { + for (action, building) in Action::get_factory_actions() { app.add_systems( InputSchedule, - place_draft(building_type) + place_draft(building) .run_if(in_state(GameState::Playing)) - .run_if(KeyCondition::single(key).build()) + .run_if(action_pressed(action)) .before(DraftSet::New) .after(PointerSet::Update), ); @@ -76,22 +66,22 @@ impl Plugin for HandlersPlugin { app.add_systems( InputSchedule, ( - right_click_handler - .run_if(on_click(MouseButton::Right)) + secondary_click_handler + .run_if(action_pressed(Action::SecondaryClick)) .after(PointerSet::Update) .after(MouseSet::Buttons) .before(CommandsSet::SendSelected) .before(CommandsSet::DeliveryLocation) .before(CommandsSet::Attack), - left_click_handler - .run_if(on_click(MouseButton::Left)) + primary_click_handler + .run_if(action_pressed(Action::PrimaryClick)) .in_set(HandlersSet::LeftClick) .before(SelectionSet::Update) .before(DraftSet::Spawn) .after(PointerSet::Update) .after(MouseSet::Buttons), double_click_handler - .run_if(on_double_click(MouseButton::Left)) + .run_if(on_double_click(Action::PrimaryClick)) .before(SelectionSet::Update) .before(DraftSet::Spawn) .after(PointerSet::Update) @@ -104,19 +94,14 @@ impl Plugin for HandlersPlugin { .before(CameraSet::RotateEvent) .before(CameraSet::TiltEvent), handle_escape - .run_if(KeyCondition::single(KeyCode::Escape).build()) + .run_if(action_pressed(Action::Exit)) .before(GameMenuSet::Toggle) .before(DraftSet::Discard), select_all - .run_if(KeyCondition::single(KeyCode::A).with_ctrl().build()) + .run_if(action_pressed(Action::SelectAll)) .before(SelectionSet::Update), select_all_visible - .run_if( - KeyCondition::single(KeyCode::A) - .with_ctrl() - .with_shift() - .build(), - ) + .run_if(action_pressed(Action::SelectAllVisible)) .before(AreaSelectSet::SelectInArea), update_drags .before(AreaSelectSet::SelectInArea) @@ -134,15 +119,7 @@ pub(crate) enum HandlersSet { LeftClick, } -fn on_click(button: MouseButton) -> impl Fn(EventReader) -> bool { - move |mut events: EventReader| { - // It is desirable to exhaust the iterator, thus .filter().count() is - // used instead of .any() - events.iter().filter(|e| e.button() == button).count() > 0 - } -} - -fn on_double_click(button: MouseButton) -> impl Fn(EventReader) -> bool { +fn on_double_click(button: Action) -> impl Fn(EventReader) -> bool { move |mut events: EventReader| { // It is desirable to exhaust the iterator, thus .filter().count() is // used instead of .any() @@ -150,7 +127,7 @@ fn on_double_click(button: MouseButton) -> impl Fn(EventReader, mut send_events: EventWriter, mut location_events: EventWriter, @@ -207,34 +184,47 @@ fn double_click_handler( } fn move_camera_arrows_system( - mut key_events: EventReader, + action_state: Res>, mut move_events: EventWriter, + mut current_direction: Local, ) { - for key_event in key_events.iter() { - let Some(key_code) = key_event.key_code else { - continue; - }; + let old_direction = *current_direction; - let mut direction = Vec2::ZERO; - if key_code == KeyCode::Left { - direction = Vec2::new(-1., 0.); - } else if key_code == KeyCode::Right { - direction = Vec2::new(1., 0.); - } else if key_code == KeyCode::Down { - direction = Vec2::new(0., -1.); - } else if key_code == KeyCode::Up { - direction = Vec2::new(0., 1.); - } + let mut stopping: bool = false; - if direction == Vec2::ZERO { - continue; - } - if key_event.state == ButtonState::Released { - direction = Vec2::ZERO; - } + if action_state.just_pressed(Action::Left) { + current_direction.x = -1.; + } else if action_state.just_released(Action::Left) { + current_direction.x = 0.; + stopping = true; + } - move_events.send(MoveCameraHorizontallyEvent::new(direction)); + if action_state.just_pressed(Action::Right) { + current_direction.x = 1.; + } else if action_state.just_released(Action::Right) { + current_direction.x = 0.; + stopping = true; } + + if action_state.just_pressed(Action::Up) { + current_direction.y = 1.; + } else if action_state.just_released(Action::Up) { + current_direction.y = 0.; + stopping = true; + } + + if action_state.just_pressed(Action::Down) { + current_direction.y = -1.; + } else if action_state.just_released(Action::Down) { + current_direction.y = 0.; + stopping = true; + } + + if *current_direction == Vec2::ZERO && !stopping || *current_direction == old_direction { + return; + } + + move_events.send(MoveCameraHorizontallyEvent::new(*current_direction)); } fn move_camera_mouse_system( @@ -287,13 +277,12 @@ fn zoom_camera( fn pivot_camera( conf: Res, - buttons: Res>, - keys: Res>, + action_state: Res>, mut mouse_event: EventReader, mut rotate_event: EventWriter, mut tilt_event: EventWriter, ) { - if !buttons.pressed(MouseButton::Middle) && !keys.pressed(KeyCode::ShiftLeft) { + if !action_state.pressed(Action::Pivot) { return; } @@ -307,19 +296,21 @@ fn pivot_camera( } } -fn left_click_handler( +fn primary_click_handler( mut select_events: EventWriter, mut draft_events: EventWriter, - keys: Res>, + action_state: Res>, pointer: Res, playable: Query<(), With>, drafts: Query<(), With>, ) { if drafts.is_empty() { - let selection_mode = if keys.pressed(KeyCode::ControlLeft) { + let selection_mode = if action_state.pressed(Action::AddToSelection) { SelectionMode::AddToggle - } else { + } else if action_state.pressed(Action::ReplaceSelection) { SelectionMode::Replace + } else { + return; }; let event = match pointer.entity().filter(|&e| playable.contains(e)) { @@ -385,13 +376,13 @@ fn select_all_visible(mut events: EventWriter) { } fn update_drags( - keys: Res>, + action_state: Res>, mut drag_events: EventReader, mut ui_events: EventWriter, mut select_events: EventWriter, ) { for drag_event in drag_events.iter() { - if drag_event.button() != MouseButton::Left { + if drag_event.button() != Action::PrimaryClick { continue; } @@ -402,13 +393,14 @@ fn update_drags( }, DragUpdateType::Released => { if let Some(rect) = drag_event.rect() { - let mode = if keys.pressed(KeyCode::ControlLeft) - || keys.pressed(KeyCode::ControlRight) - { + let mode = if action_state.pressed(Action::AddToSelection) { SelectionMode::Add - } else { + } else if action_state.pressed(Action::ReplaceSelection) { SelectionMode::Replace + } else { + continue; }; + select_events.send(SelectInRectEvent::new(rect, mode, None)); } diff --git a/crates/controller/src/commands/keyboard.rs b/crates/controller/src/commands/keyboard.rs deleted file mode 100644 index 2c67be9c..00000000 --- a/crates/controller/src/commands/keyboard.rs +++ /dev/null @@ -1,51 +0,0 @@ -use bevy::{ - input::{keyboard::KeyboardInput, ButtonState}, - prelude::*, -}; - -/// Builder of keyboard events & state based system execution condition. -#[derive(Copy, Clone)] -pub(super) struct KeyCondition { - control: bool, - shift: bool, - key: KeyCode, -} - -impl KeyCondition { - /// Run if a key is pressed and control is not. - pub(super) fn single(key: KeyCode) -> Self { - Self { - control: false, - shift: false, - key, - } - } - - /// Run if a key is pressed together with control. - pub(super) fn with_ctrl(mut self) -> Self { - self.control = true; - self - } - - /// Run if a key is pressed together with shift. - pub(super) fn with_shift(mut self) -> Self { - self.shift = true; - self - } - - pub(super) fn build(self) -> impl Fn(Res>, EventReader) -> bool { - move |keys: Res>, mut events: EventReader| { - let proper_key = events - .iter() - .filter(|k| { - k.state == ButtonState::Pressed && k.key_code.map_or(false, |c| c == self.key) - }) - .count() - > 0; - - let control = keys.pressed(KeyCode::ControlLeft) || keys.pressed(KeyCode::ControlRight); - let shift = keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight); - self.control == control && shift == self.shift && proper_key - } - } -} diff --git a/crates/controller/src/commands/mod.rs b/crates/controller/src/commands/mod.rs index 48b3dc00..e174e2d9 100644 --- a/crates/controller/src/commands/mod.rs +++ b/crates/controller/src/commands/mod.rs @@ -10,7 +10,6 @@ use self::{executor::ExecutorPlugin, handlers::HandlersPlugin}; mod executor; mod handlers; -mod keyboard; pub(crate) struct CommandsPlugin; diff --git a/crates/controller/src/hud/minimap/interaction.rs b/crates/controller/src/hud/minimap/interaction.rs index 441f838e..d2ca75e3 100644 --- a/crates/controller/src/hud/minimap/interaction.rs +++ b/crates/controller/src/hud/minimap/interaction.rs @@ -8,6 +8,7 @@ use bevy::{ prelude::*, window::PrimaryWindow, }; +use leafwing_input_manager::prelude::ActionState; use de_camera::MoveFocusEvent; use de_core::{gamestate::GameState, schedule::InputSchedule}; use de_map::size::MapBounds; @@ -16,6 +17,7 @@ use super::nodes::MinimapNode; use crate::{ commands::{CommandsSet, DeliveryLocationSelectedEvent, SendSelectedEvent}, hud::HudNodes, + actions::Action, }; pub(super) struct InteractionPlugin; @@ -58,17 +60,17 @@ enum InteractionSet { #[derive(Event)] struct MinimapPressEvent { - button: MouseButton, + action: Action, position: Vec2, } impl MinimapPressEvent { - fn new(button: MouseButton, position: Vec2) -> Self { - Self { button, position } + fn new(action: Action, position: Vec2) -> Self { + Self { action, position } } - fn button(&self) -> MouseButton { - self.button + fn button(&self) -> Action { + self.action } /// Position on the map in 2D flat coordinates (these are not minimap @@ -80,23 +82,23 @@ impl MinimapPressEvent { impl fmt::Debug for MinimapPressEvent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?} -> {:?}", self.button, self.position) + write!(f, "{:?} -> {:?}", self.action, self.position) } } #[derive(Event)] struct MinimapDragEvent { - button: MouseButton, + action: Action, position: Vec2, } impl MinimapDragEvent { - fn new(button: MouseButton, position: Vec2) -> Self { - Self { button, position } + fn new(action: Action, position: Vec2) -> Self { + Self { action, position } } - fn button(&self) -> MouseButton { - self.button + fn button(&self) -> Action { + self.action } /// Position on the map in 2D flat coordinates (these are not minimap @@ -107,11 +109,11 @@ impl MinimapDragEvent { } #[derive(Resource, Deref, DerefMut)] -struct DraggingButtons(Vec); +struct DraggingButtons(Vec); fn press_handler( window_query: Query<&Window, With>, - mut input_events: EventReader, + action_state: Res>, hud: HudNodes>, bounds: Res, mut dragging: ResMut, @@ -119,13 +121,10 @@ fn press_handler( ) { let cursor = window_query.single().cursor_position(); - for event in input_events.iter() { - match event.state { - ButtonState::Released => { - dragging.retain(|b| *b != event.button); - continue; - } - ButtonState::Pressed => (), + for mouse_action in Action::get_mouse_actions() { + if action_state.just_released(mouse_action) { + dragging.retain(|b| *b != mouse_action); + continue; } let Some(cursor) = cursor else { @@ -133,9 +132,9 @@ fn press_handler( }; if let Some(mut relative) = hud.relative_position(cursor) { - dragging.push(event.button); + dragging.push(mouse_action); relative.y = 1. - relative.y; - let event = MinimapPressEvent::new(event.button, bounds.rel_to_abs(relative)); + let event = MinimapPressEvent::new(mouse_action, bounds.rel_to_abs(relative)); info!("Sending minimap press event {event:?}."); press_events.send(event); } @@ -174,7 +173,7 @@ fn move_camera_system( mut camera_events: EventWriter, ) { for press in press_events.iter() { - if press.button() != MouseButton::Left { + if press.button() != Action::PrimaryClick { continue; } @@ -183,7 +182,7 @@ fn move_camera_system( } for drag in drag_events.iter() { - if drag.button() != MouseButton::Left { + if drag.button() != Action::PrimaryClick { continue; } @@ -197,7 +196,7 @@ fn send_units_system( mut send_events: EventWriter, ) { for press in press_events.iter() { - if press.button() != MouseButton::Right { + if press.button() != Action::SecondaryClick { continue; } send_events.send(SendSelectedEvent::new(press.position())); @@ -209,7 +208,7 @@ fn delivery_location_system( mut location_events: EventWriter, ) { for press in press_events.iter() { - if press.button() != MouseButton::Right { + if press.button() != Action::SecondaryClick { continue; } location_events.send(DeliveryLocationSelectedEvent::new(press.position())); diff --git a/crates/controller/src/lib.rs b/crates/controller/src/lib.rs index 6ee4c09f..3f8a8e11 100644 --- a/crates/controller/src/lib.rs +++ b/crates/controller/src/lib.rs @@ -6,6 +6,7 @@ use draft::DraftPlugin; use hud::HudPlugin; use mouse::MousePlugin; use selection::SelectionPlugin; +use actions::ActionPlugin; mod commands; mod draft; @@ -14,6 +15,7 @@ mod hud; mod mouse; mod ray; mod selection; +mod actions; const SELECTION_BAR_ID: u32 = 0; const POINTER_BAR_ID: u32 = 1; @@ -28,5 +30,7 @@ impl PluginGroup for ControllerPluginGroup { .add(SelectionPlugin) .add(DraftPlugin) .add(HudPlugin) + .add(ActionPlugin) + } } diff --git a/crates/controller/src/mouse/input.rs b/crates/controller/src/mouse/input.rs index 9bdab4dc..213fd403 100644 --- a/crates/controller/src/mouse/input.rs +++ b/crates/controller/src/mouse/input.rs @@ -1,3 +1,4 @@ +use crate::actions::Action; use ahash::AHashMap; use bevy::{ input::{mouse::MouseButtonInput, ButtonState}, @@ -7,6 +8,7 @@ use bevy::{ use de_core::{ gamestate::GameState, schedule::InputSchedule, screengeom::ScreenRect, state::AppState, }; +use leafwing_input_manager::prelude::ActionState; use crate::hud::HudNodes; @@ -52,17 +54,17 @@ pub(crate) enum MouseSet { #[derive(Event)] pub(crate) struct MouseClickedEvent { - button: MouseButton, + action: Action, position: Vec2, } impl MouseClickedEvent { - fn new(button: MouseButton, position: Vec2) -> Self { - Self { button, position } + fn new(action: Action, position: Vec2) -> Self { + Self { action, position } } - pub(crate) fn button(&self) -> MouseButton { - self.button + pub(crate) fn button(&self) -> Action { + self.action } pub(crate) fn position(&self) -> Vec2 { @@ -72,37 +74,37 @@ impl MouseClickedEvent { #[derive(Event)] pub(crate) struct MouseDoubleClickedEvent { - button: MouseButton, + action: Action, } impl MouseDoubleClickedEvent { - fn new(button: MouseButton) -> Self { - Self { button } + fn new(action: Action) -> Self { + Self { action } } - pub(crate) fn button(&self) -> MouseButton { - self.button + pub(crate) fn button(&self) -> Action { + self.action } } #[derive(Event)] pub(crate) struct MouseDraggedEvent { - button: MouseButton, + action: Action, rect: Option, update_type: DragUpdateType, } impl MouseDraggedEvent { - fn new(button: MouseButton, rect: Option, update_type: DragUpdateType) -> Self { + fn new(action: Action, rect: Option, update_type: DragUpdateType) -> Self { Self { - button, + action, rect, update_type, } } - pub(crate) fn button(&self) -> MouseButton { - self.button + pub(crate) fn button(&self) -> Action { + self.action } /// Screen rectangle corresponding to the drag (i.e. its starting and @@ -144,15 +146,15 @@ impl MousePosition { } #[derive(Default, Resource)] -struct MouseDragStates(AHashMap); +struct MouseDragStates(AHashMap); impl MouseDragStates { - fn set(&mut self, button: MouseButton, position: Option) { - self.0.insert(button, DragState::new(position)); + fn set(&mut self, action: Action, position: Option) { + self.0.insert(action, DragState::new(position)); } - fn resolve(&mut self, button: MouseButton) -> Option { - self.0.remove(&button).and_then(DragState::resolve) + fn resolve(&mut self, action: Action) -> Option { + self.0.remove(&action).and_then(DragState::resolve) } /// Updates the end position of all opened drags. A map of mouse buttons to @@ -160,7 +162,7 @@ impl MouseDragStates { /// /// None means that the drag is (temporarily) canceled, Some means that the /// drag has been updated to this new rectangle. - fn update(&mut self, position: Option) -> AHashMap> { + fn update(&mut self, position: Option) -> AHashMap> { let mut updates = AHashMap::new(); for (&button, drag) in self.0.iter_mut() { if let Some(update) = drag.update(position) { @@ -269,31 +271,29 @@ fn update_drags( fn update_buttons( mouse_position: Res, mut mouse_state: ResMut, - mut input_events: EventReader, + action_state: Res>, mut clicks: EventWriter, mut drags: EventWriter, ) { - for event in input_events.iter() { - match event.state { - ButtonState::Released => { - if let Some(drag_resolution) = mouse_state.resolve(event.button) { - match drag_resolution { - DragResolution::Point(position) => { - clicks.send(MouseClickedEvent::new(event.button, position)); - } - DragResolution::Rect(rect) => { - drags.send(MouseDraggedEvent::new( - event.button, - rect, - DragUpdateType::Released, - )); - } + for action in Action::get_mouse_actions() { + if action_state.just_pressed(action) { + mouse_state.set(action, mouse_position.ndc()); + } else if action_state.just_released(action) { + println!("released drag"); + if let Some(drag_resolution) = mouse_state.resolve(action) { + match drag_resolution { + DragResolution::Point(position) => { + clicks.send(MouseClickedEvent::new(action, position)); + } + DragResolution::Rect(rect) => { + drags.send(MouseDraggedEvent::new( + action, + rect, + DragUpdateType::Released, + )); } } } - ButtonState::Pressed => { - mouse_state.set(event.button, mouse_position.ndc()); - } } } } diff --git a/crates/core/src/schedule.rs b/crates/core/src/schedule.rs index 41e5b676..825f5f4e 100644 --- a/crates/core/src/schedule.rs +++ b/crates/core/src/schedule.rs @@ -11,12 +11,17 @@ impl Plugin for GameSchedulesPlugin { } fn setup(mut main: ResMut) { - main.insert_after(First, InputSchedule); + main.insert_after(First, PreInputSchedule); + main.insert_after(PreInputSchedule, InputSchedule); main.insert_after(InputSchedule, PreMovement); main.insert_after(PreMovement, Movement); main.insert_after(Movement, PostMovement); } +/// The user input is turned into actions during this schedule. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct PreInputSchedule; + /// All user input is handled during this schedule. #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] pub struct InputSchedule; diff --git a/crates/input/Cargo.toml b/crates/input/Cargo.toml index b94cfe2e..0fdea9aa 100644 --- a/crates/input/Cargo.toml +++ b/crates/input/Cargo.toml @@ -12,7 +12,12 @@ license.workspace = true categories.workspace = true [dependencies] +# DE +de_core.workspace = true # Other bevy.workspace = true -serde.workspace = true \ No newline at end of file +serde.workspace = true +serde_yaml.workspace = true +leafwing-input-manager.workspace = true +ron = "0.8.1" \ No newline at end of file diff --git a/crates/input/src/io.rs b/crates/input/src/io.rs new file mode 100644 index 00000000..163de8ab --- /dev/null +++ b/crates/input/src/io.rs @@ -0,0 +1,32 @@ +use crate::BindableActionlike; +use leafwing_input_manager::prelude::InputMap; +use std::io::{prelude, Read}; +use ron::ser::PrettyConfig; +use de_core::fs::conf_dir; + +pub(crate) fn get_keybindings( + action_set_name: String, + default_keybindings: InputMap, +) -> InputMap { + let mut file = + match std::fs::File::open( conf_dir().expect("Could not get config dir").join(format!("keybindings.{}.ron", action_set_name))) { + Ok(file) => file, + Err(_) => { + std::fs::write( + conf_dir().expect("Could not get config dir").join(format!("keybindings.{}.ron", action_set_name)), + ron::ser::to_string_pretty(&default_keybindings, PrettyConfig::new()).unwrap(), + ) + .unwrap(); + return default_keybindings; + } + }; + + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + let mut keybindings: InputMap = ron::from_str(&contents).unwrap(); + + // fill unset keys with default keybindings + keybindings.merge(&default_keybindings); + + keybindings +} diff --git a/crates/input/src/key.rs b/crates/input/src/key.rs deleted file mode 100644 index d2854d71..00000000 --- a/crates/input/src/key.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::hash::Hash; -use std::marker::PhantomData; - -use bevy::app::PreUpdate; -use bevy::input::InputSystem; -use bevy::prelude::{App, Input, IntoSystemConfigs, Plugin, Res, ResMut, Resource, SystemSet}; - -use crate::ActionTrait; - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, SystemSet)] -pub struct ActionSet; - -#[derive(Resource)] -pub struct Action { - inputs: Vec, - is_currently_pressed: bool, - just_pressed: bool, - just_released: bool, - _marker: PhantomData, -} - -impl Action { - pub fn inputs(&self) -> &Vec { - &self.inputs - } -} - -pub struct KeyPlugin { - keys: Vec, - #[allow(dead_code)] // TODO: Remove this and use this field - config_name: String, - _marker: PhantomData, -} - -impl KeyPlugin { - pub fn new(keys: Vec, config_name: String) -> Self { - Self { - keys, - config_name, - _marker: PhantomData, - } - } -} - -impl Plugin for KeyPlugin { - fn build(&self, app: &mut App) { - app.add_systems( - PreUpdate, - keyboard_input::.after(InputSystem).in_set(ActionSet), - ) - .insert_resource(Action:: { - inputs: self.keys.clone(), - is_currently_pressed: false, - just_pressed: false, - just_released: false, - _marker: PhantomData, - }); - } -} - -fn keyboard_input( - input: Res>, - mut action: ResMut>, -) { - let mut just_pressed = false; - let mut just_released = false; - let mut is_currently_pressed = false; - - for key in action.inputs() { - if input.just_pressed(*key) { - just_pressed = true; - } - - if input.just_released(*key) { - just_released = true; - } - - if input.pressed(*key) { - is_currently_pressed = true; - } - } - - action.just_pressed = just_pressed; - action.just_released = just_released; - action.is_currently_pressed = is_currently_pressed; -} - -#[cfg(test)] -mod tests { - use bevy::input::InputPlugin; - use bevy::prelude::KeyCode; - use bevy::prelude::KeyCode::Key0; - - use super::*; - use crate::AppKeybinding; - - #[test] - fn test_keybinding() { - struct TestAction; - - impl ActionTrait for TestAction { - type InputType = KeyCode; - } - - let mut app = App::new(); - - app.add_plugins(InputPlugin); - - app.add_keybinding::(Key0, "test_key".to_string()); - - app.update(); - let action = app.world.get_resource::>().unwrap(); - - assert!(!action.just_pressed); - assert!(!action.just_released); - assert!(!action.is_currently_pressed); - - fn test_press_key(mut keyboard_input_events: ResMut>) { - keyboard_input_events.press(Key0); - } - - app.add_systems( - PreUpdate, - test_press_key.before(ActionSet).after(InputSystem), - ); - - app.update(); - - let action = app.world.get_resource::>().unwrap(); - let keyboard_input_events = app.world.get_resource::>().unwrap(); - - println!("{:?}", keyboard_input_events); - - assert!(action.just_pressed); - assert!(!action.just_released); - assert!(action.is_currently_pressed); - } -} diff --git a/crates/input/src/lib.rs b/crates/input/src/lib.rs index dc07c8e6..a6610c62 100644 --- a/crates/input/src/lib.rs +++ b/crates/input/src/lib.rs @@ -1,61 +1,104 @@ -mod key; - -use std::hash::Hash; - use bevy::prelude::App; +use bevy::prelude::Reflect; +use std::collections::HashMap; +use leafwing_input_manager::prelude::{ActionState, InputMap}; +use leafwing_input_manager::{Actionlike, InputManagerBundle}; +use serde::{Deserialize, Serialize}; +use std::hash::Hash; use serde::de::DeserializeOwned; -use serde::Serialize; +use crate::plugin::InputManagerPlugin; + +mod io; +mod plugin; -use crate::key::KeyPlugin; -pub trait KeyBinding: - Copy + Eq + Hash + Send + Sync + Serialize + DeserializeOwned + 'static +pub trait BindableActionlike: + Actionlike + DeserializeOwned + Serialize + Clone + Send + Sync + Eq + Hash + Ord { } -impl KeyBinding for T {} +impl + BindableActionlike for T +{ +} -pub trait ActionTrait { - type InputType: KeyBinding; +pub trait DefaultKeybindings: BindableActionlike { + fn default_keybindings() -> InputMap where Self: Sized; } pub trait AppKeybinding { - /// Add a keybinding to the app. - /// - /// # Arguments - /// * `E` - The event type to be sent when the keybinding is pressed. - /// * `I` - The type of Input. - /// * `default` - The default keybinding. - /// * `config_name` - The name of the keybinding in the config file. - fn add_keybinding( + /// Add a keybinding with config to the app. + fn add_action_set( &mut self, - default_keys: impl IntoKeys, - config_name: String, + config_name: impl Into, ) -> &mut Self; } -pub trait IntoKeys { - fn into_keys(self) -> Vec; -} +impl AppKeybinding for App { + fn add_action_set( + &mut self, + config_name: impl Into, + ) -> &mut Self { + let keybindings: InputMap = io::get_keybindings(config_name.into(), A::default_keybindings()); + self.world.insert_resource(keybindings); + self.world.insert_resource(ActionState::::default()); -impl IntoKeys for T { - fn into_keys(self) -> Vec { - vec![self] - } -} + self.add_plugins(InputManagerPlugin::::default()); -impl IntoKeys for Vec { - fn into_keys(self) -> Vec { self } + } -impl AppKeybinding for App { - fn add_keybinding( - &mut self, - default_keys: impl IntoKeys, - config_name: String, - ) -> &mut Self { - self.add_plugins(KeyPlugin::::new(default_keys.into_keys(), config_name)) +#[cfg(test)] +mod tests { + use super::*; + use bevy::prelude::KeyCode; + use leafwing_input_manager::prelude::UserInput; + use leafwing_input_manager::user_input::InputKind::Keyboard; + + #[test] + fn test_keybindings() { + let mut app = App::new(); + #[derive( + Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, Actionlike, Reflect, PartialOrd, Ord + )] + enum PlayerAction { + // Movement + Up, + Down, + Left, + Right, + // Abilities + Ability1, + Ability2, + Ability3, + Ability4, + Ultimate, + } + + impl DefaultKeybindings for PlayerAction { + fn default_keybindings() -> InputMap where Self: Sized { + InputMap::from( + vec![ + (Self::Up, vec![UserInput::Single(Keyboard(KeyCode::W))]), + (Self::Down, vec![UserInput::Single(Keyboard(KeyCode::S))]), + (Self::Left, vec![UserInput::Single(Keyboard(KeyCode::A))]), + (Self::Right, vec![UserInput::Single(Keyboard(KeyCode::D))]), + (Self::Ability1, vec![UserInput::Single(Keyboard(KeyCode::Q))]), + (Self::Ability2, vec![UserInput::Single(Keyboard(KeyCode::E))]), + (Self::Ability3, vec![UserInput::Single(Keyboard(KeyCode::F))]), + (Self::Ability4, vec![UserInput::Single(Keyboard(KeyCode::R))]), + (Self::Ultimate, vec![UserInput::Single(Keyboard(KeyCode::Space))]), + ].into_iter().collect::>>() + ) + } + } + + app.add_action_set::( + "player".to_string(), + ); + + app.update(); } } diff --git a/crates/input/src/plugin.rs b/crates/input/src/plugin.rs new file mode 100644 index 00000000..b0794552 --- /dev/null +++ b/crates/input/src/plugin.rs @@ -0,0 +1,108 @@ +//! Contains main plugin exported by this crate. + +use core::hash::Hash; +use core::marker::PhantomData; +use std::fmt::Debug; + +use bevy::app::{App, Plugin}; +use bevy::ecs::prelude::*; +use bevy::input::InputSystem; +use bevy::prelude::{PostUpdate, PreUpdate}; +use leafwing_input_manager::Actionlike; +use leafwing_input_manager::plugin::InputManagerSystem; + +use de_core::schedule::{InputSchedule, PreInputSchedule}; +use leafwing_input_manager::prelude::{ActionState, ClashStrategy, ToggleActions}; + +/// A [`Plugin`] that collects [`Input`](bevy::input::Input) +/// from disparate sources, producing an [`ActionState`] that +/// can be conveniently checked +/// +/// This plugin needs to be passed in an [`Actionlike`] enum type +/// that you've created for your game. Each variant represents a +/// "virtual button" whose state is stored in an [`ActionState`] struct. +/// +/// Each [`InputManagerBundle`](crate::InputManagerBundle) contains: +/// - an [`InputMap`](crate::input_map::InputMap) component, which +/// stores an entity-specific mapping between the assorted input +/// streams and an internal representation of "actions" +/// - an [`ActionState`] component, which stores the current +/// input state for that entity in an source-agnostic fashion +/// +/// If you have more than one distinct type of action +/// (e.g. menu actions, camera actions and player actions), +/// consider creating multiple `Actionlike` enums +/// and adding a copy of this plugin for each `Actionlike` type. +/// +/// ## Systems +/// +/// All systems added by this plugin can be dynamically enabled +/// and disabled by setting the value of the [`ToggleActions`] resource is set. +/// This can be useful when working with states to pause the game, +/// navigate menus or so on. +/// +/// Complete list: +/// +/// - [`tick_action_state`](leafwing_input_manager::systems::tick_action_state), +/// which resets the `pressed` and `just_pressed` fields of +/// the [`ActionState`] each frame +/// - [`update_action_state`](leafwing_input_manager::systems::update_action_state), +/// which collects [`Input`](bevy::input::Input) resources to update +/// the [`ActionState`] +/// - [`update_action_state_from_interaction`](leafwing_input_manager::systems::update_action_state_from_interaction), +/// for triggering actions from buttons +/// - powers the [`ActionStateDriver`](leafwing_input_manager::action_state::ActionStateDriver) +/// component based on an [`Interaction`](bevy::ui::Interaction) +/// component +/// - [`release_on_disable`](leafwing_input_manager::systems::release_on_disable), +/// which resets action states when [`ToggleActions`] is flipped, to avoid persistent presses. +pub struct InputManagerPlugin { + _phantom: PhantomData, +} + +// Deriving default induces an undesired bound on the generic +impl Default for InputManagerPlugin { + fn default() -> Self { + Self { + _phantom: PhantomData, + } + } +} + +impl Plugin for InputManagerPlugin { + fn build(&self, app: &mut App) { + use leafwing_input_manager::systems::*; + + app.add_systems( + PreInputSchedule, + tick_action_state:: + .run_if(run_if_enabled::) + .in_set(InputManagerSystem::Tick) + .before(InputManagerSystem::Update), + ) + .add_systems( + PreInputSchedule, + release_on_disable:: + .in_set(InputManagerSystem::ReleaseOnDisable) + .after(InputManagerSystem::Update), + ) + .add_systems(PostUpdate, release_on_input_map_removed::); + + app.add_systems( + PreInputSchedule, + update_action_state::.in_set(InputManagerSystem::Update), + ); + + app.configure_set( + PreInputSchedule, + InputManagerSystem::Update + .run_if(run_if_enabled::) + .after(InputSystem), + ); + + app.register_type::>() + // Resources + .init_resource::>() + .insert_resource(ClashStrategy::UseActionOrder); + } +} \ No newline at end of file From dc4895b6fefa030607d5886eea01d94f1d7bb86a Mon Sep 17 00:00:00 2001 From: jack crump-leys Date: Wed, 25 Oct 2023 16:25:21 +1300 Subject: [PATCH 4/6] WIP - fixed some bugs --- crates/controller/src/actions.rs | 50 +++++++++++---- crates/controller/src/commands/handlers.rs | 16 ++--- .../controller/src/hud/minimap/interaction.rs | 61 ++++++++++--------- crates/controller/src/lib.rs | 1 - crates/controller/src/mouse/input.rs | 45 ++++++++------ 5 files changed, 104 insertions(+), 69 deletions(-) diff --git a/crates/controller/src/actions.rs b/crates/controller/src/actions.rs index 006db825..6db18300 100644 --- a/crates/controller/src/actions.rs +++ b/crates/controller/src/actions.rs @@ -1,5 +1,5 @@ use bevy::input::keyboard::KeyCode; -use bevy::prelude::Reflect; +use bevy::prelude::{Reflect, Update}; use bevy::prelude::{Commands, MouseButton, Res, Startup}; use de_input::{AppKeybinding, DefaultKeybindings}; use leafwing_input_manager::prelude::DualAxis; @@ -12,30 +12,53 @@ pub struct ActionPlugin; impl bevy::app::Plugin for ActionPlugin { fn build(&self, app: &mut App) { - app.add_action_set::("actions"); + app.add_action_set::("actions") + // Mouse is separate because otherwise it will clash with AddToSelection and ReplaceSelection + .add_action_set::("mouse_actions"); } } /// make actoinlike enum that has normal actions and factory actions. an action is A vareint followed by a KeyConfig macro_rules! make_actions { - {$($action:ident, ($($keybind:expr),*)),*; $($mouse_action:ident, ($($mouse_keybind:expr),*)),*; $($building_action:ident, $building_type:ident, ($($building_key:expr),*)),*} => { + { + $($action:ident, ($($keybind:expr),*)),*; + $($mouse_action:ident, ($($mouse_keybind:expr),*)),*; + $($building_action:ident, $building_type:ident, ($($building_key:expr),*)),* + } => { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, Actionlike, Reflect, PartialOrd, Ord)] pub enum Action { $($action,)* - $($mouse_action,)* $($building_action,)* } + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, Actionlike, Reflect, PartialOrd, Ord)] + pub enum MouseAction { + $($mouse_action,)* + } + impl DefaultKeybindings for Action { fn default_keybindings() -> InputMap where Self: Sized { use Action::*; let keybindings = InputMap::::from( vec![ $(($action, vec![$($keybind.into()),*])),*, - $(($mouse_action, vec![$($mouse_keybind.into()),*])),*, $(($building_action, vec![$($building_key.into()),*])),* ].into_iter().collect::>>() ); + println!("keybindings: {:?}", keybindings); + keybindings + } + } + + impl DefaultKeybindings for MouseAction { + fn default_keybindings() -> InputMap where Self: Sized { + use MouseAction::*; + let keybindings = InputMap::::from( + vec![ + $(($mouse_action, vec![$($mouse_keybind.into()),*])),* + ].into_iter().collect::>>() + ); + println!("mouse keybindings: {:?}", keybindings); keybindings } } @@ -47,11 +70,6 @@ macro_rules! make_actions { vec![$(($building_action, $building_type)),*] } - - pub fn get_mouse_actions() -> Vec { - use Action::*; - vec![$($mouse_action),*] - } } } } @@ -79,12 +97,20 @@ make_actions! { Pivot, (UserInput::Chord(PetitSet::from_iter(vec![KeyCode::ControlLeft.into(), MouseButton::Middle.into()]))); // --- mouse actions (these will trigger the drag logic) --- PrimaryClick, (MouseButton::Left), - SecondaryClick, (MouseButton::Right); + SecondaryClick, ( + MouseButton::Right); // --- building actions --- BuildBase, Base, (KeyCode::B), BuildPowerHub, PowerHub, (KeyCode::P) } pub(crate) fn action_pressed(action: A) -> impl Fn(Res>) -> bool { - move |action_state: Res>| action_state.pressed(action.clone()) + move |action_state: Res>| action_state.just_pressed(action.clone()) +} + +pub(crate) fn mouse_input_pressed(mouse_actions: Res>) -> bool { + if mouse_actions.get_pressed().is_empty() { + return false; + } + return true; } diff --git a/crates/controller/src/commands/handlers.rs b/crates/controller/src/commands/handlers.rs index 33076768..ecb77343 100644 --- a/crates/controller/src/commands/handlers.rs +++ b/crates/controller/src/commands/handlers.rs @@ -28,7 +28,7 @@ use leafwing_input_manager::prelude::ActionState; use super::{ executor::DeliveryLocationSelectedEvent, CommandsSet, GroupAttackEvent, SendSelectedEvent, }; -use crate::actions::{action_pressed, Action}; +use crate::actions::{action_pressed, Action, MouseAction}; use crate::{ draft::{DiscardDraftsEvent, DraftSet, NewDraftEvent, SpawnDraftsEvent}, hud::{GameMenuSet, ToggleGameMenuEvent, UpdateSelectionBoxEvent}, @@ -67,21 +67,21 @@ impl Plugin for HandlersPlugin { InputSchedule, ( secondary_click_handler - .run_if(action_pressed(Action::SecondaryClick)) + .run_if(action_pressed(MouseAction::SecondaryClick)) .after(PointerSet::Update) .after(MouseSet::Buttons) .before(CommandsSet::SendSelected) .before(CommandsSet::DeliveryLocation) .before(CommandsSet::Attack), primary_click_handler - .run_if(action_pressed(Action::PrimaryClick)) + .run_if(action_pressed(MouseAction::PrimaryClick)) .in_set(HandlersSet::LeftClick) .before(SelectionSet::Update) .before(DraftSet::Spawn) .after(PointerSet::Update) .after(MouseSet::Buttons), double_click_handler - .run_if(on_double_click(Action::PrimaryClick)) + .run_if(on_double_click(MouseAction::PrimaryClick)) .before(SelectionSet::Update) .before(DraftSet::Spawn) .after(PointerSet::Update) @@ -119,7 +119,7 @@ pub(crate) enum HandlersSet { LeftClick, } -fn on_double_click(button: Action) -> impl Fn(EventReader) -> bool { +fn on_double_click(button: MouseAction) -> impl Fn(EventReader) -> bool { move |mut events: EventReader| { // It is desirable to exhaust the iterator, thus .filter().count() is // used instead of .any() @@ -382,7 +382,7 @@ fn update_drags( mut select_events: EventWriter, ) { for drag_event in drag_events.iter() { - if drag_event.button() != Action::PrimaryClick { + if drag_event.button() != MouseAction::PrimaryClick { continue; } @@ -393,9 +393,9 @@ fn update_drags( }, DragUpdateType::Released => { if let Some(rect) = drag_event.rect() { - let mode = if action_state.pressed(Action::AddToSelection) { + let mode = if action_state.just_released(Action::AddToSelection) { SelectionMode::Add - } else if action_state.pressed(Action::ReplaceSelection) { + } else if action_state.just_released(Action::ReplaceSelection) { SelectionMode::Replace } else { continue; diff --git a/crates/controller/src/hud/minimap/interaction.rs b/crates/controller/src/hud/minimap/interaction.rs index d2ca75e3..064c88ed 100644 --- a/crates/controller/src/hud/minimap/interaction.rs +++ b/crates/controller/src/hud/minimap/interaction.rs @@ -8,6 +8,7 @@ use bevy::{ prelude::*, window::PrimaryWindow, }; +use leafwing_input_manager::Actionlike; use leafwing_input_manager::prelude::ActionState; use de_camera::MoveFocusEvent; use de_core::{gamestate::GameState, schedule::InputSchedule}; @@ -19,6 +20,7 @@ use crate::{ hud::HudNodes, actions::Action, }; +use crate::actions::{mouse_input_pressed, MouseAction}; pub(super) struct InteractionPlugin; @@ -32,11 +34,12 @@ impl Plugin for InteractionPlugin { ( press_handler .in_set(InteractionSet::PressHandler) - .run_if(on_event::()), + .run_if(mouse_input_pressed) + .in_set(InteractionSet::PressHandler), drag_handler .in_set(InteractionSet::DragHandler) - .after(InteractionSet::PressHandler) - .run_if(on_event::()), + .run_if(mouse_input_pressed) + .after(InteractionSet::PressHandler), move_camera_system .after(InteractionSet::PressHandler) .after(InteractionSet::DragHandler), @@ -60,16 +63,16 @@ enum InteractionSet { #[derive(Event)] struct MinimapPressEvent { - action: Action, + action: MouseAction, position: Vec2, } impl MinimapPressEvent { - fn new(action: Action, position: Vec2) -> Self { + fn new(action: MouseAction, position: Vec2) -> Self { Self { action, position } } - fn button(&self) -> Action { + fn button(&self) -> MouseAction { self.action } @@ -88,16 +91,16 @@ impl fmt::Debug for MinimapPressEvent { #[derive(Event)] struct MinimapDragEvent { - action: Action, + action: MouseAction, position: Vec2, } impl MinimapDragEvent { - fn new(action: Action, position: Vec2) -> Self { + fn new(action: MouseAction, position: Vec2) -> Self { Self { action, position } } - fn button(&self) -> Action { + fn button(&self) -> MouseAction { self.action } @@ -109,11 +112,11 @@ impl MinimapDragEvent { } #[derive(Resource, Deref, DerefMut)] -struct DraggingButtons(Vec); +struct DraggingButtons(Vec); fn press_handler( window_query: Query<&Window, With>, - action_state: Res>, + mouse_action_state: Res>, hud: HudNodes>, bounds: Res, mut dragging: ResMut, @@ -121,22 +124,24 @@ fn press_handler( ) { let cursor = window_query.single().cursor_position(); - for mouse_action in Action::get_mouse_actions() { - if action_state.just_released(mouse_action) { + for mouse_action in MouseAction::variants() { + if mouse_action_state.just_released(mouse_action) { + println!("released drag point {:?}", mouse_action); dragging.retain(|b| *b != mouse_action); continue; - } + } else if mouse_action_state.just_pressed(mouse_action) { + let Some(cursor) = cursor else { + continue; + }; + + if let Some(mut relative) = hud.relative_position(cursor) { + dragging.push(mouse_action); + relative.y = 1. - relative.y; + let event = MinimapPressEvent::new(mouse_action, bounds.rel_to_abs(relative)); + info!("Sending minimap press event {event:?}."); + press_events.send(event); + } - let Some(cursor) = cursor else { - continue; - }; - - if let Some(mut relative) = hud.relative_position(cursor) { - dragging.push(mouse_action); - relative.y = 1. - relative.y; - let event = MinimapPressEvent::new(mouse_action, bounds.rel_to_abs(relative)); - info!("Sending minimap press event {event:?}."); - press_events.send(event); } } } @@ -173,7 +178,7 @@ fn move_camera_system( mut camera_events: EventWriter, ) { for press in press_events.iter() { - if press.button() != Action::PrimaryClick { + if press.button() != MouseAction::PrimaryClick { continue; } @@ -182,7 +187,7 @@ fn move_camera_system( } for drag in drag_events.iter() { - if drag.button() != Action::PrimaryClick { + if drag.button() != MouseAction::PrimaryClick { continue; } @@ -196,7 +201,7 @@ fn send_units_system( mut send_events: EventWriter, ) { for press in press_events.iter() { - if press.button() != Action::SecondaryClick { + if press.button() != MouseAction::SecondaryClick { continue; } send_events.send(SendSelectedEvent::new(press.position())); @@ -208,7 +213,7 @@ fn delivery_location_system( mut location_events: EventWriter, ) { for press in press_events.iter() { - if press.button() != Action::SecondaryClick { + if press.button() != MouseAction::SecondaryClick { continue; } location_events.send(DeliveryLocationSelectedEvent::new(press.position())); diff --git a/crates/controller/src/lib.rs b/crates/controller/src/lib.rs index 3f8a8e11..46956455 100644 --- a/crates/controller/src/lib.rs +++ b/crates/controller/src/lib.rs @@ -31,6 +31,5 @@ impl PluginGroup for ControllerPluginGroup { .add(DraftPlugin) .add(HudPlugin) .add(ActionPlugin) - } } diff --git a/crates/controller/src/mouse/input.rs b/crates/controller/src/mouse/input.rs index 213fd403..14fc82ec 100644 --- a/crates/controller/src/mouse/input.rs +++ b/crates/controller/src/mouse/input.rs @@ -1,10 +1,11 @@ -use crate::actions::Action; +use crate::actions::{MouseAction}; use ahash::AHashMap; use bevy::{ input::{mouse::MouseButtonInput, ButtonState}, prelude::*, window::PrimaryWindow, }; +use leafwing_input_manager::Actionlike; use de_core::{ gamestate::GameState, schedule::InputSchedule, screengeom::ScreenRect, state::AppState, }; @@ -54,16 +55,16 @@ pub(crate) enum MouseSet { #[derive(Event)] pub(crate) struct MouseClickedEvent { - action: Action, + action: MouseAction, position: Vec2, } impl MouseClickedEvent { - fn new(action: Action, position: Vec2) -> Self { + fn new(action: MouseAction, position: Vec2) -> Self { Self { action, position } } - pub(crate) fn button(&self) -> Action { + pub(crate) fn button(&self) -> MouseAction { self.action } @@ -74,28 +75,28 @@ impl MouseClickedEvent { #[derive(Event)] pub(crate) struct MouseDoubleClickedEvent { - action: Action, + action: MouseAction, } impl MouseDoubleClickedEvent { - fn new(action: Action) -> Self { + fn new(action: MouseAction) -> Self { Self { action } } - pub(crate) fn button(&self) -> Action { + pub(crate) fn button(&self) -> MouseAction { self.action } } #[derive(Event)] pub(crate) struct MouseDraggedEvent { - action: Action, + action: MouseAction, rect: Option, update_type: DragUpdateType, } impl MouseDraggedEvent { - fn new(action: Action, rect: Option, update_type: DragUpdateType) -> Self { + fn new(action: MouseAction, rect: Option, update_type: DragUpdateType) -> Self { Self { action, rect, @@ -103,7 +104,7 @@ impl MouseDraggedEvent { } } - pub(crate) fn button(&self) -> Action { + pub(crate) fn button(&self) -> MouseAction { self.action } @@ -145,15 +146,16 @@ impl MousePosition { } } -#[derive(Default, Resource)] -struct MouseDragStates(AHashMap); +#[derive(Default, Resource, Debug)] +struct MouseDragStates(AHashMap); impl MouseDragStates { - fn set(&mut self, action: Action, position: Option) { + fn set(&mut self, action: MouseAction, position: Option) { self.0.insert(action, DragState::new(position)); } - fn resolve(&mut self, action: Action) -> Option { + fn resolve(&mut self, action: MouseAction) -> Option { + println!("resolve drag {:?}, {:?}", action, self); self.0.remove(&action).and_then(DragState::resolve) } @@ -162,7 +164,7 @@ impl MouseDragStates { /// /// None means that the drag is (temporarily) canceled, Some means that the /// drag has been updated to this new rectangle. - fn update(&mut self, position: Option) -> AHashMap> { + fn update(&mut self, position: Option) -> AHashMap> { let mut updates = AHashMap::new(); for (&button, drag) in self.0.iter_mut() { if let Some(update) = drag.update(position) { @@ -173,6 +175,7 @@ impl MouseDragStates { } } +#[derive(Debug)] struct DragState { start: Option, stop: Option, @@ -271,21 +274,23 @@ fn update_drags( fn update_buttons( mouse_position: Res, mut mouse_state: ResMut, - action_state: Res>, + mouse_action_state: Res>, mut clicks: EventWriter, mut drags: EventWriter, ) { - for action in Action::get_mouse_actions() { - if action_state.just_pressed(action) { + for action in MouseAction::variants() { + if mouse_action_state.just_pressed(action) { mouse_state.set(action, mouse_position.ndc()); - } else if action_state.just_released(action) { - println!("released drag"); + } else if mouse_action_state.just_released(action) { + if let Some(drag_resolution) = mouse_state.resolve(action) { match drag_resolution { DragResolution::Point(position) => { + println!("released drag point {:?}", action); clicks.send(MouseClickedEvent::new(action, position)); } DragResolution::Rect(rect) => { + println!("released drag rect {:?}", action); drags.send(MouseDraggedEvent::new( action, rect, From 867cd48f75577482ddfb761f61d07edf03bc99cc Mon Sep 17 00:00:00 2001 From: jack crump-leys Date: Wed, 25 Oct 2023 16:30:03 +1300 Subject: [PATCH 5/6] clippy + fmt --- crates/controller/src/actions.rs | 11 ++- .../controller/src/hud/minimap/interaction.rs | 17 +--- crates/controller/src/lib.rs | 4 +- crates/controller/src/mouse/input.rs | 11 +-- crates/controller/src/mouse/mod.rs | 3 +- crates/input/src/io.rs | 41 ++++++---- crates/input/src/lib.rs | 78 +++++++++++++------ crates/input/src/plugin.rs | 11 +-- 8 files changed, 99 insertions(+), 77 deletions(-) diff --git a/crates/controller/src/actions.rs b/crates/controller/src/actions.rs index 6db18300..8ff30e3d 100644 --- a/crates/controller/src/actions.rs +++ b/crates/controller/src/actions.rs @@ -1,12 +1,10 @@ use bevy::input::keyboard::KeyCode; -use bevy::prelude::{Reflect, Update}; -use bevy::prelude::{Commands, MouseButton, Res, Startup}; +use bevy::prelude::Reflect; +use bevy::prelude::{MouseButton, Res}; use de_input::{AppKeybinding, DefaultKeybindings}; -use leafwing_input_manager::prelude::DualAxis; use leafwing_input_manager::prelude::{ActionState, InputMap, UserInput}; use leafwing_input_manager::Actionlike; use serde::{Deserialize, Serialize}; -use std::marker::PhantomData; pub struct ActionPlugin; @@ -74,9 +72,10 @@ macro_rules! make_actions { } } +use std::collections::HashMap; + use bevy::app::App; use petitset::PetitSet; -use std::collections::HashMap; make_actions! { // --- general actions --- @@ -112,5 +111,5 @@ pub(crate) fn mouse_input_pressed(mouse_actions: Res>) if mouse_actions.get_pressed().is_empty() { return false; } - return true; + true } diff --git a/crates/controller/src/hud/minimap/interaction.rs b/crates/controller/src/hud/minimap/interaction.rs index 064c88ed..2b0b207b 100644 --- a/crates/controller/src/hud/minimap/interaction.rs +++ b/crates/controller/src/hud/minimap/interaction.rs @@ -1,26 +1,18 @@ use std::fmt; -use bevy::{ - input::{ - mouse::{MouseButtonInput, MouseMotion}, - ButtonState, - }, - prelude::*, - window::PrimaryWindow, -}; -use leafwing_input_manager::Actionlike; -use leafwing_input_manager::prelude::ActionState; +use bevy::{prelude::*, window::PrimaryWindow}; use de_camera::MoveFocusEvent; use de_core::{gamestate::GameState, schedule::InputSchedule}; use de_map::size::MapBounds; +use leafwing_input_manager::prelude::ActionState; +use leafwing_input_manager::Actionlike; use super::nodes::MinimapNode; +use crate::actions::{mouse_input_pressed, MouseAction}; use crate::{ commands::{CommandsSet, DeliveryLocationSelectedEvent, SendSelectedEvent}, hud::HudNodes, - actions::Action, }; -use crate::actions::{mouse_input_pressed, MouseAction}; pub(super) struct InteractionPlugin; @@ -141,7 +133,6 @@ fn press_handler( info!("Sending minimap press event {event:?}."); press_events.send(event); } - } } } diff --git a/crates/controller/src/lib.rs b/crates/controller/src/lib.rs index 46956455..d5cf3b0c 100644 --- a/crates/controller/src/lib.rs +++ b/crates/controller/src/lib.rs @@ -1,13 +1,14 @@ //! This crate implements handling of user input. +use actions::ActionPlugin; use bevy::{app::PluginGroupBuilder, prelude::*}; use commands::CommandsPlugin; use draft::DraftPlugin; use hud::HudPlugin; use mouse::MousePlugin; use selection::SelectionPlugin; -use actions::ActionPlugin; +mod actions; mod commands; mod draft; mod frustum; @@ -15,7 +16,6 @@ mod hud; mod mouse; mod ray; mod selection; -mod actions; const SELECTION_BAR_ID: u32 = 0; const POINTER_BAR_ID: u32 = 1; diff --git a/crates/controller/src/mouse/input.rs b/crates/controller/src/mouse/input.rs index 14fc82ec..d67ea7ab 100644 --- a/crates/controller/src/mouse/input.rs +++ b/crates/controller/src/mouse/input.rs @@ -1,16 +1,12 @@ -use crate::actions::{MouseAction}; use ahash::AHashMap; -use bevy::{ - input::{mouse::MouseButtonInput, ButtonState}, - prelude::*, - window::PrimaryWindow, -}; -use leafwing_input_manager::Actionlike; +use bevy::{prelude::*, window::PrimaryWindow}; use de_core::{ gamestate::GameState, schedule::InputSchedule, screengeom::ScreenRect, state::AppState, }; use leafwing_input_manager::prelude::ActionState; +use leafwing_input_manager::Actionlike; +use crate::actions::MouseAction; use crate::hud::HudNodes; const DRAGGING_THRESHOLD: f32 = 0.02; @@ -282,7 +278,6 @@ fn update_buttons( if mouse_action_state.just_pressed(action) { mouse_state.set(action, mouse_position.ndc()); } else if mouse_action_state.just_released(action) { - if let Some(drag_resolution) = mouse_state.resolve(action) { match drag_resolution { DragResolution::Point(position) => { diff --git a/crates/controller/src/mouse/mod.rs b/crates/controller/src/mouse/mod.rs index 97021660..8f5e9cd4 100644 --- a/crates/controller/src/mouse/mod.rs +++ b/crates/controller/src/mouse/mod.rs @@ -1,8 +1,7 @@ use bevy::prelude::*; use input::InputPlugin; pub(crate) use input::{ - DragUpdateType, MouseClickedEvent, MouseDoubleClickedEvent, MouseDraggedEvent, MousePosition, - MouseSet, + DragUpdateType, MouseDoubleClickedEvent, MouseDraggedEvent, MousePosition, MouseSet, }; use pointer::PointerPlugin; pub(crate) use pointer::{Pointer, PointerSet}; diff --git a/crates/input/src/io.rs b/crates/input/src/io.rs index 163de8ab..f1a66c17 100644 --- a/crates/input/src/io.rs +++ b/crates/input/src/io.rs @@ -1,32 +1,39 @@ -use crate::BindableActionlike; +use std::io::Read; + +use de_core::fs::conf_dir; use leafwing_input_manager::prelude::InputMap; -use std::io::{prelude, Read}; use ron::ser::PrettyConfig; -use de_core::fs::conf_dir; + +use crate::BindableActionlike; pub(crate) fn get_keybindings( action_set_name: String, default_keybindings: InputMap, ) -> InputMap { - let mut file = - match std::fs::File::open( conf_dir().expect("Could not get config dir").join(format!("keybindings.{}.ron", action_set_name))) { - Ok(file) => file, - Err(_) => { - std::fs::write( - conf_dir().expect("Could not get config dir").join(format!("keybindings.{}.ron", action_set_name)), - ron::ser::to_string_pretty(&default_keybindings, PrettyConfig::new()).unwrap(), - ) - .unwrap(); - return default_keybindings; - } - }; + let mut file = match std::fs::File::open( + conf_dir() + .expect("Could not get config dir") + .join(format!("keybindings.{}.ron", action_set_name)), + ) { + Ok(file) => file, + Err(_) => { + std::fs::write( + conf_dir() + .expect("Could not get config dir") + .join(format!("keybindings.{}.ron", action_set_name)), + ron::ser::to_string_pretty(&default_keybindings, PrettyConfig::new()).unwrap(), + ) + .unwrap(); + return default_keybindings; + } + }; let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); let mut keybindings: InputMap = ron::from_str(&contents).unwrap(); - + // fill unset keys with default keybindings keybindings.merge(&default_keybindings); - + keybindings } diff --git a/crates/input/src/lib.rs b/crates/input/src/lib.rs index a6610c62..4bd973e3 100644 --- a/crates/input/src/lib.rs +++ b/crates/input/src/lib.rs @@ -1,17 +1,16 @@ +use std::hash::Hash; + use bevy::prelude::App; -use bevy::prelude::Reflect; -use std::collections::HashMap; use leafwing_input_manager::prelude::{ActionState, InputMap}; -use leafwing_input_manager::{Actionlike, InputManagerBundle}; -use serde::{Deserialize, Serialize}; -use std::hash::Hash; +use leafwing_input_manager::Actionlike; use serde::de::DeserializeOwned; +use serde::Serialize; + use crate::plugin::InputManagerPlugin; mod io; mod plugin; - pub trait BindableActionlike: Actionlike + DeserializeOwned + Serialize + Clone + Send + Sync + Eq + Hash + Ord { @@ -23,7 +22,9 @@ impl InputMap where Self: Sized; + fn default_keybindings() -> InputMap + where + Self: Sized; } pub trait AppKeybinding { @@ -39,7 +40,8 @@ impl AppKeybinding for App { &mut self, config_name: impl Into, ) -> &mut Self { - let keybindings: InputMap = io::get_keybindings(config_name.into(), A::default_keybindings()); + let keybindings: InputMap = + io::get_keybindings(config_name.into(), A::default_keybindings()); self.world.insert_resource(keybindings); self.world.insert_resource(ActionState::::default()); @@ -47,21 +49,35 @@ impl AppKeybinding for App { self } - } #[cfg(test)] mod tests { - use super::*; - use bevy::prelude::KeyCode; + use std::collections::HashMap; + + use bevy::prelude::{KeyCode, Reflect}; use leafwing_input_manager::prelude::UserInput; use leafwing_input_manager::user_input::InputKind::Keyboard; + use serde::Deserialize; + + use super::*; #[test] fn test_keybindings() { let mut app = App::new(); #[derive( - Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, Actionlike, Reflect, PartialOrd, Ord + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + Deserialize, + Serialize, + Actionlike, + Reflect, + PartialOrd, + Ord, )] enum PlayerAction { // Movement @@ -78,26 +94,44 @@ mod tests { } impl DefaultKeybindings for PlayerAction { - fn default_keybindings() -> InputMap where Self: Sized { + fn default_keybindings() -> InputMap + where + Self: Sized, + { InputMap::from( vec![ (Self::Up, vec![UserInput::Single(Keyboard(KeyCode::W))]), (Self::Down, vec![UserInput::Single(Keyboard(KeyCode::S))]), (Self::Left, vec![UserInput::Single(Keyboard(KeyCode::A))]), (Self::Right, vec![UserInput::Single(Keyboard(KeyCode::D))]), - (Self::Ability1, vec![UserInput::Single(Keyboard(KeyCode::Q))]), - (Self::Ability2, vec![UserInput::Single(Keyboard(KeyCode::E))]), - (Self::Ability3, vec![UserInput::Single(Keyboard(KeyCode::F))]), - (Self::Ability4, vec![UserInput::Single(Keyboard(KeyCode::R))]), - (Self::Ultimate, vec![UserInput::Single(Keyboard(KeyCode::Space))]), - ].into_iter().collect::>>() + ( + Self::Ability1, + vec![UserInput::Single(Keyboard(KeyCode::Q))], + ), + ( + Self::Ability2, + vec![UserInput::Single(Keyboard(KeyCode::E))], + ), + ( + Self::Ability3, + vec![UserInput::Single(Keyboard(KeyCode::F))], + ), + ( + Self::Ability4, + vec![UserInput::Single(Keyboard(KeyCode::R))], + ), + ( + Self::Ultimate, + vec![UserInput::Single(Keyboard(KeyCode::Space))], + ), + ] + .into_iter() + .collect::>>(), ) } } - app.add_action_set::( - "player".to_string(), - ); + app.add_action_set::("player".to_string()); app.update(); } diff --git a/crates/input/src/plugin.rs b/crates/input/src/plugin.rs index b0794552..11d8f66b 100644 --- a/crates/input/src/plugin.rs +++ b/crates/input/src/plugin.rs @@ -1,18 +1,15 @@ //! Contains main plugin exported by this crate. -use core::hash::Hash; use core::marker::PhantomData; -use std::fmt::Debug; use bevy::app::{App, Plugin}; use bevy::ecs::prelude::*; use bevy::input::InputSystem; -use bevy::prelude::{PostUpdate, PreUpdate}; -use leafwing_input_manager::Actionlike; +use bevy::prelude::PostUpdate; +use de_core::schedule::PreInputSchedule; use leafwing_input_manager::plugin::InputManagerSystem; - -use de_core::schedule::{InputSchedule, PreInputSchedule}; use leafwing_input_manager::prelude::{ActionState, ClashStrategy, ToggleActions}; +use leafwing_input_manager::Actionlike; /// A [`Plugin`] that collects [`Input`](bevy::input::Input) /// from disparate sources, producing an [`ActionState`] that @@ -105,4 +102,4 @@ impl Plugin for InputManagerPlugin { .init_resource::>() .insert_resource(ClashStrategy::UseActionOrder); } -} \ No newline at end of file +} From db062fa5785139dbde6af7f99ae21d024280c469 Mon Sep 17 00:00:00 2001 From: jack crump-leys Date: Sun, 10 Dec 2023 13:22:03 +1300 Subject: [PATCH 6/6] resolve comments --- crates/controller/src/actions.rs | 185 +++++++++--------- crates/controller/src/commands/handlers.rs | 35 ++-- .../controller/src/hud/minimap/interaction.rs | 65 +++--- crates/controller/src/mouse/input.rs | 77 ++++---- crates/controller/src/mouse/mod.rs | 2 +- crates/input/src/io.rs | 39 ---- crates/input/src/lib.rs | 16 +- 7 files changed, 195 insertions(+), 224 deletions(-) delete mode 100644 crates/input/src/io.rs diff --git a/crates/controller/src/actions.rs b/crates/controller/src/actions.rs index 8ff30e3d..9d1c645c 100644 --- a/crates/controller/src/actions.rs +++ b/crates/controller/src/actions.rs @@ -2,7 +2,7 @@ use bevy::input::keyboard::KeyCode; use bevy::prelude::Reflect; use bevy::prelude::{MouseButton, Res}; use de_input::{AppKeybinding, DefaultKeybindings}; -use leafwing_input_manager::prelude::{ActionState, InputMap, UserInput}; +use leafwing_input_manager::prelude::{ActionState, InputMap, QwertyScanCode, UserInput}; use leafwing_input_manager::Actionlike; use serde::{Deserialize, Serialize}; @@ -10,106 +10,115 @@ pub struct ActionPlugin; impl bevy::app::Plugin for ActionPlugin { fn build(&self, app: &mut App) { - app.add_action_set::("actions") - // Mouse is separate because otherwise it will clash with AddToSelection and ReplaceSelection - .add_action_set::("mouse_actions"); + app.add_action_set::(); } } -/// make actoinlike enum that has normal actions and factory actions. an action is A vareint followed by a KeyConfig -macro_rules! make_actions { - { - $($action:ident, ($($keybind:expr),*)),*; - $($mouse_action:ident, ($($mouse_keybind:expr),*)),*; - $($building_action:ident, $building_type:ident, ($($building_key:expr),*)),* - } => { - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, Actionlike, Reflect, PartialOrd, Ord)] - pub enum Action { - $($action,)* - $($building_action,)* - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, Actionlike, Reflect, PartialOrd, Ord)] - pub enum MouseAction { - $($mouse_action,)* - } +use std::collections::HashMap; - impl DefaultKeybindings for Action { - fn default_keybindings() -> InputMap where Self: Sized { - use Action::*; - let keybindings = InputMap::::from( - vec![ - $(($action, vec![$($keybind.into()),*])),*, - $(($building_action, vec![$($building_key.into()),*])),* - ].into_iter().collect::>>() - ); - println!("keybindings: {:?}", keybindings); - keybindings - } - } +use bevy::app::App; +use petitset::PetitSet; - impl DefaultKeybindings for MouseAction { - fn default_keybindings() -> InputMap where Self: Sized { - use MouseAction::*; - let keybindings = InputMap::::from( +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + Deserialize, + Serialize, + Actionlike, + Reflect, + PartialOrd, + Ord, +)] +pub enum Action { + Exit, + SelectAllVisible, + SelectAll, + AddToSelection, + ReplaceSelection, + Up, + Down, + Left, + Right, + Pivot, + BuildBase, + BuildPowerHub, +} +impl DefaultKeybindings for Action { + fn default_keybindings() -> InputMap + where + Self: Sized, + { + use Action::*; + let keybindings = InputMap::::from( + vec![ + (Exit, vec![KeyCode::Escape.into()]), + ( + SelectAllVisible, + vec![UserInput::chord(vec![ + KeyCode::ControlLeft, + KeyCode::ShiftLeft, + KeyCode::A, + ])], + ), + ( + SelectAll, + vec![UserInput::chord(vec![KeyCode::ControlLeft, KeyCode::A])], + ), + ( + AddToSelection, vec![ - $(($mouse_action, vec![$($mouse_keybind.into()),*])),* - ].into_iter().collect::>>() - ); - println!("mouse keybindings: {:?}", keybindings); - keybindings - } - } - - impl Action { - pub fn get_factory_actions() -> Vec<(Self, de_types::objects::BuildingType)> { - use Action::*; - use de_types::objects::BuildingType::*; - - vec![$(($building_action, $building_type)),*] - } - } + UserInput::Chord(PetitSet::from_iter(vec![ + KeyCode::ControlLeft.into(), + MouseButton::Left.into(), + ])), + UserInput::Chord(PetitSet::from_iter(vec![ + KeyCode::ControlRight.into(), + MouseButton::Left.into(), + ])), + ], + ), + (ReplaceSelection, vec![MouseButton::Left.into()]), + (Up, vec![QwertyScanCode::W.into(), KeyCode::Up.into()]), + (Down, vec![QwertyScanCode::S.into(), KeyCode::Down.into()]), + (Left, vec![QwertyScanCode::A.into(), KeyCode::Left.into()]), + (Right, vec![QwertyScanCode::D.into(), KeyCode::Right.into()]), + ( + Pivot, + vec![UserInput::Chord(PetitSet::from_iter(vec![ + KeyCode::ControlLeft.into(), + MouseButton::Middle.into(), + ]))], + ), + (BuildBase, vec![KeyCode::B.into()]), + (BuildPowerHub, vec![KeyCode::P.into()]), + ] + .into_iter() + .collect::>>(), + ); + println!("keybindings: {:?}", keybindings); + keybindings } } -use std::collections::HashMap; - -use bevy::app::App; -use petitset::PetitSet; +impl Action { + pub fn get_factory_actions() -> Vec<(Self, de_types::objects::BuildingType)> { + use de_types::objects::BuildingType::*; + use Action::*; -make_actions! { - // --- general actions --- - // keyboard actions - Exit, (KeyCode::Escape), - SelectAllVisible, (UserInput::chord(vec![KeyCode::ControlLeft, KeyCode::ShiftLeft, KeyCode::A])), - SelectAll, (UserInput::chord(vec![KeyCode::ControlLeft, KeyCode::A])), - // mouse selections - AddToSelection, ( - UserInput::Chord(PetitSet::from_iter(vec![KeyCode::ControlLeft.into(),MouseButton::Left.into()])), - UserInput::Chord(PetitSet::from_iter(vec![KeyCode::ControlRight.into(), MouseButton::Left.into()]))), - ReplaceSelection, (MouseButton::Left), - // camera controls - Up, (KeyCode::W, KeyCode::Up), - Down, (KeyCode::S, KeyCode::Down), - Left, (KeyCode::A, KeyCode::Left), - Right, (KeyCode::D, KeyCode::Right), - Pivot, (UserInput::Chord(PetitSet::from_iter(vec![KeyCode::ControlLeft.into(), MouseButton::Middle.into()]))); - // --- mouse actions (these will trigger the drag logic) --- - PrimaryClick, (MouseButton::Left), - SecondaryClick, ( - MouseButton::Right); - // --- building actions --- - BuildBase, Base, (KeyCode::B), - BuildPowerHub, PowerHub, (KeyCode::P) + vec![(BuildBase, Base), (BuildPowerHub, PowerHub)] + } } -pub(crate) fn action_pressed(action: A) -> impl Fn(Res>) -> bool { +pub(crate) fn action_just_pressed( + action: A, +) -> impl Fn(Res>) -> bool { move |action_state: Res>| action_state.just_pressed(action.clone()) } -pub(crate) fn mouse_input_pressed(mouse_actions: Res>) -> bool { - if mouse_actions.get_pressed().is_empty() { - return false; - } - true +pub(crate) fn action_pressed(action: A) -> impl Fn(Res>) -> bool { + move |action_state: Res>| action_state.pressed(action.clone()) } diff --git a/crates/controller/src/commands/handlers.rs b/crates/controller/src/commands/handlers.rs index ecb77343..83478371 100644 --- a/crates/controller/src/commands/handlers.rs +++ b/crates/controller/src/commands/handlers.rs @@ -28,7 +28,8 @@ use leafwing_input_manager::prelude::ActionState; use super::{ executor::DeliveryLocationSelectedEvent, CommandsSet, GroupAttackEvent, SendSelectedEvent, }; -use crate::actions::{action_pressed, Action, MouseAction}; +use crate::actions::{action_just_pressed, action_pressed, Action}; +use crate::mouse::input::pressed_mouse_button; use crate::{ draft::{DiscardDraftsEvent, DraftSet, NewDraftEvent, SpawnDraftsEvent}, hud::{GameMenuSet, ToggleGameMenuEvent, UpdateSelectionBoxEvent}, @@ -53,7 +54,7 @@ impl HandlersPlugin { InputSchedule, place_draft(building) .run_if(in_state(GameState::Playing)) - .run_if(action_pressed(action)) + .run_if(action_just_pressed(action)) .before(DraftSet::New) .after(PointerSet::Update), ); @@ -67,21 +68,21 @@ impl Plugin for HandlersPlugin { InputSchedule, ( secondary_click_handler - .run_if(action_pressed(MouseAction::SecondaryClick)) + .run_if(pressed_mouse_button(MouseButton::Right)) .after(PointerSet::Update) .after(MouseSet::Buttons) .before(CommandsSet::SendSelected) .before(CommandsSet::DeliveryLocation) .before(CommandsSet::Attack), primary_click_handler - .run_if(action_pressed(MouseAction::PrimaryClick)) + .run_if(pressed_mouse_button(MouseButton::Left)) .in_set(HandlersSet::LeftClick) .before(SelectionSet::Update) .before(DraftSet::Spawn) .after(PointerSet::Update) .after(MouseSet::Buttons), double_click_handler - .run_if(on_double_click(MouseAction::PrimaryClick)) + .run_if(on_double_click(MouseButton::Left)) .before(SelectionSet::Update) .before(DraftSet::Spawn) .after(PointerSet::Update) @@ -91,17 +92,18 @@ impl Plugin for HandlersPlugin { move_camera_mouse_system.before(CameraSet::MoveHorizontallEvent), zoom_camera.before(CameraSet::ZoomEvent), pivot_camera + .run_if(action_pressed(Action::Pivot)) .before(CameraSet::RotateEvent) .before(CameraSet::TiltEvent), handle_escape - .run_if(action_pressed(Action::Exit)) + .run_if(action_just_pressed(Action::Exit)) .before(GameMenuSet::Toggle) .before(DraftSet::Discard), select_all - .run_if(action_pressed(Action::SelectAll)) + .run_if(action_just_pressed(Action::SelectAll)) .before(SelectionSet::Update), select_all_visible - .run_if(action_pressed(Action::SelectAllVisible)) + .run_if(action_just_pressed(Action::SelectAllVisible)) .before(AreaSelectSet::SelectInArea), update_drags .before(AreaSelectSet::SelectInArea) @@ -119,7 +121,7 @@ pub(crate) enum HandlersSet { LeftClick, } -fn on_double_click(button: MouseAction) -> impl Fn(EventReader) -> bool { +fn on_double_click(button: MouseButton) -> impl Fn(EventReader) -> bool { move |mut events: EventReader| { // It is desirable to exhaust the iterator, thus .filter().count() is // used instead of .any() @@ -277,15 +279,10 @@ fn zoom_camera( fn pivot_camera( conf: Res, - action_state: Res>, mut mouse_event: EventReader, mut rotate_event: EventWriter, mut tilt_event: EventWriter, ) { - if !action_state.pressed(Action::Pivot) { - return; - } - let delta = mouse_event.iter().fold(Vec2::ZERO, |sum, e| sum + e.delta); let sensitivity = conf.camera().rotation_sensitivity(); if delta.x != 0. { @@ -382,7 +379,7 @@ fn update_drags( mut select_events: EventWriter, ) { for drag_event in drag_events.iter() { - if drag_event.button() != MouseAction::PrimaryClick { + if drag_event.button() != MouseButton::Left { continue; } @@ -393,9 +390,13 @@ fn update_drags( }, DragUpdateType::Released => { if let Some(rect) = drag_event.rect() { - let mode = if action_state.just_released(Action::AddToSelection) { + let mode = if action_state.just_released(Action::AddToSelection) + || action_state.pressed(Action::AddToSelection) + { SelectionMode::Add - } else if action_state.just_released(Action::ReplaceSelection) { + } else if action_state.just_released(Action::ReplaceSelection) + || action_state.pressed(Action::ReplaceSelection) + { SelectionMode::Replace } else { continue; diff --git a/crates/controller/src/hud/minimap/interaction.rs b/crates/controller/src/hud/minimap/interaction.rs index 2b0b207b..00274052 100644 --- a/crates/controller/src/hud/minimap/interaction.rs +++ b/crates/controller/src/hud/minimap/interaction.rs @@ -1,14 +1,13 @@ use std::fmt; +use bevy::input::mouse::{MouseButtonInput, MouseMotion}; +use bevy::input::ButtonState; use bevy::{prelude::*, window::PrimaryWindow}; use de_camera::MoveFocusEvent; use de_core::{gamestate::GameState, schedule::InputSchedule}; use de_map::size::MapBounds; -use leafwing_input_manager::prelude::ActionState; -use leafwing_input_manager::Actionlike; use super::nodes::MinimapNode; -use crate::actions::{mouse_input_pressed, MouseAction}; use crate::{ commands::{CommandsSet, DeliveryLocationSelectedEvent, SendSelectedEvent}, hud::HudNodes, @@ -26,11 +25,11 @@ impl Plugin for InteractionPlugin { ( press_handler .in_set(InteractionSet::PressHandler) - .run_if(mouse_input_pressed) + .run_if(on_event::()) .in_set(InteractionSet::PressHandler), drag_handler .in_set(InteractionSet::DragHandler) - .run_if(mouse_input_pressed) + .run_if(on_event::()) .after(InteractionSet::PressHandler), move_camera_system .after(InteractionSet::PressHandler) @@ -55,16 +54,16 @@ enum InteractionSet { #[derive(Event)] struct MinimapPressEvent { - action: MouseAction, + action: MouseButton, position: Vec2, } impl MinimapPressEvent { - fn new(action: MouseAction, position: Vec2) -> Self { + fn new(action: MouseButton, position: Vec2) -> Self { Self { action, position } } - fn button(&self) -> MouseAction { + fn button(&self) -> MouseButton { self.action } @@ -83,16 +82,16 @@ impl fmt::Debug for MinimapPressEvent { #[derive(Event)] struct MinimapDragEvent { - action: MouseAction, + action: MouseButton, position: Vec2, } impl MinimapDragEvent { - fn new(action: MouseAction, position: Vec2) -> Self { + fn new(action: MouseButton, position: Vec2) -> Self { Self { action, position } } - fn button(&self) -> MouseAction { + fn button(&self) -> MouseButton { self.action } @@ -104,11 +103,11 @@ impl MinimapDragEvent { } #[derive(Resource, Deref, DerefMut)] -struct DraggingButtons(Vec); +struct DraggingButtons(Vec); fn press_handler( window_query: Query<&Window, With>, - mouse_action_state: Res>, + mut input_events: EventReader, hud: HudNodes>, bounds: Res, mut dragging: ResMut, @@ -116,23 +115,25 @@ fn press_handler( ) { let cursor = window_query.single().cursor_position(); - for mouse_action in MouseAction::variants() { - if mouse_action_state.just_released(mouse_action) { - println!("released drag point {:?}", mouse_action); - dragging.retain(|b| *b != mouse_action); - continue; - } else if mouse_action_state.just_pressed(mouse_action) { - let Some(cursor) = cursor else { + for event in input_events.iter() { + match event.state { + ButtonState::Released => { + dragging.retain(|b| *b != event.button); continue; - }; - - if let Some(mut relative) = hud.relative_position(cursor) { - dragging.push(mouse_action); - relative.y = 1. - relative.y; - let event = MinimapPressEvent::new(mouse_action, bounds.rel_to_abs(relative)); - info!("Sending minimap press event {event:?}."); - press_events.send(event); } + ButtonState::Pressed => (), + } + + let Some(cursor) = cursor else { + continue; + }; + + if let Some(mut relative) = hud.relative_position(cursor) { + dragging.push(event.button); + relative.y = 1. - relative.y; + let event = MinimapPressEvent::new(event.button, bounds.rel_to_abs(relative)); + info!("Sending minimap press event {event:?}."); + press_events.send(event); } } } @@ -169,7 +170,7 @@ fn move_camera_system( mut camera_events: EventWriter, ) { for press in press_events.iter() { - if press.button() != MouseAction::PrimaryClick { + if press.button() != MouseButton::Left { continue; } @@ -178,7 +179,7 @@ fn move_camera_system( } for drag in drag_events.iter() { - if drag.button() != MouseAction::PrimaryClick { + if drag.button() != MouseButton::Left { continue; } @@ -192,7 +193,7 @@ fn send_units_system( mut send_events: EventWriter, ) { for press in press_events.iter() { - if press.button() != MouseAction::SecondaryClick { + if press.button() != MouseButton::Right { continue; } send_events.send(SendSelectedEvent::new(press.position())); @@ -204,7 +205,7 @@ fn delivery_location_system( mut location_events: EventWriter, ) { for press in press_events.iter() { - if press.button() != MouseAction::SecondaryClick { + if press.button() != MouseButton::Right { continue; } location_events.send(DeliveryLocationSelectedEvent::new(press.position())); diff --git a/crates/controller/src/mouse/input.rs b/crates/controller/src/mouse/input.rs index d67ea7ab..6d580487 100644 --- a/crates/controller/src/mouse/input.rs +++ b/crates/controller/src/mouse/input.rs @@ -1,12 +1,11 @@ use ahash::AHashMap; +use bevy::input::mouse::MouseButtonInput; +use bevy::input::ButtonState; use bevy::{prelude::*, window::PrimaryWindow}; use de_core::{ gamestate::GameState, schedule::InputSchedule, screengeom::ScreenRect, state::AppState, }; -use leafwing_input_manager::prelude::ActionState; -use leafwing_input_manager::Actionlike; -use crate::actions::MouseAction; use crate::hud::HudNodes; const DRAGGING_THRESHOLD: f32 = 0.02; @@ -51,16 +50,16 @@ pub(crate) enum MouseSet { #[derive(Event)] pub(crate) struct MouseClickedEvent { - action: MouseAction, + action: MouseButton, position: Vec2, } impl MouseClickedEvent { - fn new(action: MouseAction, position: Vec2) -> Self { + fn new(action: MouseButton, position: Vec2) -> Self { Self { action, position } } - pub(crate) fn button(&self) -> MouseAction { + pub(crate) fn button(&self) -> MouseButton { self.action } @@ -71,28 +70,28 @@ impl MouseClickedEvent { #[derive(Event)] pub(crate) struct MouseDoubleClickedEvent { - action: MouseAction, + action: MouseButton, } impl MouseDoubleClickedEvent { - fn new(action: MouseAction) -> Self { + fn new(action: MouseButton) -> Self { Self { action } } - pub(crate) fn button(&self) -> MouseAction { + pub(crate) fn button(&self) -> MouseButton { self.action } } #[derive(Event)] pub(crate) struct MouseDraggedEvent { - action: MouseAction, + action: MouseButton, rect: Option, update_type: DragUpdateType, } impl MouseDraggedEvent { - fn new(action: MouseAction, rect: Option, update_type: DragUpdateType) -> Self { + fn new(action: MouseButton, rect: Option, update_type: DragUpdateType) -> Self { Self { action, rect, @@ -100,7 +99,7 @@ impl MouseDraggedEvent { } } - pub(crate) fn button(&self) -> MouseAction { + pub(crate) fn button(&self) -> MouseButton { self.action } @@ -143,15 +142,14 @@ impl MousePosition { } #[derive(Default, Resource, Debug)] -struct MouseDragStates(AHashMap); +struct MouseDragStates(AHashMap); impl MouseDragStates { - fn set(&mut self, action: MouseAction, position: Option) { + fn set(&mut self, action: MouseButton, position: Option) { self.0.insert(action, DragState::new(position)); } - fn resolve(&mut self, action: MouseAction) -> Option { - println!("resolve drag {:?}, {:?}", action, self); + fn resolve(&mut self, action: MouseButton) -> Option { self.0.remove(&action).and_then(DragState::resolve) } @@ -160,7 +158,7 @@ impl MouseDragStates { /// /// None means that the drag is (temporarily) canceled, Some means that the /// drag has been updated to this new rectangle. - fn update(&mut self, position: Option) -> AHashMap> { + fn update(&mut self, position: Option) -> AHashMap> { let mut updates = AHashMap::new(); for (&button, drag) in self.0.iter_mut() { if let Some(update) = drag.update(position) { @@ -270,30 +268,31 @@ fn update_drags( fn update_buttons( mouse_position: Res, mut mouse_state: ResMut, - mouse_action_state: Res>, + mut input_events: EventReader, mut clicks: EventWriter, mut drags: EventWriter, ) { - for action in MouseAction::variants() { - if mouse_action_state.just_pressed(action) { - mouse_state.set(action, mouse_position.ndc()); - } else if mouse_action_state.just_released(action) { - if let Some(drag_resolution) = mouse_state.resolve(action) { - match drag_resolution { - DragResolution::Point(position) => { - println!("released drag point {:?}", action); - clicks.send(MouseClickedEvent::new(action, position)); - } - DragResolution::Rect(rect) => { - println!("released drag rect {:?}", action); - drags.send(MouseDraggedEvent::new( - action, - rect, - DragUpdateType::Released, - )); + for event in input_events.iter() { + match event.state { + ButtonState::Released => { + if let Some(drag_resolution) = mouse_state.resolve(event.button) { + match drag_resolution { + DragResolution::Point(position) => { + clicks.send(MouseClickedEvent::new(event.button, position)); + } + DragResolution::Rect(rect) => { + drags.send(MouseDraggedEvent::new( + event.button, + rect, + DragUpdateType::Released, + )); + } } } } + ButtonState::Pressed => { + mouse_state.set(event.button, mouse_position.ndc()); + } } } } @@ -321,3 +320,11 @@ fn check_double_click( *last_click_position = Some(mouse_clicked.position()); } } + +pub(crate) fn pressed_mouse_button( + mouse_button: MouseButton, +) -> impl Fn(EventReader) -> bool { + move |mut click: EventReader| { + click.iter().any(|button| button.action == mouse_button) + } +} diff --git a/crates/controller/src/mouse/mod.rs b/crates/controller/src/mouse/mod.rs index 8f5e9cd4..cc908d04 100644 --- a/crates/controller/src/mouse/mod.rs +++ b/crates/controller/src/mouse/mod.rs @@ -6,7 +6,7 @@ pub(crate) use input::{ use pointer::PointerPlugin; pub(crate) use pointer::{Pointer, PointerSet}; -mod input; +pub(crate) mod input; mod pointer; pub(crate) struct MousePlugin; diff --git a/crates/input/src/io.rs b/crates/input/src/io.rs deleted file mode 100644 index f1a66c17..00000000 --- a/crates/input/src/io.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::io::Read; - -use de_core::fs::conf_dir; -use leafwing_input_manager::prelude::InputMap; -use ron::ser::PrettyConfig; - -use crate::BindableActionlike; - -pub(crate) fn get_keybindings( - action_set_name: String, - default_keybindings: InputMap, -) -> InputMap { - let mut file = match std::fs::File::open( - conf_dir() - .expect("Could not get config dir") - .join(format!("keybindings.{}.ron", action_set_name)), - ) { - Ok(file) => file, - Err(_) => { - std::fs::write( - conf_dir() - .expect("Could not get config dir") - .join(format!("keybindings.{}.ron", action_set_name)), - ron::ser::to_string_pretty(&default_keybindings, PrettyConfig::new()).unwrap(), - ) - .unwrap(); - return default_keybindings; - } - }; - - let mut contents = String::new(); - file.read_to_string(&mut contents).unwrap(); - let mut keybindings: InputMap = ron::from_str(&contents).unwrap(); - - // fill unset keys with default keybindings - keybindings.merge(&default_keybindings); - - keybindings -} diff --git a/crates/input/src/lib.rs b/crates/input/src/lib.rs index 4bd973e3..86da611f 100644 --- a/crates/input/src/lib.rs +++ b/crates/input/src/lib.rs @@ -8,7 +8,6 @@ use serde::Serialize; use crate::plugin::InputManagerPlugin; -mod io; mod plugin; pub trait BindableActionlike: @@ -29,19 +28,12 @@ pub trait DefaultKeybindings: BindableActionlike { pub trait AppKeybinding { /// Add a keybinding with config to the app. - fn add_action_set( - &mut self, - config_name: impl Into, - ) -> &mut Self; + fn add_action_set(&mut self) -> &mut Self; } impl AppKeybinding for App { - fn add_action_set( - &mut self, - config_name: impl Into, - ) -> &mut Self { - let keybindings: InputMap = - io::get_keybindings(config_name.into(), A::default_keybindings()); + fn add_action_set(&mut self) -> &mut Self { + let keybindings: InputMap = A::default_keybindings(); self.world.insert_resource(keybindings); self.world.insert_resource(ActionState::::default()); @@ -131,7 +123,7 @@ mod tests { } } - app.add_action_set::("player".to_string()); + app.add_action_set::(); app.update(); }