diff --git a/src/conflicting_inputs.rs b/src/conflicting_inputs.rs new file mode 100644 index 00000000..21f6424a --- /dev/null +++ b/src/conflicting_inputs.rs @@ -0,0 +1,90 @@ +//! 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 temporarily disabled by setting their fields to `false` +/// and will be re-enabled after handling all conflicting inputs. +/// +/// 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) { +/// tracking_input.keyboard = false; +/// } +/// +/// let mut app = App::new(); +/// +/// // Remember to set +/// app.add_systems(PreUpdate, disable_keyboard.in_set(InputManagerSystem::PreUpdate)); +/// ``` +#[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, +) { + 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, +) { + 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; + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f0a2bdd5..b7ea08b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -45,9 +46,9 @@ pub mod prelude { pub use crate::input_mocking::{MockInput, QueryInput}; pub use crate::user_input::{InputKind, Modifier, UserInput}; + pub use crate::conflicting_inputs::TrackingInputType; pub use crate::plugin::ToggleActions; pub use crate::plugin::{InputManagerPlugin, InputManagerSystem}; - pub use crate::systems::TrackingInputType; pub use crate::{Actionlike, InputManagerBundle}; } diff --git a/src/plugin.rs b/src/plugin.rs index 24a8d643..d8e6d570 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -7,6 +7,11 @@ use crate::axislike::{ }; use crate::buttonlike::{MouseMotionDirection, MouseWheelDirection}; use crate::clashing_inputs::ClashStrategy; +#[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}; diff --git a/src/systems.rs b/src/systems.rs index 74247ed4..635e57fa 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -2,6 +2,7 @@ #[cfg(feature = "ui")] use crate::action_driver::ActionStateDriver; +use crate::conflicting_inputs::TrackingInputType; use crate::{ action_state::ActionState, clashing_inputs::ClashStrategy, input_map::InputMap, input_streams::InputStreams, plugin::ToggleActions, Actionlike, @@ -58,86 +59,6 @@ pub fn tick_action_state( *stored_previous_instant = time.last_update(); } -/// Flags to enable specific input type tracking. -/// -/// They can be temporarily disabled by setting their fields to `false` -/// and will be re-enabled after handling all conflicting inputs. -/// -/// 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) { -/// tracking_input.keyboard = false; -/// } -/// -/// let mut app = App::new(); -/// -/// // Remember to set -/// app.add_systems(PreUpdate, disable_keyboard.in_set(InputManagerSystem::PreUpdate)); -/// ``` -#[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, -) { - 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; - } - } -} - -/// 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, -) { - 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; - } - } -} - /// Fetches all of the relevant [`ButtonInput`] resources to update [`ActionState`] according to the [`InputMap`]. /// /// Missing resources will be ignored, and treated as if none of the corresponding inputs were pressed. @@ -157,14 +78,13 @@ pub fn update_action_state( input_map: Option>>, mut query: Query<(&mut ActionState, &InputMap)>, ) { + // Track these inputs only when the corresponding flags are enabled let gamepad_buttons = tracking_input.gamepad.then(|| gamepad_buttons.into_inner()); let gamepad_button_axes = tracking_input .gamepad .then(|| gamepad_button_axes.into_inner()); let gamepad_axes = tracking_input.gamepad.then(|| gamepad_axes.into_inner()); let gamepads = tracking_input.gamepad.then(|| gamepads.into_inner()); - - // Track these inputs only when the corresponding flags are enabled let keycodes: Option<&ButtonInput> = tracking_input .keyboard .then(|| keycodes.map(|keycodes| keycodes.into_inner()))