Skip to content

Commit

Permalink
Add BlockBy::events_only
Browse files Browse the repository at this point in the history
  • Loading branch information
Shatur committed Oct 28, 2024
1 parent 670f18b commit 3e2e619
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Logging for binding.
- `ActionsData::insert_action`.
- `BlockBy::events` to block only events. Could be used for chords to avoid triggering required actions.

### Changed

Expand Down
8 changes: 6 additions & 2 deletions examples/all_conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ impl InputContext for DummyContext {
ctx.bind::<TapAction>()
.with(TapAction::KEY)
.with_condition(Tap::new(0.5));
ctx.bind::<ChordMember1>().with(ChordMember1::KEY);
ctx.bind::<ChordMember2>().with(ChordMember2::KEY);
ctx.bind::<ChordMember1>()
.with(ChordMember1::KEY)
.with_condition(BlockBy::<ChordAction>::events_only()); // Don't trigger the action when the chord is active.
ctx.bind::<ChordMember2>()
.with(ChordMember2::KEY)
.with_condition(BlockBy::<ChordAction>::events_only());
ctx.bind::<ChordAction>()
.with_condition(Chord::<ChordMember1>::default())
.with_condition(Chord::<ChordMember2>::default());
Expand Down
4 changes: 3 additions & 1 deletion src/input_context/context_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,9 @@ impl ActionBind {
let value = tracker.value().convert(self.dim);

action.update_time(time);
action.trigger_events(commands, entities, state, value);
if !tracker.events_blocked() {
action.trigger_events(commands, entities, state, value);
}
action.set_state(state);
}
}
Expand Down
11 changes: 9 additions & 2 deletions src/input_context/input_condition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub trait InputCondition: Sync + Send + Debug + 'static {
/// If no conditions are provided, the state will be set to [`ActionState::Fired`]
/// on any non-zero value, functioning similarly to a [`Down`](down::Down) condition
/// with a zero actuation threshold.
///
/// For details about how actions are combined, see [`ContextInstance`](super::context_instance::ContextInstance).
pub enum ConditionKind {
/// The most significant [`ActionState`] from all explicit conditions will be the
/// resulting state.
Expand All @@ -59,8 +61,13 @@ pub enum ConditionKind {
/// Otherwise, the most significant state will be capped at [`ActionState::Ongoing`].
Implicit,
/// If any blocking condition fails to return [`ActionState::Fired`],
/// it will override the state with [`ActionState::None`].
/// it will override the state with [`ActionState::None`] or block the events.
///
/// Doesn't contribute to the state on its own.
Blocker,
Blocker {
/// Block only events instead of overriding the state.
///
/// Other actions will be able to see the action state in [`ActionsData`].
events_only: bool,
},
}
27 changes: 24 additions & 3 deletions src/input_context/input_condition/block_by.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,37 @@ use crate::{
};

/// Requires another action to not be triggered within the same context.
///
/// Could be used for chords to avoid triggering required actions.
#[derive(Debug)]
pub struct BlockBy<A: InputAction> {
/// Action that blocks this condition when active.
marker: PhantomData<A>,

/// Wheter to block the state or only the events.
///
/// By default set to false.
pub events_only: bool,
}

impl<A: InputAction> BlockBy<A> {
/// Block only events.
///
/// For details, see [`ConditionKind::Blocker::events_only`].
///
/// This can be used for chords to avoid triggering required actions.
/// Otherwise, the chord will register the release and cancel itself.
pub fn events_only() -> Self {
Self {
marker: PhantomData,
events_only: true,
}
}
}

impl<A: InputAction> Default for BlockBy<A> {
fn default() -> Self {
Self {
marker: PhantomData,
events_only: false,
}
}
}
Expand Down Expand Up @@ -55,7 +74,9 @@ impl<A: InputAction> InputCondition for BlockBy<A> {
}

fn kind(&self) -> ConditionKind {
ConditionKind::Blocker
ConditionKind::Blocker {
events_only: self.events_only,
}
}
}

Expand Down
16 changes: 14 additions & 2 deletions src/input_context/trigger_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub(super) struct TriggerTracker {
found_implicit: bool,
all_implicits_fired: bool,
blocked: bool,
events_blocked: bool,
}

impl TriggerTracker {
Expand All @@ -33,6 +34,7 @@ impl TriggerTracker {
found_implicit: false,
all_implicits_fired: true,
blocked: false,
events_blocked: false,
}
}

Expand Down Expand Up @@ -74,8 +76,13 @@ impl TriggerTracker {
self.all_implicits_fired &= state == ActionState::Fired;
self.found_active |= state != ActionState::None;
}
ConditionKind::Blocker => {
self.blocked = state == ActionState::None;
ConditionKind::Blocker { events_only } => {
let blocked = state == ActionState::None;
if events_only {
self.events_blocked = blocked;
} else {
self.blocked = blocked;
}
}
}
}
Expand Down Expand Up @@ -107,6 +114,10 @@ impl TriggerTracker {
self.value
}

pub(super) fn events_blocked(&self) -> bool {
self.events_blocked
}

/// Merges input-level tracker into an action-level tracker.
pub(super) fn merge(&mut self, other: Self, accumulation: Accumulation) {
match self.state().cmp(&other.state()) {
Expand Down Expand Up @@ -137,6 +148,7 @@ impl TriggerTracker {
self.found_implicit |= other.found_implicit;
self.all_implicits_fired &= other.all_implicits_fired;
self.blocked |= other.blocked;
self.events_blocked |= other.events_blocked;
}
Ordering::Greater => (),
}
Expand Down
85 changes: 85 additions & 0 deletions tests/condition_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,80 @@ fn blocker() {
assert_eq!(event.state, ActionState::Fired);
}

#[test]
fn events_blocker() {
let mut app = App::new();
app.add_plugins((
MinimalPlugins,
InputPlugin,
EnhancedInputPlugin,
ActionRecorderPlugin,
))
.add_input_context::<DummyContext>()
.record_action::<ReleaseAction>()
.record_action::<EventsBlocker>();

let entity = app.world_mut().spawn(DummyContext).id();

app.update();

let recorded = app.world().resource::<RecordedActions>();

let events = recorded.get::<ReleaseAction>(entity).unwrap();
assert!(events.is_empty());

let events = recorded.get::<EventsBlocker>(entity).unwrap();
assert!(events.is_empty());

let mut keys = app.world_mut().resource_mut::<ButtonInput<KeyCode>>();
keys.press(ReleaseAction::KEY);
keys.press(EventsBlocker::KEY);

app.update();

let recorded = app.world().resource::<RecordedActions>();

let events = recorded.get::<ReleaseAction>(entity).unwrap();
let event = events.last().unwrap();
assert_eq!(event.value, true.into());
assert_eq!(event.state, ActionState::Ongoing);

let events = recorded.get::<EventsBlocker>(entity).unwrap();
let event = events.last().unwrap();
assert_eq!(event.value, true.into());
assert_eq!(event.state, ActionState::Fired);

app.world_mut()
.resource_mut::<ButtonInput<KeyCode>>()
.release(ReleaseAction::KEY);

app.update();

let recorded = app.world().resource::<RecordedActions>();

let events = recorded.get::<ReleaseAction>(entity).unwrap();
let event = events.last().unwrap();
assert_eq!(event.value, false.into());
assert_eq!(event.state, ActionState::Fired);

let events = recorded.get::<EventsBlocker>(entity).unwrap();
assert!(events.is_empty());

app.update();

let recorded = app.world().resource::<RecordedActions>();

let events = recorded.get::<ReleaseAction>(entity).unwrap();
let event = events.last().unwrap();
assert_eq!(event.value, false.into());
assert_eq!(event.state, ActionState::None);

let events = recorded.get::<EventsBlocker>(entity).unwrap();
let event = events.last().unwrap();
assert_eq!(event.value, true.into());
assert_eq!(event.state, ActionState::Fired);
}

#[derive(Debug, Component)]
struct DummyContext;

Expand All @@ -220,6 +294,9 @@ impl InputContext for DummyContext {
ctx.bind::<Blocker>()
.with(Blocker::KEY)
.with_condition(BlockBy::<ReleaseAction>::default());
ctx.bind::<EventsBlocker>()
.with(EventsBlocker::KEY)
.with_condition(BlockBy::<ReleaseAction>::events_only());

ctx
}
Expand Down Expand Up @@ -252,3 +329,11 @@ struct Blocker;
impl Blocker {
const KEY: KeyCode = KeyCode::KeyD;
}

#[derive(Debug, InputAction)]
#[input_action(dim = Bool)]
struct EventsBlocker;

impl EventsBlocker {
const KEY: KeyCode = KeyCode::KeyE;
}
44 changes: 44 additions & 0 deletions tests/state_and_value_merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ fn input_level() {
ActionState::None,
"if a blocker condition fails, it should override other conditions"
);

let mut keys = app.world_mut().resource_mut::<ButtonInput<KeyCode>>();
keys.release(Blocker::KEY);
keys.press(EventsBlocker::KEY);

app.update();

let recorded = app.world().resource::<RecordedActions>();
let events = recorded.get::<InputLevel>(entity).unwrap();
assert!(events.is_empty());
}

#[test]
Expand Down Expand Up @@ -165,6 +175,16 @@ fn action_level() {
ActionState::None,
"if a blocker condition fails, it should override other conditions"
);

let mut keys = app.world_mut().resource_mut::<ButtonInput<KeyCode>>();
keys.release(Blocker::KEY);
keys.press(EventsBlocker::KEY);

app.update();

let recorded = app.world().resource::<RecordedActions>();
let events = recorded.get::<ActionLevel>(entity).unwrap();
assert!(events.is_empty());
}

#[test]
Expand Down Expand Up @@ -246,6 +266,16 @@ fn both_levels() {
ActionState::None,
"if a blocker condition fails, it should override other conditions"
);

let mut keys = app.world_mut().resource_mut::<ButtonInput<KeyCode>>();
keys.release(Blocker::KEY);
keys.press(EventsBlocker::KEY);

app.update();

let recorded = app.world().resource::<RecordedActions>();
let events = recorded.get::<BothLevels>(entity).unwrap();
assert!(events.is_empty());
}

#[derive(Debug, Component)]
Expand All @@ -259,17 +289,20 @@ impl InputContext for DummyContext {
let release = Release::default();
let chord = Chord::<ChordMember>::default();
let block_by = BlockBy::<Blocker>::default();
let block_events_by = BlockBy::<EventsBlocker>::events_only();
let swizzle_axis = SwizzleAxis::YXZ;
let negate = Negate::default();
let scale = Scale::splat(2.0);

ctx.bind::<ChordMember>().with(ChordMember::KEY);
ctx.bind::<Blocker>().with(Blocker::KEY);
ctx.bind::<EventsBlocker>().with(EventsBlocker::KEY);
ctx.bind::<InputLevel>()
.with(
InputBind::new(InputLevel::KEY1)
.with_condition(chord)
.with_condition(block_by)
.with_condition(block_events_by)
.with_condition(down)
.with_condition(release)
.with_modifier(swizzle_axis)
Expand All @@ -279,6 +312,7 @@ impl InputContext for DummyContext {
InputBind::new(InputLevel::KEY2)
.with_condition(chord)
.with_condition(block_by)
.with_condition(block_events_by)
.with_condition(down)
.with_condition(release)
.with_modifier(swizzle_axis)
Expand All @@ -291,6 +325,7 @@ impl InputContext for DummyContext {
.with_condition(release)
.with_condition(chord)
.with_condition(block_by)
.with_condition(block_events_by)
.with_modifier(swizzle_axis)
.with_modifier(negate)
.with_modifier(scale);
Expand All @@ -308,6 +343,7 @@ impl InputContext for DummyContext {
.with_condition(release)
.with_condition(chord)
.with_condition(block_by)
.with_condition(block_events_by)
.with_modifier(swizzle_axis);

ctx
Expand Down Expand Up @@ -356,3 +392,11 @@ struct Blocker;
impl Blocker {
const KEY: KeyCode = KeyCode::KeyH;
}

#[derive(Debug, InputAction)]
#[input_action(dim = Bool)]
struct EventsBlocker;

impl EventsBlocker {
const KEY: KeyCode = KeyCode::KeyI;
}

0 comments on commit 3e2e619

Please sign in to comment.