use teksilo_canvas::Point;
use teksilo_tokens::GestureProfile;
use crate::event::PointerButton;
use crate::pointer::EventTime;
use super::config::{TapContact, TapStreak};
use super::{GestureEvent, GestureRecognizer, GestureResult, RawPointerEvent, RecognizerContext};
pub struct GestureArena {
entries: Vec<ArenaEntry>,
contact: TapContact,
fallback_streak: TapStreak,
}
struct ArenaEntry {
recognizer: Box<dyn GestureRecognizer>,
failed: bool,
}
impl GestureArena {
pub fn new() -> Self {
Self {
entries: Vec::new(),
contact: TapContact::default(),
fallback_streak: TapStreak::EMPTY,
}
}
pub fn add(&mut self, recognizer: impl GestureRecognizer + 'static) {
self.add_boxed(Box::new(recognizer));
}
pub fn add_boxed(&mut self, recognizer: Box<dyn GestureRecognizer>) {
self.entries.push(ArenaEntry {
recognizer,
failed: false,
});
}
pub(crate) fn observe_tap(
&mut self,
event: &RawPointerEvent,
profile: &GestureProfile,
) -> Option<(Point, PointerButton)> {
self.contact.observe(event, profile)
}
pub fn process_with(
&mut self,
event: &RawPointerEvent,
cx: &RecognizerContext,
) -> Option<GestureEvent> {
if matches!(event, RawPointerEvent::Down { .. }) {
for entry in &mut self.entries {
entry.failed = false;
}
}
self.arbitrate(|recognizer| recognizer.process(event, cx))
}
pub fn process(&mut self, event: &RawPointerEvent) -> Option<GestureEvent> {
let base = RecognizerContext::for_event(event);
if let Some((press, button)) = self.contact.observe(event, &base.profile) {
self.fallback_streak
.advance(base.now, &base.profile, press, button);
}
let streak = std::mem::take(&mut self.fallback_streak);
let cx = base.with_streak(&streak);
let recognized = self.process_with(event, &cx);
self.fallback_streak = streak;
if recognized.is_some_and(|g| !super::arena_set::preserves_streak(&g)) {
self.fallback_streak.reset();
}
recognized
}
pub fn tick_with(&mut self, cx: &RecognizerContext) -> Option<GestureEvent> {
self.arbitrate(|recognizer| recognizer.tick(cx))
}
fn arbitrate(
&mut self,
mut step: impl FnMut(&mut dyn GestureRecognizer) -> GestureResult,
) -> Option<GestureEvent> {
let mut best: Option<(usize, u32, GestureEvent)> = None;
for (i, entry) in self.entries.iter_mut().enumerate() {
if entry.failed {
continue;
}
match step(entry.recognizer.as_mut()) {
GestureResult::Recognized(gesture) => {
let prio = entry.recognizer.priority();
if best.as_ref().is_none_or(|(_, bp, _)| prio > *bp) {
best = Some((i, prio, gesture));
}
}
GestureResult::Failed => {
entry.failed = true;
}
GestureResult::Pending => {}
}
}
if let Some((winner_idx, _, _)) = &best {
for (i, entry) in self.entries.iter_mut().enumerate() {
if i == *winner_idx || entry.failed {
continue;
}
if !entry.recognizer.resets_on_peer_recognition() {
continue;
}
entry.recognizer.reset();
entry.failed = false;
}
}
best.map(|(_, _, gesture)| gesture)
}
pub fn next_deadline(&self) -> Option<EventTime> {
self.entries
.iter()
.filter(|entry| !entry.failed)
.filter_map(|entry| entry.recognizer.next_deadline())
.min()
}
pub fn reset(&mut self) {
for entry in &mut self.entries {
entry.recognizer.reset();
entry.failed = false;
}
self.contact = TapContact::default();
}
pub fn cancel(&mut self) {
for entry in &mut self.entries {
entry.recognizer.cancel();
entry.failed = true;
}
self.contact = TapContact::default();
}
pub fn cancel_taps(&mut self) {
for entry in &mut self.entries {
if entry.recognizer.tap_family() {
entry.recognizer.cancel();
entry.failed = true;
}
}
self.contact = TapContact::default();
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn len(&self) -> usize {
self.entries.len()
}
}
impl Default for GestureArena {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for GestureArena {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GestureArena")
.field("num_recognizers", &self.entries.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use teksilo_canvas::Point;
use super::*;
use crate::gesture::test_helpers::*;
use crate::gesture::{DoubleTapRecognizer, DragRecognizer, TapRecognizer, TripleTapRecognizer};
#[test]
fn arena_tap_wins_over_nothing() {
let mut arena = GestureArena::new();
arena.add(TapRecognizer::new());
arena.process(&down(Point::new(10.0, 10.0)));
let result = arena.process(&up(Point::new(10.0, 10.0)));
assert!(matches!(result, Some(GestureEvent::Tap(_))));
}
#[test]
fn arena_drag_wins_over_tap_on_movement() {
let mut arena = GestureArena::new();
arena.add(TapRecognizer::new().max_distance(5.0));
arena.add(DragRecognizer::new().threshold(5.0));
arena.process(&down(Point::new(10.0, 10.0)));
let result = arena.process(&move_to(Point::new(30.0, 10.0)));
assert!(matches!(result, Some(GestureEvent::DragStarted { .. })));
}
#[test]
fn arena_tap_recognized_when_drag_fails() {
let mut arena = GestureArena::new();
arena.add(TapRecognizer::new());
arena.add(DragRecognizer::new().threshold(5.0));
arena.process(&down(Point::new(10.0, 10.0)));
let result = arena.process(&up(Point::new(10.0, 10.0)));
assert!(matches!(result, Some(GestureEvent::Tap(_))));
}
#[test]
fn arena_double_and_triple_tap_cooperate() {
let mut arena = GestureArena::new();
arena.add(DoubleTapRecognizer::new());
arena.add(TripleTapRecognizer::new());
let pos = Point::new(10.0, 10.0);
assert!(arena.process(&down(pos)).is_none());
assert!(arena.process(&up(pos)).is_none());
assert!(arena.process(&down(pos)).is_none());
let second = arena.process(&up(pos));
assert!(
matches!(second, Some(GestureEvent::DoubleTap(_))),
"click 2 must produce DoubleTap, got {:?}",
second
);
assert!(arena.process(&down(pos)).is_none());
let third = arena.process(&up(pos));
assert!(
matches!(third, Some(GestureEvent::TripleTap(_))),
"click 3 must produce TripleTap, got {:?}",
third
);
}
#[test]
fn arena_resets_on_new_down() {
let mut arena = GestureArena::new();
arena.add(TapRecognizer::new().max_distance(5.0));
arena.process(&down(Point::new(10.0, 10.0)));
arena.process(&move_to(Point::new(50.0, 50.0)));
arena.process(&down(Point::new(10.0, 10.0)));
let result = arena.process(&up(Point::new(10.0, 10.0)));
assert!(matches!(result, Some(GestureEvent::Tap(_))));
}
#[test]
fn arena_empty_returns_none() {
let mut arena = GestureArena::new();
assert!(arena.is_empty());
let result = arena.process(&down(Point::new(10.0, 10.0)));
assert!(result.is_none());
}
}