diff --git a/Cargo.toml b/Cargo.toml index 1989858a..70940c8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,10 +45,10 @@ 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", default-features = false, features = [ "serialize", ] } -bevy_egui = { version = "0.30", optional = true } +bevy_egui = { version = "0.31", optional = true } derive_more = { version = "0.99", default-features = false, features = [ "display", @@ -62,7 +62,7 @@ dyn-eq = "0.1" dyn-hash = "0.2" [dev-dependencies] -bevy = { version = "0.14.0-rc.3", default-features = false, features = [ +bevy = { version = "0.15.0", default-features = false, features = [ "bevy_asset", "bevy_sprite", "bevy_text", @@ -70,6 +70,7 @@ bevy = { version = "0.14.0-rc.3", default-features = false, features = [ "bevy_render", "bevy_core_pipeline", "bevy_state", + "bevy_window", "x11", # TODO Remove these before release. See https://github.com/bevyengine/bevy/issues/13728 "ktx2", diff --git a/README.md b/README.md index 614acc98..2ad7bd93 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ and a single input can result in multiple actions being triggered, which can be - Look up your current input state in a single `ActionState` component - That pesky maximum of 16 system parameters got you down? Say goodbye to that input handling mega-system - Ergonomic insertion API that seamlessly blends multiple input types for you - - Can't decide between `input_map.insert(Action::Jump, KeyCode::Space)` and `input_map.insert(Action::Jump, GamepadButtonType::South)`? Have both! + - Can't decide between `input_map.insert(Action::Jump, KeyCode::Space)` and `input_map.insert(Action::Jump, GamepadButton::South)`? Have both! - Full support for arbitrary button combinations: chord your heart out. - `input_map.insert(Action::Console, ButtonlikeChord::new([KeyCode::ControlLeft, KeyCode::Shift, KeyCode::KeyC]))` - Sophisticated input disambiguation with the `ClashStrategy` enum: stop triggering individual buttons when you meant to press a chord! diff --git a/examples/axis_inputs.rs b/examples/axis_inputs.rs index 69556065..fad8c81a 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. @@ -57,7 +57,7 @@ fn move_player(query: Query<&ActionState, With>) { if action_state.pressed(&Action::Throttle) { // Note that some gamepad buttons are also tied to axes, so even though we used a - // GamepadButtonType::RightTrigger2 binding to trigger the throttle action, + // GamepadButton::RightTrigger2 binding to trigger the throttle action, // we can get a variable value here if you have a variable right trigger on your gamepad. // // If you don't have a variable trigger, this will just return 0.0 when not pressed and 1.0 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..e921b3a8 100644 --- a/examples/mouse_motion.rs +++ b/examples/mouse_motion.rs @@ -22,13 +22,13 @@ 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 { - transform: Transform::from_scale(Vec3::new(100., 100., 1.)), - ..default() - }); + commands.spawn(( + Sprite::default(), + Transform::from_scale(Vec3::new(100., 100., 1.)), + )); } fn pan_camera(mut query: Query<(&mut Transform, &ActionState), With>) { diff --git a/examples/mouse_wheel.rs b/examples/mouse_wheel.rs index 9d457071..c861be97 100644 --- a/examples/mouse_wheel.rs +++ b/examples/mouse_wheel.rs @@ -33,13 +33,13 @@ 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 { - transform: Transform::from_scale(Vec3::new(100., 100., 1.)), - ..default() - }); + commands.spawn(( + Sprite::default(), + Transform::from_scale(Vec3::new(100., 100., 1.)), + )); } fn zoom_camera( diff --git a/examples/multiplayer.rs b/examples/multiplayer.rs index 1bda0f52..03250102 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,25 @@ 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..274ebfe4 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))) }) @@ -160,7 +160,7 @@ fn control_player( if action_state.axis_pair(&PlayerAction::Move) != Vec2::ZERO { // Note: In a real game we'd feed this into an actual player controller // and respects the camera extrinsics to ensure the direction is correct - let move_delta = time.delta_seconds() * action_state.clamped_axis_pair(&PlayerAction::Move); + let move_delta = time.delta_secs() * action_state.clamped_axis_pair(&PlayerAction::Move); player_transform.translation += Vec3::new(move_delta.x, 0.0, move_delta.y); println!("Player moved to: {}", player_transform.translation.xz()); } @@ -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 8a142c47..031f4fed 100644 --- a/src/action_state/mod.rs +++ b/src/action_state/mod.rs @@ -1155,14 +1155,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 { @@ -1184,11 +1184,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: {:?}", @@ -1206,11 +1202,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)); @@ -1219,11 +1211,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)); @@ -1235,11 +1223,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)); @@ -1248,11 +1232,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)); @@ -1290,7 +1270,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; @@ -1314,7 +1294,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(); @@ -1322,7 +1302,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, )); @@ -1336,7 +1316,7 @@ mod tests { let input_store = app.world().resource::(); action_state.update(input_map.process_actions( - &Gamepads::default(), + None, input_store, ClashStrategy::PrioritizeLongest, )); @@ -1348,7 +1328,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, )); @@ -1363,7 +1343,7 @@ mod tests { let input_store = app.world().resource::(); action_state.update(input_map.process_actions( - &Gamepads::default(), + None, input_store, ClashStrategy::PrioritizeLongest, )); @@ -1377,7 +1357,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 2a0f9829..b2f9d893 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 { @@ -441,16 +441,16 @@ impl InputMap { // Configuration impl InputMap { - /// Fetches the [`Gamepad`] associated with the entity controlled by this input map. + /// Fetches the gamepad [`Entity`] associated with the one controlled by this input map. /// /// 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 } - /// Assigns a particular [`Gamepad`] to the entity controlled by this input map. + /// Assigns a particular gamepad [`Entity`] to the one controlled by this input map. /// /// Use this when an [`InputMap`] should exclusively accept input /// from a particular gamepad. @@ -462,12 +462,12 @@ 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 } - /// Assigns a particular [`Gamepad`] to the entity controlled by this input map. + /// Assigns a particular gamepad [`Entity`] to the one controlled by this input map. /// /// Use this when an [`InputMap`] should exclusively accept input /// from a particular gamepad. @@ -479,12 +479,12 @@ 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 } - /// Clears any [`Gamepad`] associated with the entity controlled by this input map. + /// Clears any gamepad [`Entity`] associated with the one controlled by this input map. #[inline] pub fn clear_gamepad(&mut self) -> &mut Self { self.associated_gamepad = None; @@ -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 864b3abf..c53d0e0a 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::{LazyLock, 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; @@ -107,110 +106,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::())) } } @@ -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/input_processing/single_axis/custom.rs b/src/input_processing/single_axis/custom.rs index ef367974..bbc0ecfd 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::{LazyLock, 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; @@ -106,110 +105,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::())) } } @@ -255,8 +255,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 287683ad..49880966 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_buttonlike_input::() .register_axislike_input::() @@ -223,7 +216,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::() @@ -312,40 +305,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::Filter) - .before(InputManagerSystem::Unify), - ); - } - } -} - /// A plugin that keeps track of all inputs in a central store. /// /// This plugin is added by default by [`InputManagerPlugin`], @@ -359,6 +318,7 @@ impl Plugin for CentralInputStorePlugin { let mut central_input_store = CentralInputStore::default(); central_input_store.register_standard_input_kinds(app); - app.insert_resource(central_input_store); + app.insert_resource(central_input_store) + .configure_sets(PreUpdate, InputManagerSystem::Unify.after(InputSystem)); } } diff --git a/src/systems.rs b/src/systems.rs index 93cf4526..8ae5a190 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, + )); } } @@ -139,8 +113,6 @@ pub fn filter_captured_input( bevy::input::ButtonInput, >, #[cfg(feature = "egui")] mut egui_query: Query<&'static mut bevy_egui::EguiContext>, - #[cfg(feature = "egui")] mut mouse_scroll: ResMut, - #[cfg(feature = "egui")] mut mouse_motion: ResMut, ) { // If the user clicks on a button, do not apply it to the game state #[cfg(feature = "ui")] @@ -157,8 +129,6 @@ pub fn filter_captured_input( // Don't ask me why get doesn't exist :shrug: if egui_context.get_mut().wants_pointer_input() { mouse_buttons.reset_all(); - mouse_motion.reset(); - mouse_scroll.reset(); } if egui_context.get_mut().wants_keyboard_input() { diff --git a/src/typetag.rs b/src/typetag.rs index 313ab526..b5b00b3d 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::fmt::Debug; +use std::{borrow::Cow, collections::BTreeMap}; 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..ca8a3452 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,12 +352,10 @@ 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::{ - GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo, - }; + use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent}; use bevy::input::InputPlugin; use bevy::prelude::*; @@ -365,17 +363,20 @@ 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 }, - connection: GamepadConnection::Connected(GamepadInfo { + gamepad, + connection: GamepadConnection::Connected { name: "TestController".into(), - }), + vendor_id: None, + product_id: None, + }, })); // Ensure that the gamepad is picked up by the appropriate system @@ -408,8 +409,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 +420,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 +431,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 +443,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 83d9b464..3aeb69a2 100644 --- a/src/user_input/gamepad.rs +++ b/src/user_input/gamepad.rs @@ -2,14 +2,16 @@ use std::hash::{Hash, Hasher}; -use bevy::ecs::system::lifetimeless::SRes; -use bevy::ecs::system::{StaticSystemParam, SystemParam}; -use bevy::input::gamepad::{GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadEvent}; +use bevy::ecs::system::lifetimeless::{Read, SQuery}; +use bevy::ecs::system::{StaticSystemParam, SystemParam, SystemState}; +use bevy::input::gamepad::{ + GamepadInput, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, +}; use bevy::input::{Axis, ButtonInput}; 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, Res, ResMut, Vec2, With, + World, }; use leafwing_input_manager_macros::serde_typetag; use serde::{Deserialize, Serialize}; @@ -30,25 +32,57 @@ 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) } -/// Provides button-like behavior for a specific direction on a [`GamepadAxisType`]. +/// 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 [`GamepadAxis`]. /// /// By default, it reads from **any connected gamepad**. /// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones. @@ -79,7 +113,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, @@ -92,7 +126,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, @@ -102,7 +136,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, @@ -127,28 +161,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 { @@ -170,36 +204,40 @@ 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 [`Entity`]. + 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); } - 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.press_as_gamepad(world, gamepad); } else { @@ -219,23 +257,31 @@ impl Hash for GamepadControlDirection { } impl UpdatableInput for GamepadAxis { - type SourceData = SRes>; + type SourceData = SQuery<(Entity, Read)>; fn compute( mut central_input_store: ResMut, 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 @@ -243,20 +289,43 @@ 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) } } -/// A wrapper around a specific [`GamepadAxisType`] (e.g., left stick X-axis, right stick Y-axis). +/// 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) + } +} + +/// A wrapper around a specific [`GamepadAxis`] (e.g., left stick X-axis, right stick Y-axis). /// /// By default, it reads from **any connected gamepad**. /// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones. @@ -290,7 +359,7 @@ impl Axislike for GamepadAxis { #[must_use] pub struct GamepadControlAxis { /// The wrapped axis. - pub axis: GamepadAxisType, + pub axis: GamepadAxis, /// A processing pipeline that handles input values. pub processors: Vec, @@ -300,7 +369,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(), @@ -309,25 +378,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 { @@ -352,23 +421,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); } } @@ -429,10 +500,10 @@ impl WithAxisProcessingPipelineExt for GamepadControlAxis { #[must_use] pub struct GamepadStick { /// Horizontal movement of the stick. - pub x: GamepadAxisType, + pub x: GamepadAxis, /// Vertical movement of the stick. - pub y: GamepadAxisType, + pub y: GamepadAxis, /// A processing pipeline that handles input values. pub processors: Vec, @@ -441,15 +512,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(), }; } @@ -478,7 +549,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 @@ -486,23 +557,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 [`Entity`]. + 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); } } @@ -529,31 +602,23 @@ 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) } -/// Retrieves the current value of the given [`GamepadButtonType`]. +/// Retrieves the current value of the given [`GamepadButton`]. /// /// This will be 0.0 if the button is released, and 1.0 if it is pressed. /// Physically triggerlike buttons will return a value between 0.0 and 1.0, /// depending on how far the button is pressed. #[must_use] #[inline] -fn button_value( - input_store: &CentralInputStore, - gamepad: Gamepad, - button: GamepadButtonType, -) -> f32 { - let button = GamepadButton::new(gamepad, button); +fn button_value(input_store: &CentralInputStore, gamepad: Entity, button: GamepadButton) -> f32 { + let button = SpecificGamepadButton::new(gamepad, button); input_store.button_value(&button) } @@ -568,28 +633,40 @@ pub struct GamepadButtonInput<'w> { } impl UpdatableInput for GamepadButton { - type SourceData = GamepadButtonInput<'static>; + type SourceData = SQuery<(Entity, Read)>; fn compute( mut central_input_store: ResMut, source_data: StaticSystemParam, ) { - for button in source_data.buttons.get_pressed() { - let value = source_data.axes.get(*button).unwrap_or(1.0); - central_input_store.update_buttonlike(*button, ButtonValue::new(true, value)); - } - - for button in source_data.buttons.get_just_released() { - let value = source_data.axes.get(*button).unwrap_or(0.0); - central_input_store.update_buttonlike(*button, ButtonValue::new(false, value)); + for (gamepad_entity, gamepad) in source_data.iter() { + for key in gamepad.get_pressed() { + let specific_button = SpecificGamepadButton { + gamepad: gamepad_entity, + button: *key, + }; + let value = specific_button.value(¢ral_input_store, gamepad_entity); + central_input_store + .update_buttonlike(specific_button, ButtonValue::new(true, value)); + } + + for key in gamepad.get_just_released() { + let specific_button = SpecificGamepadButton { + gamepad: gamepad_entity, + button: *key, + }; + let value = specific_button.value(¢ral_input_store, gamepad_entity); + central_input_store + .update_buttonlike(specific_button, ButtonValue::new(false, value)); + } } } } -/// Unlike [`GamepadButtonType`], this struct represents a specific button on a specific gamepad. +/// Unlike [`GamepadButton`], this struct represents a specific button on a specific gamepad. /// -/// In the majority of cases, [`GamepadButtonType`] should be used instead. -impl UserInput for GamepadButton { +/// In the majority of cases, [`GamepadButton`] should be used instead. +impl UserInput for SpecificGamepadButton { fn kind(&self) -> InputControlKind { InputControlKind::Button } @@ -600,15 +677,15 @@ 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) } /// WARNING: The supplied gamepad is ignored, as the button is already specific to a gamepad. - fn value(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> f32 { - button_value(input_store, self.gamepad, self.button_type) + fn value(&self, input_store: &CentralInputStore, _gamepad: Entity) -> f32 { + button_value(input_store, self.gamepad, self.button) } fn press(&self, world: &mut World) { @@ -620,24 +697,24 @@ impl Buttonlike for GamepadButton { } fn set_value(&self, world: &mut World, value: f32) { - let event = GamepadEvent::Button(GamepadButtonChangedEvent { + let event = RawGamepadEvent::Button(RawGamepadButtonChangedEvent { gamepad: self.gamepad, - button_type: self.button_type, + button: self.button, value, }); - 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. +// Built-in support for Bevy's GamepadButton. +impl UserInput for GamepadButton { + /// [`GamepadButton`] acts as a button. #[inline] fn kind(&self) -> InputControlKind { InputControlKind::Button } - /// Creates a [`BasicInputs`] that only contains the [`GamepadButtonType`] itself, + /// Creates a [`BasicInputs`] that only contains the [`GamepadButton`] itself, /// as it represents a simple physical button. #[inline] fn decompose(&self) -> BasicInputs { @@ -646,11 +723,11 @@ 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) } @@ -661,59 +738,64 @@ impl Buttonlike for GamepadButtonType { /// depending on how far the button is pressed. #[must_use] #[inline] - fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 { + fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 { button_value(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) { + /// Sends a [`RawGamepadEvent::Button`] event with a magnitude of 1.0 in the direction defined by `self` on the provided gamepad [`Entity`]. + fn press_as_gamepad(&self, world: &mut World, gamepad: Option) { self.set_value_as_gamepad(world, 1.0, gamepad); } - /// 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) { + /// Sends a [`RawGamepadEvent::Button`] event with a magnitude of 0.0 in the direction defined by `self` on the provided gamepad [`Entity`]. + fn release_as_gamepad(&self, world: &mut World, gamepad: Option) { self.set_value_as_gamepad(world, 0.0, gamepad); } - /// Sends a [`GamepadEvent::Button`] event with the specified value in the direction defined by `self` on the provided [`Gamepad`]. + /// Sends a [`RawGamepadEvent::Button`] event with the specified value in the direction defined by `self` on the provided gamepad [`Entity`]. #[inline] - fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option) { - let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::())); + 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::Button(GamepadButtonChangedEvent { + let event = RawGamepadEvent::Button(RawGamepadButtonChangedEvent { gamepad, - button_type: *self, + button: *self, value, }); - 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}; 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 = 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 { + gamepad, + connection: GamepadConnection::Connected { name: "TestController".into(), - }), - })); + vendor_id: None, + product_id: None, + }, + }); // Ensure that the gamepad is picked up by the appropriate system app.update(); @@ -754,8 +836,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)); @@ -769,6 +856,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::(); @@ -785,6 +878,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::(); @@ -801,6 +900,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::(); @@ -818,25 +923,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)); @@ -844,7 +948,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 2e522f1c..80ba7a70 100644 --- a/src/user_input/keyboard.rs +++ b/src/user_input/keyboard.rs @@ -4,7 +4,7 @@ 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, ResMut, World}; +use bevy::prelude::{Entity, Events, KeyCode, Reflect, ResMut, World}; use leafwing_input_manager_macros::serde_typetag; use serde::{Deserialize, Serialize}; @@ -55,7 +55,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) } @@ -70,6 +70,7 @@ impl Buttonlike for KeyCode { key_code: *self, logical_key: Key::Unidentified(NativeKey::Unidentified), state: ButtonState::Pressed, + repeat: false, window: Entity::PLACEHOLDER, }); } @@ -85,6 +86,7 @@ impl Buttonlike for KeyCode { key_code: *self, logical_key: Key::Unidentified(NativeKey::Unidentified), state: ButtonState::Released, + repeat: false, window: Entity::PLACEHOLDER, }); } @@ -177,7 +179,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()) } @@ -218,14 +220,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 } @@ -243,10 +245,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 96174013..6bb98d7e 100644 --- a/src/user_input/mod.rs +++ b/src/user_input/mod.rs @@ -38,7 +38,7 @@ //! //! ### Gamepad Inputs //! -//! - Check gamepad button presses using Bevy's [`GamepadButtonType`] directly. +//! - Check gamepad button presses using Bevy's [`GamepadButton`] directly. //! - Access physical sticks using [`GamepadStick`], [`GamepadControlAxis`], and [`GamepadControlDirection`]. //! //! ### Keyboard Inputs @@ -74,15 +74,14 @@ //! - [`TripleAxislikeChord`]: A combined input that groups a [`Buttonlike`] and a [`TripleAxislike`] together, //! allowing you to only read the dual axis data when the button is pressed. //! -//! [`GamepadAxisType`]: bevy::prelude::GamepadAxisType -//! [`GamepadButtonType`]: bevy::prelude::GamepadButtonType +//! [`GamepadButton`]: bevy::prelude::GamepadButton //! [`KeyCode`]: bevy::prelude::KeyCode //! [`MouseButton`]: bevy::prelude::MouseButton 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; @@ -137,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) } @@ -149,7 +148,7 @@ pub trait Buttonlike: /// The returned value should be between `0.0` and `1.0`, /// with `0.0` representing the input being fully released /// and `1.0` representing the input being fully pressed. - fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 { + fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 { f32::from(self.pressed(input_store, gamepad)) } @@ -161,14 +160,14 @@ pub trait Buttonlike: self.press_as_gamepad(world, None); } - /// Simulate a press of the buttonlike input, pretending to be the provided [`Gamepad`]. + /// Simulate a press of the buttonlike input, pretending to be the provided gamepad [`Entity`]. /// /// This method defaults to calling [`Buttonlike::press`] if not overridden, /// as is the case for things like mouse buttons and keyboard keys. /// /// 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); } @@ -180,14 +179,14 @@ pub trait Buttonlike: self.release_as_gamepad(world, None); } - /// Simulate a release of the buttonlike input, pretending to be the provided [`Gamepad`]. + /// Simulate a release of the buttonlike input, pretending to be the provided gamepad [`Entity`]. /// /// This method defaults to calling [`Buttonlike::release`] if not overridden, /// as is the case for things like mouse buttons and keyboard keys. /// /// 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); } @@ -203,7 +202,7 @@ pub trait Buttonlike: self.set_value_as_gamepad(world, value, None); } - /// Simulate a value change of the buttonlike input, pretending to be the provided [`Gamepad`]. + /// Simulate a value change of the buttonlike input, pretending to be the provided gamepad [`Entity`]. /// /// This method defaults to calling [`Buttonlike::set_value`] if not overridden, /// as is the case for things like a mouse wheel. @@ -214,7 +213,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 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); } } @@ -224,7 +223,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. /// @@ -234,14 +233,14 @@ pub trait Axislike: self.set_value_as_gamepad(world, value, None); } - /// Simulate an axis-like input, pretending to be the provided [`Gamepad`]. + /// Simulate an axis-like input, pretending to be the provided gamepad [`Entity`]. /// /// This method defaults to calling [`Axislike::set_value`] if not overridden, /// as is the case for things like a mouse wheel. /// /// 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); } } @@ -251,7 +250,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. /// @@ -261,14 +260,14 @@ pub trait DualAxislike: self.set_axis_pair_as_gamepad(world, value, None); } - /// Simulate a dual-axis-like input, pretending to be the provided [`Gamepad`]. + /// Simulate a dual-axis-like input, pretending to be the provided gamepad [`Entity`]. /// /// This method defaults to calling [`DualAxislike::set_axis_pair`] if not overridden, /// as is the case for things like a mouse wheel. /// /// 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); } } @@ -278,7 +277,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. /// @@ -288,19 +287,14 @@ pub trait TripleAxislike: self.set_axis_triple_as_gamepad(world, value, None); } - /// Simulate a triple-axis-like input, pretending to be the provided [`Gamepad`]. + /// Simulate a triple-axis-like input, pretending to be the provided gamepad [`Entity`]. /// /// This method defaults to calling [`TripleAxislike::set_axis_triple`] if not overridden, /// as is the case for things like a space mouse. /// /// 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 cb06545f..0c59a643 100644 --- a/src/user_input/mouse.rs +++ b/src/user_input/mouse.rs @@ -8,10 +8,13 @@ use crate::input_processing::*; use crate::user_input::{InputControlKind, UserInput}; use bevy::ecs::system::lifetimeless::SRes; use bevy::ecs::system::StaticSystemParam; -use bevy::input::mouse::{MouseButton, MouseButtonInput, MouseMotion, MouseWheel}; +use bevy::input::mouse::{ + AccumulatedMouseMotion, AccumulatedMouseScroll, MouseButton, MouseButtonInput, MouseMotion, + MouseWheel, +}; use bevy::input::{ButtonInput, ButtonState}; use bevy::math::FloatOrd; -use bevy::prelude::{Entity, Events, Gamepad, Reflect, ResMut, Resource, Vec2, World}; +use bevy::prelude::{Entity, Events, Reflect, ResMut, Vec2, World}; use leafwing_input_manager_macros::serde_typetag; use serde::{Deserialize, Serialize}; use std::hash::{Hash, Hasher}; @@ -56,7 +59,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) } @@ -103,12 +106,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; @@ -195,7 +198,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) } @@ -235,12 +238,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; @@ -307,7 +310,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 @@ -360,12 +363,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(); /// @@ -386,13 +389,13 @@ pub struct MouseMove { } impl UpdatableInput for MouseMove { - type SourceData = SRes; + type SourceData = SRes; fn compute( mut central_input_store: ResMut, source_data: StaticSystemParam, ) { - central_input_store.update_dualaxislike(Self::default(), source_data.0); + central_input_store.update_dualaxislike(Self::default(), source_data.delta); } } @@ -420,7 +423,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() @@ -463,12 +466,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; @@ -555,7 +558,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) } @@ -602,12 +605,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; @@ -674,7 +677,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 @@ -739,12 +742,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(); /// @@ -771,7 +774,7 @@ impl UpdatableInput for MouseScroll { mut central_input_store: ResMut, source_data: StaticSystemParam, ) { - central_input_store.update_dualaxislike(Self::default(), source_data.0); + central_input_store.update_dualaxislike(Self::default(), source_data.delta); } } @@ -799,7 +802,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() @@ -843,69 +846,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 } @@ -923,10 +874,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)); @@ -976,10 +926,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)); @@ -1053,10 +1002,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)); @@ -1118,14 +1066,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 d4db5f63..0fccecaa 100644 --- a/src/user_input/testing_utils.rs +++ b/src/user_input/testing_utils.rs @@ -2,8 +2,9 @@ use bevy::{ app::App, + ecs::system::SystemState, math::Vec2, - prelude::{Gamepad, Gamepads, World}, + prelude::{Entity, Gamepad, Query, With, World}, }; use super::{updating::CentralInputStore, Axislike, Buttonlike, DualAxislike}; @@ -12,8 +13,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). @@ -35,41 +36,37 @@ 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_button_value(&mut self, input: impl Buttonlike) -> 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_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 508e2090..99aa0382 100644 --- a/src/user_input/updating.rs +++ b/src/user_input/updating.rs @@ -368,7 +368,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 7246e05e..cf7bead5 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 = self.negative.value(input_store, gamepad); let positive = self.positive.value(input_store, gamepad); let value = positive - negative; @@ -218,7 +218,7 @@ impl Axislike for VirtualAxis { /// When `value` is non-zero, set its absolute value to the value of: /// - the negative button if the `value` is negative; /// - the positive button if the `value` is positive. - 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 .set_value_as_gamepad(world, value.abs(), gamepad); @@ -274,10 +274,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(); @@ -377,35 +377,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, ) } } @@ -417,7 +417,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![ @@ -434,7 +434,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 = self.up.value(input_store, gamepad); let down = self.down.value(input_store, gamepad); let left = self.left.value(input_store, gamepad); @@ -450,7 +450,7 @@ impl DualAxislike for VirtualDPad { /// When `value` along an axis is non-zero, set its absolute value to the value of: /// - the negative button of the axis if the `value` is negative; /// - the positive button of the axis if the `value` is positive. - 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) { let Vec2 { x, y } = value; if x < 0.0 { @@ -570,7 +570,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 = self.up.value(input_store, gamepad); let down = self.down.value(input_store, gamepad); let left = self.left.value(input_store, gamepad); @@ -585,7 +585,7 @@ impl TripleAxislike for VirtualDPad3D { /// When `value` along an axis is non-zero, set its absolute value to the value of: /// - the negative button of the axis if the `value` is negative; /// - the positive button of the axis if the `value` is positive. - 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) { let Vec3 { x, y, z } = value; if x < 0.0 { @@ -614,14 +614,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 } @@ -643,7 +643,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 0fd34cc0..95f37452 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/buttonlike.rs b/tests/buttonlike.rs index 78511a45..0e4ee018 100644 --- a/tests/buttonlike.rs +++ b/tests/buttonlike.rs @@ -2,7 +2,7 @@ use bevy::{ input::{ - gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo}, + gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent}, InputPlugin, }, prelude::*, @@ -24,8 +24,8 @@ fn test_app() -> App { )); let mut input_map = InputMap::::default() - .with(TestAction::Throttle, GamepadButtonType::South) - .with(TestAction::Throttle, GamepadButtonType::RightTrigger); + .with(TestAction::Throttle, GamepadButton::South) + .with(TestAction::Throttle, GamepadButton::RightTrigger); input_map .insert(TestAction::Throttle, KeyCode::Space) @@ -34,13 +34,16 @@ fn test_app() -> App { app.insert_resource(input_map) .init_resource::>(); + let gamepad = app.world_mut().spawn_empty().id(); let mut gamepad_events: Mut<'_, Events> = app.world_mut().resource_mut::>(); gamepad_events.send(GamepadEvent::Connection(GamepadConnectionEvent { - gamepad: Gamepad { id: 1 }, - connection: GamepadConnection::Connected(GamepadInfo { + gamepad, + connection: GamepadConnection::Connected { name: "FirstController".into(), - }), + product_id: None, + vendor_id: None, + }, })); // Ensure the gamepads are picked up @@ -56,7 +59,7 @@ fn test_app() -> App { fn set_gamepad_updates_central_input_store() { let mut app = test_app(); - let gamepad_trigger = GamepadButtonType::RightTrigger; + let gamepad_trigger = GamepadButton::RightTrigger; gamepad_trigger.set_value(app.world_mut(), 0.7); app.update(); @@ -83,6 +86,8 @@ fn set_keyboard_updates_central_input_store() { } #[test] +// FIXME: Should be fixed in a follow-up PR and the ignore should be removed +#[ignore = "Ignoring as per https://github.com/Leafwing-Studios/leafwing-input-manager/pull/664#issuecomment-2529188830"] fn gamepad_button_value() { let mut app = test_app(); @@ -90,7 +95,7 @@ fn gamepad_button_value() { let button_value = action_state.button_value(&TestAction::Throttle); assert_eq!(button_value, 0.0); - let relevant_button = GamepadButtonType::South; + let relevant_button = GamepadButton::South; relevant_button.press(app.world_mut()); app.update(); @@ -145,7 +150,7 @@ fn gamepad_trigger() { let button_value = action_state.button_value(&TestAction::Throttle); assert_eq!(button_value, 0.0); - let gamepad_trigger = GamepadButtonType::RightTrigger; + let gamepad_trigger = GamepadButton::RightTrigger; gamepad_trigger.set_value(app.world_mut(), 0.7); app.update(); @@ -181,10 +186,12 @@ fn buttonlike_actions_can_be_pressed_and_released_when_pressed() { } #[test] +// FIXME: Should be fixed in a follow-up PR and the ignore should be removed +#[ignore = "Ignoring as per https://github.com/Leafwing-Studios/leafwing-input-manager/pull/664#issuecomment-2529188830"] fn buttonlike_actions_can_be_pressed_and_released_when_button_value_set() { let mut app = test_app(); - let gamepad_trigger = GamepadButtonType::RightTrigger; + let gamepad_trigger = GamepadButton::RightTrigger; gamepad_trigger.set_value(app.world_mut(), 1.0); app.update(); @@ -194,7 +201,7 @@ fn buttonlike_actions_can_be_pressed_and_released_when_button_value_set() { assert!(!action_state.released(&TestAction::Throttle)); assert!(!action_state.just_released(&TestAction::Throttle)); - let gamepad_trigger = GamepadButtonType::RightTrigger; + let gamepad_trigger = GamepadButton::RightTrigger; gamepad_trigger.set_value(app.world_mut(), 0.0); app.update(); diff --git a/tests/gamepad_axis.rs b/tests/gamepad_axis.rs index 9ab828f7..99701d35 100644 --- a/tests/gamepad_axis.rs +++ b/tests/gamepad_axis.rs @@ -1,6 +1,6 @@ #![cfg(feature = "gamepad")] -use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo}; +use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent, RawGamepadEvent}; use bevy::input::InputPlugin; use bevy::prelude::*; use leafwing_input_manager::input_processing::{ @@ -35,14 +35,19 @@ 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 = app.world_mut().spawn_empty().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 { + gamepad, + connection: GamepadConnection::Connected { name: "TestController".into(), - }), - })); + vendor_id: None, + product_id: None, + }, + }); // Ensure that the gamepad is picked up by the appropriate system app.update(); @@ -56,13 +61,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 +75,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 +304,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..2d2d6c85 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, RawGamepadEvent, +}; use bevy::input::InputPlugin; use bevy::prelude::*; use leafwing_input_manager::prelude::*; @@ -15,20 +17,27 @@ 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 }, - connection: GamepadConnection::Connected(GamepadInfo { + gamepad: gamepad_1, + connection: GamepadConnection::Connected { 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 }, - connection: GamepadConnection::Connected(GamepadInfo { + gamepad: gamepad_2, + connection: GamepadConnection::Connected { name: "SecondController".into(), - }), + vendor_id: None, + product_id: None, + }, })); // Ensure the gamepads are picked up @@ -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,32 @@ 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 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 { + name: "Preferred gamepad".to_owned(), + vendor_id: None, + product_id: None, + }, + }); + // 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 +99,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!