diff --git a/RELEASES.md b/RELEASES.md index 381d3f07..2ec08f6c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -6,6 +6,7 @@ - Changed `entity` field of `ActionStateDriver` to `targets: ActionStateDriverTarget` with variants for 0, 1, or multiple targets, to allow for one driver to update multiple entities if needed. +- Added builder-style functions to `SingleAxis`, `DualAxis`, and `VirtualDPad` that invert their output values, allowing, for example, binding inverted camera controls. ### Docs diff --git a/src/axislike.rs b/src/axislike.rs index 17dff9c4..f980f64e 100644 --- a/src/axislike.rs +++ b/src/axislike.rs @@ -28,6 +28,8 @@ pub struct SingleAxis { pub positive_low: f32, /// Any axis value lower than this will trigger the input. pub negative_low: f32, + /// Whether to invert output values from this axis. + pub inverted: bool, /// The target value for this input, used for input mocking. /// /// WARNING: this field is ignored for the sake of [`Eq`] and [`Hash`](std::hash::Hash) @@ -42,6 +44,7 @@ impl SingleAxis { axis_type: axis_type.into(), positive_low: threshold, negative_low: -threshold, + inverted: false, value: None, } } @@ -56,6 +59,7 @@ impl SingleAxis { axis_type: axis_type.into(), positive_low: 0.0, negative_low: 0.0, + inverted: false, value: Some(value), } } @@ -67,6 +71,7 @@ impl SingleAxis { axis_type: AxisType::MouseWheel(MouseWheelAxisType::X), positive_low: 0., negative_low: 0., + inverted: false, value: None, } } @@ -78,6 +83,7 @@ impl SingleAxis { axis_type: AxisType::MouseWheel(MouseWheelAxisType::Y), positive_low: 0., negative_low: 0., + inverted: false, value: None, } } @@ -89,6 +95,7 @@ impl SingleAxis { axis_type: AxisType::MouseMotion(MouseMotionAxisType::X), positive_low: 0., negative_low: 0., + inverted: false, value: None, } } @@ -100,6 +107,7 @@ impl SingleAxis { axis_type: AxisType::MouseMotion(MouseMotionAxisType::Y), positive_low: 0., negative_low: 0., + inverted: false, value: None, } } @@ -112,6 +120,7 @@ impl SingleAxis { axis_type: axis_type.into(), negative_low: threshold, positive_low: f32::MAX, + inverted: false, value: None, } } @@ -124,6 +133,7 @@ impl SingleAxis { axis_type: axis_type.into(), negative_low: f32::MIN, positive_low: threshold, + inverted: false, value: None, } } @@ -135,6 +145,13 @@ impl SingleAxis { self.positive_low = deadzone; self } + + /// Returns this [`SingleAxis`] inverted. + #[must_use] + pub fn inverted(mut self) -> Self { + self.inverted = !self.inverted; + self + } } impl PartialEq for SingleAxis { @@ -250,6 +267,28 @@ impl DualAxis { self.y = self.y.with_deadzone(deadzone); self } + + /// Returns this [`DualAxis`] with an inverted X-axis. + #[must_use] + pub fn inverted_x(mut self) -> DualAxis { + self.x = self.x.inverted(); + self + } + + /// Returns this [`DualAxis`] with an inverted Y-axis. + #[must_use] + pub fn inverted_y(mut self) -> DualAxis { + self.y = self.y.inverted(); + self + } + + /// Returns this [`DualAxis`] with both axes inverted. + #[must_use] + pub fn inverted(mut self) -> DualAxis { + self.x = self.x.inverted(); + self.y = self.y.inverted(); + self + } } #[allow(clippy::doc_markdown)] // False alarm because it thinks DPad is an un-quoted item @@ -339,6 +378,25 @@ impl VirtualDPad { right: InputKind::MouseMotion(MouseMotionDirection::Right), } } + + /// Returns this [`VirtualDPad`] but with `up` and `down` swapped. + pub fn inverted_y(mut self) -> Self { + std::mem::swap(&mut self.up, &mut self.down); + self + } + + /// Returns this [`VirtualDPad`] but with `left` and `right` swapped. + pub fn inverted_x(mut self) -> Self { + std::mem::swap(&mut self.left, &mut self.right); + self + } + + /// Returns this [`VirtualDPad`] but with inverted inputs. + pub fn inverted(mut self) -> Self { + std::mem::swap(&mut self.up, &mut self.down); + std::mem::swap(&mut self.left, &mut self.right); + self + } } /// A virtual Axis that you can get a value between -1 and 1 from. @@ -405,6 +463,13 @@ impl VirtualAxis { positive: InputKind::GamepadButton(GamepadButtonType::DPadUp), } } + + /// Returns this [`VirtualAxis`] but with flipped positive/negative inputs. + #[must_use] + pub fn inverted(mut self) -> Self { + std::mem::swap(&mut self.positive, &mut self.negative); + self + } } /// The type of axis used by a [`UserInput`](crate::user_input::UserInput). diff --git a/src/input_streams.rs b/src/input_streams.rs index ae593ba9..1f5366bb 100644 --- a/src/input_streams.rs +++ b/src/input_streams.rs @@ -261,6 +261,8 @@ impl<'a> InputStreams<'a> { let value_in_axis_range = |axis: &SingleAxis, value: f32| -> f32 { if value >= axis.negative_low && value <= axis.positive_low { 0.0 + } else if axis.inverted { + -value } else { value } diff --git a/tests/gamepad_axis.rs b/tests/gamepad_axis.rs index 115a101d..d4949416 100644 --- a/tests/gamepad_axis.rs +++ b/tests/gamepad_axis.rs @@ -79,6 +79,7 @@ fn game_pad_single_axis_mocking() { value: Some(-1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); @@ -98,12 +99,14 @@ fn game_pad_dual_axis_mocking() { value: Some(1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }, y: SingleAxis { axis_type: AxisType::Gamepad(GamepadAxisType::LeftStickY), value: Some(0.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }, }; app.send_input(input); @@ -132,6 +135,7 @@ fn game_pad_single_axis() { value: Some(1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -144,6 +148,7 @@ fn game_pad_single_axis() { value: Some(-1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -156,6 +161,7 @@ fn game_pad_single_axis() { value: Some(1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -168,6 +174,7 @@ fn game_pad_single_axis() { value: Some(-1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -181,6 +188,7 @@ fn game_pad_single_axis() { // Usually a small deadzone threshold will be set positive_low: 0.1, negative_low: 0.1, + inverted: false, }; app.send_input(input); app.update(); @@ -193,6 +201,7 @@ fn game_pad_single_axis() { value: None, positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); diff --git a/tests/mouse_motion.rs b/tests/mouse_motion.rs index e48316f7..9e33341a 100644 --- a/tests/mouse_motion.rs +++ b/tests/mouse_motion.rs @@ -74,6 +74,7 @@ fn mouse_motion_single_axis_mocking() { value: Some(-1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); @@ -93,12 +94,14 @@ fn mouse_motion_dual_axis_mocking() { value: Some(1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }, y: SingleAxis { axis_type: AxisType::MouseMotion(MouseMotionAxisType::Y), value: Some(0.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }, }; app.send_input(input); @@ -166,6 +169,7 @@ fn mouse_motion_single_axis() { value: Some(1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -178,6 +182,7 @@ fn mouse_motion_single_axis() { value: Some(-1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -190,6 +195,7 @@ fn mouse_motion_single_axis() { value: Some(1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -202,6 +208,7 @@ fn mouse_motion_single_axis() { value: Some(-1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -215,6 +222,7 @@ fn mouse_motion_single_axis() { // Usually a small deadzone threshold will be set positive_low: 0.1, negative_low: 0.1, + inverted: false, }; app.send_input(input); app.update(); @@ -227,6 +235,7 @@ fn mouse_motion_single_axis() { value: None, positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); diff --git a/tests/mouse_wheel.rs b/tests/mouse_wheel.rs index 40536b24..5e420cb0 100644 --- a/tests/mouse_wheel.rs +++ b/tests/mouse_wheel.rs @@ -74,6 +74,7 @@ fn mouse_wheel_single_axis_mocking() { value: Some(-1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); @@ -93,12 +94,14 @@ fn mouse_wheel_dual_axis_mocking() { value: Some(1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }, y: SingleAxis { axis_type: AxisType::MouseWheel(MouseWheelAxisType::Y), value: Some(0.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }, }; app.send_input(input); @@ -166,6 +169,7 @@ fn mouse_wheel_single_axis() { value: Some(1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -178,6 +182,7 @@ fn mouse_wheel_single_axis() { value: Some(-1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -190,6 +195,7 @@ fn mouse_wheel_single_axis() { value: Some(1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -202,6 +208,7 @@ fn mouse_wheel_single_axis() { value: Some(-1.), positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update(); @@ -215,6 +222,7 @@ fn mouse_wheel_single_axis() { // Usually a small deadzone threshold will be set positive_low: 0.1, negative_low: 0.1, + inverted: false, }; app.send_input(input); app.update(); @@ -227,6 +235,7 @@ fn mouse_wheel_single_axis() { value: None, positive_low: 0.0, negative_low: 0.0, + inverted: false, }; app.send_input(input); app.update();