diff --git a/Cargo.toml b/Cargo.toml index dbc774df..b3acb312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ egui = ['dep:bevy_egui'] [dependencies] leafwing_input_manager_macros = { path = "macros", version = "0.15.1" } -bevy = { version = "0.14.0-rc.3", default-features = false, features = [ +bevy = { version = "0.15.0-dev", default-features = false, features = [ "serialize", ] } bevy_egui = { version = "0.29", optional = true } @@ -63,7 +63,7 @@ dyn-hash = "0.2" once_cell = "1.19" [dev-dependencies] -bevy = { version = "0.14.0-rc.3", default-features = false, features = [ +bevy = { version = "0.15.0-dev", default-features = false, features = [ "bevy_asset", "bevy_sprite", "bevy_text", diff --git a/examples/axis_inputs.rs b/examples/axis_inputs.rs index df5efb32..e0a9e050 100644 --- a/examples/axis_inputs.rs +++ b/examples/axis_inputs.rs @@ -32,7 +32,7 @@ fn spawn_player(mut commands: Commands) { // Let's bind the left stick for the move action .with_dual_axis(Action::Move, GamepadStick::LEFT) // And then bind the right gamepad trigger to the throttle action - .with(Action::Throttle, GamepadButtonType::RightTrigger2) + .with(Action::Throttle, GamepadButton::RightTrigger2) // And we'll use the right stick's x-axis as a rudder control .with_axis( // Add an AxisDeadzone to process horizontal values of the right stick. diff --git a/examples/default_controls.rs b/examples/default_controls.rs index 14953838..fdfa087b 100644 --- a/examples/default_controls.rs +++ b/examples/default_controls.rs @@ -27,8 +27,8 @@ impl PlayerAction { // Default gamepad input bindings input_map.insert_dual_axis(Self::Run, GamepadStick::LEFT); - input_map.insert(Self::Jump, GamepadButtonType::South); - input_map.insert(Self::UseItem, GamepadButtonType::RightTrigger2); + input_map.insert(Self::Jump, GamepadButton::South); + input_map.insert(Self::UseItem, GamepadButton::RightTrigger2); // Default kbm input bindings input_map.insert_dual_axis(Self::Run, VirtualDPad::wasd()); diff --git a/examples/mouse_motion.rs b/examples/mouse_motion.rs index af7c9ba0..20362728 100644 --- a/examples/mouse_motion.rs +++ b/examples/mouse_motion.rs @@ -22,7 +22,7 @@ fn setup(mut commands: Commands) { // via the `MouseMoveDirection` enum. let input_map = InputMap::default().with_dual_axis(CameraMovement::Pan, MouseMove::default()); commands - .spawn(Camera2dBundle::default()) + .spawn(Camera2d) .insert(InputManagerBundle::with_map(input_map)); commands.spawn(SpriteBundle { diff --git a/examples/mouse_wheel.rs b/examples/mouse_wheel.rs index 9d457071..a47a733e 100644 --- a/examples/mouse_wheel.rs +++ b/examples/mouse_wheel.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{prelude::*, render::camera::ScalingMode}; use leafwing_input_manager::prelude::*; fn main() { @@ -33,7 +33,7 @@ fn setup(mut commands: Commands) { // Or even a digital dual-axis input! .with_dual_axis(CameraMovement::Pan, MouseScroll::default().digital()); commands - .spawn(Camera2dBundle::default()) + .spawn(Camera2d) .insert(InputManagerBundle::with_map(input_map)); commands.spawn(SpriteBundle { @@ -55,7 +55,10 @@ fn zoom_camera( // We want to zoom in when we use mouse wheel up, // so we increase the scale proportionally // Note that the projection's scale should always be positive (or our images will flip) - camera_projection.scale *= 1. - zoom_delta * CAMERA_ZOOM_RATE; + match camera_projection.scaling_mode { + ScalingMode::WindowSize(ref mut scale) => *scale *= 1. - zoom_delta * CAMERA_ZOOM_RATE, + ref mut scale => *scale = ScalingMode::WindowSize(1. - zoom_delta * CAMERA_ZOOM_RATE), + } } fn pan_camera(mut query: Query<(&mut Transform, &ActionState), With>) { diff --git a/examples/multiplayer.rs b/examples/multiplayer.rs index 1bda0f52..778bc94f 100644 --- a/examples/multiplayer.rs +++ b/examples/multiplayer.rs @@ -30,7 +30,7 @@ struct PlayerBundle { } impl PlayerBundle { - fn input_map(player: Player) -> InputMap { + fn input_map(player: Player, gamepad_0: Entity, gamepad_1: Entity) -> InputMap { let mut input_map = match player { Player::One => InputMap::new([ (Action::Left, KeyCode::KeyA), @@ -42,21 +42,21 @@ impl PlayerBundle { // and gracefully handle disconnects // Note that this step is not required: // if it is skipped, all input maps will read from all connected gamepads - .with_gamepad(Gamepad { id: 0 }), + .with_gamepad(gamepad_0), Player::Two => InputMap::new([ (Action::Left, KeyCode::ArrowLeft), (Action::Right, KeyCode::ArrowRight), (Action::Jump, KeyCode::ArrowUp), ]) - .with_gamepad(Gamepad { id: 1 }), + .with_gamepad(gamepad_1), }; // Each player will use the same gamepad controls, but on separate gamepads. input_map.insert_multiple([ - (Action::Left, GamepadButtonType::DPadLeft), - (Action::Right, GamepadButtonType::DPadRight), - (Action::Jump, GamepadButtonType::DPadUp), - (Action::Jump, GamepadButtonType::South), + (Action::Left, GamepadButton::DPadLeft), + (Action::Right, GamepadButton::DPadRight), + (Action::Jump, GamepadButton::DPadUp), + (Action::Jump, GamepadButton::South), ]); input_map @@ -64,14 +64,17 @@ impl PlayerBundle { } fn spawn_players(mut commands: Commands) { + let gamepad_0 = commands.spawn(()).id(); + let gamepad_1 = commands.spawn(()).id(); + commands.spawn(PlayerBundle { player: Player::One, - input_manager: InputManagerBundle::with_map(PlayerBundle::input_map(Player::One)), + input_manager: InputManagerBundle::with_map(PlayerBundle::input_map(Player::One, gamepad_0, gamepad_1)), }); commands.spawn(PlayerBundle { player: Player::Two, - input_manager: InputManagerBundle::with_map(PlayerBundle::input_map(Player::Two)), + input_manager: InputManagerBundle::with_map(PlayerBundle::input_map(Player::Two, gamepad_0, gamepad_1)), }); } diff --git a/examples/register_gamepads.rs b/examples/register_gamepads.rs index 3e22eb53..18094482 100644 --- a/examples/register_gamepads.rs +++ b/examples/register_gamepads.rs @@ -19,45 +19,50 @@ enum Action { } // This is used to check if a player already exists and which entity to disconnect +// +// This maps gamepad entity to player. #[derive(Resource, Default)] -struct JoinedPlayers(pub HashMap); +struct JoinedPlayers(pub HashMap); #[derive(Component)] struct Player { // This gamepad is used to index each player - gamepad: Gamepad, + gamepad: Entity, } fn join( mut commands: Commands, mut joined_players: ResMut, - gamepads: Res, - button_inputs: Res>, + gamepads: Query<(Entity, &Gamepad)>, ) { - for gamepad in gamepads.iter() { + for (gamepad_entity, gamepad) in gamepads.iter() { // Join the game when both bumpers (L+R) on the controller are pressed // We drop down the Bevy's input to get the input from each gamepad - if button_inputs.pressed(GamepadButton::new(gamepad, GamepadButtonType::LeftTrigger)) - && button_inputs.pressed(GamepadButton::new(gamepad, GamepadButtonType::RightTrigger)) + if gamepad.pressed(GamepadButton::LeftTrigger) + && gamepad.pressed(GamepadButton::RightTrigger) { // Make sure a player cannot join twice - if !joined_players.0.contains_key(&gamepad) { - println!("Player {} has joined the game!", gamepad.id); + if !joined_players.0.contains_key(&gamepad_entity) { + println!("Player {} has joined the game!", gamepad_entity); let input_map = InputMap::new([ - (Action::Jump, GamepadButtonType::South), - (Action::Disconnect, GamepadButtonType::Select), + (Action::Jump, GamepadButton::South), + (Action::Disconnect, GamepadButton::Select), ]) // Make sure to set the gamepad or all gamepads will be used! - .with_gamepad(gamepad); + .with_gamepad(gamepad_entity); let player = commands .spawn(InputManagerBundle::with_map(input_map)) - .insert(Player { gamepad }) + .insert(Player { + gamepad: gamepad_entity, + }) .id(); // Insert the created player and its gamepad to the hashmap of joined players // Since uniqueness was already checked above, we can insert here unchecked - joined_players.0.insert_unique_unchecked(gamepad, player); + joined_players + .0 + .insert_unique_unchecked(gamepad_entity, player); } } } @@ -67,7 +72,7 @@ fn jump(action_query: Query<(&ActionState, &Player)>) { // Iterate through each player to see if they jumped for (action_state, player) in action_query.iter() { if action_state.just_pressed(&Action::Jump) { - println!("Player {} jumped!", player.gamepad.id); + println!("Player {} jumped!", player.gamepad); } } } @@ -85,7 +90,7 @@ fn disconnect( commands.entity(player_entity).despawn(); joined_players.0.remove(&player.gamepad); - println!("Player {} has disconnected!", player.gamepad.id); + println!("Player {} has disconnected!", player.gamepad); } } } diff --git a/examples/send_actions_over_network.rs b/examples/send_actions_over_network.rs index 85af3e8a..d13a8cde 100644 --- a/examples/send_actions_over_network.rs +++ b/examples/send_actions_over_network.rs @@ -6,7 +6,7 @@ //! Note that [`ActionState`] can also be serialized and sent directly. //! This approach will be less bandwidth efficient, but involve less complexity and CPU work. -use bevy::ecs::event::ManualEventReader; +use bevy::ecs::event::EventCursor; use bevy::input::InputPlugin; use bevy::prelude::*; use leafwing_input_manager::action_diff::ActionDiffEvent; @@ -137,13 +137,13 @@ fn spawn_player(mut commands: Commands) { fn send_events( client_app: &App, server_app: &mut App, - reader: Option>, -) -> ManualEventReader { + reader: Option>, +) -> EventCursor { let client_events: &Events = client_app.world().resource(); let mut server_events: Mut> = server_app.world_mut().resource_mut(); // Get an event reader, one way or another - let mut reader = reader.unwrap_or_else(|| client_events.get_reader()); + let mut reader = reader.unwrap_or_else(|| client_events.get_cursor()); // Push the clients' events to the server for client_event in reader.read(client_events) { diff --git a/examples/single_player.rs b/examples/single_player.rs index 81f13846..f73513c5 100644 --- a/examples/single_player.rs +++ b/examples/single_player.rs @@ -74,34 +74,34 @@ impl PlayerBundle { // Movement input_map.insert(Up, KeyCode::ArrowUp); - input_map.insert(Up, GamepadButtonType::DPadUp); + input_map.insert(Up, GamepadButton::DPadUp); input_map.insert(Down, KeyCode::ArrowDown); - input_map.insert(Down, GamepadButtonType::DPadDown); + input_map.insert(Down, GamepadButton::DPadDown); input_map.insert(Left, KeyCode::ArrowLeft); - input_map.insert(Left, GamepadButtonType::DPadLeft); + input_map.insert(Left, GamepadButton::DPadLeft); input_map.insert(Right, KeyCode::ArrowRight); - input_map.insert(Right, GamepadButtonType::DPadRight); + input_map.insert(Right, GamepadButton::DPadRight); // Abilities input_map.insert(Ability1, KeyCode::KeyQ); - input_map.insert(Ability1, GamepadButtonType::West); + input_map.insert(Ability1, GamepadButton::West); input_map.insert(Ability1, MouseButton::Left); input_map.insert(Ability2, KeyCode::KeyW); - input_map.insert(Ability2, GamepadButtonType::North); + input_map.insert(Ability2, GamepadButton::North); input_map.insert(Ability2, MouseButton::Right); input_map.insert(Ability3, KeyCode::KeyE); - input_map.insert(Ability3, GamepadButtonType::East); + input_map.insert(Ability3, GamepadButton::East); input_map.insert(Ability4, KeyCode::Space); - input_map.insert(Ability4, GamepadButtonType::South); + input_map.insert(Ability4, GamepadButton::South); input_map.insert(Ultimate, KeyCode::KeyR); - input_map.insert(Ultimate, GamepadButtonType::LeftTrigger2); + input_map.insert(Ultimate, GamepadButton::LeftTrigger2); input_map } diff --git a/examples/twin_stick_controller.rs b/examples/twin_stick_controller.rs index 3b5d9e61..75f9022f 100644 --- a/examples/twin_stick_controller.rs +++ b/examples/twin_stick_controller.rs @@ -48,7 +48,7 @@ impl PlayerAction { // Default gamepad input bindings input_map.insert_dual_axis(Self::Move, GamepadStick::LEFT); input_map.insert_dual_axis(Self::Look, GamepadStick::RIGHT); - input_map.insert(Self::Shoot, GamepadButtonType::RightTrigger); + input_map.insert(Self::Shoot, GamepadButton::RightTrigger); // Default kbm input bindings input_map.insert_dual_axis(Self::Move, VirtualDPad::wasd()); @@ -132,7 +132,7 @@ fn player_mouse_look( let player_position = player_transform.translation; if let Some(p) = window .cursor_position() - .and_then(|cursor| camera.viewport_to_world(camera_transform, cursor)) + .and_then(|cursor| camera.viewport_to_world(camera_transform, cursor).ok()) .and_then(|ray| { Some(ray).zip(ray.intersect_plane(player_position, InfinitePlane3d::new(Vec3::Y))) }) @@ -182,11 +182,9 @@ struct Player; fn setup_scene(mut commands: Commands) { // We need a camera - commands.spawn(Camera3dBundle { - transform: Transform::from_xyz(0.0, 10.0, 15.0) - .looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y), - ..default() - }); + commands + .spawn(Camera3d::default()) + .insert(Transform::from_xyz(0.0, 10.0, 15.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y)); // And a player commands.spawn(Player).insert(Transform::default()); diff --git a/examples/virtual_dpad.rs b/examples/virtual_dpad.rs index a4938c54..6ef63bb9 100644 --- a/examples/virtual_dpad.rs +++ b/examples/virtual_dpad.rs @@ -31,8 +31,8 @@ fn spawn_player(mut commands: Commands) { VirtualDPad::new( KeyCode::KeyW, KeyCode::KeyS, - GamepadButtonType::DPadLeft, - GamepadButtonType::DPadRight, + GamepadButton::DPadLeft, + GamepadButton::DPadRight, ), ); commands diff --git a/macros/src/typetag.rs b/macros/src/typetag.rs index 52b828e3..0dcdd99e 100644 --- a/macros/src/typetag.rs +++ b/macros/src/typetag.rs @@ -42,7 +42,7 @@ pub(crate) fn expand_serde_typetag(input: &ItemImpl) -> syn::Result ) { #crate_path::typetag::Registry::register( registry, - #ident, + (#ident).into(), |de| Ok(::std::boxed::Box::new( ::bevy::reflect::erased_serde::deserialize::<#self_ty>(de)?, )), diff --git a/src/action_state/mod.rs b/src/action_state/mod.rs index 9485cbe9..851175c0 100644 --- a/src/action_state/mod.rs +++ b/src/action_state/mod.rs @@ -1109,14 +1109,14 @@ mod tests { use std::time::{Duration, Instant}; use crate::input_map::InputMap; - use crate::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; + use crate::plugin::CentralInputStorePlugin; use crate::prelude::updating::CentralInputStore; use crate::prelude::ClashStrategy; use crate::user_input::Buttonlike; use bevy::input::InputPlugin; let mut app = App::new(); - app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); + app.add_plugins((InputPlugin, CentralInputStorePlugin)); #[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, bevy::prelude::Reflect)] enum Action { @@ -1138,11 +1138,7 @@ mod tests { // Starting state let input_store = app.world().resource::(); - action_state.update(input_map.process_actions( - &Gamepads::default(), - input_store, - ClashStrategy::PressAll, - )); + action_state.update(input_map.process_actions(None, input_store, ClashStrategy::PressAll)); println!( "Initialized button data: {:?}", @@ -1160,11 +1156,7 @@ mod tests { app.update(); let input_store = app.world().resource::(); - action_state.update(input_map.process_actions( - &Gamepads::default(), - input_store, - ClashStrategy::PressAll, - )); + action_state.update(input_map.process_actions(None, input_store, ClashStrategy::PressAll)); assert!(action_state.pressed(&Action::Run)); assert!(action_state.just_pressed(&Action::Run)); @@ -1173,11 +1165,7 @@ mod tests { // Waiting action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1)); - action_state.update(input_map.process_actions( - &Gamepads::default(), - input_store, - ClashStrategy::PressAll, - )); + action_state.update(input_map.process_actions(None, input_store, ClashStrategy::PressAll)); assert!(action_state.pressed(&Action::Run)); assert!(!action_state.just_pressed(&Action::Run)); @@ -1189,11 +1177,7 @@ mod tests { app.update(); let input_store = app.world().resource::(); - action_state.update(input_map.process_actions( - &Gamepads::default(), - input_store, - ClashStrategy::PressAll, - )); + action_state.update(input_map.process_actions(None, input_store, ClashStrategy::PressAll)); assert!(!action_state.pressed(&Action::Run)); assert!(!action_state.just_pressed(&Action::Run)); @@ -1202,11 +1186,7 @@ mod tests { // Waiting action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1)); - action_state.update(input_map.process_actions( - &Gamepads::default(), - input_store, - ClashStrategy::PressAll, - )); + action_state.update(input_map.process_actions(None, input_store, ClashStrategy::PressAll)); assert!(!action_state.pressed(&Action::Run)); assert!(!action_state.just_pressed(&Action::Run)); @@ -1244,7 +1224,7 @@ mod tests { use std::time::{Duration, Instant}; use crate::input_map::InputMap; - use crate::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; + use crate::plugin::CentralInputStorePlugin; use crate::prelude::updating::CentralInputStore; use crate::prelude::ClashStrategy; use crate::user_input::chord::ButtonlikeChord; @@ -1268,7 +1248,7 @@ mod tests { let mut app = App::new(); app.add_plugins(InputPlugin) - .add_plugins((AccumulatorPlugin, CentralInputStorePlugin)); + .add_plugins(CentralInputStorePlugin); // Action state let mut action_state = ActionState::::default(); @@ -1276,7 +1256,7 @@ mod tests { // Starting state let input_store = app.world().resource::(); action_state.update(input_map.process_actions( - &Gamepads::default(), + None, input_store, ClashStrategy::PrioritizeLongest, )); @@ -1290,7 +1270,7 @@ mod tests { let input_store = app.world().resource::(); action_state.update(input_map.process_actions( - &Gamepads::default(), + None, input_store, ClashStrategy::PrioritizeLongest, )); @@ -1302,7 +1282,7 @@ mod tests { // Waiting action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1)); action_state.update(input_map.process_actions( - &Gamepads::default(), + None, input_store, ClashStrategy::PrioritizeLongest, )); @@ -1317,7 +1297,7 @@ mod tests { let input_store = app.world().resource::(); action_state.update(input_map.process_actions( - &Gamepads::default(), + None, input_store, ClashStrategy::PrioritizeLongest, )); @@ -1331,7 +1311,7 @@ mod tests { // Waiting action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1)); action_state.update(input_map.process_actions( - &Gamepads::default(), + None, input_store, ClashStrategy::PrioritizeLongest, )); diff --git a/src/clashing_inputs.rs b/src/clashing_inputs.rs index 96840bfd..f45c219e 100644 --- a/src/clashing_inputs.rs +++ b/src/clashing_inputs.rs @@ -6,7 +6,7 @@ use std::cmp::Ordering; -use bevy::prelude::{Gamepad, Resource}; +use bevy::prelude::{Entity, Resource}; use serde::{Deserialize, Serialize}; use crate::input_map::{InputMap, UpdatedActions}; @@ -175,7 +175,7 @@ impl InputMap { updated_actions: &mut UpdatedActions, input_store: &CentralInputStore, clash_strategy: ClashStrategy, - gamepad: Gamepad, + gamepad: Entity, ) { for clash in self.get_clashes(updated_actions, input_store, gamepad) { // Remove the action in the pair that was overruled, if any @@ -209,7 +209,7 @@ impl InputMap { &self, updated_actions: &UpdatedActions, input_store: &CentralInputStore, - gamepad: Gamepad, + gamepad: Entity, ) -> Vec> { let mut clashes = Vec::default(); @@ -321,7 +321,7 @@ impl Clash { fn check_clash( clash: &Clash, input_store: &CentralInputStore, - gamepad: Gamepad, + gamepad: Entity, ) -> Option> { let mut actual_clash: Clash = clash.clone(); @@ -355,7 +355,7 @@ fn resolve_clash( clash: &Clash, clash_strategy: ClashStrategy, input_store: &CentralInputStore, - gamepad: Gamepad, + gamepad: Entity, ) -> Option { // Figure out why the actions are pressed let reasons_a_is_pressed: Vec<&dyn Buttonlike> = clash @@ -476,8 +476,8 @@ mod tests { use super::*; use crate::{ input_map::UpdatedValue, - plugin::{AccumulatorPlugin, CentralInputStorePlugin}, - prelude::{AccumulatedMouseMovement, AccumulatedMouseScroll, ModifierKey, VirtualDPad}, + plugin::CentralInputStorePlugin, + prelude::{ModifierKey, VirtualDPad}, }; use bevy::{input::InputPlugin, prelude::*}; use Action::*; @@ -579,9 +579,7 @@ mod tests { #[test] fn resolve_prioritize_longest() { let mut app = App::new(); - app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); - app.init_resource::(); - app.init_resource::(); + app.add_plugins((InputPlugin, CentralInputStorePlugin)); let input_map = test_input_map(); let simple_clash = input_map.possible_clash(&One, &OneAndTwo).unwrap(); @@ -589,6 +587,7 @@ mod tests { Digit2.press(app.world_mut()); app.update(); + let gamepad = app.world_mut().spawn(()).id(); let input_store = app.world().resource::(); assert_eq!( @@ -596,7 +595,7 @@ mod tests { &simple_clash, ClashStrategy::PrioritizeLongest, input_store, - Gamepad::new(0), + gamepad, ), Some(One) ); @@ -609,7 +608,7 @@ mod tests { &reversed_clash, ClashStrategy::PrioritizeLongest, input_store, - Gamepad::new(0), + gamepad, ), Some(One) ); @@ -627,7 +626,7 @@ mod tests { &chord_clash, ClashStrategy::PrioritizeLongest, input_store, - Gamepad::new(0), + gamepad, ), Some(OneAndTwo) ); @@ -636,8 +635,9 @@ mod tests { #[test] fn handle_simple_clash() { let mut app = App::new(); - app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); + app.add_plugins((InputPlugin, CentralInputStorePlugin)); let input_map = test_input_map(); + let gamepad = app.world_mut().spawn(()).id(); Digit1.press(app.world_mut()); Digit2.press(app.world_mut()); @@ -655,7 +655,7 @@ mod tests { &mut updated_actions, input_store, ClashStrategy::PrioritizeLongest, - Gamepad::new(0), + gamepad, ); let mut expected = UpdatedActions::default(); @@ -670,9 +670,8 @@ mod tests { fn handle_clashes_dpad_chord() { let mut app = App::new(); app.add_plugins(InputPlugin); - app.init_resource::(); - app.init_resource::(); let input_map = test_input_map(); + let gamepad = app.world_mut().spawn(()).id(); ControlLeft.press(app.world_mut()); ArrowUp.press(app.world_mut()); @@ -710,7 +709,7 @@ mod tests { &mut updated_actions, input_store, ClashStrategy::PrioritizeLongest, - Gamepad::new(0), + gamepad, ); // Only the chord should be pressed, @@ -724,9 +723,7 @@ mod tests { #[test] fn check_which_pressed() { let mut app = App::new(); - app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); - app.init_resource::(); - app.init_resource::(); + app.add_plugins((InputPlugin, CentralInputStorePlugin)); let input_map = test_input_map(); Digit1.press(app.world_mut()); @@ -736,11 +733,8 @@ mod tests { let input_store = app.world().resource::(); - let action_data = input_map.process_actions( - &Gamepads::default(), - input_store, - ClashStrategy::PrioritizeLongest, - ); + let action_data = + input_map.process_actions(None, input_store, ClashStrategy::PrioritizeLongest); for (action, &updated_value) in action_data.iter() { if *action == CtrlOne || *action == OneAndTwo { diff --git a/src/input_map.rs b/src/input_map.rs index ee19be96..a63fb4ac 100644 --- a/src/input_map.rs +++ b/src/input_map.rs @@ -5,7 +5,7 @@ use std::hash::Hash; #[cfg(feature = "asset")] use bevy::asset::Asset; -use bevy::prelude::{Component, Deref, DerefMut, Gamepad, Gamepads, Reflect, Resource}; +use bevy::prelude::{Component, Deref, DerefMut, Entity, Gamepad, Query, Reflect, Resource, With}; use bevy::utils::HashMap; use bevy::{log::error, prelude::ReflectComponent}; use bevy::{ @@ -25,8 +25,8 @@ use crate::{Actionlike, InputControlKind}; use crate::user_input::gamepad::find_gamepad; #[cfg(not(feature = "gamepad"))] -fn find_gamepad(_gamepads: &Gamepads) -> Gamepad { - Gamepad::new(0) +fn find_gamepad(_: Option>>) -> Entity { + Entity::PLACEHOLDER } /// A Multi-Map that allows you to map actions to multiple [`UserInputs`](crate::user_input::UserInput)s, @@ -117,8 +117,8 @@ pub struct InputMap { /// The underlying map that stores action-input mappings for [`TripleAxislike`] actions. triple_axislike_map: HashMap>>, - /// The specified [`Gamepad`] from which this map exclusively accepts input. - associated_gamepad: Option, + /// The specified gamepad from which this map exclusively accepts input. + associated_gamepad: Option, } impl Default for InputMap { @@ -446,7 +446,7 @@ impl InputMap { /// If this is [`None`], input from any connected gamepad will be used. #[must_use] #[inline] - pub const fn gamepad(&self) -> Option { + pub const fn gamepad(&self) -> Option { self.associated_gamepad } @@ -462,7 +462,7 @@ impl InputMap { /// Because of this robust fallback behavior, /// this method can typically be ignored when writing single-player games. #[inline] - pub fn with_gamepad(mut self, gamepad: Gamepad) -> Self { + pub fn with_gamepad(mut self, gamepad: Entity) -> Self { self.set_gamepad(gamepad); self } @@ -479,7 +479,7 @@ impl InputMap { /// Because of this robust fallback behavior, /// this method can typically be ignored when writing single-player games. #[inline] - pub fn set_gamepad(&mut self, gamepad: Gamepad) -> &mut Self { + pub fn set_gamepad(&mut self, gamepad: Entity) -> &mut Self { self.associated_gamepad = Some(gamepad); self } @@ -504,8 +504,7 @@ impl InputMap { input_store: &CentralInputStore, clash_strategy: ClashStrategy, ) -> bool { - let processed_actions = - self.process_actions(&Gamepads::default(), input_store, clash_strategy); + let processed_actions = self.process_actions(None, input_store, clash_strategy); let Some(updated_value) = processed_actions.get(action) else { return false; @@ -530,7 +529,7 @@ impl InputMap { #[must_use] pub fn process_actions( &self, - gamepads: &Gamepads, + gamepads: Option>>, input_store: &CentralInputStore, clash_strategy: ClashStrategy, ) -> UpdatedActions { @@ -1099,13 +1098,11 @@ mod tests { #[cfg(feature = "gamepad")] #[test] fn gamepad_swapping() { - use bevy::input::gamepad::Gamepad; - let mut input_map = InputMap::::default(); assert_eq!(input_map.gamepad(), None); - input_map.set_gamepad(Gamepad { id: 0 }); - assert_eq!(input_map.gamepad(), Some(Gamepad { id: 0 })); + input_map.set_gamepad(Entity::from_raw(123)); + assert_eq!(input_map.gamepad(), Some(Entity::from_raw(123))); input_map.clear_gamepad(); assert_eq!(input_map.gamepad(), None); diff --git a/src/input_processing/dual_axis/custom.rs b/src/input_processing/dual_axis/custom.rs index 351b536f..07627a99 100644 --- a/src/input_processing/dual_axis/custom.rs +++ b/src/input_processing/dual_axis/custom.rs @@ -1,14 +1,13 @@ -use std::any::{Any, TypeId}; -use std::fmt::{Debug, Formatter}; -use std::hash::{Hash, Hasher}; +use std::any::Any; +use std::fmt::Debug; use std::sync::RwLock; use bevy::app::App; use bevy::prelude::{FromReflect, Reflect, ReflectDeserialize, ReflectSerialize, TypePath, Vec2}; -use bevy::reflect::utility::{reflect_hasher, GenericTypePathCell, NonGenericTypeInfoCell}; +use bevy::reflect::utility::{GenericTypePathCell, NonGenericTypeInfoCell}; use bevy::reflect::{ - erased_serde, FromType, GetTypeRegistration, ReflectFromPtr, ReflectKind, ReflectMut, - ReflectOwned, ReflectRef, TypeInfo, TypeRegistration, Typed, ValueInfo, + erased_serde, FromType, GetTypeRegistration, OpaqueInfo, PartialReflect, ReflectFromPtr, + ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypeRegistration, Typed, }; use dyn_clone::DynClone; use dyn_eq::DynEq; @@ -108,110 +107,111 @@ dyn_clone::clone_trait_object!(CustomDualAxisProcessor); dyn_eq::eq_trait_object!(CustomDualAxisProcessor); dyn_hash::hash_trait_object!(CustomDualAxisProcessor); -impl Reflect for Box { +impl PartialReflect for Box { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(Self::type_info()) } - fn into_any(self: Box) -> Box { - self + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Opaque } - fn as_any(&self) -> &dyn Any { - self + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) } - fn as_any_mut(&mut self) -> &mut dyn Any { - self + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) } - fn into_reflect(self: Box) -> Box { - self + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) } - fn as_reflect(&self) -> &dyn Reflect { + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), bevy::reflect::ApplyError> { + if let Some(value) = value.try_downcast_ref::() { + *self = value.clone(); + Ok(()) + } else { + Err(bevy::reflect::ApplyError::MismatchedTypes { + from_type: self + .reflect_type_ident() + .unwrap_or_default() + .to_string() + .into_boxed_str(), + to_type: self + .reflect_type_ident() + .unwrap_or_default() + .to_string() + .into_boxed_str(), + }) + } + } + + fn into_partial_reflect(self: Box) -> Box { self } - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + fn as_partial_reflect(&self) -> &dyn PartialReflect { self } - fn apply(&mut self, value: &dyn Reflect) { - self.try_apply(value).unwrap() + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self } - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) } - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Value + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) } - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Value(self) + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) } +} - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Value(self) +impl Reflect for Box { + fn into_any(self: Box) -> Box { + self } - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Value(self) + fn as_any(&self) -> &dyn Any { + self } - fn clone_value(&self) -> Box { - Box::new(self.clone()) + fn as_any_mut(&mut self) -> &mut dyn Any { + self } - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - let type_id = TypeId::of::(); - Hash::hash(&type_id, &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) + fn into_reflect(self: Box) -> Box { + self } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - value - .as_any() - .downcast_ref::() - .map(|value| self.dyn_eq(value)) - .or(Some(false)) + fn as_reflect(&self) -> &dyn Reflect { + self } - fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self } - fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), bevy::reflect::ApplyError> { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { - *self = value.clone(); - Ok(()) - } else { - Err(bevy::reflect::ApplyError::MismatchedTypes { - from_type: self - .reflect_type_ident() - .unwrap_or_default() - .to_string() - .into_boxed_str(), - to_type: self - .reflect_type_ident() - .unwrap_or_default() - .to_string() - .into_boxed_str(), - }) - } + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) } } impl Typed for Box { fn type_info() -> &'static TypeInfo { static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::())) + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) } } @@ -257,8 +257,8 @@ impl GetTypeRegistration for Box { } impl FromReflect for Box { - fn from_reflect(reflect: &dyn Reflect) -> Option { - Some(reflect.as_any().downcast_ref::()?.clone()) + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + Some(reflect.try_downcast_ref::()?.clone()) } } diff --git a/src/input_processing/single_axis/custom.rs b/src/input_processing/single_axis/custom.rs index 4286ede8..1121a17c 100644 --- a/src/input_processing/single_axis/custom.rs +++ b/src/input_processing/single_axis/custom.rs @@ -1,14 +1,13 @@ -use std::any::{Any, TypeId}; -use std::fmt::{Debug, Formatter}; -use std::hash::{Hash, Hasher}; +use std::any::Any; +use std::fmt::Debug; use std::sync::RwLock; use bevy::app::App; use bevy::prelude::{FromReflect, Reflect, ReflectDeserialize, ReflectSerialize, TypePath}; -use bevy::reflect::utility::{reflect_hasher, GenericTypePathCell, NonGenericTypeInfoCell}; +use bevy::reflect::utility::{GenericTypePathCell, NonGenericTypeInfoCell}; use bevy::reflect::{ - erased_serde, FromType, GetTypeRegistration, ReflectFromPtr, ReflectKind, ReflectMut, - ReflectOwned, ReflectRef, TypeInfo, TypeRegistration, Typed, ValueInfo, + erased_serde, FromType, GetTypeRegistration, OpaqueInfo, PartialReflect, ReflectFromPtr, + ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypeRegistration, Typed, }; use dyn_clone::DynClone; use dyn_eq::DynEq; @@ -107,110 +106,111 @@ dyn_clone::clone_trait_object!(CustomAxisProcessor); dyn_eq::eq_trait_object!(CustomAxisProcessor); dyn_hash::hash_trait_object!(CustomAxisProcessor); -impl Reflect for Box { +impl PartialReflect for Box { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(Self::type_info()) } - fn into_any(self: Box) -> Box { - self + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Opaque } - fn as_any(&self) -> &dyn Any { - self + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) } - fn as_any_mut(&mut self) -> &mut dyn Any { - self + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) } - fn into_reflect(self: Box) -> Box { - self + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) } - fn as_reflect(&self) -> &dyn Reflect { + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), bevy::reflect::ApplyError> { + if let Some(value) = value.try_downcast_ref::() { + *self = value.clone(); + Ok(()) + } else { + Err(bevy::reflect::ApplyError::MismatchedTypes { + from_type: self + .reflect_type_ident() + .unwrap_or_default() + .to_string() + .into_boxed_str(), + to_type: self + .reflect_type_ident() + .unwrap_or_default() + .to_string() + .into_boxed_str(), + }) + } + } + + fn into_partial_reflect(self: Box) -> Box { self } - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + fn as_partial_reflect(&self) -> &dyn PartialReflect { self } - fn apply(&mut self, value: &dyn Reflect) { - self.try_apply(value).unwrap() + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self } - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) } - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Value + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) } - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Value(self) + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) } +} - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Value(self) +impl Reflect for Box { + fn into_any(self: Box) -> Box { + self } - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Value(self) + fn as_any(&self) -> &dyn Any { + self } - fn clone_value(&self) -> Box { - Box::new(self.clone()) + fn as_any_mut(&mut self) -> &mut dyn Any { + self } - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - let type_id = TypeId::of::(); - Hash::hash(&type_id, &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) + fn into_reflect(self: Box) -> Box { + self } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - value - .as_any() - .downcast_ref::() - .map(|value| self.dyn_eq(value)) - .or(Some(false)) + fn as_reflect(&self) -> &dyn Reflect { + self } - fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self } - fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), bevy::reflect::ApplyError> { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { - *self = value.clone(); - Ok(()) - } else { - Err(bevy::reflect::ApplyError::MismatchedTypes { - from_type: self - .reflect_type_ident() - .unwrap_or_default() - .to_string() - .into_boxed_str(), - to_type: self - .reflect_type_ident() - .unwrap_or_default() - .to_string() - .into_boxed_str(), - }) - } + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) } } impl Typed for Box { fn type_info() -> &'static TypeInfo { static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::())) + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) } } @@ -256,8 +256,8 @@ impl GetTypeRegistration for Box { } impl FromReflect for Box { - fn from_reflect(reflect: &dyn Reflect) -> Option { - Some(reflect.as_any().downcast_ref::()?.clone()) + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + Some(reflect.try_downcast_ref::()?.clone()) } } diff --git a/src/lib.rs b/src/lib.rs index 66c685cf..24f87502 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ use crate::action_state::ActionState; use crate::input_map::InputMap; use bevy::ecs::prelude::*; -use bevy::reflect::{FromReflect, Reflect, TypePath}; +use bevy::reflect::{FromReflect, Reflect, TypePath, Typed}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::hash::Hash; @@ -103,7 +103,7 @@ pub mod prelude { /// } /// ``` pub trait Actionlike: - Debug + Eq + Hash + Send + Sync + Clone + Reflect + TypePath + FromReflect + 'static + Debug + Eq + Hash + Send + Sync + Clone + Reflect + Typed + TypePath + FromReflect + 'static { /// Returns the kind of input control this action represents: buttonlike, axislike, or dual-axislike. fn input_control_kind(&self) -> InputControlKind; diff --git a/src/plugin.rs b/src/plugin.rs index 6125e892..ebb711d9 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -8,7 +8,6 @@ use bevy::app::{App, FixedPostUpdate, Plugin, RunFixedMainLoop}; use bevy::input::InputSystem; use bevy::prelude::*; use bevy::reflect::TypePath; -use bevy::time::run_fixed_main_schedule; #[cfg(feature = "ui")] use bevy::ui::UiSystem; use updating::CentralInputStore; @@ -17,8 +16,6 @@ use crate::action_state::{ActionState, ButtonData}; use crate::clashing_inputs::ClashStrategy; use crate::input_map::InputMap; use crate::input_processing::*; -#[cfg(feature = "mouse")] -use crate::systems::{accumulate_mouse_movement, accumulate_mouse_scroll}; #[cfg(feature = "timing")] use crate::timing::Timing; use crate::user_input::*; @@ -97,11 +94,6 @@ impl Plugin match self.machine { Machine::Client => { - // TODO: this should be part of `bevy_input` - if !app.is_plugin_added::() { - app.add_plugins((AccumulatorPlugin, CentralInputStorePlugin)); - } - if !app.is_plugin_added::() { app.add_plugins(CentralInputStorePlugin); } @@ -130,7 +122,9 @@ impl Plugin app.configure_sets( PreUpdate, - InputManagerSystem::Unify.after(InputManagerSystem::Filter), + InputManagerSystem::Unify + .after(InputManagerSystem::Filter) + .after(InputSystem), ); app.configure_sets( @@ -178,7 +172,7 @@ impl Plugin update_action_state::, ) .chain() - .before(run_fixed_main_schedule), + .in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop), ); app.add_systems(FixedPostUpdate, release_on_input_map_removed::); @@ -191,7 +185,7 @@ impl Plugin ); app.add_systems( RunFixedMainLoop, - swap_to_update::.after(run_fixed_main_schedule), + swap_to_update::.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop), ); } Machine::Server => { @@ -205,8 +199,7 @@ impl Plugin }; #[cfg(feature = "mouse")] - app.register_type::() - .register_type::() + app.register_buttonlike_input::() .register_buttonlike_input::() .register_axislike_input::() .register_dual_axislike_input::() @@ -222,7 +215,7 @@ impl Plugin app.register_buttonlike_input::() .register_axislike_input::() .register_dual_axislike_input::() - .register_buttonlike_input::(); + .register_buttonlike_input::(); // Virtual Axes app.register_axislike_input::() @@ -311,39 +304,6 @@ impl Default for TickActionStateSystem { } } -/// A plugin to handle accumulating mouse movement and scroll events. -/// -/// This is a clearer, more reliable and more efficient approach to computing the total mouse movement and scroll for the frame. -/// -/// This plugin is public to allow it to be used in tests: users should always have this plugin implicitly added by [`InputManagerPlugin`]. -/// Ultimately, this should be included as part of [`InputPlugin`](bevy::input::InputPlugin): see [bevy#13915](https://github.com/bevyengine/bevy/issues/13915). -pub struct AccumulatorPlugin; - -impl Plugin for AccumulatorPlugin { - #[allow(unused_variables)] - fn build(&self, app: &mut App) { - #[cfg(feature = "mouse")] - { - app.init_resource::(); - app.init_resource::(); - - // TODO: these should be part of bevy_input - app.add_systems( - PreUpdate, - (accumulate_mouse_movement, accumulate_mouse_scroll) - .in_set(InputManagerSystem::Accumulate), - ); - - app.configure_sets( - PreUpdate, - InputManagerSystem::Accumulate - .after(InputSystem) - .before(InputManagerSystem::Unify), - ); - } - } -} - /// A plugin that keeps track of all inputs in a central store. /// /// This plugin is added by default by [`InputManagerPlugin`], diff --git a/src/systems.rs b/src/systems.rs index 61546844..b2909e28 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -1,11 +1,7 @@ //! The systems that power each [`InputManagerPlugin`](crate::plugin::InputManagerPlugin). use crate::prelude::updating::CentralInputStore; -#[cfg(feature = "mouse")] -use crate::user_input::{AccumulatedMouseMovement, AccumulatedMouseScroll}; use bevy::ecs::query::QueryFilter; -#[cfg(feature = "mouse")] -use bevy::input::mouse::{MouseMotion, MouseWheel}; use bevy::log::debug; use crate::{ @@ -13,7 +9,7 @@ use crate::{ }; use bevy::ecs::prelude::*; -use bevy::prelude::Gamepads; +use bevy::prelude::Gamepad; use bevy::{ time::{Real, Time}, utils::Instant, @@ -83,32 +79,6 @@ pub fn tick_action_state( *stored_previous_instant = time.last_update(); } -/// Sums the[`MouseMotion`] events received since during this frame. -#[cfg(feature = "mouse")] -pub fn accumulate_mouse_movement( - mut mouse_motion: ResMut, - mut events: EventReader, -) { - mouse_motion.reset(); - - for event in events.read() { - mouse_motion.accumulate(event); - } -} - -/// Sums the [`MouseWheel`] events received since during this frame. -#[cfg(feature = "mouse")] -pub fn accumulate_mouse_scroll( - mut mouse_scroll: ResMut, - mut events: EventReader, -) { - mouse_scroll.reset(); - - for event in events.read() { - mouse_scroll.accumulate(event); - } -} - /// Fetches the [`CentralInputStore`] /// to update [`ActionState`] according to the [`InputMap`]. /// @@ -116,7 +86,7 @@ pub fn accumulate_mouse_scroll( pub fn update_action_state( input_store: Res, clash_strategy: Res, - gamepads: Res, + mut gamepads: Query>, action_state: Option>>, input_map: Option>>, mut query: Query<(&mut ActionState, &InputMap)>, @@ -126,7 +96,11 @@ pub fn update_action_state( .map(|(input_map, action_state)| (Mut::from(action_state), input_map.into_inner())); for (mut action_state, input_map) in query.iter_mut().chain(resources) { - action_state.update(input_map.process_actions(&gamepads, &input_store, *clash_strategy)); + action_state.update(input_map.process_actions( + Some(gamepads.reborrow()), + &input_store, + *clash_strategy, + )); } } diff --git a/src/typetag.rs b/src/typetag.rs index 313ab526..d20279c5 100644 --- a/src/typetag.rs +++ b/src/typetag.rs @@ -1,6 +1,7 @@ //! Type tag registration for trait objects -use std::collections::BTreeMap; +use std::{borrow::Cow, collections::BTreeMap}; +use std::fmt::Debug; pub use serde_flexitos::Registry; use serde_flexitos::{DeserializeFn, GetError}; @@ -13,7 +14,7 @@ pub trait RegisterTypeTag<'de, T: ?Sized> { /// An infallible version of [`MapRegistry`](serde_flexitos::MapRegistry) /// that allows multiple registrations of deserializers. -pub struct InfallibleMapRegistry { +pub struct InfallibleMapRegistry> { deserialize_fns: BTreeMap>>, trait_object_name: &'static str, } @@ -29,7 +30,7 @@ impl InfallibleMapRegistry { } } -impl Registry for InfallibleMapRegistry { +impl Registry for InfallibleMapRegistry { type Identifier = I; type TraitObject = O; diff --git a/src/user_input/chord.rs b/src/user_input/chord.rs index 698ba447..800633c4 100644 --- a/src/user_input/chord.rs +++ b/src/user_input/chord.rs @@ -1,7 +1,7 @@ //! This module contains [`ButtonlikeChord`] and its impls. use bevy::math::{Vec2, Vec3}; -use bevy::prelude::{Gamepad, Reflect, World}; +use bevy::prelude::{Entity, Reflect, World}; use leafwing_input_manager_macros::serde_typetag; use serde::{Deserialize, Serialize}; @@ -24,12 +24,12 @@ use super::{Axislike, DualAxislike}; /// ```rust /// use bevy::prelude::*; /// use bevy::input::InputPlugin; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// use leafwing_input_manager::plugin::CentralInputStorePlugin; /// use leafwing_input_manager::prelude::*; /// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; /// /// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// app.add_plugins((InputPlugin, CentralInputStorePlugin)); /// /// // Define a chord using A and B keys /// let input = ButtonlikeChord::new([KeyCode::KeyA, KeyCode::KeyB]); @@ -130,7 +130,7 @@ impl Buttonlike for ButtonlikeChord { /// Checks if all the inner inputs within the chord are active simultaneously. #[must_use] #[inline] - fn pressed(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> bool { + fn pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool { self.0 .iter() .all(|input| input.pressed(input_store, gamepad)) @@ -148,13 +148,13 @@ impl Buttonlike for ButtonlikeChord { } } - fn press_as_gamepad(&self, world: &mut World, gamepad: Option) { + fn press_as_gamepad(&self, world: &mut World, gamepad: Option) { for input in &self.0 { input.press_as_gamepad(world, gamepad); } } - fn release_as_gamepad(&self, world: &mut World, gamepad: Option) { + fn release_as_gamepad(&self, world: &mut World, gamepad: Option) { for input in &self.0 { input.release_as_gamepad(world, gamepad); } @@ -209,7 +209,7 @@ impl UserInput for AxislikeChord { #[serde_typetag] impl Axislike for AxislikeChord { - fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 { + fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 { if self.button.pressed(input_store, gamepad) { self.axis.value(input_store, gamepad) } else { @@ -221,7 +221,7 @@ impl Axislike for AxislikeChord { self.axis.set_value(world, value); } - fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option) { + fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option) { self.axis.set_value_as_gamepad(world, value, gamepad); } } @@ -264,7 +264,7 @@ impl UserInput for DualAxislikeChord { #[serde_typetag] impl DualAxislike for DualAxislikeChord { - fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec2 { + fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec2 { if self.button.pressed(input_store, gamepad) { self.dual_axis.axis_pair(input_store, gamepad) } else { @@ -280,7 +280,7 @@ impl DualAxislike for DualAxislikeChord { &self, world: &mut World, axis_pair: Vec2, - gamepad: Option, + gamepad: Option, ) { self.dual_axis .set_axis_pair_as_gamepad(world, axis_pair, gamepad); @@ -325,7 +325,7 @@ impl UserInput for TripleAxislikeChord { #[serde_typetag] impl TripleAxislike for TripleAxislikeChord { - fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec3 { + fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec3 { if self.button.pressed(input_store, gamepad) { self.triple_axis.axis_triple(input_store, gamepad) } else { @@ -341,7 +341,7 @@ impl TripleAxislike for TripleAxislikeChord { &self, world: &mut World, axis_triple: Vec3, - gamepad: Option, + gamepad: Option, ) { self.triple_axis .set_axis_triple_as_gamepad(world, axis_triple, gamepad); @@ -352,7 +352,7 @@ impl TripleAxislike for TripleAxislikeChord { #[cfg(test)] mod tests { use super::ButtonlikeChord; - use crate::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; + use crate::plugin::CentralInputStorePlugin; use crate::user_input::updating::CentralInputStore; use crate::user_input::Buttonlike; use bevy::input::gamepad::{ @@ -365,16 +365,19 @@ mod tests { let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_plugins(InputPlugin) - .add_plugins((AccumulatorPlugin, CentralInputStorePlugin)); + .add_plugins(CentralInputStorePlugin); // WARNING: you MUST register your gamepad during tests, // or all gamepad input mocking actions will fail + let gamepad = app.world_mut().spawn(()).id(); let mut gamepad_events = app.world_mut().resource_mut::>(); gamepad_events.send(GamepadEvent::Connection(GamepadConnectionEvent { // This MUST be consistent with any other mocked events - gamepad: Gamepad { id: 1 }, + gamepad, connection: GamepadConnection::Connected(GamepadInfo { name: "TestController".into(), + vendor_id: None, + product_id: None, }), })); @@ -408,8 +411,9 @@ mod tests { // No keys pressed, resulting in a released chord with a value of zero. let mut app = test_app(); app.update(); + let gamepad = app.world_mut().spawn(()).id(); let inputs = app.world().resource::(); - assert!(!chord.pressed(inputs, Gamepad::new(0))); + assert!(!chord.pressed(inputs, gamepad)); // All required keys pressed, resulting in a pressed chord with a value of one. let mut app = test_app(); @@ -418,7 +422,7 @@ mod tests { } app.update(); let inputs = app.world().resource::(); - assert!(chord.pressed(inputs, Gamepad::new(0))); + assert!(chord.pressed(inputs, gamepad)); // Some required keys pressed, but not all required keys for the chord, // resulting in a released chord with a value of zero. @@ -429,7 +433,7 @@ mod tests { } app.update(); let inputs = app.world().resource::(); - assert!(!chord.pressed(inputs, Gamepad::new(0))); + assert!(!chord.pressed(inputs, gamepad)); } // Five keys pressed, but not all required keys for the chord, @@ -441,6 +445,6 @@ mod tests { KeyCode::KeyB.press(app.world_mut()); app.update(); let inputs = app.world().resource::(); - assert!(!chord.pressed(inputs, Gamepad::new(0))); + assert!(!chord.pressed(inputs, gamepad)); } } diff --git a/src/user_input/gamepad.rs b/src/user_input/gamepad.rs index 9f460e15..2080f59e 100644 --- a/src/user_input/gamepad.rs +++ b/src/user_input/gamepad.rs @@ -1,16 +1,17 @@ //! Gamepad inputs -use std::hash::{Hash, Hasher}; - -use bevy::input::gamepad::{GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadEvent}; -use bevy::input::{Axis, ButtonInput}; +use bevy::ecs::system::lifetimeless::{Read, SQuery}; +use bevy::ecs::system::{StaticSystemParam, SystemState}; +use bevy::input::gamepad::{ + GamepadInput, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, +}; use bevy::math::FloatOrd; use bevy::prelude::{ - Events, Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, Gamepads, - Reflect, Res, ResMut, Vec2, World, + Entity, Events, Gamepad, GamepadAxis, GamepadButton, Query, Reflect, ResMut, Vec2, With, World, }; use leafwing_input_manager_macros::serde_typetag; use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; use crate as leafwing_input_manager; use crate::axislike::AxisDirection; @@ -27,24 +28,56 @@ use super::{Axislike, Buttonlike, DualAxislike}; /// Retrieves the first connected gamepad. /// -/// If no gamepad is connected, a synthetic gamepad with an ID of 0 is returned. +/// If no gamepad is connected, `Entity::PLACEHOLDER` is returned. #[must_use] -pub fn find_gamepad(gamepads: &Gamepads) -> Gamepad { - gamepads.iter().next().unwrap_or(Gamepad { id: 0 }) +pub fn find_gamepad(gamepads: Option>>) -> Entity { + match gamepads { + None => Entity::PLACEHOLDER, + Some(gamepads) => gamepads.iter().next().unwrap_or(Entity::PLACEHOLDER), + } } /// Retrieves the current value of the specified `axis`. #[must_use] #[inline] -fn read_axis_value( - input_store: &CentralInputStore, - gamepad: Gamepad, - axis: GamepadAxisType, -) -> f32 { - let axis = GamepadAxis::new(gamepad, axis); +fn read_axis_value(input_store: &CentralInputStore, gamepad: Entity, axis: GamepadAxis) -> f32 { + let axis = SpecificGamepadAxis::new(gamepad, axis); input_store.value(&axis) } +/// A [`GamepadAxis`] for a specific gamepad (as opposed to all gamepads). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] +pub struct SpecificGamepadAxis { + /// The gamepad that this axis is attached to. + pub gamepad: Entity, + /// The axis. + pub axis: GamepadAxis, +} + +impl SpecificGamepadAxis { + /// Creates a new [`SpecificGamepadAxis`] with the given gamepad and axis. + pub fn new(gamepad: Entity, axis: GamepadAxis) -> Self { + Self { gamepad, axis } + } +} + +/// A [`GamepadButton`] for a specific gamepad (as opposed to all gamepads). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] +pub struct SpecificGamepadButton { + /// The gamepad that this button is attached to. + pub gamepad: Entity, + /// The button. + pub button: GamepadButton, +} + +impl SpecificGamepadButton { + /// Creates a new [`SpecificGamepadButton`] with the given gamepad and + /// button. + pub fn new(gamepad: Entity, button: GamepadButton) -> Self { + Self { gamepad, button } + } +} + /// Provides button-like behavior for a specific direction on a [`GamepadAxisType`]. /// /// By default, it reads from **any connected gamepad**. @@ -76,7 +109,7 @@ fn read_axis_value( #[must_use] pub struct GamepadControlDirection { /// The axis that this input tracks. - pub axis: GamepadAxisType, + pub axis: GamepadAxis, /// The direction of the axis to monitor (positive or negative). pub direction: AxisDirection, @@ -89,7 +122,7 @@ pub struct GamepadControlDirection { impl GamepadControlDirection { /// Creates a [`GamepadControlDirection`] triggered by a negative value on the specified `axis`. #[inline] - pub const fn negative(axis: GamepadAxisType) -> Self { + pub const fn negative(axis: GamepadAxis) -> Self { Self { axis, direction: AxisDirection::Negative, @@ -99,7 +132,7 @@ impl GamepadControlDirection { /// Creates a [`GamepadControlDirection`] triggered by a positive value on the specified `axis`. #[inline] - pub const fn positive(axis: GamepadAxisType) -> Self { + pub const fn positive(axis: GamepadAxis) -> Self { Self { axis, direction: AxisDirection::Positive, @@ -124,28 +157,28 @@ impl GamepadControlDirection { } /// "Up" on the left analog stick (positive Y-axis movement). - pub const LEFT_UP: Self = Self::positive(GamepadAxisType::LeftStickY); + pub const LEFT_UP: Self = Self::positive(GamepadAxis::LeftStickY); /// "Down" on the left analog stick (negative Y-axis movement). - pub const LEFT_DOWN: Self = Self::negative(GamepadAxisType::LeftStickY); + pub const LEFT_DOWN: Self = Self::negative(GamepadAxis::LeftStickY); /// "Left" on the left analog stick (negative X-axis movement). - pub const LEFT_LEFT: Self = Self::negative(GamepadAxisType::LeftStickX); + pub const LEFT_LEFT: Self = Self::negative(GamepadAxis::LeftStickX); /// "Right" on the left analog stick (positive X-axis movement). - pub const LEFT_RIGHT: Self = Self::positive(GamepadAxisType::LeftStickX); + pub const LEFT_RIGHT: Self = Self::positive(GamepadAxis::LeftStickX); /// "Up" on the right analog stick (positive Y-axis movement). - pub const RIGHT_UP: Self = Self::positive(GamepadAxisType::RightStickY); + pub const RIGHT_UP: Self = Self::positive(GamepadAxis::RightStickY); /// "Down" on the right analog stick (negative Y-axis movement). - pub const RIGHT_DOWN: Self = Self::negative(GamepadAxisType::RightStickY); + pub const RIGHT_DOWN: Self = Self::negative(GamepadAxis::RightStickY); /// "Left" on the right analog stick (negative X-axis movement). - pub const RIGHT_LEFT: Self = Self::negative(GamepadAxisType::RightStickX); + pub const RIGHT_LEFT: Self = Self::negative(GamepadAxis::RightStickX); /// "Right" on the right analog stick (positive X-axis movement). - pub const RIGHT_RIGHT: Self = Self::positive(GamepadAxisType::RightStickX); + pub const RIGHT_RIGHT: Self = Self::positive(GamepadAxis::RightStickX); } impl UserInput for GamepadControlDirection { @@ -167,33 +200,37 @@ impl Buttonlike for GamepadControlDirection { /// Checks if there is any recent stick movement along the specified direction. #[must_use] #[inline] - fn pressed(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> bool { + fn pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool { let value = read_axis_value(input_store, gamepad, self.axis); self.direction.is_active(value, self.threshold) } - /// Sends a [`GamepadEvent::Axis`] event with a magnitude of 1.0 for the specified direction on the provided [`Gamepad`]. - fn press_as_gamepad(&self, world: &mut World, gamepad: Option) { - let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::())); + /// Sends a [`RawGamepadEvent::Axis`] event with a magnitude of 1.0 for the specified direction on the provided [`Gamepad`]. + fn press_as_gamepad(&self, world: &mut World, gamepad: Option) { + let mut query_state = SystemState::>>::new(world); + let query = query_state.get(world); + let gamepad = gamepad.unwrap_or(find_gamepad(Some(query))); - let event = GamepadEvent::Axis(GamepadAxisChangedEvent { + let event = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent { gamepad, - axis_type: self.axis, + axis: self.axis, value: self.direction.full_active_value(), }); - world.resource_mut::>().send(event); + world.resource_mut::>().send(event); } - /// Sends a [`GamepadEvent::Axis`] event with a magnitude of 0.0 for the specified direction. - fn release_as_gamepad(&self, world: &mut World, gamepad: Option) { - let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::())); + /// Sends a [`RawGamepadEvent::Axis`] event with a magnitude of 0.0 for the specified direction. + fn release_as_gamepad(&self, world: &mut World, gamepad: Option) { + let mut query_state = SystemState::>>::new(world); + let query = query_state.get(world); + let gamepad = gamepad.unwrap_or(find_gamepad(Some(query))); - let event = GamepadEvent::Axis(GamepadAxisChangedEvent { + let event = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent { gamepad, - axis_type: self.axis, + axis: self.axis, value: 0.0, }); - world.resource_mut::>().send(event); + world.resource_mut::>().send(event); } } @@ -208,23 +245,31 @@ impl Hash for GamepadControlDirection { } impl UpdatableInput for GamepadAxis { - type SourceData = Axis; + type SourceData = SQuery<(Entity, Read)>; fn compute( mut central_input_store: ResMut, - source_data: Res, + source_data: StaticSystemParam, ) { - for axis in source_data.devices() { - let value = source_data.get(*axis).unwrap_or_default(); - - central_input_store.update_axislike(*axis, value); + for (gamepad_entity, gamepad) in source_data.iter() { + for input in gamepad.get_analog_axes() { + let GamepadInput::Axis(axis) = input else { + continue; + }; + let value = gamepad.get(*axis).unwrap_or_default(); + central_input_store.update_axislike( + SpecificGamepadAxis { + gamepad: gamepad_entity, + axis: *axis, + }, + value, + ); + central_input_store.update_axislike(*axis, value); + } } } } -/// Unlike [`GamepadButtonType`], this struct represents a specific axis on a specific gamepad. -/// -/// In the majority of cases, [`GamepadControlAxis`] or [`GamepadStick`] should be used instead. impl UserInput for GamepadAxis { fn kind(&self) -> InputControlKind { InputControlKind::Axis @@ -232,16 +277,39 @@ impl UserInput for GamepadAxis { fn decompose(&self) -> BasicInputs { BasicInputs::Composite(vec![ - Box::new(GamepadControlDirection::negative(self.axis_type)), - Box::new(GamepadControlDirection::positive(self.axis_type)), + Box::new(GamepadControlDirection::negative(*self)), + Box::new(GamepadControlDirection::positive(*self)), ]) } } #[serde_typetag] impl Axislike for GamepadAxis { - fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 { - read_axis_value(input_store, gamepad, self.axis_type) + fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 { + read_axis_value(input_store, gamepad, *self) + } +} + +/// Unlike [`GamepadButton`], this struct represents a specific axis on a specific gamepad. +/// +/// In the majority of cases, [`GamepadControlAxis`] or [`GamepadStick`] should be used instead. +impl UserInput for SpecificGamepadAxis { + fn kind(&self) -> InputControlKind { + InputControlKind::Axis + } + + fn decompose(&self) -> BasicInputs { + BasicInputs::Composite(vec![ + Box::new(GamepadControlDirection::negative(self.axis)), + Box::new(GamepadControlDirection::positive(self.axis)), + ]) + } +} + +#[serde_typetag] +impl Axislike for SpecificGamepadAxis { + fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 { + read_axis_value(input_store, gamepad, self.axis) } } @@ -279,7 +347,7 @@ impl Axislike for GamepadAxis { #[must_use] pub struct GamepadControlAxis { /// The wrapped axis. - pub(crate) axis: GamepadAxisType, + pub(crate) axis: GamepadAxis, /// A processing pipeline that handles input values. pub(crate) processors: Vec, @@ -289,7 +357,7 @@ impl GamepadControlAxis { /// Creates a [`GamepadControlAxis`] for continuous input from the given axis. /// No processing is applied to raw data from the gamepad. #[inline] - pub const fn new(axis: GamepadAxisType) -> Self { + pub const fn new(axis: GamepadAxis) -> Self { Self { axis, processors: Vec::new(), @@ -298,25 +366,25 @@ impl GamepadControlAxis { /// The horizontal axis (X-axis) of the left stick. /// No processing is applied to raw data from the gamepad. - pub const LEFT_X: Self = Self::new(GamepadAxisType::LeftStickX); + pub const LEFT_X: Self = Self::new(GamepadAxis::LeftStickX); /// The vertical axis (Y-axis) of the left stick. /// No processing is applied to raw data from the gamepad. - pub const LEFT_Y: Self = Self::new(GamepadAxisType::LeftStickY); + pub const LEFT_Y: Self = Self::new(GamepadAxis::LeftStickY); /// The left `Z` button. No processing is applied to raw data from the gamepad. - pub const LEFT_Z: Self = Self::new(GamepadAxisType::LeftZ); + pub const LEFT_Z: Self = Self::new(GamepadAxis::LeftZ); /// The horizontal axis (X-axis) of the right stick. /// No processing is applied to raw data from the gamepad. - pub const RIGHT_X: Self = Self::new(GamepadAxisType::RightStickX); + pub const RIGHT_X: Self = Self::new(GamepadAxis::RightStickX); /// The vertical axis (Y-axis) of the right stick. /// No processing is applied to raw data from the gamepad. - pub const RIGHT_Y: Self = Self::new(GamepadAxisType::RightStickY); + pub const RIGHT_Y: Self = Self::new(GamepadAxis::RightStickY); /// The right `Z` button. No processing is applied to raw data from the gamepad. - pub const RIGHT_Z: Self = Self::new(GamepadAxisType::RightZ); + pub const RIGHT_Z: Self = Self::new(GamepadAxis::RightZ); } impl UserInput for GamepadControlAxis { @@ -341,23 +409,25 @@ impl Axislike for GamepadControlAxis { /// Retrieves the current value of this axis after processing by the associated processors. #[must_use] #[inline] - fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 { + fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 { let value = read_axis_value(input_store, gamepad, self.axis); self.processors .iter() .fold(value, |value, processor| processor.process(value)) } - /// Sends a [`GamepadEvent::Axis`] event with the specified value on the provided [`Gamepad`]. - fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option) { - let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::())); + /// Sends a [`RawGamepadEvent::Axis`] event with the specified value on the provided gamepad. + fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option) { + let mut query_state = SystemState::>>::new(world); + let query = query_state.get(world); + let gamepad = gamepad.unwrap_or(find_gamepad(Some(query))); - let event = GamepadEvent::Axis(GamepadAxisChangedEvent { + let event = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent { gamepad, - axis_type: self.axis, + axis: self.axis, value, }); - world.resource_mut::>().send(event); + world.resource_mut::>().send(event); } } @@ -418,10 +488,10 @@ impl WithAxisProcessingPipelineExt for GamepadControlAxis { #[must_use] pub struct GamepadStick { /// Horizontal movement of the stick. - pub(crate) x: GamepadAxisType, + pub(crate) x: GamepadAxis, /// Vertical movement of the stick. - pub(crate) y: GamepadAxisType, + pub(crate) y: GamepadAxis, /// A processing pipeline that handles input values. pub(crate) processors: Vec, @@ -430,15 +500,15 @@ pub struct GamepadStick { impl GamepadStick { /// The left gamepad stick. No processing is applied to raw data from the gamepad. pub const LEFT: Self = Self { - x: GamepadAxisType::LeftStickX, - y: GamepadAxisType::LeftStickY, + x: GamepadAxis::LeftStickX, + y: GamepadAxis::LeftStickY, processors: Vec::new(), }; /// The right gamepad stick. No processing is applied to raw data from the gamepad. pub const RIGHT: Self = Self { - x: GamepadAxisType::RightStickX, - y: GamepadAxisType::RightStickY, + x: GamepadAxis::RightStickX, + y: GamepadAxis::RightStickY, processors: Vec::new(), }; } @@ -467,7 +537,7 @@ impl DualAxislike for GamepadStick { /// Retrieves the current X and Y values of this stick after processing by the associated processors. #[must_use] #[inline] - fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec2 { + fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec2 { let x = read_axis_value(input_store, gamepad, self.x); let y = read_axis_value(input_store, gamepad, self.y); self.processors @@ -475,23 +545,25 @@ impl DualAxislike for GamepadStick { .fold(Vec2::new(x, y), |value, processor| processor.process(value)) } - /// Sends a [`GamepadEvent::Axis`] event with the specified values on the provided [`Gamepad`]. - fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option) { - let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::())); + /// Sends a [`RawGamepadEvent::Axis`] event with the specified values on the provided [`Gamepad`]. + fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option) { + let mut query_state = SystemState::>>::new(world); + let query = query_state.get(world); + let gamepad = gamepad.unwrap_or(find_gamepad(Some(query))); - let event = GamepadEvent::Axis(GamepadAxisChangedEvent { + let event = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent { gamepad, - axis_type: self.x, + axis: self.x, value: value.x, }); - world.resource_mut::>().send(event); + world.resource_mut::>().send(event); - let event = GamepadEvent::Axis(GamepadAxisChangedEvent { + let event = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent { gamepad, - axis_type: self.y, + axis: self.y, value: value.y, }); - world.resource_mut::>().send(event); + world.resource_mut::>().send(event); } } @@ -518,31 +590,43 @@ impl WithDualAxisProcessingPipelineExt for GamepadStick { } } -/// Checks if the given [`GamepadButtonType`] is currently pressed. +/// Checks if the given [`GamepadButton`] is currently pressed. #[must_use] #[inline] -fn button_pressed( - input_store: &CentralInputStore, - gamepad: Gamepad, - button: GamepadButtonType, -) -> bool { - let button = GamepadButton::new(gamepad, button); +fn button_pressed(input_store: &CentralInputStore, gamepad: Entity, button: GamepadButton) -> bool { + let button = SpecificGamepadButton::new(gamepad, button); input_store.pressed(&button) } impl UpdatableInput for GamepadButton { - type SourceData = ButtonInput; + type SourceData = SQuery<(Entity, Read)>; fn compute( mut central_input_store: ResMut, - source_data: Res, + source_data: StaticSystemParam, ) { - for key in source_data.get_pressed() { - central_input_store.update_buttonlike(*key, true); - } - - for key in source_data.get_just_released() { - central_input_store.update_buttonlike(*key, false); + for (gamepad_entity, gamepad) in source_data.iter() { + for key in gamepad.get_pressed() { + central_input_store.update_buttonlike( + SpecificGamepadButton { + gamepad: gamepad_entity, + button: *key, + }, + true, + ); + central_input_store.update_buttonlike(*key, true); + } + + for key in gamepad.get_just_released() { + central_input_store.update_buttonlike( + SpecificGamepadButton { + gamepad: gamepad_entity, + button: *key, + }, + false, + ); + central_input_store.update_buttonlike(*key, false); + } } } } @@ -550,7 +634,7 @@ impl UpdatableInput for GamepadButton { /// Unlike [`GamepadButtonType`], this struct represents a specific button on a specific gamepad. /// /// In the majority of cases, [`GamepadButtonType`] should be used instead. -impl UserInput for GamepadButton { +impl UserInput for SpecificGamepadButton { fn kind(&self) -> InputControlKind { InputControlKind::Button } @@ -561,34 +645,34 @@ impl UserInput for GamepadButton { } #[serde_typetag] -impl Buttonlike for GamepadButton { +impl Buttonlike for SpecificGamepadButton { /// WARNING: The supplied gamepad is ignored, as the button is already specific to a gamepad. - fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool { - button_pressed(input_store, self.gamepad, self.button_type) + fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool { + button_pressed(input_store, self.gamepad, self.button) } fn press(&self, world: &mut World) { - let event = GamepadEvent::Button(GamepadButtonChangedEvent { + let event = RawGamepadEvent::Button(RawGamepadButtonChangedEvent { gamepad: self.gamepad, - button_type: self.button_type, + button: self.button, value: 1.0, }); - world.resource_mut::>().send(event); + world.resource_mut::>().send(event); } fn release(&self, world: &mut World) { - let event = GamepadEvent::Button(GamepadButtonChangedEvent { + let event = RawGamepadEvent::Button(RawGamepadButtonChangedEvent { gamepad: self.gamepad, - button_type: self.button_type, + button: self.button, value: 0.0, }); - world.resource_mut::>().send(event); + world.resource_mut::>().send(event); } } // Built-in support for Bevy's GamepadButtonType. -impl UserInput for GamepadButtonType { - /// [`GamepadButtonType`] acts as a button. +impl UserInput for GamepadButton { + /// [`GamepadButton`] acts as a button. #[inline] fn kind(&self) -> InputControlKind { InputControlKind::Button @@ -603,64 +687,72 @@ impl UserInput for GamepadButtonType { } #[serde_typetag] -impl Buttonlike for GamepadButtonType { +impl Buttonlike for GamepadButton { /// Checks if the specified button is currently pressed down. #[must_use] #[inline] - fn pressed(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> bool { + fn pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool { button_pressed(input_store, gamepad, *self) } - /// Sends a [`GamepadEvent::Button`] event with a magnitude of 1.0 in the direction defined by `self` on the provided [`Gamepad`]. - fn press_as_gamepad(&self, world: &mut World, gamepad: Option) { - let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::())); + /// Sends a [`RawGamepadEvent::Button`] event with a magnitude of 1.0 in the direction defined by `self` on the provided [`Gamepad`]. + fn press_as_gamepad(&self, world: &mut World, gamepad: Option) { + let mut query_state = SystemState::>>::new(world); + let query = query_state.get(world); + let gamepad = gamepad.unwrap_or(find_gamepad(Some(query))); - let event = GamepadEvent::Button(GamepadButtonChangedEvent { + let event = RawGamepadEvent::Button(RawGamepadButtonChangedEvent { gamepad, - button_type: *self, + button: *self, value: 1.0, }); - world.resource_mut::>().send(event); + world.resource_mut::>().send(event); } - /// Sends a [`GamepadEvent::Button`] event with a magnitude of 0.0 in the direction defined by `self` on the provided [`Gamepad`]. - fn release_as_gamepad(&self, world: &mut World, gamepad: Option) { - let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::())); + /// Sends a [`RawGamepadEvent::Button`] event with a magnitude of 0.0 in the direction defined by `self` on the provided [`Gamepad`]. + fn release_as_gamepad(&self, world: &mut World, gamepad: Option) { + let mut query_state = SystemState::>>::new(world); + let query = query_state.get(world); + let gamepad = gamepad.unwrap_or(find_gamepad(Some(query))); - let event = GamepadEvent::Button(GamepadButtonChangedEvent { + let event = RawGamepadEvent::Button(RawGamepadButtonChangedEvent { gamepad, - button_type: *self, + button: *self, value: 0.0, }); - world.resource_mut::>().send(event); + world.resource_mut::>().send(event); } } #[cfg(test)] mod tests { use super::*; - use crate::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; - use bevy::input::gamepad::{ - GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo, - }; + use crate::plugin::CentralInputStorePlugin; + use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadInfo}; use bevy::input::InputPlugin; use bevy::prelude::*; fn test_app() -> App { let mut app = App::new(); app.add_plugins(MinimalPlugins); - app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); + app.add_plugins((InputPlugin, CentralInputStorePlugin)); // WARNING: you MUST register your gamepad during tests, // or all gamepad input mocking actions will fail - let mut gamepad_events = app.world_mut().resource_mut::>(); - gamepad_events.send(GamepadEvent::Connection(GamepadConnectionEvent { + let gamepad_info = GamepadInfo { + name: "TestController".into(), + vendor_id: None, + product_id: None, + }; + let gamepad = app.world_mut().spawn(()).id(); + let mut gamepad_connection_events = app + .world_mut() + .resource_mut::>(); + gamepad_connection_events.send(GamepadConnectionEvent { // This MUST be consistent with any other mocked events - gamepad: Gamepad { id: 1 }, - connection: GamepadConnection::Connected(GamepadInfo { - name: "TestController".into(), - }), - })); + gamepad, + connection: GamepadConnection::Connected(gamepad_info), + }); // Ensure that the gamepad is picked up by the appropriate system app.update(); @@ -701,8 +793,13 @@ mod tests { // No inputs let mut app = test_app(); app.update(); + let gamepad = app + .world_mut() + .query_filtered::>() + .iter(app.world()) + .next() + .unwrap(); let inputs = app.world().resource::(); - let gamepad = Gamepad::new(0); assert!(!left_up.pressed(inputs, gamepad)); assert!(!left_down.pressed(inputs, gamepad)); @@ -716,6 +813,12 @@ mod tests { // Left stick moves upward let data = Vec2::new(0.0, 1.0); let mut app = test_app(); + let gamepad = app + .world_mut() + .query_filtered::>() + .iter(app.world()) + .next() + .unwrap(); GamepadControlDirection::LEFT_UP.press_as_gamepad(app.world_mut(), Some(gamepad)); app.update(); let inputs = app.world().resource::(); @@ -732,6 +835,12 @@ mod tests { // Set Y-axis of left stick to 0.6 let data = Vec2::new(0.0, 0.6); let mut app = test_app(); + let gamepad = app + .world_mut() + .query_filtered::>() + .iter(app.world()) + .next() + .unwrap(); GamepadControlAxis::LEFT_Y.set_value_as_gamepad(app.world_mut(), data.y, Some(gamepad)); app.update(); let inputs = app.world().resource::(); @@ -748,6 +857,12 @@ mod tests { // Set left stick to (0.6, 0.4) let data = Vec2::new(0.6, 0.4); let mut app = test_app(); + let gamepad = app + .world_mut() + .query_filtered::>() + .iter(app.world()) + .next() + .unwrap(); GamepadStick::LEFT.set_axis_pair_as_gamepad(app.world_mut(), data, Some(gamepad)); app.update(); let inputs = app.world().resource::(); @@ -765,25 +880,24 @@ mod tests { #[test] #[ignore = "Input mocking is subtly broken: https://github.com/Leafwing-Studios/leafwing-input-manager/issues/516"] fn test_gamepad_buttons() { - let up = GamepadButtonType::DPadUp; + let up = GamepadButton::DPadUp; assert_eq!(up.kind(), InputControlKind::Button); - let left = GamepadButtonType::DPadLeft; + let left = GamepadButton::DPadLeft; assert_eq!(left.kind(), InputControlKind::Button); - let down = GamepadButtonType::DPadDown; + let down = GamepadButton::DPadDown; assert_eq!(left.kind(), InputControlKind::Button); - let right = GamepadButtonType::DPadRight; + let right = GamepadButton::DPadRight; assert_eq!(left.kind(), InputControlKind::Button); // No inputs let mut app = test_app(); app.update(); + let gamepad = app.world_mut().spawn(()).id(); let inputs = app.world().resource::(); - let gamepad = Gamepad::new(0); - assert!(!up.pressed(inputs, gamepad)); assert!(!left.pressed(inputs, gamepad)); assert!(!down.pressed(inputs, gamepad)); @@ -791,7 +905,7 @@ mod tests { // Press DPadLeft let mut app = test_app(); - GamepadButtonType::DPadLeft.press(app.world_mut()); + GamepadButton::DPadLeft.press(app.world_mut()); app.update(); let inputs = app.world().resource::(); diff --git a/src/user_input/keyboard.rs b/src/user_input/keyboard.rs index 122b60d0..f8e52612 100644 --- a/src/user_input/keyboard.rs +++ b/src/user_input/keyboard.rs @@ -1,8 +1,10 @@ //! Keyboard inputs +use bevy::ecs::system::lifetimeless::SRes; +use bevy::ecs::system::StaticSystemParam; use bevy::input::keyboard::{Key, KeyboardInput, NativeKey}; use bevy::input::{ButtonInput, ButtonState}; -use bevy::prelude::{Entity, Events, Gamepad, KeyCode, Reflect, Res, ResMut, World}; +use bevy::prelude::{Entity, Events, KeyCode, Reflect, ResMut, World}; use leafwing_input_manager_macros::serde_typetag; use serde::{Deserialize, Serialize}; @@ -31,11 +33,11 @@ impl UserInput for KeyCode { } impl UpdatableInput for KeyCode { - type SourceData = ButtonInput; + type SourceData = SRes>; fn compute( mut central_input_store: ResMut, - source_data: Res, + source_data: StaticSystemParam, ) { for key in source_data.get_pressed() { central_input_store.update_buttonlike(*key, true); @@ -52,7 +54,7 @@ impl Buttonlike for KeyCode { /// Checks if the specified key is currently pressed down. #[must_use] #[inline] - fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool { + fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool { input_store.pressed(self) } @@ -67,6 +69,7 @@ impl Buttonlike for KeyCode { key_code: *self, logical_key: Key::Unidentified(NativeKey::Unidentified), state: ButtonState::Pressed, + repeat: false, window: Entity::PLACEHOLDER, }); } @@ -82,6 +85,7 @@ impl Buttonlike for KeyCode { key_code: *self, logical_key: Key::Unidentified(NativeKey::Unidentified), state: ButtonState::Released, + repeat: false, window: Entity::PLACEHOLDER, }); } @@ -165,7 +169,7 @@ impl Buttonlike for ModifierKey { /// Checks if the specified modifier key is currently pressed down. #[must_use] #[inline] - fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool { + fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool { input_store.pressed(&self.left()) || input_store.pressed(&self.right()) } @@ -197,14 +201,14 @@ impl Buttonlike for ModifierKey { #[cfg(test)] mod tests { use super::*; - use crate::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; + use crate::plugin::CentralInputStorePlugin; use bevy::input::InputPlugin; use bevy::prelude::*; fn test_app() -> App { let mut app = App::new(); app.add_plugins(InputPlugin) - .add_plugins((AccumulatorPlugin, CentralInputStorePlugin)); + .add_plugins(CentralInputStorePlugin); app } @@ -222,10 +226,9 @@ mod tests { // No inputs let mut app = test_app(); app.update(); + let gamepad = app.world_mut().spawn(()).id(); let inputs = app.world().resource::(); - let gamepad = Gamepad::new(0); - assert!(!up.pressed(inputs, gamepad)); assert!(!left.pressed(inputs, gamepad)); assert!(!alt.pressed(inputs, gamepad)); diff --git a/src/user_input/mod.rs b/src/user_input/mod.rs index 736d4dc2..ef907f79 100644 --- a/src/user_input/mod.rs +++ b/src/user_input/mod.rs @@ -82,7 +82,7 @@ use std::fmt::Debug; use bevy::math::{Vec2, Vec3}; -use bevy::prelude::{Gamepad, World}; +use bevy::prelude::{Entity, World}; use bevy::reflect::{erased_serde, Reflect}; use dyn_clone::DynClone; use dyn_eq::DynEq; @@ -136,10 +136,10 @@ pub trait Buttonlike: UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize { /// Checks if the input is currently active. - fn pressed(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> bool; + fn pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool; /// Checks if the input is currently inactive. - fn released(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> bool { + fn released(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool { !self.pressed(input_store, gamepad) } @@ -158,7 +158,7 @@ pub trait Buttonlike: /// /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on /// if the provided gamepad is `None`. - fn press_as_gamepad(&self, world: &mut World, _gamepad: Option) { + fn press_as_gamepad(&self, world: &mut World, _gamepad: Option) { self.press(world); } @@ -177,7 +177,7 @@ pub trait Buttonlike: /// /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on /// if the provided gamepad is `None`. - fn release_as_gamepad(&self, world: &mut World, _gamepad: Option) { + fn release_as_gamepad(&self, world: &mut World, _gamepad: Option) { self.release(world); } } @@ -187,7 +187,7 @@ pub trait Axislike: UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize { /// Gets the current value of the input as an `f32`. - fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32; + fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32; /// Simulate an axis-like input by sending the appropriate event. /// @@ -204,7 +204,7 @@ pub trait Axislike: /// /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on /// if the provided gamepad is `None`. - fn set_value_as_gamepad(&self, world: &mut World, value: f32, _gamepad: Option) { + fn set_value_as_gamepad(&self, world: &mut World, value: f32, _gamepad: Option) { self.set_value(world, value); } } @@ -214,7 +214,7 @@ pub trait DualAxislike: UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize { /// Gets the values of this input along the X and Y axes (if applicable). - fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec2; + fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec2; /// Simulate a dual-axis-like input by sending the appropriate event. /// @@ -231,7 +231,7 @@ pub trait DualAxislike: /// /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on /// if the provided gamepad is `None`. - fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, _gamepad: Option) { + fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, _gamepad: Option) { self.set_axis_pair(world, value); } } @@ -241,7 +241,7 @@ pub trait TripleAxislike: UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize { /// Gets the values of this input along the X, Y, and Z axes (if applicable). - fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec3; + fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec3; /// Simulate a triple-axis-like input by sending the appropriate event. /// @@ -258,12 +258,7 @@ pub trait TripleAxislike: /// /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on /// if the provided gamepad is `None`. - fn set_axis_triple_as_gamepad( - &self, - world: &mut World, - value: Vec3, - _gamepad: Option, - ) { + fn set_axis_triple_as_gamepad(&self, world: &mut World, value: Vec3, _gamepad: Option) { self.set_axis_triple(world, value); } } diff --git a/src/user_input/mouse.rs b/src/user_input/mouse.rs index 2e2cec10..7fa7cf0f 100644 --- a/src/user_input/mouse.rs +++ b/src/user_input/mouse.rs @@ -1,9 +1,13 @@ //! Mouse inputs -use bevy::input::mouse::{MouseButton, MouseButtonInput, MouseMotion, MouseWheel}; +use bevy::ecs::system::lifetimeless::SRes; +use bevy::ecs::system::StaticSystemParam; +use bevy::input::mouse::{ + AccumulatedMouseMotion, AccumulatedMouseScroll, MouseButtonInput, MouseMotion, MouseWheel, +}; use bevy::input::{ButtonInput, ButtonState}; use bevy::math::FloatOrd; -use bevy::prelude::{Entity, Events, Gamepad, Reflect, Res, ResMut, Resource, Vec2, World}; +use bevy::prelude::{Entity, Events, MouseButton, Reflect, ResMut, Vec2, World}; use leafwing_input_manager_macros::serde_typetag; use serde::{Deserialize, Serialize}; use std::hash::{Hash, Hasher}; @@ -34,11 +38,11 @@ impl UserInput for MouseButton { } impl UpdatableInput for MouseButton { - type SourceData = ButtonInput; + type SourceData = SRes>; fn compute( mut central_input_store: ResMut, - source_data: Res, + source_data: StaticSystemParam, ) { for key in source_data.get_pressed() { central_input_store.update_buttonlike(*key, true); @@ -54,7 +58,7 @@ impl UpdatableInput for MouseButton { impl Buttonlike for MouseButton { /// Checks if the specified button is currently pressed down. #[inline] - fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool { + fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool { input_store.pressed(self) } @@ -92,12 +96,12 @@ impl Buttonlike for MouseButton { /// ```rust /// use bevy::prelude::*; /// use bevy::input::InputPlugin; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// use leafwing_input_manager::plugin::CentralInputStorePlugin; /// use leafwing_input_manager::prelude::*; /// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; /// /// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// app.add_plugins((InputPlugin, CentralInputStorePlugin)); /// /// // Positive Y-axis movement /// let input = MouseMoveDirection::UP; @@ -184,7 +188,7 @@ impl Buttonlike for MouseMoveDirection { /// Checks if there is any recent mouse movement along the specified direction. #[must_use] #[inline] - fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool { + fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool { let mouse_movement = input_store.pair(&MouseMove::default()); self.direction.is_active(mouse_movement, self.threshold) } @@ -224,12 +228,12 @@ impl Hash for MouseMoveDirection { /// ```rust /// use bevy::prelude::*; /// use bevy::input::InputPlugin; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// use leafwing_input_manager::plugin::CentralInputStorePlugin; /// use leafwing_input_manager::prelude::*; /// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; /// /// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// app.add_plugins((InputPlugin, CentralInputStorePlugin)); /// /// // Y-axis movement /// let input = MouseMoveAxis::Y; @@ -296,7 +300,7 @@ impl Axislike for MouseMoveAxis { /// after processing by the associated processors. #[must_use] #[inline] - fn value(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> f32 { + fn value(&self, input_store: &CentralInputStore, _gamepad: Entity) -> f32 { let movement = input_store.pair(&MouseMove::default()); let value = self.axis.get_value(movement); self.processors @@ -349,12 +353,12 @@ impl WithAxisProcessingPipelineExt for MouseMoveAxis { /// ```rust /// use bevy::prelude::*; /// use bevy::input::InputPlugin; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// use leafwing_input_manager::plugin::CentralInputStorePlugin; /// use leafwing_input_manager::prelude::*; /// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; /// /// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// app.add_plugins((InputPlugin, CentralInputStorePlugin)); /// /// let input = MouseMove::default(); /// @@ -375,13 +379,13 @@ pub struct MouseMove { } impl UpdatableInput for MouseMove { - type SourceData = AccumulatedMouseMovement; + type SourceData = SRes; fn compute( mut central_input_store: ResMut, - source_data: Res, + source_data: StaticSystemParam, ) { - central_input_store.update_dualaxislike(Self::default(), source_data.0); + central_input_store.update_dualaxislike(Self::default(), source_data.delta); } } @@ -409,7 +413,7 @@ impl DualAxislike for MouseMove { /// Retrieves the mouse displacement after processing by the associated processors. #[must_use] #[inline] - fn axis_pair(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> Vec2 { + fn axis_pair(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Vec2 { let movement = input_store.pair(&MouseMove::default()); self.processors .iter() @@ -452,12 +456,12 @@ impl WithDualAxisProcessingPipelineExt for MouseMove { /// ```rust /// use bevy::prelude::*; /// use bevy::input::InputPlugin; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// use leafwing_input_manager::plugin::CentralInputStorePlugin; /// use leafwing_input_manager::prelude::*; /// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; /// /// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// app.add_plugins((InputPlugin, CentralInputStorePlugin)); /// /// // Positive Y-axis scrolling /// let input = MouseScrollDirection::UP; @@ -544,7 +548,7 @@ impl Buttonlike for MouseScrollDirection { /// Checks if there is any recent mouse wheel movement along the specified direction. #[must_use] #[inline] - fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool { + fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool { let movement = input_store.pair(&MouseScroll::default()); self.direction.is_active(movement, self.threshold) } @@ -591,12 +595,12 @@ impl Hash for MouseScrollDirection { /// ```rust /// use bevy::prelude::*; /// use bevy::input::InputPlugin; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// use leafwing_input_manager::plugin::CentralInputStorePlugin; /// use leafwing_input_manager::prelude::*; /// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; /// /// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// app.add_plugins((InputPlugin, CentralInputStorePlugin)); /// /// // Y-axis movement /// let input = MouseScrollAxis::Y; @@ -663,7 +667,7 @@ impl Axislike for MouseScrollAxis { /// after processing by the associated processors. #[must_use] #[inline] - fn value(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> f32 { + fn value(&self, input_store: &CentralInputStore, _gamepad: Entity) -> f32 { let movement = input_store.pair(&MouseScroll::default()); let value = self.axis.get_value(movement); self.processors @@ -728,12 +732,12 @@ impl WithAxisProcessingPipelineExt for MouseScrollAxis { /// ```rust /// use bevy::prelude::*; /// use bevy::input::InputPlugin; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// use leafwing_input_manager::plugin::CentralInputStorePlugin; /// use leafwing_input_manager::prelude::*; /// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; /// /// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// app.add_plugins((InputPlugin, CentralInputStorePlugin)); /// /// let input = MouseScroll::default(); /// @@ -754,13 +758,13 @@ pub struct MouseScroll { } impl UpdatableInput for MouseScroll { - type SourceData = AccumulatedMouseScroll; + type SourceData = SRes; fn compute( mut central_input_store: ResMut, - source_data: Res, + source_data: StaticSystemParam, ) { - central_input_store.update_dualaxislike(Self::default(), source_data.0); + central_input_store.update_dualaxislike(Self::default(), source_data.delta); } } @@ -788,7 +792,7 @@ impl DualAxislike for MouseScroll { /// Retrieves the mouse scroll movement on both axes after processing by the associated processors. #[must_use] #[inline] - fn axis_pair(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> Vec2 { + fn axis_pair(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Vec2 { let movement = input_store.pair(&MouseScroll::default()); self.processors .iter() @@ -832,69 +836,17 @@ impl WithDualAxisProcessingPipelineExt for MouseScroll { } } -/// A resource that records the accumulated mouse movement for the frame. -/// -/// These values are computed by summing the [`MouseMotion`] events. -/// -/// This resource is automatically added by [`InputManagerPlugin`](crate::plugin::InputManagerPlugin). -/// Its value is updated during [`InputManagerSystem::Update`](crate::plugin::InputManagerSystem::Update). -#[derive(Debug, Default, Resource, Reflect, Serialize, Deserialize, Clone, PartialEq)] -pub struct AccumulatedMouseMovement(pub Vec2); - -impl AccumulatedMouseMovement { - /// Resets the accumulated mouse movement to zero. - #[inline] - pub fn reset(&mut self) { - self.0 = Vec2::ZERO; - } - - /// Accumulates the specified mouse movement. - #[inline] - pub fn accumulate(&mut self, event: &MouseMotion) { - self.0 += event.delta; - } -} - -/// A resource that records the accumulated mouse wheel (scrolling) movement for the frame. -/// -/// These values are computed by summing the [`MouseWheel`] events. -/// -/// This resource is automatically added by [`InputManagerPlugin`](crate::plugin::InputManagerPlugin). -/// Its value is updated during [`InputManagerSystem::Update`](crate::plugin::InputManagerSystem::Update). -#[derive(Debug, Default, Resource, Reflect, Serialize, Deserialize, Clone, PartialEq)] -pub struct AccumulatedMouseScroll(pub Vec2); - -impl AccumulatedMouseScroll { - /// Resets the accumulated mouse scroll to zero. - #[inline] - pub fn reset(&mut self) { - self.0 = Vec2::ZERO; - } - - /// Accumulates the specified mouse wheel movement. - /// - /// # Warning - /// - /// This ignores the mouse scroll unit: all values are treated as equal. - /// All scrolling, no matter what window it is on, is added to the same total. - #[inline] - pub fn accumulate(&mut self, event: &MouseWheel) { - self.0.x += event.x; - self.0.y += event.y; - } -} - #[cfg(test)] mod tests { use super::*; - use crate::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; + use crate::plugin::CentralInputStorePlugin; use bevy::input::InputPlugin; use bevy::prelude::*; fn test_app() -> App { let mut app = App::new(); app.add_plugins(InputPlugin) - .add_plugins((AccumulatorPlugin, CentralInputStorePlugin)); + .add_plugins(CentralInputStorePlugin); app } @@ -912,10 +864,9 @@ mod tests { // No inputs let mut app = test_app(); app.update(); + let gamepad = app.world_mut().spawn(()).id(); let inputs = app.world().resource::(); - let gamepad = Gamepad::new(0); - assert!(!left.pressed(inputs, gamepad)); assert!(!middle.pressed(inputs, gamepad)); assert!(!right.pressed(inputs, gamepad)); @@ -965,10 +916,9 @@ mod tests { // No inputs let mut app = test_app(); app.update(); + let gamepad = app.world_mut().spawn(()).id(); let inputs = app.world().resource::(); - let gamepad = Gamepad::new(0); - assert!(!mouse_move_up.pressed(inputs, gamepad)); assert_eq!(mouse_move_y.value(inputs, gamepad), 0.0); assert_eq!(mouse_move.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.0)); @@ -1042,10 +992,9 @@ mod tests { // No inputs let mut app = test_app(); app.update(); + let gamepad = app.world_mut().spawn(()).id(); let inputs = app.world().resource::(); - let gamepad = Gamepad::new(0); - assert!(!mouse_scroll_up.pressed(inputs, gamepad)); assert_eq!(mouse_scroll_y.value(inputs, gamepad), 0.0); assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.0)); @@ -1107,14 +1056,14 @@ mod tests { } // The haven't been processed yet - let accumulated_mouse_movement = app.world().resource::(); - assert_eq!(accumulated_mouse_movement.0, Vec2::new(0.0, 0.0)); + let accumulated_mouse_movement = app.world().resource::(); + assert_eq!(accumulated_mouse_movement.delta, Vec2::new(0.0, 0.0)); app.update(); // Now the events should be processed - let accumulated_mouse_movement = app.world().resource::(); - assert_eq!(accumulated_mouse_movement.0, Vec2::new(0.0, 5.0)); + let accumulated_mouse_movement = app.world().resource::(); + assert_eq!(accumulated_mouse_movement.delta, Vec2::new(0.0, 5.0)); let inputs = app.world().resource::(); assert_eq!(inputs.pair(&MouseMove::default()), Vec2::new(0.0, 5.0)); diff --git a/src/user_input/testing_utils.rs b/src/user_input/testing_utils.rs index 7a22c9f9..5975165a 100644 --- a/src/user_input/testing_utils.rs +++ b/src/user_input/testing_utils.rs @@ -1,9 +1,7 @@ //! Utilities for testing user input. use bevy::{ - app::App, - math::Vec2, - prelude::{Gamepad, Gamepads, World}, + app::App, ecs::system::SystemState, math::Vec2, prelude::{Entity, Gamepad, Query, With, World} }; use super::{updating::CentralInputStore, Axislike, Buttonlike, DualAxislike}; @@ -12,8 +10,8 @@ use super::{updating::CentralInputStore, Axislike, Buttonlike, DualAxislike}; use crate::user_input::gamepad::find_gamepad; #[cfg(not(feature = "gamepad"))] -fn find_gamepad(_gamepads: &Gamepads) -> Gamepad { - Gamepad::new(0) +fn find_gamepad(_: Option>>) -> Entity { + Entity::PLACEHOLDER } /// A trait used to quickly fetch the value of a given [`UserInput`](crate::user_input::UserInput). @@ -32,31 +30,28 @@ pub trait FetchUserInput { impl FetchUserInput for World { fn read_pressed(&mut self, input: impl Buttonlike) -> bool { + let mut query_state = SystemState::>>::new(self); + let query = query_state.get(self); + let gamepad = find_gamepad(Some(query)); let input_store = self.resource::(); - let gamepad = match self.get_resource::() { - Some(gamepads) => find_gamepad(gamepads), - None => Gamepad::new(0), - }; input.pressed(input_store, gamepad) } fn read_axis_value(&mut self, input: impl Axislike) -> f32 { + let mut query_state = SystemState::>>::new(self); + let query = query_state.get(self); + let gamepad = find_gamepad(Some(query)); let input_store = self.resource::(); - let gamepad = match self.get_resource::() { - Some(gamepads) => find_gamepad(gamepads), - None => Gamepad::new(0), - }; input.value(input_store, gamepad) } fn read_dual_axis_values(&mut self, input: impl DualAxislike) -> Vec2 { + let mut query_state = SystemState::>>::new(self); + let query = query_state.get(self); + let gamepad = find_gamepad(Some(query)); let input_store = self.resource::(); - let gamepad = match self.get_resource::() { - Some(gamepads) => find_gamepad(gamepads), - None => Gamepad::new(0), - }; input.axis_pair(input_store, gamepad) } diff --git a/src/user_input/trait_reflection.rs b/src/user_input/trait_reflection.rs index fce961e6..e51cd81e 100644 --- a/src/user_input/trait_reflection.rs +++ b/src/user_input/trait_reflection.rs @@ -2,22 +2,20 @@ //! //! Note that [bevy #3392](https://github.com/bevyengine/bevy/issues/3392) would eliminate the need for this. -use std::{ - any::{Any, TypeId}, - fmt::{Debug, Formatter}, - hash::{Hash, Hasher}, -}; +use std::any::Any; use bevy::reflect::{ - utility::{reflect_hasher, GenericTypePathCell, NonGenericTypeInfoCell}, - FromReflect, FromType, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectFromPtr, - ReflectKind, ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize, TypeInfo, TypePath, - TypeRegistration, Typed, ValueInfo, + utility::{GenericTypePathCell, NonGenericTypeInfoCell}, + FromReflect, FromType, GetTypeRegistration, OpaqueInfo, Reflect, ReflectDeserialize, + ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize, TypeInfo, + TypePath, TypeRegistration, Typed, }; use dyn_eq::DynEq; mod buttonlike { + use bevy::reflect::PartialReflect; + use super::*; use crate::user_input::Buttonlike; @@ -26,38 +24,36 @@ mod buttonlike { dyn_eq::eq_trait_object!(Buttonlike); dyn_hash::hash_trait_object!(Buttonlike); - impl Reflect for Box { + impl PartialReflect for Box { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(Self::type_info()) } - fn into_any(self: Box) -> Box { - self - } - - fn as_any(&self) -> &dyn Any { - self + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Opaque } - fn as_any_mut(&mut self) -> &mut dyn Any { - self + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) } - fn into_reflect(self: Box) -> Box { - self + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) } - fn as_reflect(&self) -> &dyn Reflect { - self + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) } - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self + fn clone_value(&self) -> Box { + Box::new(self.clone()) } - fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), bevy::reflect::ApplyError> { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { + fn try_apply( + &mut self, + value: &dyn PartialReflect, + ) -> Result<(), bevy::reflect::ApplyError> { + if let Some(value) = value.try_downcast_ref::() { *self = value.clone(); Ok(()) } else { @@ -76,60 +72,66 @@ mod buttonlike { } } - fn apply(&mut self, value: &dyn Reflect) { - Self::try_apply(self, value).unwrap(); + fn into_partial_reflect(self: Box) -> Box { + self } - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self } - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Value + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self } - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Value(self) + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) } - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Value(self) + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) } - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Value(self) + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) } + } - fn clone_value(&self) -> Box { - Box::new(self.clone()) + impl Reflect for Box { + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self } - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - let type_id = TypeId::of::(); - Hash::hash(&type_id, &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) + fn as_reflect(&self) -> &dyn Reflect { + self } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - value - .as_any() - .downcast_ref::() - .map(|value| self.dyn_eq(value)) - .or(Some(false)) + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self } - fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) } } impl Typed for Box { fn type_info() -> &'static TypeInfo { static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::())) + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) } } @@ -172,13 +174,15 @@ mod buttonlike { } impl FromReflect for Box { - fn from_reflect(reflect: &dyn Reflect) -> Option { - Some(reflect.as_any().downcast_ref::()?.clone()) + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + Some(reflect.try_downcast_ref::()?.clone()) } } } mod axislike { + use bevy::reflect::PartialReflect; + use super::*; use crate::user_input::Axislike; @@ -187,38 +191,36 @@ mod axislike { dyn_eq::eq_trait_object!(Axislike); dyn_hash::hash_trait_object!(Axislike); - impl Reflect for Box { + impl PartialReflect for Box { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(Self::type_info()) } - fn into_any(self: Box) -> Box { - self - } - - fn as_any(&self) -> &dyn Any { - self + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Opaque } - fn as_any_mut(&mut self) -> &mut dyn Any { - self + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) } - fn into_reflect(self: Box) -> Box { - self + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) } - fn as_reflect(&self) -> &dyn Reflect { - self + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) } - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self + fn clone_value(&self) -> Box { + Box::new(self.clone()) } - fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), bevy::reflect::ApplyError> { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { + fn try_apply( + &mut self, + value: &dyn PartialReflect, + ) -> Result<(), bevy::reflect::ApplyError> { + if let Some(value) = value.try_downcast_ref::() { *self = value.clone(); Ok(()) } else { @@ -237,60 +239,66 @@ mod axislike { } } - fn apply(&mut self, value: &dyn Reflect) { - Self::try_apply(self, value).unwrap(); + fn into_partial_reflect(self: Box) -> Box { + self } - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self } - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Value + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self } - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Value(self) + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) } - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Value(self) + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) } - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Value(self) + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) } + } - fn clone_value(&self) -> Box { - Box::new(self.clone()) + impl Reflect for Box { + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self } - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - let type_id = TypeId::of::(); - Hash::hash(&type_id, &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) + fn as_reflect(&self) -> &dyn Reflect { + self } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - value - .as_any() - .downcast_ref::() - .map(|value| self.dyn_eq(value)) - .or(Some(false)) + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self } - fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) } } impl Typed for Box { fn type_info() -> &'static TypeInfo { static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::())) + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) } } @@ -333,13 +341,15 @@ mod axislike { } impl FromReflect for Box { - fn from_reflect(reflect: &dyn Reflect) -> Option { - Some(reflect.as_any().downcast_ref::()?.clone()) + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + Some(reflect.try_downcast_ref::()?.clone()) } } } mod dualaxislike { + use bevy::reflect::{OpaqueInfo, PartialReflect}; + use super::*; use crate::user_input::DualAxislike; @@ -348,38 +358,16 @@ mod dualaxislike { dyn_eq::eq_trait_object!(DualAxislike); dyn_hash::hash_trait_object!(DualAxislike); - impl Reflect for Box { + impl PartialReflect for Box { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(Self::type_info()) } - fn into_any(self: Box) -> Box { - self - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn into_reflect(self: Box) -> Box { - self - } - - fn as_reflect(&self) -> &dyn Reflect { - self - } - - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } - - fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), bevy::reflect::ApplyError> { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { + fn try_apply( + &mut self, + value: &dyn PartialReflect, + ) -> Result<(), bevy::reflect::ApplyError> { + if let Some(value) = value.try_downcast_ref::() { *self = value.clone(); Ok(()) } else { @@ -398,60 +386,86 @@ mod dualaxislike { } } - fn apply(&mut self, value: &dyn Reflect) { - Self::try_apply(self, value).unwrap(); - } - - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Value + ReflectKind::Opaque } fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Value(self) + ReflectRef::Opaque(self) } fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Value(self) + ReflectMut::Opaque(self) } fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Value(self) + ReflectOwned::Opaque(self) } - fn clone_value(&self) -> Box { + fn clone_value(&self) -> Box { Box::new(self.clone()) } - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - let type_id = TypeId::of::(); - Hash::hash(&type_id, &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + } + + impl Reflect for Box { + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self + } + + fn as_reflect(&self) -> &dyn Reflect { + self } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - value - .as_any() - .downcast_ref::() - .map(|value| self.dyn_eq(value)) - .or(Some(false)) + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self } - fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) } } impl Typed for Box { fn type_info() -> &'static TypeInfo { static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::())) + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) } } @@ -494,13 +508,15 @@ mod dualaxislike { } impl FromReflect for Box { - fn from_reflect(reflect: &dyn Reflect) -> Option { - Some(reflect.as_any().downcast_ref::()?.clone()) + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + Some(reflect.try_downcast_ref::()?.clone()) } } } mod tripleaxislike { + use bevy::reflect::{OpaqueInfo, PartialReflect}; + use super::*; use crate::user_input::TripleAxislike; @@ -509,38 +525,16 @@ mod tripleaxislike { dyn_eq::eq_trait_object!(TripleAxislike); dyn_hash::hash_trait_object!(TripleAxislike); - impl Reflect for Box { + impl PartialReflect for Box { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(Self::type_info()) } - fn into_any(self: Box) -> Box { - self - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn into_reflect(self: Box) -> Box { - self - } - - fn as_reflect(&self) -> &dyn Reflect { - self - } - - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } - - fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), bevy::reflect::ApplyError> { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { + fn try_apply( + &mut self, + value: &dyn PartialReflect, + ) -> Result<(), bevy::reflect::ApplyError> { + if let Some(value) = value.try_downcast_ref::() { *self = value.clone(); Ok(()) } else { @@ -559,60 +553,86 @@ mod tripleaxislike { } } - fn apply(&mut self, value: &dyn Reflect) { - Self::try_apply(self, value).unwrap(); - } - - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Value + ReflectKind::Opaque } fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Value(self) + ReflectRef::Opaque(self) } fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Value(self) + ReflectMut::Opaque(self) } fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Value(self) + ReflectOwned::Opaque(self) } - fn clone_value(&self) -> Box { + fn clone_value(&self) -> Box { Box::new(self.clone()) } - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - let type_id = TypeId::of::(); - Hash::hash(&type_id, &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + } + + impl Reflect for Box { + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self + } + + fn as_reflect(&self) -> &dyn Reflect { + self } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - value - .as_any() - .downcast_ref::() - .map(|value| self.dyn_eq(value)) - .or(Some(false)) + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self } - fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) } } impl Typed for Box { fn type_info() -> &'static TypeInfo { static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::())) + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) } } @@ -655,8 +675,8 @@ mod tripleaxislike { } impl FromReflect for Box { - fn from_reflect(reflect: &dyn Reflect) -> Option { - Some(reflect.as_any().downcast_ref::()?.clone()) + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + Some(reflect.try_downcast_ref::()?.clone()) } } } diff --git a/src/user_input/updating.rs b/src/user_input/updating.rs index 79de6482..56379200 100644 --- a/src/user_input/updating.rs +++ b/src/user_input/updating.rs @@ -5,8 +5,9 @@ use std::hash::Hash; use bevy::{ app::{App, PreUpdate}, + ecs::system::{StaticSystemParam, SystemParam}, math::{Vec2, Vec3}, - prelude::{IntoSystemConfigs, Res, ResMut, Resource}, + prelude::{IntoSystemConfigs, ResMut, Resource}, reflect::Reflect, utils::{HashMap, HashSet}, }; @@ -272,7 +273,7 @@ pub trait UpdatableInput: 'static { /// /// This type cannot be [`CentralInputStore`], as that would cause mutable aliasing and panic at runtime. // TODO: Ideally this should be a `SystemParam` for more flexibility. - type SourceData: Resource; + type SourceData: SystemParam; /// A system that updates the central store of user input based on the state of the world. /// @@ -281,7 +282,10 @@ pub trait UpdatableInput: 'static { /// # Warning /// /// This system should not be added manually: instead, call [`CentralInputStore::register_input_kind`]. - fn compute(central_input_store: ResMut, source_data: Res); + fn compute( + central_input_store: ResMut, + source_data: StaticSystemParam, + ); } #[cfg(test)] @@ -337,7 +341,7 @@ mod tests { dbg!(&mouse_button_input); world.insert_resource(mouse_button_input); - world.run_system_once(MouseButton::compute); + world.run_system_once(MouseButton::compute).unwrap(); let central_input_store = world.resource::(); dbg!(central_input_store); assert!(central_input_store.pressed(&MouseButton::Left)); diff --git a/src/user_input/virtual_axial.rs b/src/user_input/virtual_axial.rs index 330da4ec..86ece497 100644 --- a/src/user_input/virtual_axial.rs +++ b/src/user_input/virtual_axial.rs @@ -12,10 +12,10 @@ use crate::user_input::Buttonlike; use crate::InputControlKind; use bevy::math::{Vec2, Vec3}; #[cfg(feature = "gamepad")] -use bevy::prelude::GamepadButtonType; +use bevy::prelude::GamepadButton; #[cfg(feature = "keyboard")] use bevy::prelude::KeyCode; -use bevy::prelude::{Gamepad, Reflect, World}; +use bevy::prelude::{Entity, Reflect, World}; use leafwing_input_manager_macros::serde_typetag; use serde::{Deserialize, Serialize}; @@ -38,10 +38,10 @@ use serde::{Deserialize, Serialize}; /// use bevy::input::InputPlugin; /// use leafwing_input_manager::prelude::*; /// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// use leafwing_input_manager::plugin::CentralInputStorePlugin; /// /// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// app.add_plugins((InputPlugin, CentralInputStorePlugin)); /// /// // Define a virtual Y-axis using arrow "up" and "down" keys /// let axis = VirtualAxis::vertical_arrow_keys(); @@ -143,45 +143,45 @@ impl VirtualAxis { /// The [`VirtualAxis`] using the horizontal D-Pad button mappings. /// No processing is applied to raw data from the gamepad. /// - /// - [`GamepadButtonType::DPadLeft`] for negative direction. - /// - [`GamepadButtonType::DPadRight`] for positive direction. + /// - [`GamepadButton::DPadLeft`] for negative direction. + /// - [`GamepadButton::DPadRight`] for positive direction. #[cfg(feature = "gamepad")] #[inline] pub fn dpad_x() -> Self { - Self::new(GamepadButtonType::DPadLeft, GamepadButtonType::DPadRight) + Self::new(GamepadButton::DPadLeft, GamepadButton::DPadRight) } /// The [`VirtualAxis`] using the vertical D-Pad button mappings. /// No processing is applied to raw data from the gamepad. /// - /// - [`GamepadButtonType::DPadDown`] for negative direction. - /// - [`GamepadButtonType::DPadUp`] for positive direction. + /// - [`GamepadButton::DPadDown`] for negative direction. + /// - [`GamepadButton::DPadUp`] for positive direction. #[cfg(feature = "gamepad")] #[inline] pub fn dpad_y() -> Self { - Self::new(GamepadButtonType::DPadDown, GamepadButtonType::DPadUp) + Self::new(GamepadButton::DPadDown, GamepadButton::DPadUp) } /// The [`VirtualAxis`] using the horizontal action pad button mappings. /// No processing is applied to raw data from the gamepad. /// - /// - [`GamepadButtonType::West`] for negative direction. - /// - [`GamepadButtonType::East`] for positive direction. + /// - [`GamepadButton::West`] for negative direction. + /// - [`GamepadButton::East`] for positive direction. #[cfg(feature = "gamepad")] #[inline] pub fn action_pad_x() -> Self { - Self::new(GamepadButtonType::West, GamepadButtonType::East) + Self::new(GamepadButton::West, GamepadButton::East) } /// The [`VirtualAxis`] using the vertical action pad button mappings. /// No processing is applied to raw data from the gamepad. /// - /// - [`GamepadButtonType::South`] for negative direction. - /// - [`GamepadButtonType::North`] for positive direction. + /// - [`GamepadButton::South`] for negative direction. + /// - [`GamepadButton::North`] for positive direction. #[cfg(feature = "gamepad")] #[inline] pub fn action_pad_y() -> Self { - Self::new(GamepadButtonType::South, GamepadButtonType::North) + Self::new(GamepadButton::South, GamepadButton::North) } } @@ -204,7 +204,7 @@ impl Axislike for VirtualAxis { /// Retrieves the current value of this axis after processing by the associated processors. #[must_use] #[inline] - fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 { + fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 { let negative = f32::from(self.negative.pressed(input_store, gamepad)); let positive = f32::from(self.positive.pressed(input_store, gamepad)); let value = positive - negative; @@ -218,7 +218,7 @@ impl Axislike for VirtualAxis { /// If the value is negative, the negative button is pressed. /// If the value is positive, the positive button is pressed. /// If the value is zero, neither button is pressed. - fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option) { + fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option) { if value < 0.0 { self.negative.press_as_gamepad(world, gamepad); } else if value > 0.0 { @@ -273,10 +273,10 @@ impl WithAxisProcessingPipelineExt for VirtualAxis { /// use bevy::input::InputPlugin; /// use leafwing_input_manager::user_input::testing_utils::FetchUserInput; /// use leafwing_input_manager::prelude::*; -/// use leafwing_input_manager::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; +/// use leafwing_input_manager::plugin::CentralInputStorePlugin; /// /// let mut app = App::new(); -/// app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin)); +/// app.add_plugins((InputPlugin, CentralInputStorePlugin)); /// /// // Define a virtual D-pad using the WASD keys /// let input = VirtualDPad::wasd(); @@ -376,35 +376,35 @@ impl VirtualDPad { /// Creates a new [`VirtualDPad`] using the common D-Pad button mappings. /// - /// - [`GamepadButtonType::DPadUp`] for upward direction. - /// - [`GamepadButtonType::DPadDown`] for downward direction. - /// - [`GamepadButtonType::DPadLeft`] for leftward direction. - /// - [`GamepadButtonType::DPadRight`] for rightward direction. + /// - [`GamepadButton::DPadUp`] for upward direction. + /// - [`GamepadButton::DPadDown`] for downward direction. + /// - [`GamepadButton::DPadLeft`] for leftward direction. + /// - [`GamepadButton::DPadRight`] for rightward direction. #[cfg(feature = "gamepad")] #[inline] pub fn dpad() -> Self { Self::new( - GamepadButtonType::DPadUp, - GamepadButtonType::DPadDown, - GamepadButtonType::DPadLeft, - GamepadButtonType::DPadRight, + GamepadButton::DPadUp, + GamepadButton::DPadDown, + GamepadButton::DPadLeft, + GamepadButton::DPadRight, ) } /// Creates a new [`VirtualDPad`] using the common action pad button mappings. /// - /// - [`GamepadButtonType::North`] for upward direction. - /// - [`GamepadButtonType::South`] for downward direction. - /// - [`GamepadButtonType::West`] for leftward direction. - /// - [`GamepadButtonType::East`] for rightward direction. + /// - [`GamepadButton::North`] for upward direction. + /// - [`GamepadButton::South`] for downward direction. + /// - [`GamepadButton::West`] for leftward direction. + /// - [`GamepadButton::East`] for rightward direction. #[cfg(feature = "gamepad")] #[inline] pub fn action_pad() -> Self { Self::new( - GamepadButtonType::North, - GamepadButtonType::South, - GamepadButtonType::West, - GamepadButtonType::East, + GamepadButton::North, + GamepadButton::South, + GamepadButton::West, + GamepadButton::East, ) } } @@ -416,7 +416,7 @@ impl UserInput for VirtualDPad { InputControlKind::DualAxis } - /// Returns the four [`GamepadButtonType`]s used by this D-pad. + /// Returns the four [`GamepadButton`]s used by this D-pad. #[inline] fn decompose(&self) -> BasicInputs { BasicInputs::Composite(vec![ @@ -433,7 +433,7 @@ impl DualAxislike for VirtualDPad { /// Retrieves the current X and Y values of this D-pad after processing by the associated processors. #[must_use] #[inline] - fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec2 { + fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec2 { let up = f32::from(self.up.pressed(input_store, gamepad)); let down = f32::from(self.down.pressed(input_store, gamepad)); let left = f32::from(self.left.pressed(input_store, gamepad)); @@ -445,7 +445,7 @@ impl DualAxislike for VirtualDPad { } /// Presses the corresponding buttons based on the quadrant of the given value. - fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option) { + fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option) { if value.x < 0.0 { self.left.press_as_gamepad(world, gamepad); } else if value.x > 0.0 { @@ -563,7 +563,7 @@ impl TripleAxislike for VirtualDPad3D { /// Retrieves the current X, Y, and Z values of this D-pad. #[must_use] #[inline] - fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec3 { + fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec3 { let up = f32::from(self.up.pressed(input_store, gamepad)); let down = f32::from(self.down.pressed(input_store, gamepad)); let left = f32::from(self.left.pressed(input_store, gamepad)); @@ -574,7 +574,7 @@ impl TripleAxislike for VirtualDPad3D { } /// Presses the corresponding buttons based on the octant of the given value. - fn set_axis_triple_as_gamepad(&self, world: &mut World, value: Vec3, gamepad: Option) { + fn set_axis_triple_as_gamepad(&self, world: &mut World, value: Vec3, gamepad: Option) { if value.x < 0.0 { self.left.press_as_gamepad(world, gamepad); } else if value.x > 0.0 { @@ -601,14 +601,14 @@ mod tests { use bevy::input::InputPlugin; use bevy::prelude::*; - use crate::plugin::{AccumulatorPlugin, CentralInputStorePlugin}; + use crate::plugin::CentralInputStorePlugin; use crate::prelude::updating::CentralInputStore; use crate::prelude::*; fn test_app() -> App { let mut app = App::new(); app.add_plugins(InputPlugin) - .add_plugins((AccumulatorPlugin, CentralInputStorePlugin)); + .add_plugins(CentralInputStorePlugin); app } @@ -630,7 +630,7 @@ mod tests { app.update(); let inputs = app.world().resource::(); - let gamepad = Gamepad::new(0); + let gamepad = Entity::PLACEHOLDER; assert_eq!(x.value(inputs, gamepad), 0.0); assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::ZERO); diff --git a/tests/action_diffs.rs b/tests/action_diffs.rs index d8e91f09..717d1876 100644 --- a/tests/action_diffs.rs +++ b/tests/action_diffs.rs @@ -64,7 +64,7 @@ fn send_action_diff(app: &mut App, action_diff: ActionDiffEvent) { #[track_caller] fn assert_has_no_action_diffs(app: &mut App) { let action_diff_events = get_events::>(app); - let action_diff_event_reader = &mut action_diff_events.get_reader(); + let action_diff_event_reader = &mut action_diff_events.get_cursor(); if let Some(action_diff) = action_diff_event_reader.read(action_diff_events).next() { panic!( "Expected no `ActionDiff` variants. Received: {:?}", @@ -76,7 +76,7 @@ fn assert_has_no_action_diffs(app: &mut App) { #[track_caller] fn assert_action_diff_created(app: &mut App, predicate: impl Fn(&ActionDiffEvent)) { let mut action_diff_events = get_events_mut::>(app); - let action_diff_event_reader = &mut action_diff_events.get_reader(); + let action_diff_event_reader = &mut action_diff_events.get_cursor(); assert!(action_diff_event_reader.len(action_diff_events.as_ref()) < 2); match action_diff_event_reader .read(action_diff_events.as_ref()) diff --git a/tests/gamepad_axis.rs b/tests/gamepad_axis.rs index 9ab828f7..3b36f2ac 100644 --- a/tests/gamepad_axis.rs +++ b/tests/gamepad_axis.rs @@ -1,6 +1,8 @@ #![cfg(feature = "gamepad")] -use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo}; +use bevy::input::gamepad::{ + GamepadConnection, GamepadConnectionEvent, GamepadInfo, RawGamepadEvent, +}; use bevy::input::InputPlugin; use bevy::prelude::*; use leafwing_input_manager::input_processing::{ @@ -35,14 +37,20 @@ fn test_app() -> App { .init_resource::>(); // WARNING: you MUST register your gamepad during tests, or all gamepad input mocking will fail - let mut gamepad_events = app.world_mut().resource_mut::>(); - gamepad_events.send(GamepadEvent::Connection(GamepadConnectionEvent { + let gamepad_info = GamepadInfo { + name: "TestController".into(), + vendor_id: None, + product_id: None, + }; + let gamepad = app.world_mut().spawn(()).id(); + let mut gamepad_connection_events = app + .world_mut() + .resource_mut::>(); + gamepad_connection_events.send(GamepadConnectionEvent { // This MUST be consistent with any other mocked events - gamepad: Gamepad { id: 1 }, - connection: GamepadConnection::Connected(GamepadInfo { - name: "TestController".into(), - }), - })); + gamepad, + connection: GamepadConnection::Connected(gamepad_info), + }); // Ensure that the gamepad is picked up by the appropriate system app.update(); @@ -56,13 +64,13 @@ fn test_app() -> App { #[ignore = "Broken upstream; tracked in https://github.com/Leafwing-Studios/leafwing-input-manager/issues/419"] fn gamepad_single_axis_mocking() { let mut app = test_app(); - let mut events = app.world_mut().resource_mut::>(); + let mut events = app.world_mut().resource_mut::>(); assert_eq!(events.drain().count(), 0); let input = GamepadControlAxis::LEFT_X; input.set_value(app.world_mut(), -1.0); - let mut events = app.world_mut().resource_mut::>(); + let mut events = app.world_mut().resource_mut::>(); assert_eq!(events.drain().count(), 1); } @@ -70,13 +78,13 @@ fn gamepad_single_axis_mocking() { #[ignore = "Broken upstream; tracked in https://github.com/Leafwing-Studios/leafwing-input-manager/issues/419"] fn gamepad_dual_axis_mocking() { let mut app = test_app(); - let mut events = app.world_mut().resource_mut::>(); + let mut events = app.world_mut().resource_mut::>(); assert_eq!(events.drain().count(), 0); let input = GamepadStick::LEFT; input.set_axis_pair(app.world_mut(), Vec2::new(1.0, 0.0)); - let mut events = app.world_mut().resource_mut::>(); + let mut events = app.world_mut().resource_mut::>(); // Dual axis events are split out assert_eq!(events.drain().count(), 2); } @@ -299,7 +307,7 @@ fn gamepad_virtual_dpad() { InputMap::default().with_dual_axis(AxislikeTestAction::XY, VirtualDPad::dpad()), ); - GamepadButtonType::DPadLeft.press(app.world_mut()); + GamepadButton::DPadLeft.press(app.world_mut()); app.update(); let action_state = app.world().resource::>(); diff --git a/tests/multiple_gamepads.rs b/tests/multiple_gamepads.rs index 16042b3e..d27a516a 100644 --- a/tests/multiple_gamepads.rs +++ b/tests/multiple_gamepads.rs @@ -1,6 +1,8 @@ #![cfg(feature = "gamepad")] -use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo}; +use bevy::input::gamepad::{ + GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo, RawGamepadEvent, +}; use bevy::input::InputPlugin; use bevy::prelude::*; use leafwing_input_manager::prelude::*; @@ -15,19 +17,26 @@ fn create_test_app() -> App { app.add_plugins(MinimalPlugins).add_plugins(InputPlugin); app.add_plugins(InputManagerPlugin::::default()); + let gamepad_1 = app.world_mut().spawn(()).id(); + let gamepad_2 = app.world_mut().spawn(()).id(); + let mut gamepad_events = app.world_mut().resource_mut::>(); gamepad_events.send(GamepadEvent::Connection(GamepadConnectionEvent { // Must be consistent with mocked events - gamepad: Gamepad { id: 1 }, + gamepad: gamepad_1, connection: GamepadConnection::Connected(GamepadInfo { name: "FirstController".into(), + vendor_id: None, + product_id: None, }), })); gamepad_events.send(GamepadEvent::Connection(GamepadConnectionEvent { // Must be consistent with mocked events - gamepad: Gamepad { id: 2 }, + gamepad: gamepad_2, connection: GamepadConnection::Connected(GamepadInfo { name: "SecondController".into(), + vendor_id: None, + product_id: None, }), })); @@ -39,12 +48,12 @@ fn create_test_app() -> App { app } -fn jump_button_press_event(gamepad: Gamepad) -> GamepadEvent { - use bevy::input::gamepad::GamepadButtonChangedEvent; +fn jump_button_press_event(gamepad: Entity) -> RawGamepadEvent { + use bevy::input::gamepad::RawGamepadButtonChangedEvent; - GamepadEvent::Button(GamepadButtonChangedEvent::new( + RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new( gamepad, - GamepadButtonType::South, + GamepadButton::South, 1.0, )) } @@ -53,16 +62,33 @@ fn jump_button_press_event(gamepad: Gamepad) -> GamepadEvent { fn accepts_preferred_gamepad() { let mut app = create_test_app(); - const PREFERRED_GAMEPAD: Gamepad = Gamepad { id: 2 }; + let gamepad_info = GamepadInfo { + name: "Preferred gamepad".to_owned(), + vendor_id: None, + product_id: None, + }; + let preferred_gamepad = app.world_mut().spawn(()).id(); + let mut gamepad_connection_events = app + .world_mut() + .resource_mut::>(); + gamepad_connection_events.send(GamepadConnectionEvent { + // This MUST be consistent with any other mocked events + gamepad: preferred_gamepad, + connection: GamepadConnection::Connected(gamepad_info), + }); + // Ensure that the gamepad is picked up by the appropriate system + app.update(); + // Ensure that the connection event is flushed through + app.update(); - let mut input_map = InputMap::new([(MyAction::Jump, GamepadButtonType::South)]); - input_map.set_gamepad(PREFERRED_GAMEPAD); + let mut input_map = InputMap::new([(MyAction::Jump, GamepadButton::South)]); + input_map.set_gamepad(preferred_gamepad); app.insert_resource(input_map); app.init_resource::>(); // When we press the Jump button... - let mut events = app.world_mut().resource_mut::>(); - events.send(jump_button_press_event(PREFERRED_GAMEPAD)); + let mut events = app.world_mut().resource_mut::>(); + events.send(jump_button_press_event(preferred_gamepad)); app.update(); // ... We should receive a Jump action! @@ -74,17 +100,17 @@ fn accepts_preferred_gamepad() { fn filters_out_other_gamepads() { let mut app = create_test_app(); - const PREFERRED_GAMEPAD: Gamepad = Gamepad { id: 2 }; - const OTHER_GAMEPAD: Gamepad = Gamepad { id: 1 }; + let preferred_gamepad = app.world_mut().spawn(()).id(); + let other_gamepad = app.world_mut().spawn(()).id(); - let mut input_map = InputMap::new([(MyAction::Jump, GamepadButtonType::South)]); - input_map.set_gamepad(PREFERRED_GAMEPAD); + let mut input_map = InputMap::new([(MyAction::Jump, GamepadButton::South)]); + input_map.set_gamepad(preferred_gamepad); app.insert_resource(input_map); app.init_resource::>(); // When we press the Jump button... - let mut events = app.world_mut().resource_mut::>(); - events.send(jump_button_press_event(OTHER_GAMEPAD)); + let mut events = app.world_mut().resource_mut::>(); + events.send(jump_button_press_event(other_gamepad)); app.update(); // ... We should receive a Jump action!