Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runtime disabling of specific input type tracking #478

Closed
wants to merge 10 commits into from
Closed
5 changes: 5 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
- by default, this library will prioritize `bevy::ui`.
- if you want to disable this priority, add the newly added `no_ui_priority` feature to your configuration.

### Usability

- allowed disabling of specific input type tracking via `TrackingInputType` resource
- added run conditions that are active when the `TrackingInputType` resource is enabled for specific input types

## Version 0.13.0

### Breaking Changes
Expand Down
20 changes: 19 additions & 1 deletion src/common_conditions.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Run conditions for actions.

use crate::{prelude::ActionState, Actionlike};
use crate::{
prelude::{ActionState, TrackingInputType},
Actionlike,
};
use bevy::prelude::Res;

/// Stateful run condition that can be toggled via an action press using [`ActionState::just_pressed`].
Expand Down Expand Up @@ -38,3 +41,18 @@ where
{
move |action_state: Res<ActionState<A>>| action_state.just_released(&action)
}

/// Run condition that is active if [`TrackingInputType`] is enabled for gamepad.
pub fn tracking_gamepad_input(tracking_input: Res<TrackingInputType>) -> bool {
tracking_input.gamepad
}

/// Run condition that is active if [`TrackingInputType`] is enabled for keyboard.
pub fn tracking_keyboard_input(tracking_input: Res<TrackingInputType>) -> bool {
tracking_input.keyboard
}

/// Run condition that is active if [`TrackingInputType`] is enabled for mouse.
pub fn tracking_mouse_input(tracking_input: Res<TrackingInputType>) -> bool {
tracking_input.mouse
}
87 changes: 87 additions & 0 deletions src/conflicting_inputs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Handles conflicting inputs from outside

use bevy::prelude::*;
#[cfg(feature = "egui")]
use bevy_egui::EguiContext;

/// Flags to enable specific input type tracking.
///
/// They can be disabled by setting their fields to `false`.
/// If you are dealing with conflicting input from other crates, this might be useful.
///
/// # Examples
///
/// ```
/// use bevy::prelude::*;
/// use leafwing_input_manager::prelude::*;
///
/// pub fn disable_keyboard(mut tracking_input: ResMut<TrackingInputType>) {
/// tracking_input.keyboard = false;
/// }
///
/// let mut app = App::new();
///
/// app.add_systems(PreUpdate, disable_keyboard);
/// ```
#[derive(Resource)]
pub struct TrackingInputType {
/// Is tracking gamepad input?
pub gamepad: bool,

/// Is tracking keyboard input?
pub keyboard: bool,

/// Is tracking mouse input?
pub mouse: bool,
}

impl Default for TrackingInputType {
fn default() -> Self {
Self {
gamepad: true,
keyboard: true,
mouse: true,
}
}
}

/// Allow `bevy::ui` to take priority over actions when processing inputs.
#[cfg(all(feature = "ui", not(feature = "no_ui_priority")))]
pub fn prioritize_ui_inputs(
query_interactions: Query<&Interaction>,
mut tracking_input: ResMut<TrackingInputType>,
) {
for interaction in query_interactions.iter() {
// If use clicks on a button, do not apply them to the game state
if *interaction != Interaction::None {
tracking_input.mouse = false;
return;
}
}
}

/// Allow `egui` to take priority over actions when processing inputs.
#[cfg(feature = "egui")]
pub fn prioritize_egui_inputs(
mut query_egui_context: Query<(Entity, &'static mut EguiContext)>,
mut tracking_input: ResMut<TrackingInputType>,
) {
for (_, mut egui_context) in query_egui_context.iter_mut() {
let context = egui_context.get_mut();

// If egui wants to own inputs, don't also apply them to the game state
if context.wants_keyboard_input() {
tracking_input.keyboard = false;
}

// `wants_pointer_input` sometimes returns `false` after clicking or holding a button over a widget,
// so `is_pointer_over_area` is also needed.
if context.is_pointer_over_area() || context.wants_pointer_input() {
tracking_input.mouse = false;
}

if !tracking_input.keyboard && !tracking_input.mouse {
return;
}
}
}
54 changes: 30 additions & 24 deletions src/input_streams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ use crate::user_input::{InputKind, UserInput};
#[derive(Debug, Clone)]
pub struct InputStreams<'a> {
/// A [`GamepadButton`] [`Input`](ButtonInput) stream
pub gamepad_buttons: &'a ButtonInput<GamepadButton>,
pub gamepad_buttons: Option<&'a ButtonInput<GamepadButton>>,
/// A [`GamepadButton`] [`Axis`] stream
pub gamepad_button_axes: &'a Axis<GamepadButton>,
pub gamepad_button_axes: Option<&'a Axis<GamepadButton>>,
/// A [`GamepadAxis`] [`Axis`] stream
pub gamepad_axes: &'a Axis<GamepadAxis>,
pub gamepad_axes: Option<&'a Axis<GamepadAxis>>,
/// A list of registered gamepads
pub gamepads: &'a Gamepads,
pub gamepads: Option<&'a Gamepads>,
/// A [`KeyCode`] [`ButtonInput`] stream
pub keycodes: Option<&'a ButtonInput<KeyCode>>,
/// A [`MouseButton`] [`Input`](ButtonInput) stream
Expand Down Expand Up @@ -61,10 +61,10 @@ impl<'a> InputStreams<'a> {
let mouse_motion: Vec<MouseMotion> = collect_events_cloned(mouse_motion);

InputStreams {
gamepad_buttons,
gamepad_button_axes,
gamepad_axes,
gamepads,
gamepad_buttons: Some(gamepad_buttons),
gamepad_button_axes: Some(gamepad_button_axes),
gamepad_axes: Some(gamepad_axes),
gamepads: Some(gamepads),
keycodes,
mouse_buttons,
mouse_wheel: Some(mouse_wheel),
Expand Down Expand Up @@ -109,11 +109,13 @@ impl<'a> InputStreams<'a> {
InputKind::GamepadButton(button_type) => self
.associated_gamepad
.into_iter()
.chain(self.gamepads.iter())
.chain(self.gamepads.iter().flat_map(|gamepads| gamepads.iter()))
.any(|gamepad| {
self.gamepad_buttons.pressed(GamepadButton {
gamepad,
button_type,
self.gamepad_buttons.is_some_and(|buttons| {
buttons.pressed(GamepadButton {
gamepad,
button_type,
})
})
}),
InputKind::PhysicalKey(keycode) => {
Expand Down Expand Up @@ -210,7 +212,7 @@ impl<'a> InputStreams<'a> {
AxisType::Gamepad(axis_type) => {
let get_gamepad_value = |gamepad: Gamepad| -> f32 {
self.gamepad_axes
.get(GamepadAxis { gamepad, axis_type })
.and_then(|axes| axes.get(GamepadAxis { gamepad, axis_type }))
.unwrap_or_default()
};
if let Some(gamepad) = self.associated_gamepad {
Expand All @@ -219,6 +221,7 @@ impl<'a> InputStreams<'a> {
} else {
self.gamepads
.iter()
.flat_map(|gamepads| gamepads.iter())
.map(get_gamepad_value)
.find(|value| *value != 0.0)
.map_or(0.0, |value| value_in_axis_range(single_axis, value))
Expand Down Expand Up @@ -293,9 +296,11 @@ impl<'a> InputStreams<'a> {
UserInput::Single(InputKind::GamepadButton(button_type)) => {
let get_gamepad_value = |gamepad: Gamepad| -> f32 {
self.gamepad_button_axes
.get(GamepadButton {
gamepad,
button_type: *button_type,
.and_then(|axes| {
axes.get(GamepadButton {
gamepad,
button_type: *button_type,
})
})
.unwrap_or_else(use_button_value)
};
Expand All @@ -304,6 +309,7 @@ impl<'a> InputStreams<'a> {
} else {
self.gamepads
.iter()
.flat_map(|gamepads| gamepads.iter())
.map(get_gamepad_value)
.find(|value| *value != 0.0)
.unwrap_or_default()
Expand Down Expand Up @@ -468,10 +474,10 @@ impl<'a> MutableInputStreams<'a> {
impl<'a> From<MutableInputStreams<'a>> for InputStreams<'a> {
fn from(mutable_streams: MutableInputStreams<'a>) -> Self {
InputStreams {
gamepad_buttons: mutable_streams.gamepad_buttons,
gamepad_button_axes: mutable_streams.gamepad_button_axes,
gamepad_axes: mutable_streams.gamepad_axes,
gamepads: mutable_streams.gamepads,
gamepad_buttons: Some(mutable_streams.gamepad_buttons),
gamepad_button_axes: Some(mutable_streams.gamepad_button_axes),
gamepad_axes: Some(mutable_streams.gamepad_axes),
gamepads: Some(mutable_streams.gamepads),
keycodes: Some(mutable_streams.keycodes),
mouse_buttons: Some(mutable_streams.mouse_buttons),
mouse_wheel: Some(collect_events_cloned(mutable_streams.mouse_wheel)),
Expand All @@ -484,10 +490,10 @@ impl<'a> From<MutableInputStreams<'a>> for InputStreams<'a> {
impl<'a> From<&'a MutableInputStreams<'a>> for InputStreams<'a> {
fn from(mutable_streams: &'a MutableInputStreams<'a>) -> Self {
InputStreams {
gamepad_buttons: mutable_streams.gamepad_buttons,
gamepad_button_axes: mutable_streams.gamepad_button_axes,
gamepad_axes: mutable_streams.gamepad_axes,
gamepads: mutable_streams.gamepads,
gamepad_buttons: Some(mutable_streams.gamepad_buttons),
gamepad_button_axes: Some(mutable_streams.gamepad_button_axes),
gamepad_axes: Some(mutable_streams.gamepad_axes),
gamepads: Some(mutable_streams.gamepads),
keycodes: Some(mutable_streams.keycodes),
mouse_buttons: Some(mutable_streams.mouse_buttons),
mouse_wheel: Some(collect_events_cloned(mutable_streams.mouse_wheel)),
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod axislike;
pub mod buttonlike;
pub mod clashing_inputs;
pub mod common_conditions;
pub mod conflicting_inputs;
mod display_impl;
pub mod errors;
pub mod input_map;
Expand Down Expand Up @@ -45,8 +46,9 @@ pub mod prelude {
pub use crate::input_mocking::{MockInput, QueryInput};
pub use crate::user_input::{InputKind, Modifier, UserInput};

pub use crate::plugin::InputManagerPlugin;
pub use crate::conflicting_inputs::TrackingInputType;
pub use crate::plugin::ToggleActions;
pub use crate::plugin::{InputManagerPlugin, InputManagerSystem};
pub use crate::{Actionlike, InputManagerBundle};
}

Expand Down
37 changes: 37 additions & 0 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ use crate::axislike::{
};
use crate::buttonlike::{MouseMotionDirection, MouseWheelDirection};
use crate::clashing_inputs::ClashStrategy;
#[cfg(feature = "egui")]
use crate::common_conditions::tracking_keyboard_input;
#[cfg(any(all(feature = "ui", not(feature = "no_ui_priority")), feature = "egui"))]
use crate::common_conditions::tracking_mouse_input;
#[cfg(feature = "egui")]
use crate::conflicting_inputs::prioritize_egui_inputs;
#[cfg(all(feature = "ui", not(feature = "no_ui_priority")))]
use crate::conflicting_inputs::prioritize_ui_inputs;
use crate::conflicting_inputs::TrackingInputType;
use crate::input_map::InputMap;
use crate::timing::Timing;
use crate::user_input::{InputKind, Modifier, UserInput};
Expand Down Expand Up @@ -99,6 +108,7 @@ impl<A: Actionlike + TypePath> Plugin for InputManagerPlugin<A> {
tick_action_state::<A>
.run_if(run_if_enabled::<A>)
.in_set(InputManagerSystem::Tick)
.after(InputManagerSystem::PreUpdate)
.before(InputManagerSystem::Update),
)
.add_systems(
Expand All @@ -116,6 +126,27 @@ impl<A: Actionlike + TypePath> Plugin for InputManagerPlugin<A> {
.in_set(InputManagerSystem::Update),
);

#[cfg(all(feature = "ui", not(feature = "no_ui_priority")))]
app.add_systems(
PreUpdate,
prioritize_ui_inputs
.run_if(tracking_mouse_input)
.in_set(InputManagerSystem::PreUpdate),
);

#[cfg(feature = "egui")]
app.add_systems(
PreUpdate,
prioritize_egui_inputs
.run_if(tracking_keyboard_input.or_else(tracking_mouse_input))
.in_set(InputManagerSystem::PreUpdate),
);

app.configure_sets(
PreUpdate,
InputManagerSystem::PreUpdate.before(InputManagerSystem::Update),
);

app.configure_sets(PreUpdate, InputManagerSystem::Update.after(InputSystem));

#[cfg(feature = "egui")]
Expand Down Expand Up @@ -179,6 +210,7 @@ impl<A: Actionlike + TypePath> Plugin for InputManagerPlugin<A> {
.register_type::<MouseWheelDirection>()
.register_type::<MouseMotionDirection>()
// Resources
.init_resource::<TrackingInputType>()
.init_resource::<ToggleActions<A>>()
.init_resource::<ClashStrategy>();
}
Expand Down Expand Up @@ -225,6 +257,11 @@ impl<A: Actionlike> Default for ToggleActions<A> {
/// `Reset` must occur before `Update`
#[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)]
pub enum InputManagerSystem {
/// Performs actions before [`InputManagerSystem::Update`]
///
/// This is useful for temporarily disabling [`TrackingInputType`],
/// and it will be re-enabled after handling all conflicting inputs
PreUpdate,
Shute052 marked this conversation as resolved.
Show resolved Hide resolved
/// Advances action timers.
///
/// Cleans up the state of the input manager, clearing `just_pressed` and just_released`
Expand Down
Loading
Loading