diff --git a/src/action_state.rs b/src/action_state.rs index 2003abb4..c9250b90 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -320,13 +320,7 @@ impl ActionState { /// Instead, this is set through [`ActionState::tick()`] #[inline] pub fn press(&mut self, action: &A) { - let action_data = match self.action_data_mut(action) { - Some(action_data) => action_data, - None => { - self.set_action_data(action.clone(), ActionData::default()); - self.action_data_mut(action).unwrap() - } - }; + let action_data = self.action_data.entry(action.clone()).or_default(); // Consumed actions cannot be pressed until they are released if action_data.consumed { @@ -346,13 +340,7 @@ impl ActionState { /// Instead, this is set through [`ActionState::tick()`] #[inline] pub fn release(&mut self, action: &A) { - let action_data = match self.action_data_mut(action) { - Some(action_data) => action_data, - None => { - self.set_action_data(action.clone(), ActionData::default()); - self.action_data_mut(action).unwrap() - } - }; + let action_data = self.action_data.entry(action.clone()).or_default(); // Once released, consumed actions can be pressed again action_data.consumed = false; @@ -405,13 +393,7 @@ impl ActionState { /// ``` #[inline] pub fn consume(&mut self, action: &A) { - let action_data = match self.action_data_mut(action) { - Some(action_data) => action_data, - None => { - self.set_action_data(action.clone(), ActionData::default()); - self.action_data_mut(action).unwrap() - } - }; + let action_data = self.action_data.entry(action.clone()).or_default(); // This is the only difference from action_state.release(&action) action_data.consumed = true; @@ -438,30 +420,21 @@ impl ActionState { #[inline] #[must_use] pub fn consumed(&self, action: &A) -> bool { - match self.action_data(action) { - Some(action_data) => action_data.consumed, - None => false, - } + matches!(self.action_data(action), Some(action_data) if action_data.consumed) } /// Is this `action` currently pressed? #[inline] #[must_use] pub fn pressed(&self, action: &A) -> bool { - match self.action_data(action) { - Some(action_data) => action_data.state.pressed(), - None => false, - } + matches!(self.action_data(action), Some(action_data) if action_data.state.pressed()) } /// Was this `action` pressed since the last time [tick](ActionState::tick) was called? #[inline] #[must_use] pub fn just_pressed(&self, action: &A) -> bool { - match self.action_data(action) { - Some(action_data) => action_data.state.just_pressed(), - None => false, - } + matches!(self.action_data(action), Some(action_data) if action_data.state.just_pressed()) } /// Is this `action` currently released? @@ -480,10 +453,7 @@ impl ActionState { #[inline] #[must_use] pub fn just_released(&self, action: &A) -> bool { - match self.action_data(action) { - Some(action_data) => action_data.state.just_released(), - None => false, - } + matches!(self.action_data(action), Some(action_data) if action_data.state.just_released()) } #[must_use] diff --git a/src/axislike.rs b/src/axislike.rs index 1cef8c0c..545cab41 100644 --- a/src/axislike.rs +++ b/src/axislike.rs @@ -686,10 +686,7 @@ impl DualAxisData { #[inline] pub fn direction(&self) -> Option { // TODO: replace this quick-n-dirty hack once Direction::new no longer panics - if self.xy.length() > 0.00001 { - return Some(Direction::new(self.xy)); - } - None + (self.xy.length() > 0.00001).then(|| Direction::new(self.xy)) } /// The [`Rotation`] (measured clockwise from midnight) that this axis is pointing towards, if any @@ -698,10 +695,7 @@ impl DualAxisData { #[must_use] #[inline] pub fn rotation(&self) -> Option { - match Rotation::from_xy(self.xy) { - Ok(rotation) => Some(rotation), - Err(_) => None, - } + Rotation::from_xy(self.xy).ok() } /// How far from the origin is this axis's position? @@ -741,20 +735,20 @@ impl From for Vec2 { /// The shape of the deadzone for a [`DualAxis`] input. /// /// Input values that are on the boundary of the shape are counted as inside. -/// If a size of a shape is 0.0, then all input values are read, except for 0.0. +/// If the size of a shape is 0.0, then all input values are read, except for 0.0. /// /// All inputs are scaled to be continuous. -/// So with a ellipse deadzone of a radius of 0.1, the input range `0.1..=1.0` will be scaled to `0.0..=1.0`. +/// So with an ellipse deadzone of a radius of 0.1, the input range `0.1..=1.0` will be scaled to `0.0..=1.0`. /// /// Deadzone values should be in the range `0.0..=1.0`. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Reflect)] pub enum DeadZoneShape { /// Deadzone with the shape of a cross. /// - /// The cross is represented by horizonal and vertical rectangles. - /// Each axis is handled seperately which creates a per-axis "snapping" effect. + /// The cross is represented by horizontal and vertical rectangles. + /// Each axis is handled separately which creates a per-axis "snapping" effect. Cross { - /// The width of the horizonal axis. + /// The width of the horizontal axis. /// /// Affects the snapping of the y-axis. horizontal_width: f32, @@ -794,15 +788,13 @@ impl std::hash::Hash for DeadZoneShape { impl DeadZoneShape { /// Computes the input value based on the deadzone. pub fn deadzone_input_value(&self, x: f32, y: f32) -> Option { - let value = Vec2::new(x, y); - match self { DeadZoneShape::Cross { horizontal_width, vertical_width, - } => self.cross_deadzone_value(value, *horizontal_width, *vertical_width), + } => self.cross_deadzone_value(x, y, *horizontal_width, *vertical_width), DeadZoneShape::Ellipse { radius_x, radius_y } => { - self.ellipse_deadzone_value(value, *radius_x, *radius_y) + self.ellipse_deadzone_value(x, y, *radius_x, *radius_y) } } } @@ -810,41 +802,46 @@ impl DeadZoneShape { /// Computes the input value based on the cross deadzone. fn cross_deadzone_value( &self, - value: Vec2, + x: f32, + y: f32, horizontal_width: f32, vertical_width: f32, ) -> Option { - let new_x = f32::from(value.x.abs() > vertical_width) * value.x; - let new_y = f32::from(value.y.abs() > horizontal_width) * value.y; - let new_value = Vec2::new(new_x, new_y); - - if new_value == Vec2::ZERO { - None - } else { - let scaled_value = - Self::scale_value(new_value, Vec2::new(vertical_width, horizontal_width)); - Some(DualAxisData::from_xy(scaled_value)) - } + let new_x = deadzone_axis_value(x, vertical_width); + let new_y = deadzone_axis_value(y, horizontal_width); + let is_outside_deadzone = new_x != 0.0 || new_y != 0.0; + is_outside_deadzone.then(|| DualAxisData::new(new_x, new_y)) } /// Computes the input value based on the ellipse deadzone. fn ellipse_deadzone_value( &self, - value: Vec2, + x: f32, + y: f32, radius_x: f32, radius_y: f32, ) -> Option { - let clamped_radius_x = radius_x.max(f32::EPSILON); - let clamped_radius_y = radius_y.max(f32::EPSILON); - if (value.x / clamped_radius_x).powi(2) + (value.y / clamped_radius_y).powi(2) < 1.0 { - return None; - } - - let scaled_value = Self::scale_value(value, Vec2::new(radius_x, radius_y)); - Some(DualAxisData::from_xy(scaled_value)) + let x_ratio = x / radius_x.max(f32::EPSILON); + let y_ratio = y / radius_y.max(f32::EPSILON); + let is_outside_deadzone = x_ratio.powi(2) + y_ratio.powi(2) >= 1.0; + is_outside_deadzone.then(|| { + let new_x = deadzone_axis_value(x, radius_x); + let new_y = deadzone_axis_value(y, radius_y); + DualAxisData::new(new_x, new_y) + }) } +} - fn scale_value(value: Vec2, deadzone_size: Vec2) -> Vec2 { - value.signum() * (value.abs() - deadzone_size).max(Vec2::ZERO) / (1.0 - deadzone_size) +/// Applies the given deadzone to the axis value. +/// +/// Returns 0.0 if the axis value is within the deadzone. +/// Otherwise, returns the normalized axis value between -1.0 and 1.0. +#[inline(always)] +pub(crate) fn deadzone_axis_value(axis_value: f32, deadzone: f32) -> f32 { + let abs_axis_value = axis_value.abs(); + if abs_axis_value <= deadzone { + 0.0 + } else { + axis_value.signum() * (abs_axis_value - deadzone) / (1.0 - deadzone) } } diff --git a/src/clashing_inputs.rs b/src/clashing_inputs.rs index c0b18782..d6f930c6 100644 --- a/src/clashing_inputs.rs +++ b/src/clashing_inputs.rs @@ -103,8 +103,8 @@ impl InputMap { pub(crate) fn possible_clashes(&self) -> Vec> { let mut clashes = Vec::default(); - for (action_a, _) in self.iter() { - for (action_b, _) in self.iter() { + for action_a in self.actions() { + for action_b in self.actions() { if let Some(clash) = self.possible_clash(action_a, action_b) { clashes.push(clash); } @@ -162,11 +162,8 @@ impl InputMap { } } - if !clash.inputs_a.is_empty() { - Some(clash) - } else { - None - } + let not_empty = !clash.inputs_a.is_empty(); + not_empty.then_some(clash) } } @@ -196,49 +193,25 @@ impl Clash { // Does the `button` clash with the `chord`? #[must_use] fn button_chord_clash(button: &InputKind, chord: &[InputKind]) -> bool { - if chord.len() <= 1 { - return false; - } - - chord.contains(button) + chord.len() > 1 && chord.contains(button) } // Does the `dpad` clash with the `chord`? #[must_use] fn dpad_chord_clash(dpad: &VirtualDPad, chord: &[InputKind]) -> bool { - if chord.len() <= 1 { - return false; - } - - for button in &[dpad.up, dpad.down, dpad.left, dpad.right] { - if chord.contains(button) { - return true; - } - } - - false + chord.len() > 1 && chord.iter().any(|button| dpad_button_clash(dpad, button)) } fn dpad_button_clash(dpad: &VirtualDPad, button: &InputKind) -> bool { - for dpad_button in &[dpad.up, dpad.down, dpad.left, dpad.right] { - if button == dpad_button { - return true; - } - } - - false + [dpad.up, dpad.down, dpad.left, dpad.right] + .iter() + .any(|dpad_button| button == dpad_button) } fn dpad_dpad_clash(dpad1: &VirtualDPad, dpad2: &VirtualDPad) -> bool { - for button1 in &[dpad1.up, dpad1.down, dpad1.left, dpad1.right] { - for button2 in &[dpad2.up, dpad2.down, dpad2.left, dpad2.right] { - if button1 == button2 { - return true; - } - } - } - - false + let iter1 = [&dpad1.up, &dpad1.down, &dpad1.left, &dpad1.right].into_iter(); + let iter2 = [&dpad2.up, &dpad2.down, &dpad2.left, &dpad2.right].into_iter(); + iter1.zip(iter2).any(|(left, right)| left == right) } #[must_use] @@ -248,30 +221,23 @@ fn virtual_axis_button_clash(axis: &VirtualAxis, button: &InputKind) -> bool { #[must_use] fn virtual_axis_dpad_clash(axis: &VirtualAxis, dpad: &VirtualDPad) -> bool { - for dpad_button in &[dpad.up, dpad.down, dpad.left, dpad.right] { - if dpad_button == &axis.negative || dpad_button == &axis.positive { - return true; - } - } - - false + [&dpad.up, &dpad.down, &dpad.left, &dpad.right] + .iter() + .any(|button| virtual_axis_button_clash(axis, button)) } #[must_use] fn virtual_axis_chord_clash(axis: &VirtualAxis, chord: &[InputKind]) -> bool { - if chord.len() <= 1 { - return false; - } - - chord.contains(&axis.negative) || chord.contains(&axis.positive) + chord.len() > 1 + && chord + .iter() + .any(|button| virtual_axis_button_clash(axis, button)) } #[must_use] fn virtual_axis_virtual_axis_clash(axis1: &VirtualAxis, axis2: &VirtualAxis) -> bool { - axis1.negative == axis2.negative - || axis1.negative == axis2.positive - || axis1.positive == axis2.negative - || axis1.positive == axis2.positive + virtual_axis_button_clash(axis1, &axis2.negative) + || virtual_axis_button_clash(axis1, &axis2.positive) } /// Does the `chord_a` clash with `chord_b`? @@ -285,17 +251,11 @@ fn chord_chord_clash(chord_a: &Vec, chord_b: &Vec) -> bool return false; } - is_subset(chord_a, chord_b) || is_subset(chord_b, chord_a) -} - -fn is_subset(slice_a: &[InputKind], slice_b: &[InputKind]) -> bool { - for a in slice_a { - if !slice_b.contains(a) { - return false; - } + fn is_subset(slice_a: &[InputKind], slice_b: &[InputKind]) -> bool { + slice_a.iter().all(|a| slice_b.contains(a)) } - true + is_subset(chord_a, chord_b) || is_subset(chord_b, chord_a) } /// Given the `input_streams`, does the provided clash actually occur? @@ -325,11 +285,8 @@ fn check_clash(clash: &Clash, input_streams: &InputStreams) -> } } - if !clash.inputs_a.is_empty() { - Some(actual_clash) - } else { - None - } + let not_empty = !clash.inputs_a.is_empty(); + not_empty.then_some(actual_clash) } /// Which (if any) of the actions in the [`Clash`] should be discarded? diff --git a/src/display_impl.rs b/src/display_impl.rs index abc8ee17..0e66fa4c 100644 --- a/src/display_impl.rs +++ b/src/display_impl.rs @@ -2,6 +2,7 @@ use crate::axislike::{VirtualAxis, VirtualDPad}; use crate::user_input::{InputKind, UserInput}; +use itertools::Itertools; use std::fmt::Display; impl Display for UserInput { @@ -10,14 +11,7 @@ impl Display for UserInput { // The representation of the button UserInput::Single(button) => write!(f, "{button}"), // The representation of each button, separated by "+" - UserInput::Chord(button_set) => { - let mut string = String::default(); - for button in button_set.iter() { - string.push('+'); - string.push_str(&button.to_string()); - } - write!(f, "{string}") - } + UserInput::Chord(button_set) => f.write_str(&button_set.iter().join("+")), UserInput::VirtualDPad(VirtualDPad { up, down, diff --git a/src/input_map.rs b/src/input_map.rs index afcef5fe..48f62ae0 100644 --- a/src/input_map.rs +++ b/src/input_map.rs @@ -357,6 +357,10 @@ impl InputMap { pub fn iter(&self) -> impl Iterator)> { self.map.iter() } + /// Returns an iterator over actions + pub(crate) fn actions(&self) -> impl Iterator { + self.map.keys() + } /// Returns a reference to the inputs mapped to `action` #[must_use] pub fn get(&self, action: &A) -> Option<&Vec> { @@ -372,11 +376,7 @@ impl InputMap { /// How many input bindings are registered total? #[must_use] pub fn len(&self) -> usize { - let mut i = 0; - for inputs in self.map.values() { - i += inputs.len(); - } - i + self.map.values().map(|inputs| inputs.len()).sum() } /// Are any input bindings registered at all? @@ -406,11 +406,7 @@ impl InputMap { /// Returns `Some(input)` if found. pub fn remove_at(&mut self, action: &A, index: usize) -> Option { let input_vec = self.map.get_mut(action)?; - if input_vec.len() <= index { - None - } else { - Some(input_vec.remove(index)) - } + (input_vec.len() > index).then(|| input_vec.remove(index)) } /// Removes the input for the `action`, if it exists diff --git a/src/input_mocking.rs b/src/input_mocking.rs index fc397ca2..4147d98b 100644 --- a/src/input_mocking.rs +++ b/src/input_mocking.rs @@ -10,7 +10,7 @@ use crate::axislike::{AxisType, MouseMotionAxisType, MouseWheelAxisType}; use crate::buttonlike::{MouseMotionDirection, MouseWheelDirection}; use crate::input_streams::{InputStreams, MutableInputStreams}; -use crate::user_input::UserInput; +use crate::user_input::{RawInputs, UserInput}; use bevy::app::App; use bevy::ecs::event::Events; @@ -161,129 +161,60 @@ impl MockInput for MutableInputStreams<'_> { // Extract the raw inputs let raw_inputs = input_to_send.raw_inputs(); - // Keyboard buttons - for button in raw_inputs.keycodes { - self.keyboard_events.send(KeyboardInput { - scan_code: u32::MAX, - key_code: Some(button), - state: ButtonState::Pressed, - window: Entity::PLACEHOLDER, - }); - } + self.send_keyboard_input(ButtonState::Pressed, &raw_inputs); // Mouse buttons - for button in raw_inputs.mouse_buttons { + for button in raw_inputs.mouse_buttons.iter() { self.mouse_button_events.send(MouseButtonInput { - button, + button: *button, state: ButtonState::Pressed, window: Entity::PLACEHOLDER, }); } // Discrete mouse wheel events - for mouse_wheel_direction in raw_inputs.mouse_wheel { - match mouse_wheel_direction { - MouseWheelDirection::Left => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: -1.0, - y: 0.0, - window: Entity::PLACEHOLDER, - }), - MouseWheelDirection::Right => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: 1.0, - y: 0.0, - window: Entity::PLACEHOLDER, - }), - MouseWheelDirection::Up => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: 0.0, - y: 1.0, - window: Entity::PLACEHOLDER, - }), - MouseWheelDirection::Down => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: 0.0, - y: -1.0, - window: Entity::PLACEHOLDER, - }), - } + for mouse_wheel_direction in raw_inputs.mouse_wheel.iter() { + match *mouse_wheel_direction { + MouseWheelDirection::Left => self.send_mouse_wheel(-1.0, 0.0), + MouseWheelDirection::Right => self.send_mouse_wheel(1.0, 0.0), + MouseWheelDirection::Up => self.send_mouse_wheel(0.0, 1.0), + MouseWheelDirection::Down => self.send_mouse_wheel(0.0, -1.0), + }; } // Discrete mouse motion event - for mouse_motion_direction in raw_inputs.mouse_motion { - match mouse_motion_direction { - MouseMotionDirection::Up => self.mouse_motion.send(MouseMotion { - delta: Vec2 { x: 0.0, y: 1.0 }, - }), - MouseMotionDirection::Down => self.mouse_motion.send(MouseMotion { - delta: Vec2 { x: 0.0, y: -1.0 }, - }), - MouseMotionDirection::Right => self.mouse_motion.send(MouseMotion { - delta: Vec2 { x: 1.0, y: 0.0 }, - }), - MouseMotionDirection::Left => self.mouse_motion.send(MouseMotion { - delta: Vec2 { x: -1.0, y: 0.0 }, - }), - } + for mouse_motion_direction in raw_inputs.mouse_motion.iter() { + match *mouse_motion_direction { + MouseMotionDirection::Up => self.send_mouse_motion(0.0, 1.0), + MouseMotionDirection::Down => self.send_mouse_motion(0.0, -1.0), + MouseMotionDirection::Right => self.send_mouse_motion(1.0, 0.0), + MouseMotionDirection::Left => self.send_mouse_motion(-1.0, 0.0), + }; } - // Gamepad buttons - for button_type in raw_inputs.gamepad_buttons { - if let Some(gamepad) = gamepad { - self.gamepad_events - .send(GamepadEvent::Button(GamepadButtonChangedEvent { - gamepad, - button_type, - value: 1.0, - })); - } - } + self.send_gamepad_button_changed(gamepad, &raw_inputs); // Axis data - for (outer_axis_type, maybe_position_data) in raw_inputs.axis_data { - if let Some(position_data) = maybe_position_data { + for (outer_axis_type, maybe_position_data) in raw_inputs.axis_data.iter() { + if let Some(position_data) = *maybe_position_data { match outer_axis_type { AxisType::Gamepad(axis_type) => { if let Some(gamepad) = gamepad { self.gamepad_events .send(GamepadEvent::Axis(GamepadAxisChangedEvent { gamepad, - axis_type, + axis_type: *axis_type, value: position_data, })); } } - AxisType::MouseWheel(axis_type) => { - match axis_type { - // FIXME: MouseScrollUnit is not recorded and is always assumed to be Pixel - MouseWheelAxisType::X => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: position_data, - y: 0.0, - window: Entity::PLACEHOLDER, - }), - MouseWheelAxisType::Y => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: 0.0, - y: position_data, - window: Entity::PLACEHOLDER, - }), - } - } - AxisType::MouseMotion(axis_type) => match axis_type { - MouseMotionAxisType::X => self.mouse_motion.send(MouseMotion { - delta: Vec2 { - x: position_data, - y: 0.0, - }, - }), - MouseMotionAxisType::Y => self.mouse_motion.send(MouseMotion { - delta: Vec2 { - x: 0.0, - y: position_data, - }, - }), + AxisType::MouseWheel(axis_type) => match *axis_type { + MouseWheelAxisType::X => self.send_mouse_wheel(position_data, 0.0), + MouseWheelAxisType::Y => self.send_mouse_wheel(0.0, position_data), + }, + AxisType::MouseMotion(axis_type) => match *axis_type { + MouseMotionAxisType::X => self.send_mouse_motion(position_data, 0.0), + MouseMotionAxisType::Y => self.send_mouse_motion(0.0, position_data), }, } } @@ -300,25 +231,9 @@ impl MockInput for MutableInputStreams<'_> { let input_to_release: UserInput = input.into(); let raw_inputs = input_to_release.raw_inputs(); - for button_type in raw_inputs.gamepad_buttons { - if let Some(gamepad) = gamepad { - self.gamepad_events - .send(GamepadEvent::Button(GamepadButtonChangedEvent { - gamepad, - button_type, - value: 1.0, - })); - } - } + self.send_gamepad_button_changed(gamepad, &raw_inputs); - for button in raw_inputs.keycodes { - self.keyboard_events.send(KeyboardInput { - scan_code: u32::MAX, - key_code: Some(button), - state: ButtonState::Released, - window: Entity::PLACEHOLDER, - }); - } + self.send_keyboard_input(ButtonState::Released, &raw_inputs); for button in raw_inputs.mouse_buttons { self.mouse_button_events.send(MouseButtonInput { @@ -342,6 +257,44 @@ impl MockInput for MutableInputStreams<'_> { } } +impl MutableInputStreams<'_> { + fn send_keyboard_input(&mut self, button_state: ButtonState, raw_inputs: &RawInputs) { + for button in raw_inputs.keycodes.iter() { + self.keyboard_events.send(KeyboardInput { + scan_code: u32::MAX, + key_code: Some(*button), + state: button_state, + window: Entity::PLACEHOLDER, + }); + } + } + + fn send_mouse_wheel(&mut self, x: f32, y: f32) { + // FIXME: MouseScrollUnit is not recorded and is always assumed to be Pixel + let unit = MouseScrollUnit::Pixel; + let window = Entity::PLACEHOLDER; + self.mouse_wheel.send(MouseWheel { unit, x, y, window }); + } + + fn send_mouse_motion(&mut self, x: f32, y: f32) { + let delta = Vec2::new(x, y); + self.mouse_motion.send(MouseMotion { delta }); + } + + fn send_gamepad_button_changed(&mut self, gamepad: Option, raw_inputs: &RawInputs) { + for button_type in raw_inputs.gamepad_buttons.iter() { + if let Some(gamepad) = gamepad { + self.gamepad_events + .send(GamepadEvent::Button(GamepadButtonChangedEvent { + gamepad, + button_type: *button_type, + value: 1.0, + })); + } + } + } +} + impl QueryInput for InputStreams<'_> { fn pressed(&self, input: impl Into) -> bool { self.input_pressed(&input.into()) diff --git a/src/input_streams.rs b/src/input_streams.rs index 2503074a..9c590728 100644 --- a/src/input_streams.rs +++ b/src/input_streams.rs @@ -1,6 +1,6 @@ //! Unified input streams for working with [`bevy::input`] data. -use bevy::ecs::prelude::{Events, ResMut, World}; +use bevy::ecs::prelude::{Event, Events, ResMut, World}; use bevy::ecs::system::SystemState; use bevy::input::{ gamepad::{Gamepad, GamepadAxis, GamepadButton, GamepadEvent, Gamepads}, @@ -8,11 +8,12 @@ use bevy::input::{ mouse::{MouseButton, MouseButtonInput, MouseMotion, MouseWheel}, Axis, Input, }; +use bevy::math::Vec2; use bevy::utils::HashSet; use crate::axislike::{ - AxisType, DualAxisData, MouseMotionAxisType, MouseWheelAxisType, SingleAxis, VirtualAxis, - VirtualDPad, + deadzone_axis_value, AxisType, DualAxisData, MouseMotionAxisType, MouseWheelAxisType, + SingleAxis, VirtualAxis, }; use crate::buttonlike::{MouseMotionDirection, MouseWheelDirection}; use crate::prelude::DualAxis; @@ -59,16 +60,8 @@ impl<'a> InputStreams<'a> { let mouse_wheel = world.resource::>(); let mouse_motion = world.resource::>(); - let mouse_wheel: Vec = mouse_wheel - .get_reader() - .read(mouse_wheel) - .cloned() - .collect(); - let mouse_motion: Vec = mouse_motion - .get_reader() - .read(mouse_motion) - .cloned() - .collect(); + let mouse_wheel: Vec = collect_events_cloned(mouse_wheel); + let mouse_motion: Vec = collect_events_cloned(mouse_motion); InputStreams { gamepad_buttons, @@ -92,19 +85,9 @@ impl<'a> InputStreams<'a> { match input { UserInput::Single(button) => self.button_pressed(*button), UserInput::Chord(buttons) => self.all_buttons_pressed(buttons), - UserInput::VirtualDPad(VirtualDPad { - up, - down, - left, - right, - }) => { - for button in [up, down, left, right] { - if self.button_pressed(*button) { - return true; - } - } - false - } + UserInput::VirtualDPad(dpad) => [&dpad.up, &dpad.down, &dpad.left, &dpad.right] + .into_iter() + .any(|button| self.button_pressed(*button)), UserInput::VirtualAxis(VirtualAxis { negative, positive }) => { self.button_pressed(*negative) || self.button_pressed(*positive) } @@ -114,55 +97,29 @@ impl<'a> InputStreams<'a> { /// Is at least one of the `inputs` pressed? #[must_use] pub fn any_pressed(&self, inputs: &HashSet) -> bool { - for input in inputs.iter() { - if self.input_pressed(input) { - return true; - } - } - // If none of the inputs matched, return false - false + inputs.iter().any(|input| self.input_pressed(input)) } /// Is the `button` pressed? #[must_use] pub fn button_pressed(&self, button: InputKind) -> bool { match button { - InputKind::DualAxis(axis) => { - let x_value = - self.input_value(&UserInput::Single(InputKind::SingleAxis(axis.x)), false); - let y_value = - self.input_value(&UserInput::Single(InputKind::SingleAxis(axis.y)), false); - - axis.deadzone - .deadzone_input_value(x_value, y_value) - .is_some() - } + InputKind::DualAxis(axis) => self.extract_dual_axis_data(&axis).is_some(), InputKind::SingleAxis(axis) => { - let value = self.input_value(&UserInput::Single(button), false); + let value = self.input_value(&button.into(), false); value < axis.negative_low || value > axis.positive_low } - InputKind::GamepadButton(gamepad_button) => { - if let Some(gamepad) = self.associated_gamepad { + InputKind::GamepadButton(button_type) => self + .associated_gamepad + .into_iter() + .chain(self.gamepads.iter()) + .any(|gamepad| { self.gamepad_buttons.pressed(GamepadButton { gamepad, - button_type: gamepad_button, + button_type, }) - } else { - for gamepad in self.gamepads.iter() { - if self.gamepad_buttons.pressed(GamepadButton { - gamepad, - button_type: gamepad_button, - }) { - // Return early if *any* gamepad is pressing this button - return true; - } - } - - // If we don't have the required data, fall back to false - false - } - } + }), InputKind::Keyboard(keycode) => { matches!(self.keycodes, Some(keycodes) if keycodes.pressed(keycode)) } @@ -182,51 +139,30 @@ impl<'a> InputStreams<'a> { return false; }; - let mut total_mouse_wheel_movement = 0.0; - + // The compiler will compile this into a direct f64 accumulation when opt-level >= 1. + // // PERF: this summing is computed for every individual input // This should probably be computed once, and then cached / read // Fix upstream! - for mouse_wheel_event in mouse_wheel { - total_mouse_wheel_movement += match mouse_wheel_direction { - MouseWheelDirection::Up | MouseWheelDirection::Down => mouse_wheel_event.y, - MouseWheelDirection::Left | MouseWheelDirection::Right => { - mouse_wheel_event.x - } - } - } - + let Vec2 { x, y } = mouse_wheel + .iter() + .map(|wheel| Vec2::new(wheel.x, wheel.y)) + .sum(); match mouse_wheel_direction { - MouseWheelDirection::Up | MouseWheelDirection::Right => { - total_mouse_wheel_movement > 0.0 - } - MouseWheelDirection::Down | MouseWheelDirection::Left => { - total_mouse_wheel_movement < 0.0 - } + MouseWheelDirection::Up => y > 0.0, + MouseWheelDirection::Down => y < 0.0, + MouseWheelDirection::Left => x < 0.0, + MouseWheelDirection::Right => x > 0.0, } } - // CLEANUP: refactor to share code with MouseWheel InputKind::MouseMotion(mouse_motion_direction) => { - let mut total_mouse_movement = 0.0; - - for mouse_motion_event in &self.mouse_motion { - total_mouse_movement += match mouse_motion_direction { - MouseMotionDirection::Up | MouseMotionDirection::Down => { - mouse_motion_event.delta.y - } - MouseMotionDirection::Left | MouseMotionDirection::Right => { - mouse_motion_event.delta.x - } - } - } - + // The compiler will compile this into a direct f64 accumulation when opt-level >= 1. + let Vec2 { x, y } = self.mouse_motion.iter().map(|motion| motion.delta).sum(); match mouse_motion_direction { - MouseMotionDirection::Up | MouseMotionDirection::Right => { - total_mouse_movement > 0.0 - } - MouseMotionDirection::Down | MouseMotionDirection::Left => { - total_mouse_movement < 0.0 - } + MouseMotionDirection::Up => y > 0.0, + MouseMotionDirection::Down => y < 0.0, + MouseMotionDirection::Left => x < 0.0, + MouseMotionDirection::Right => x > 0.0, } } } @@ -235,14 +171,7 @@ impl<'a> InputStreams<'a> { /// Are all of the `buttons` pressed? #[must_use] pub fn all_buttons_pressed(&self, buttons: &[InputKind]) -> bool { - for &button in buttons.iter() { - // If any of the appropriate inputs failed to match, the action is considered pressed - if !self.button_pressed(button) { - return false; - } - } - // If none of the inputs failed to match, return true - true + buttons.iter().all(|button| self.button_pressed(*button)) } /// Get the "value" of the input. @@ -258,13 +187,7 @@ impl<'a> InputStreams<'a> { /// If you need to ensure that this value is always in the range `[-1., 1.]`, /// be sure to clamp the returned data. pub fn input_value(&self, input: &UserInput, include_deadzone: bool) -> f32 { - let use_button_value = || -> f32 { - if self.input_pressed(input) { - 1.0 - } else { - 0.0 - } - }; + let use_button_value = || -> f32 { f32::from(self.input_pressed(input)) }; // Helper that takes the value returned by an axis and returns 0.0 if it is not within the // triggering range. @@ -274,12 +197,12 @@ impl<'a> InputStreams<'a> { return 0.0; } - let width = if value.is_sign_positive() { + let deadzone = if value.is_sign_positive() { axis.positive_low.abs() } else { axis.negative_low.abs() }; - value = value.signum() * (value.abs() - width).max(0.0) / (1.0 - width); + value = deadzone_axis_value(value, deadzone); } if axis.inverted { value *= -1.0; @@ -292,28 +215,20 @@ impl<'a> InputStreams<'a> { UserInput::Single(InputKind::SingleAxis(single_axis)) => { match single_axis.axis_type { AxisType::Gamepad(axis_type) => { - if let Some(gamepad) = self.associated_gamepad { - let value = self - .gamepad_axes + let get_gamepad_value = |gamepad: Gamepad| -> f32 { + self.gamepad_axes .get(GamepadAxis { gamepad, axis_type }) - .unwrap_or_default(); - + .unwrap_or_default() + }; + if let Some(gamepad) = self.associated_gamepad { + let value = get_gamepad_value(gamepad); value_in_axis_range(single_axis, value) } else { - for gamepad in self.gamepads.iter() { - let value = self - .gamepad_axes - .get(GamepadAxis { gamepad, axis_type }) - .unwrap_or_default(); - - // Return early if *any* gamepad is pressing this axis - if value != 0.0 { - return value_in_axis_range(single_axis, value); - } - } - - // If we don't have the required data, fall back to 0.0 - 0.0 + self.gamepads + .iter() + .map(get_gamepad_value) + .find(|value| *value != 0.0) + .map_or(0.0, |value| value_in_axis_range(single_axis, value)) } } AxisType::MouseWheel(axis_type) => { @@ -321,33 +236,30 @@ impl<'a> InputStreams<'a> { return 0.0; }; - let mut total_mouse_wheel_movement = 0.0; - - for mouse_wheel_event in mouse_wheel { - total_mouse_wheel_movement += match axis_type { - MouseWheelAxisType::X => mouse_wheel_event.x, - MouseWheelAxisType::Y => mouse_wheel_event.y, - } - } - value_in_axis_range(single_axis, total_mouse_wheel_movement) + // The compiler will compile this into a direct f64 accumulation when opt-level >= 1. + let Vec2 { x, y } = mouse_wheel + .iter() + .map(|wheel| Vec2::new(wheel.x, wheel.y)) + .sum(); + let movement = match axis_type { + MouseWheelAxisType::X => x, + MouseWheelAxisType::Y => y, + }; + value_in_axis_range(single_axis, movement) } - // CLEANUP: deduplicate code with MouseWheel AxisType::MouseMotion(axis_type) => { - let mut total_mouse_motion_movement = 0.0; - - for mouse_wheel_event in &self.mouse_motion { - total_mouse_motion_movement += match axis_type { - MouseMotionAxisType::X => mouse_wheel_event.delta.x, - MouseMotionAxisType::Y => mouse_wheel_event.delta.y, - } - } - value_in_axis_range(single_axis, total_mouse_motion_movement) + // The compiler will compile this into a direct f64 accumulation when opt-level >= 1. + let Vec2 { x, y } = self.mouse_motion.iter().map(|e| e.delta).sum(); + let movement = match axis_type { + MouseMotionAxisType::X => x, + MouseMotionAxisType::Y => y, + }; + value_in_axis_range(single_axis, movement) } } } - UserInput::VirtualAxis(VirtualAxis { negative, positive }) => { - self.input_value(&UserInput::Single(*positive), true).abs() - - self.input_value(&UserInput::Single(*negative), true).abs() + UserInput::VirtualAxis(axis) => { + self.extract_single_axis_data(&axis.positive, &axis.negative) } UserInput::Single(InputKind::DualAxis(_)) => { self.input_axis_pair(input).unwrap_or_default().length() @@ -364,18 +276,15 @@ impl<'a> InputStreams<'a> { value += match input { InputKind::SingleAxis(axis) => { has_axis = true; - self.input_value(&UserInput::Single(InputKind::SingleAxis(*axis)), true) + self.input_value(&InputKind::SingleAxis(*axis).into(), true) } InputKind::MouseWheel(axis) => { has_axis = true; - self.input_value(&UserInput::Single(InputKind::MouseWheel(*axis)), true) + self.input_value(&InputKind::MouseWheel(*axis).into(), true) } InputKind::MouseMotion(axis) => { has_axis = true; - self.input_value( - &UserInput::Single(InputKind::MouseMotion(*axis)), - true, - ) + self.input_value(&InputKind::MouseMotion(*axis).into(), true) } _ => 0.0, } @@ -389,32 +298,22 @@ impl<'a> InputStreams<'a> { } // This is required because upstream bevy::input still waffles about whether triggers are buttons or axes UserInput::Single(InputKind::GamepadButton(button_type)) => { - if let Some(gamepad) = self.associated_gamepad { - // Get the value from the registered gamepad + let get_gamepad_value = |gamepad: Gamepad| -> f32 { self.gamepad_button_axes .get(GamepadButton { gamepad, button_type: *button_type, }) .unwrap_or_else(use_button_value) + }; + if let Some(gamepad) = self.associated_gamepad { + get_gamepad_value(gamepad) } else { - for gamepad in self.gamepads.iter() { - let value = self - .gamepad_button_axes - .get(GamepadButton { - gamepad, - button_type: *button_type, - }) - .unwrap_or_else(use_button_value); - - // Return early if *any* gamepad is pressing this button - if value != 0.0 { - return value; - } - } - - // If we don't have the required data, fall back to 0.0 - 0.0 + self.gamepads + .iter() + .map(get_gamepad_value) + .find(|value| *value != 0.0) + .unwrap_or_default() } } _ => use_button_value(), @@ -424,8 +323,7 @@ impl<'a> InputStreams<'a> { /// Get the axis pair associated to the user input. /// /// If `input` is a chord, returns result of the first dual axis in the chord. - - /// If `input` is not a [`DualAxis`] or [`VirtualDPad`], returns [`None`]. + /// If `input` is not a [`DualAxis`] or [`VirtualDPad`](crate::axislike::VirtualDPad), returns [`None`]. /// /// # Warning /// @@ -450,36 +348,36 @@ impl<'a> InputStreams<'a> { UserInput::Single(InputKind::DualAxis(dual_axis)) => { Some(self.extract_dual_axis_data(dual_axis).unwrap_or_default()) } - UserInput::VirtualDPad(VirtualDPad { - up, - down, - left, - right, - }) => { - let x = self.input_value(&UserInput::Single(*right), true).abs() - - self.input_value(&UserInput::Single(*left), true).abs(); - let y = self.input_value(&UserInput::Single(*up), true).abs() - - self.input_value(&UserInput::Single(*down), true).abs(); + UserInput::VirtualDPad(dpad) => { + let x = self.extract_single_axis_data(&dpad.right, &dpad.left); + let y = self.extract_single_axis_data(&dpad.up, &dpad.down); Some(DualAxisData::new(x, y)) } _ => None, } } + fn extract_single_axis_data(&self, positive: &InputKind, negative: &InputKind) -> f32 { + let positive = self.input_value(&UserInput::Single(*positive), true); + let negative = self.input_value(&UserInput::Single(*negative), true); + + positive.abs() - negative.abs() + } + fn extract_dual_axis_data(&self, dual_axis: &DualAxis) -> Option { - let x = self.input_value( - &UserInput::Single(InputKind::SingleAxis(dual_axis.x)), - false, - ); - let y = self.input_value( - &UserInput::Single(InputKind::SingleAxis(dual_axis.y)), - false, - ); + let x = self.input_value(&dual_axis.x.into(), false); + let y = self.input_value(&dual_axis.y.into(), false); dual_axis.deadzone.deadzone_input_value(x, y) } } +// Clones and collects the received events into a `Vec`. +#[inline] +fn collect_events_cloned(events: &Events) -> Vec { + events.get_reader().read(events).cloned().collect() +} + /// A mutable collection of [`Input`] structs, which can be used for mocking user inputs. /// /// These are typically collected via a system from the [`World`] as resources. @@ -589,20 +487,8 @@ impl<'a> From> for InputStreams<'a> { keycodes: Some(mutable_streams.keycodes), scan_codes: Some(mutable_streams.scan_codes), mouse_buttons: Some(mutable_streams.mouse_buttons), - mouse_wheel: Some( - mutable_streams - .mouse_wheel - .get_reader() - .read(mutable_streams.mouse_wheel) - .cloned() - .collect(), - ), - mouse_motion: mutable_streams - .mouse_motion - .get_reader() - .read(mutable_streams.mouse_motion) - .cloned() - .collect(), + mouse_wheel: Some(collect_events_cloned(mutable_streams.mouse_wheel)), + mouse_motion: collect_events_cloned(mutable_streams.mouse_motion), associated_gamepad: mutable_streams.associated_gamepad, } } @@ -618,20 +504,8 @@ impl<'a> From<&'a MutableInputStreams<'a>> for InputStreams<'a> { keycodes: Some(mutable_streams.keycodes), scan_codes: Some(mutable_streams.scan_codes), mouse_buttons: Some(mutable_streams.mouse_buttons), - mouse_wheel: Some( - mutable_streams - .mouse_wheel - .get_reader() - .read(mutable_streams.mouse_wheel) - .cloned() - .collect(), - ), - mouse_motion: mutable_streams - .mouse_motion - .get_reader() - .read(mutable_streams.mouse_motion) - .cloned() - .collect(), + mouse_wheel: Some(collect_events_cloned(mutable_streams.mouse_wheel)), + mouse_motion: collect_events_cloned(mutable_streams.mouse_motion), associated_gamepad: mutable_streams.associated_gamepad, } } diff --git a/src/orientation.rs b/src/orientation.rs index 592f5c53..8c2aaf39 100644 --- a/src/orientation.rs +++ b/src/orientation.rs @@ -651,8 +651,9 @@ mod conversions { // for 1.0 and -1.0 can't be represented exactly, so our unit vectors start with an // approximate value and both `atan2` above and `from_radians` below magnify the // imprecision. So, we cheat. - const APPROX_SOUTH: f32 = -1.5707964; - const APPROX_NORTHWEST: f32 = 2.3561945; + use std::f32::consts::FRAC_PI_2; + const APPROX_SOUTH: f32 = -FRAC_PI_2; + const APPROX_NORTHWEST: f32 = 1.5 * FRAC_PI_2; if radians == APPROX_NORTHWEST { Rotation::from_degrees_int(135) } else if radians == APPROX_SOUTH { diff --git a/src/systems.rs b/src/systems.rs index bad94b99..72ca271c 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -201,10 +201,8 @@ pub fn generate_action_diffs( axis_pair: axis_pair.into(), }); previous_axis_pairs - .raw_entry_mut() - .from_key(&action) - .or_insert_with(|| (action.clone(), HashMap::default())) - .1 + .entry(action) + .or_insert_with(HashMap::default) .insert(maybe_entity, axis_pair.xy()); } None => { @@ -221,10 +219,8 @@ pub fn generate_action_diffs( } }); previous_values - .raw_entry_mut() - .from_key(&action) - .or_insert_with(|| (action.clone(), HashMap::default())) - .1 + .entry(action) + .or_insert_with(HashMap::default) .insert(maybe_entity, value); } } diff --git a/src/user_input.rs b/src/user_input.rs index e6a19c90..9867feb5 100644 --- a/src/user_input.rs +++ b/src/user_input.rs @@ -51,16 +51,10 @@ impl UserInput { /// If `inputs` has a length of 1, a [`UserInput::Single`] variant will be returned instead. pub fn chord(inputs: impl IntoIterator>) -> Self { // We can't just check the length unless we add an ExactSizeIterator bound :( - let mut length: u8 = 0; + let vec: Vec = inputs.into_iter().map(|input| input.into()).collect(); - let mut vec: Vec = Vec::default(); - for button in inputs { - length += 1; - vec.push(button.into()); - } - - match length { - 1 => UserInput::Single(*vec.first().unwrap()), + match vec.len() { + 1 => UserInput::Single(vec[0]), _ => UserInput::Chord(vec), } } @@ -70,12 +64,11 @@ impl UserInput { /// - A [`Single`][UserInput::Single] input returns 1 /// - A [`Chord`][UserInput::Chord] returns the number of buttons in the chord /// - A [`VirtualDPad`][UserInput::VirtualDPad] returns 1 + /// - A [`VirtualAxis`][UserInput::VirtualAxis] returns 1 pub fn len(&self) -> usize { match self { - UserInput::Single(_) => 1, UserInput::Chord(button_set) => button_set.len(), - UserInput::VirtualDPad { .. } => 1, - UserInput::VirtualAxis { .. } => 1, + _ => 1, } } @@ -104,44 +97,23 @@ impl UserInput { pub fn n_matching(&self, buttons: &HashSet) -> usize { match self { UserInput::Single(button) => usize::from(buttons.contains(button)), - UserInput::Chord(chord_buttons) => { - let mut n_matching = 0; - for button in buttons.iter() { - if chord_buttons.contains(button) { - n_matching += 1; - } - } - - n_matching - } - UserInput::VirtualDPad(VirtualDPad { - up, - down, - left, - right, - }) => { - let mut n_matching = 0; - for button in buttons.iter() { - for dpad_button in [up, down, left, right] { - if button == dpad_button { - n_matching += 1; - } - } - } - - n_matching + UserInput::Chord(chord_buttons) => buttons + .iter() + .filter(|button| chord_buttons.contains(button)) + .count(), + UserInput::VirtualDPad(dpad) => { + let dpad_buttons = [dpad.up, dpad.down, dpad.left, dpad.right]; + buttons + .iter() + .filter(|button| dpad_buttons.contains(button)) + .count() } UserInput::VirtualAxis(VirtualAxis { negative, positive }) => { - let mut n_matching = 0; - for button in buttons.iter() { - for dpad_button in [negative, positive] { - if button == dpad_button { - n_matching += 1; - } - } - } - - n_matching + let axis_buttons = [negative, positive]; + buttons + .iter() + .filter(|button| axis_buttons.contains(button)) + .count() } } } @@ -151,119 +123,22 @@ impl UserInput { let mut raw_inputs = RawInputs::default(); match self { - UserInput::Single(button) => match *button { - InputKind::DualAxis(dual_axis) => { - raw_inputs - .axis_data - .push((dual_axis.x.axis_type, dual_axis.x.value)); - raw_inputs - .axis_data - .push((dual_axis.y.axis_type, dual_axis.y.value)); - } - InputKind::SingleAxis(single_axis) => raw_inputs - .axis_data - .push((single_axis.axis_type, single_axis.value)), - InputKind::GamepadButton(button) => raw_inputs.gamepad_buttons.push(button), - InputKind::Keyboard(button) => raw_inputs.keycodes.push(button), - InputKind::KeyLocation(scan_code) => raw_inputs.scan_codes.push(scan_code), - InputKind::Modifier(modifier) => { - let key_codes = modifier.key_codes(); - raw_inputs.keycodes.push(key_codes[0]); - raw_inputs.keycodes.push(key_codes[1]); - } - InputKind::Mouse(button) => raw_inputs.mouse_buttons.push(button), - InputKind::MouseWheel(button) => raw_inputs.mouse_wheel.push(button), - InputKind::MouseMotion(button) => raw_inputs.mouse_motion.push(button), - }, + UserInput::Single(button) => { + raw_inputs.merge_input_data(button); + } UserInput::Chord(button_set) => { for button in button_set.iter() { - match *button { - InputKind::DualAxis(dual_axis) => { - raw_inputs - .axis_data - .push((dual_axis.x.axis_type, dual_axis.x.value)); - raw_inputs - .axis_data - .push((dual_axis.y.axis_type, dual_axis.y.value)); - } - InputKind::SingleAxis(single_axis) => raw_inputs - .axis_data - .push((single_axis.axis_type, single_axis.value)), - InputKind::GamepadButton(button) => raw_inputs.gamepad_buttons.push(button), - InputKind::Keyboard(button) => raw_inputs.keycodes.push(button), - InputKind::KeyLocation(scan_code) => raw_inputs.scan_codes.push(scan_code), - InputKind::Modifier(modifier) => { - let key_codes = modifier.key_codes(); - raw_inputs.keycodes.push(key_codes[0]); - raw_inputs.keycodes.push(key_codes[1]); - } - InputKind::Mouse(button) => raw_inputs.mouse_buttons.push(button), - InputKind::MouseWheel(button) => raw_inputs.mouse_wheel.push(button), - InputKind::MouseMotion(button) => raw_inputs.mouse_motion.push(button), - } + raw_inputs.merge_input_data(button); } } - UserInput::VirtualDPad(VirtualDPad { - up, - down, - left, - right, - }) => { - for button in [up, down, left, right] { - match *button { - InputKind::DualAxis(dual_axis) => { - raw_inputs - .axis_data - .push((dual_axis.x.axis_type, dual_axis.x.value)); - raw_inputs - .axis_data - .push((dual_axis.y.axis_type, dual_axis.y.value)); - } - InputKind::SingleAxis(single_axis) => raw_inputs - .axis_data - .push((single_axis.axis_type, single_axis.value)), - InputKind::GamepadButton(button) => raw_inputs.gamepad_buttons.push(button), - InputKind::Keyboard(button) => raw_inputs.keycodes.push(button), - InputKind::KeyLocation(scan_code) => raw_inputs.scan_codes.push(scan_code), - InputKind::Modifier(modifier) => { - let key_codes = modifier.key_codes(); - raw_inputs.keycodes.push(key_codes[0]); - raw_inputs.keycodes.push(key_codes[1]); - } - InputKind::Mouse(button) => raw_inputs.mouse_buttons.push(button), - InputKind::MouseWheel(button) => raw_inputs.mouse_wheel.push(button), - InputKind::MouseMotion(button) => raw_inputs.mouse_motion.push(button), - } + UserInput::VirtualDPad(dpad) => { + for button in [dpad.up, dpad.down, dpad.left, dpad.right] { + raw_inputs.merge_input_data(&button); } } UserInput::VirtualAxis(VirtualAxis { negative, positive }) => { - for button in [negative, positive] { - // todo: dedup with VirtualDPad? - match *button { - InputKind::DualAxis(dual_axis) => { - raw_inputs - .axis_data - .push((dual_axis.x.axis_type, dual_axis.x.value)); - raw_inputs - .axis_data - .push((dual_axis.y.axis_type, dual_axis.y.value)); - } - InputKind::SingleAxis(single_axis) => raw_inputs - .axis_data - .push((single_axis.axis_type, single_axis.value)), - InputKind::GamepadButton(button) => raw_inputs.gamepad_buttons.push(button), - InputKind::Keyboard(button) => raw_inputs.keycodes.push(button), - InputKind::KeyLocation(scan_code) => raw_inputs.scan_codes.push(scan_code), - InputKind::Modifier(modifier) => { - let key_codes = modifier.key_codes(); - raw_inputs.keycodes.push(key_codes[0]); - raw_inputs.keycodes.push(key_codes[1]); - } - InputKind::Mouse(button) => raw_inputs.mouse_buttons.push(button), - InputKind::MouseWheel(button) => raw_inputs.mouse_wheel.push(button), - InputKind::MouseMotion(button) => raw_inputs.mouse_motion.push(button), - } - } + raw_inputs.merge_input_data(negative); + raw_inputs.merge_input_data(positive); } }; @@ -502,6 +377,30 @@ pub struct RawInputs { pub axis_data: Vec<(AxisType, Option)>, } +impl RawInputs { + /// Merges the data from the given `input_kind` into `self`. + fn merge_input_data(&mut self, input_kind: &InputKind) { + match *input_kind { + InputKind::DualAxis(DualAxis { x, y, .. }) => { + self.axis_data.push((x.axis_type, x.value)); + self.axis_data.push((y.axis_type, y.value)); + } + InputKind::SingleAxis(single_axis) => self + .axis_data + .push((single_axis.axis_type, single_axis.value)), + InputKind::GamepadButton(button) => self.gamepad_buttons.push(button), + InputKind::Keyboard(button) => self.keycodes.push(button), + InputKind::KeyLocation(scan_code) => self.scan_codes.push(scan_code), + InputKind::Modifier(modifier) => { + self.keycodes.extend_from_slice(&modifier.key_codes()); + } + InputKind::Mouse(button) => self.mouse_buttons.push(button), + InputKind::MouseWheel(button) => self.mouse_wheel.push(button), + InputKind::MouseMotion(button) => self.mouse_motion.push(button), + } + } +} + #[cfg(test)] impl RawInputs { fn from_keycode(keycode: KeyCode) -> RawInputs { diff --git a/tests/action_diffs.rs b/tests/action_diffs.rs index 7e448b55..c8a434c0 100644 --- a/tests/action_diffs.rs +++ b/tests/action_diffs.rs @@ -73,12 +73,11 @@ fn send_action_diff(app: &mut App, action_diff: ActionDiffEvent) { 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(); - match action_diff_event_reader.read(action_diff_events).next() { - Some(action_diff) => panic!( + if let Some(action_diff) = action_diff_event_reader.read(action_diff_events).next() { + panic!( "Expected no `ActionDiff` variants. Received: {:?}", action_diff - ), - None => {} + ) } } @@ -103,23 +102,23 @@ fn assert_action_diff_received(app: &mut App, action_diff_event: ActionDiffEvent match action_diff_event.action_diffs.first().unwrap().clone() { ActionDiff::Pressed { action } => { assert!(action_state.pressed(&action)); - assert!(action_state.value(&action) == 1.); + assert_eq!(action_state.value(&action), 1.); } ActionDiff::Released { action } => { assert!(action_state.released(&action)); - assert!(action_state.value(&action) == 0.); + assert_eq!(action_state.value(&action), 0.); assert!(action_state.axis_pair(&action).is_none()); } ActionDiff::ValueChanged { action, value } => { assert!(action_state.pressed(&action)); - assert!(action_state.value(&action) == value); + assert_eq!(action_state.value(&action), value); } ActionDiff::AxisPairChanged { action, axis_pair } => { assert!(action_state.pressed(&action)); match action_state.axis_pair(&action) { Some(axis_pair_data) => { - assert!(axis_pair_data.xy() == axis_pair); - assert!(action_state.value(&action) == axis_pair_data.xy().length()); + assert_eq!(axis_pair_data.xy(), axis_pair); + assert_eq!(action_state.value(&action), axis_pair_data.xy().length()); } None => panic!("Expected an `AxisPair` variant. Received none."), } diff --git a/tests/gamepad_axis.rs b/tests/gamepad_axis.rs index 97599cab..bb717fa8 100644 --- a/tests/gamepad_axis.rs +++ b/tests/gamepad_axis.rs @@ -233,7 +233,7 @@ fn game_pad_single_axis() { app.update(); let action_state = app.world.resource::>(); assert!(action_state.pressed(&AxislikeTestAction::X)); - assert!(action_state.value(&AxislikeTestAction::X) == 0.11111112); + assert_eq!(action_state.value(&AxislikeTestAction::X), 0.11111112); } #[test] @@ -264,7 +264,7 @@ fn game_pad_single_axis_inverted() { app.update(); let action_state = app.world.resource::>(); assert!(action_state.pressed(&AxislikeTestAction::X)); - assert!(action_state.value(&AxislikeTestAction::X) == -1.0); + assert_eq!(action_state.value(&AxislikeTestAction::X), -1.0); // -X let input = SingleAxis { @@ -279,7 +279,7 @@ fn game_pad_single_axis_inverted() { app.update(); let action_state = app.world.resource::>(); assert!(action_state.pressed(&AxislikeTestAction::X)); - assert!(action_state.value(&AxislikeTestAction::X) == 1.0); + assert_eq!(action_state.value(&AxislikeTestAction::X), 1.0); // +Y let input = SingleAxis { @@ -294,7 +294,7 @@ fn game_pad_single_axis_inverted() { app.update(); let action_state = app.world.resource::>(); assert!(action_state.pressed(&AxislikeTestAction::Y)); - assert!(action_state.value(&AxislikeTestAction::Y) == -1.0); + assert_eq!(action_state.value(&AxislikeTestAction::Y), -1.0); // -Y let input = SingleAxis { @@ -309,7 +309,7 @@ fn game_pad_single_axis_inverted() { app.update(); let action_state = app.world.resource::>(); assert!(action_state.pressed(&AxislikeTestAction::Y)); - assert!(action_state.value(&AxislikeTestAction::Y) == 1.0); + assert_eq!(action_state.value(&AxislikeTestAction::Y), 1.0); } #[test]