use std::any::TypeId;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
use std::time::Duration;
use inset_embedder::{Matrix4, Offset, PointerDeviceKind};
use inset_foundation::{App, Handle, HandleId, Listener, Timer};
use crate::arena::{GestureArenaEntry, GestureArenaMember, GestureDisposition};
use crate::binding::GestureBinding;
use crate::constants::K_TOUCH_SLOP;
use crate::debug::{debug_print_gesture_arena_diagnostics, debug_print_recognizer_callbacks_trace};
use crate::events::{PointerDownEvent, PointerEvent, PointerPanZoomStartEvent};
use crate::gesture_settings::DeviceGestureSettings;
use crate::pointer_router::PointerRoute;
use crate::team::GestureArenaTeam;
pub type AllowedButtonsFilter = Rc<dyn Fn(i64) -> bool>;
pub const UNSET_TOUCH_SLOP: f64 = -1.0;
pub(crate) fn default_button_accept_behavior(_buttons: i64) -> bool {
true
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DragStartBehavior {
Down,
#[default]
Start,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MultitouchDragStrategy {
LatestPointer,
AverageBoundaryPointers,
SumAllPointers,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GestureRecognizerState {
Ready,
Possible,
Defunct,
}
#[derive(Clone, Copy)]
pub(crate) struct RecognizerEventData {
pub kind: PointerDeviceKind,
pub buttons: i64,
}
pub struct GestureRecognizerData {
pub(crate) gesture_settings: Option<DeviceGestureSettings>,
pub(crate) supported_devices: Option<HashSet<PointerDeviceKind>>,
pub(crate) allowed_buttons_filter: AllowedButtonsFilter,
pub(crate) pointer_to_event_data: HashMap<i64, RecognizerEventData>,
}
impl GestureRecognizerData {
pub fn new() -> GestureRecognizerData {
GestureRecognizerData {
gesture_settings: None,
supported_devices: None,
allowed_buttons_filter: Rc::new(default_button_accept_behavior),
pointer_to_event_data: HashMap::new(),
}
}
}
impl GestureRecognizerData {
pub fn gesture_settings(&self) -> Option<DeviceGestureSettings> {
self.gesture_settings
}
pub fn set_gesture_settings(&mut self, settings: Option<DeviceGestureSettings>) {
self.gesture_settings = settings;
}
pub fn allowed_buttons_filter(&self) -> &AllowedButtonsFilter {
&self.allowed_buttons_filter
}
pub fn set_allowed_buttons_filter(&mut self, filter: AllowedButtonsFilter) {
self.allowed_buttons_filter = filter;
}
}
impl Default for GestureRecognizerData {
fn default() -> GestureRecognizerData {
GestureRecognizerData::new()
}
}
pub struct OneSequenceData {
pub(crate) entries: HashMap<i64, GestureArenaEntry>,
pub(crate) tracked_pointers: HashSet<i64>,
pub(crate) team: Option<Handle<GestureArenaTeam>>,
}
impl OneSequenceData {
pub fn new() -> OneSequenceData {
OneSequenceData {
entries: HashMap::new(),
tracked_pointers: HashSet::new(),
team: None,
}
}
}
impl Default for OneSequenceData {
fn default() -> OneSequenceData {
OneSequenceData::new()
}
}
pub struct PrimaryPointerData {
pub(crate) deadline: Option<Duration>,
pub(crate) pre_accept_slop_tolerance: Option<f64>,
pub(crate) post_accept_slop_tolerance: Option<f64>,
pub(crate) state: GestureRecognizerState,
pub(crate) primary_pointer: Option<i64>,
pub(crate) initial_position: Option<OffsetPair>,
pub(crate) gesture_accepted: bool,
pub(crate) timer: Option<Timer>,
pub(crate) deadline_event: Option<crate::events::PointerDownEvent>,
}
impl PrimaryPointerData {
pub fn new(
deadline: Option<Duration>,
pre_accept_slop_tolerance: Option<f64>,
post_accept_slop_tolerance: Option<f64>,
) -> PrimaryPointerData {
debug_assert!(
pre_accept_slop_tolerance == Some(UNSET_TOUCH_SLOP)
|| pre_accept_slop_tolerance.is_none()
|| pre_accept_slop_tolerance.is_some_and(|value| value >= 0.0),
"The preAcceptSlopTolerance must be unspecified, positive, or null"
);
debug_assert!(
post_accept_slop_tolerance == Some(UNSET_TOUCH_SLOP)
|| post_accept_slop_tolerance.is_none()
|| post_accept_slop_tolerance.is_some_and(|value| value >= 0.0),
"The postAcceptSlopTolerance must be unspecified, positive, or null"
);
PrimaryPointerData {
deadline,
pre_accept_slop_tolerance,
post_accept_slop_tolerance,
state: GestureRecognizerState::Ready,
primary_pointer: None,
initial_position: None,
gesture_accepted: false,
timer: None,
deadline_event: None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct OffsetPair {
pub local: Offset,
pub global: Offset,
}
impl OffsetPair {
pub const ZERO: OffsetPair = OffsetPair {
local: Offset::ZERO,
global: Offset::ZERO,
};
pub const fn new(local: Offset, global: Offset) -> OffsetPair {
OffsetPair { local, global }
}
pub fn from_event_position(event: &PointerEvent) -> OffsetPair {
OffsetPair {
local: event.local_position(),
global: event.position(),
}
}
pub fn from_event_delta(event: &PointerEvent) -> OffsetPair {
OffsetPair {
local: event.local_delta(),
global: event.delta(),
}
}
}
impl std::ops::Add for OffsetPair {
type Output = OffsetPair;
fn add(self, other: OffsetPair) -> OffsetPair {
OffsetPair {
local: self.local + other.local,
global: self.global + other.global,
}
}
}
impl std::ops::Sub for OffsetPair {
type Output = OffsetPair;
fn sub(self, other: OffsetPair) -> OffsetPair {
OffsetPair {
local: self.local - other.local,
global: self.global - other.global,
}
}
}
impl fmt::Display for OffsetPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"OffsetPair(local: {:?}, global: {:?})",
self.local, self.global
)
}
}
pub trait RecognizerLeafData {
fn recognizer(&self) -> &GestureRecognizerData;
fn recognizer_mut(&mut self) -> &mut GestureRecognizerData;
}
pub trait OneSequenceLeafData: RecognizerLeafData {
fn one_sequence(&self) -> &OneSequenceData;
fn one_sequence_mut(&mut self) -> &mut OneSequenceData;
}
pub trait PrimaryPointerLeafData: OneSequenceLeafData {
fn primary(&self) -> &PrimaryPointerData;
fn primary_mut(&mut self) -> &mut PrimaryPointerData;
}
pub trait RecognizerLeaf: RecognizerLeafData + Sized + 'static {
fn handle_event_route(self: Handle<Self>, app: &mut App, event: PointerEvent) {
self.handle_event(app, event);
}
fn add_pointer(self: Handle<Self>, app: &mut App, event: PointerDownEvent) {
GestureRecognizer::add_pointer(self, app, event);
}
fn add_pointer_pan_zoom(
self: Handle<Self>,
app: &mut App,
event: crate::events::PointerPanZoomStartEvent,
) {
GestureRecognizer::add_pointer_pan_zoom(self, app, event);
}
fn add_allowed_pointer(self: Handle<Self>, _app: &mut App, _event: PointerDownEvent) {}
fn add_allowed_pointer_pan_zoom(
self: Handle<Self>,
_app: &mut App,
_event: crate::events::PointerPanZoomStartEvent,
) {
}
fn handle_non_allowed_pointer(self: Handle<Self>, _app: &mut App, _event: &PointerDownEvent) {}
fn handle_non_allowed_pointer_pan_zoom(
self: Handle<Self>,
_app: &mut App,
_event: &crate::events::PointerPanZoomStartEvent,
) {
}
fn is_pointer_allowed(self: Handle<Self>, app: &App, event: &PointerDownEvent) -> bool {
GestureRecognizer::is_pointer_allowed(self, app, event)
}
fn is_pointer_pan_zoom_allowed(
self: Handle<Self>,
app: &App,
event: &crate::events::PointerPanZoomStartEvent,
) -> bool {
GestureRecognizer::is_pointer_pan_zoom_allowed(self, app, event)
}
fn start_tracking_pointer(
self: Handle<Self>,
app: &mut App,
pointer: i64,
transform: Option<Matrix4>,
) where
Self: OneSequenceLeafData,
{
OneSequenceGestureRecognizer::start_tracking_pointer(self, app, pointer, transform);
}
fn handle_event(self: Handle<Self>, app: &mut App, event: PointerEvent);
fn accept_gesture(self: Handle<Self>, _app: &mut App, _pointer: i64) {}
fn reject_gesture(self: Handle<Self>, _app: &mut App, _pointer: i64) {}
fn did_stop_tracking_last_pointer(self: Handle<Self>, _app: &mut App, _pointer: i64) {}
fn resolve(self: Handle<Self>, app: &mut App, disposition: GestureDisposition)
where
Self: OneSequenceLeafData,
{
OneSequenceGestureRecognizer::resolve(self, app, disposition);
}
fn dispose(self: Handle<Self>, app: &mut App) {
GestureRecognizer::dispose(self, app);
}
fn debug_description(self: Handle<Self>) -> &'static str;
}
pub trait PrimaryPointerLeaf: RecognizerLeaf + PrimaryPointerLeafData {
fn deadline_fired(self: Handle<Self>, app: &mut App) {
self.did_exceed_deadline_with_event(app);
}
fn handle_primary_pointer(self: Handle<Self>, app: &mut App, event: PointerEvent);
fn did_exceed_deadline(self: Handle<Self>, app: &mut App) {
debug_assert!(
app.get(self).primary().deadline.is_none(),
"must override didExceedDeadline if deadline is set"
);
}
fn did_exceed_deadline_with_event(self: Handle<Self>, app: &mut App) {
PrimaryPointerGestureRecognizer::did_exceed_deadline_with_event(self, app);
}
}
impl<R: RecognizerLeaf> GestureArenaMember for Handle<R> {
fn accept_gesture(&self, app: &mut App, pointer: i64) {
R::accept_gesture(*self, app, pointer);
}
fn reject_gesture(&self, app: &mut App, pointer: i64) {
R::reject_gesture(*self, app, pointer);
}
fn member_id(&self) -> HandleId {
self.id()
}
}
pub trait GestureRecognizerLeaf: Sized + 'static {
fn as_recognizer(self: Handle<Self>) -> AnyGestureRecognizer;
}
impl<R: RecognizerLeaf> GestureRecognizerLeaf for R {
fn as_recognizer(self: Handle<Self>) -> AnyGestureRecognizer {
AnyGestureRecognizer {
id: self.id(),
vtable: const { &GestureRecognizerVTable::of::<R>() },
}
}
}
pub(crate) struct GestureRecognizerVTable {
type_id: fn() -> TypeId,
type_name: fn() -> &'static str,
add_pointer: fn(&mut App, HandleId, PointerDownEvent),
add_pointer_pan_zoom: fn(&mut App, HandleId, PointerPanZoomStartEvent),
is_pointer_allowed: fn(&App, HandleId, &PointerDownEvent) -> bool,
is_pointer_pan_zoom_allowed: fn(&App, HandleId, &PointerPanZoomStartEvent) -> bool,
dispose: fn(&mut App, HandleId),
set_gesture_settings: fn(&mut App, HandleId, Option<DeviceGestureSettings>),
debug_description: fn(HandleId) -> &'static str,
}
fn resolve<R: 'static>(id: HandleId) -> Handle<R> {
Handle::from_id(id)
}
impl GestureRecognizerVTable {
const fn of<R: RecognizerLeaf>() -> GestureRecognizerVTable {
GestureRecognizerVTable {
type_id: TypeId::of::<R>,
type_name: std::any::type_name::<R>,
add_pointer: |app, id, event| R::add_pointer(resolve::<R>(id), app, event),
add_pointer_pan_zoom: |app, id, event| {
R::add_pointer_pan_zoom(resolve::<R>(id), app, event)
},
is_pointer_allowed: |app, id, event| R::is_pointer_allowed(resolve(id), app, event),
is_pointer_pan_zoom_allowed: |app, id, event| {
R::is_pointer_pan_zoom_allowed(resolve(id), app, event)
},
dispose: |app, id| R::dispose(resolve(id), app),
set_gesture_settings: |app, id, value| {
app.get_mut(resolve::<R>(id))
.recognizer_mut()
.set_gesture_settings(value)
},
debug_description: |id| R::debug_description(resolve(id)),
}
}
}
#[derive(Clone, Copy)]
pub struct AnyGestureRecognizer {
id: HandleId,
vtable: &'static GestureRecognizerVTable,
}
impl PartialEq for AnyGestureRecognizer {
fn eq(&self, other: &AnyGestureRecognizer) -> bool {
self.id == other.id
}
}
impl Eq for AnyGestureRecognizer {}
impl Hash for AnyGestureRecognizer {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl fmt::Debug for AnyGestureRecognizer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({:?})", (self.vtable.type_name)(), self.id)
}
}
impl AnyGestureRecognizer {
pub fn id(self) -> HandleId {
self.id
}
pub fn type_id(self) -> TypeId {
(self.vtable.type_id)()
}
pub fn downcast<T: 'static>(self, app: &App) -> Option<Handle<T>> {
app.handle::<T>(self.id)
}
pub fn set_gesture_settings(self, app: &mut App, value: Option<DeviceGestureSettings>) {
(self.vtable.set_gesture_settings)(app, self.id, value);
}
pub fn add_pointer(self, app: &mut App, event: &PointerDownEvent) {
(self.vtable.add_pointer)(app, self.id, event.clone());
}
pub fn add_pointer_pan_zoom(self, app: &mut App, event: &PointerPanZoomStartEvent) {
(self.vtable.add_pointer_pan_zoom)(app, self.id, event.clone());
}
pub fn is_pointer_allowed(self, app: &App, event: &PointerDownEvent) -> bool {
(self.vtable.is_pointer_allowed)(app, self.id, event)
}
pub fn is_pointer_pan_zoom_allowed(self, app: &App, event: &PointerPanZoomStartEvent) -> bool {
(self.vtable.is_pointer_pan_zoom_allowed)(app, self.id, event)
}
pub fn dispose(self, app: &mut App) {
(self.vtable.dispose)(app, self.id);
}
pub fn debug_description(self) -> &'static str {
(self.vtable.debug_description)(self.id)
}
}
pub struct GestureRecognizer;
impl GestureRecognizer {
pub fn add_pointer_pan_zoom<R: RecognizerLeaf>(
this: Handle<R>,
app: &mut App,
event: crate::events::PointerPanZoomStartEvent,
) {
app.get_mut(this)
.recognizer_mut()
.pointer_to_event_data
.insert(
event.pointer,
RecognizerEventData {
kind: event.kind,
buttons: event.buttons,
},
);
if this.is_pointer_pan_zoom_allowed(app, &event) {
this.add_allowed_pointer_pan_zoom(app, event);
} else {
this.handle_non_allowed_pointer_pan_zoom(app, &event);
}
}
pub fn add_pointer<R: RecognizerLeaf>(this: Handle<R>, app: &mut App, event: PointerDownEvent) {
app.get_mut(this)
.recognizer_mut()
.pointer_to_event_data
.insert(
event.pointer,
RecognizerEventData {
kind: event.kind,
buttons: event.buttons,
},
);
if this.is_pointer_allowed(app, &event) {
this.add_allowed_pointer(app, event);
} else {
this.handle_non_allowed_pointer(app, &event);
}
}
pub fn is_pointer_allowed<R: RecognizerLeaf>(
this: Handle<R>,
app: &App,
event: &PointerDownEvent,
) -> bool {
let devices = app.get(this).recognizer().supported_devices.clone();
let filter = Rc::clone(&app.get(this).recognizer().allowed_buttons_filter);
(devices.is_none()
|| devices
.as_ref()
.is_some_and(|set| set.contains(&event.kind)))
&& filter(event.buttons)
}
pub fn is_pointer_pan_zoom_allowed<R: RecognizerLeaf>(
this: Handle<R>,
app: &App,
event: &crate::events::PointerPanZoomStartEvent,
) -> bool {
let devices = app.get(this).recognizer().supported_devices.clone();
devices.is_none()
|| devices
.as_ref()
.is_some_and(|set| set.contains(&event.kind))
}
pub fn kind_for_pointer<R: RecognizerLeaf>(
this: Handle<R>,
app: &App,
pointer: i64,
) -> PointerDeviceKind {
let data = &app.get(this).recognizer().pointer_to_event_data;
debug_assert!(data.contains_key(&pointer));
data[&pointer].kind
}
pub fn buttons_for_pointer<R: RecognizerLeaf>(this: Handle<R>, app: &App, pointer: i64) -> i64 {
let data = &app.get(this).recognizer().pointer_to_event_data;
debug_assert!(data.contains_key(&pointer));
data[&pointer].buttons
}
pub fn dispose<R: RecognizerLeaf>(this: Handle<R>, app: &mut App) {
app.destroy(this);
}
pub fn invoke_callback<R: RecognizerLeaf, T>(
this: Handle<R>,
app: &mut App,
name: &str,
callback: impl FnOnce(&mut App) -> T,
) -> Option<T> {
if cfg!(debug_assertions) && debug_print_recognizer_callbacks_trace() {
let prefix = if debug_print_gesture_arena_diagnostics() {
format!("{}❙ ", " ".repeat(19))
} else {
String::new()
};
eprintln!("{prefix}{this:?} calling {name} callback.");
}
Some(callback(app))
}
}
pub struct OneSequenceGestureRecognizer;
impl OneSequenceGestureRecognizer {
pub fn add_allowed_pointer<R: RecognizerLeaf + OneSequenceLeafData>(
this: Handle<R>,
app: &mut App,
event: &PointerDownEvent,
) {
this.start_tracking_pointer(app, event.pointer, event.transform);
}
pub fn handle_non_allowed_pointer<R: RecognizerLeaf + OneSequenceLeafData>(
this: Handle<R>,
app: &mut App,
) {
this.resolve(app, GestureDisposition::Rejected);
}
pub fn resolve<R: RecognizerLeaf + OneSequenceLeafData>(
this: Handle<R>,
app: &mut App,
disposition: GestureDisposition,
) {
let local_entries: Vec<GestureArenaEntry> = app
.get(this)
.one_sequence()
.entries
.values()
.cloned()
.collect();
app.get_mut(this).one_sequence_mut().entries.clear();
for entry in local_entries {
entry.resolve(app, disposition);
}
}
pub fn resolve_pointer<R: RecognizerLeaf + OneSequenceLeafData>(
this: Handle<R>,
app: &mut App,
pointer: i64,
disposition: GestureDisposition,
) {
let entry = app
.get_mut(this)
.one_sequence_mut()
.entries
.remove(&pointer);
if let Some(entry) = entry {
entry.resolve(app, disposition);
}
}
pub fn dispose<R: RecognizerLeaf + OneSequenceLeafData>(this: Handle<R>, app: &mut App) {
this.resolve(app, GestureDisposition::Rejected);
let pointers: Vec<i64> = app
.get(this)
.one_sequence()
.tracked_pointers
.iter()
.copied()
.collect();
let router = GestureBinding::instance(app).pointer_router(app);
for pointer in pointers {
router.remove_route(
app,
pointer,
&PointerRoute::handle_method(this, R::handle_event_route),
);
}
app.get_mut(this)
.one_sequence_mut()
.tracked_pointers
.clear();
debug_assert!(app.get(this).one_sequence().entries.is_empty());
GestureRecognizer::dispose(this, app);
}
pub(crate) fn add_pointer_to_arena<R: RecognizerLeaf + OneSequenceLeafData>(
this: Handle<R>,
app: &mut App,
pointer: i64,
) -> GestureArenaEntry {
let team = app.get(this).one_sequence().team;
if let Some(team) = team {
team.add(app, pointer, this)
} else {
GestureBinding::instance(app)
.gesture_arena(app)
.add(app, pointer, this)
}
}
pub fn start_tracking_pointer<R: RecognizerLeaf + OneSequenceLeafData>(
this: Handle<R>,
app: &mut App,
pointer: i64,
transform: Option<Matrix4>,
) {
let router = GestureBinding::instance(app).pointer_router(app);
router.add_route(
app,
pointer,
PointerRoute::handle_method(this, R::handle_event_route),
transform,
);
app.get_mut(this)
.one_sequence_mut()
.tracked_pointers
.insert(pointer);
let entry = Self::add_pointer_to_arena(this, app, pointer);
app.get_mut(this)
.one_sequence_mut()
.entries
.insert(pointer, entry);
}
pub fn stop_tracking_pointer<R: RecognizerLeaf + OneSequenceLeafData>(
this: Handle<R>,
app: &mut App,
pointer: i64,
) {
if app
.get(this)
.one_sequence()
.tracked_pointers
.contains(&pointer)
{
let router = GestureBinding::instance(app).pointer_router(app);
router.remove_route(
app,
pointer,
&PointerRoute::handle_method(this, R::handle_event_route),
);
app.get_mut(this)
.one_sequence_mut()
.tracked_pointers
.remove(&pointer);
if app.get(this).one_sequence().tracked_pointers.is_empty() {
this.did_stop_tracking_last_pointer(app, pointer);
}
}
}
pub fn stop_tracking_if_pointer_no_longer_down<R: RecognizerLeaf + OneSequenceLeafData>(
this: Handle<R>,
app: &mut App,
event: &PointerEvent,
) {
if matches!(
event,
PointerEvent::Up(_) | PointerEvent::Cancel(_) | PointerEvent::PanZoomEnd(_)
) {
Self::stop_tracking_pointer(this, app, event.pointer());
}
}
}
pub struct PrimaryPointerGestureRecognizer;
impl PrimaryPointerGestureRecognizer {
pub fn add_allowed_pointer<R: PrimaryPointerLeaf>(
this: Handle<R>,
app: &mut App,
event: PointerDownEvent,
) {
OneSequenceGestureRecognizer::add_allowed_pointer(this, app, &event);
if app.get(this).primary().state == GestureRecognizerState::Ready {
app.get_mut(this).primary_mut().state = GestureRecognizerState::Possible;
app.get_mut(this).primary_mut().primary_pointer = Some(event.pointer);
app.get_mut(this).primary_mut().initial_position =
Some(OffsetPair::new(event.local_position(), event.position));
let deadline = app.get(this).primary().deadline;
if let Some(deadline) = deadline {
app.get_mut(this).primary_mut().deadline_event = Some(event);
let timer = Timer::new(
app,
deadline,
Listener::handle_method(this, R::deadline_fired),
);
app.get_mut(this).primary_mut().timer = Some(timer);
}
}
}
pub fn handle_non_allowed_pointer<R: PrimaryPointerLeaf>(
this: Handle<R>,
app: &mut App,
_event: &PointerDownEvent,
) {
if !app.get(this).primary().gesture_accepted {
OneSequenceGestureRecognizer::handle_non_allowed_pointer(this, app);
}
}
pub fn handle_event<R: PrimaryPointerLeaf>(
this: Handle<R>,
app: &mut App,
event: PointerEvent,
) {
debug_assert!(app.get(this).primary().state != GestureRecognizerState::Ready);
let primary = app.get(this).primary().primary_pointer;
if app.get(this).primary().state == GestureRecognizerState::Possible
&& Some(event.pointer()) == primary
{
let gesture_accepted = app.get(this).primary().gesture_accepted;
let pre = Self::pre_accept_slop_tolerance(this, app);
let post = Self::post_accept_slop_tolerance(this, app);
let distance = Self::global_distance(this, app, &event);
let is_pre_accept_slop_past_tolerance =
!gesture_accepted && pre.is_some() && distance > pre.unwrap();
let is_post_accept_slop_past_tolerance =
gesture_accepted && post.is_some() && distance > post.unwrap();
if matches!(event, PointerEvent::Move(_))
&& (is_pre_accept_slop_past_tolerance || is_post_accept_slop_past_tolerance)
{
this.resolve(app, GestureDisposition::Rejected);
OneSequenceGestureRecognizer::stop_tracking_pointer(this, app, primary.unwrap());
} else {
this.handle_primary_pointer(app, event.clone());
}
}
OneSequenceGestureRecognizer::stop_tracking_if_pointer_no_longer_down(this, app, &event);
}
pub fn did_exceed_deadline_with_event<R: PrimaryPointerLeaf>(this: Handle<R>, app: &mut App) {
this.did_exceed_deadline(app);
}
pub fn accept_gesture<R: PrimaryPointerLeaf>(this: Handle<R>, app: &mut App, pointer: i64) {
if Some(pointer) == app.get(this).primary().primary_pointer {
Self::stop_timer(this, app);
app.get_mut(this).primary_mut().gesture_accepted = true;
}
}
pub fn reject_gesture<R: PrimaryPointerLeaf>(this: Handle<R>, app: &mut App, pointer: i64) {
if Some(pointer) == app.get(this).primary().primary_pointer
&& app.get(this).primary().state == GestureRecognizerState::Possible
{
Self::stop_timer(this, app);
app.get_mut(this).primary_mut().state = GestureRecognizerState::Defunct;
}
}
pub fn did_stop_tracking_last_pointer<R: PrimaryPointerLeaf>(
this: Handle<R>,
app: &mut App,
_pointer: i64,
) {
debug_assert!(app.get(this).primary().state != GestureRecognizerState::Ready);
Self::stop_timer(this, app);
app.get_mut(this).primary_mut().state = GestureRecognizerState::Ready;
app.get_mut(this).primary_mut().initial_position = None;
app.get_mut(this).primary_mut().gesture_accepted = false;
}
pub fn dispose<R: PrimaryPointerLeaf>(this: Handle<R>, app: &mut App) {
Self::stop_timer(this, app);
OneSequenceGestureRecognizer::dispose(this, app);
}
pub fn stop_timer<R: PrimaryPointerLeaf>(this: Handle<R>, app: &mut App) {
if let Some(timer) = app.get_mut(this).primary_mut().timer.take() {
timer.cancel(app);
}
}
pub fn pre_accept_slop_tolerance<R: PrimaryPointerLeaf>(
this: Handle<R>,
app: &App,
) -> Option<f64> {
let stored = app.get(this).primary().pre_accept_slop_tolerance;
if stored == Some(UNSET_TOUCH_SLOP) {
Some(Self::default_touch_slop(this, app))
} else {
stored
}
}
pub fn post_accept_slop_tolerance<R: PrimaryPointerLeaf>(
this: Handle<R>,
app: &App,
) -> Option<f64> {
let stored = app.get(this).primary().post_accept_slop_tolerance;
if stored == Some(UNSET_TOUCH_SLOP) {
Some(Self::default_touch_slop(this, app))
} else {
stored
}
}
pub fn default_touch_slop<R: PrimaryPointerLeaf>(this: Handle<R>, app: &App) -> f64 {
app.get(this)
.recognizer()
.gesture_settings
.and_then(|settings| settings.touch_slop)
.unwrap_or(K_TOUCH_SLOP)
}
pub fn global_distance<R: PrimaryPointerLeaf>(
this: Handle<R>,
app: &App,
event: &PointerEvent,
) -> f64 {
let initial = app.get(this).primary().initial_position.unwrap();
(event.position() - initial.global).distance()
}
}
#[cfg(test)]
mod tests {
use inset_foundation::AppCell;
use std::cell::Cell;
use super::*;
use inset_embedder::Offset;
use crate::events::{PointerDownEvent, PointerEvent};
use crate::long_press::LongPressGestureRecognizer;
use crate::tap::TapGestureRecognizer;
#[test]
fn an_erased_recognizer_keeps_its_identity_and_type() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let tap = TapGestureRecognizer::new(&mut app);
let long_press = LongPressGestureRecognizer::new(&mut app);
let erased = tap.as_recognizer();
assert_eq!(erased, tap.as_recognizer());
assert_ne!(erased, long_press.as_recognizer());
assert_eq!(erased.id(), tap.id());
assert_eq!(erased.type_id(), TypeId::of::<TapGestureRecognizer>());
assert_eq!(erased.downcast::<TapGestureRecognizer>(&app), Some(tap));
assert_eq!(erased.downcast::<LongPressGestureRecognizer>(&app), None);
assert_eq!(erased.debug_description(), "tap");
assert_eq!(long_press.as_recognizer().debug_description(), "long press");
assert!(format!("{erased:?}").contains("TapGestureRecognizer"));
}
#[test]
fn an_erased_recognizer_adds_pointers_and_dispose_frees_the_slot() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let tap = TapGestureRecognizer::new(&mut app);
let recognized = Rc::new(Cell::new(false));
let flag = Rc::clone(&recognized);
tap.set_on_tap(&mut app, Some(Listener::new(move |_app| flag.set(true))));
let erased = tap.as_recognizer();
let down = PointerDownEvent {
pointer: 1,
..PointerDownEvent::default()
};
assert!(erased.is_pointer_allowed(&app, &down));
erased.add_pointer(&mut app, &down);
let binding = GestureBinding::instance(&mut app);
binding.gesture_arena(&app).close(&mut app, 1);
app.drain_microtasks();
binding.pointer_router(&app).route(
&mut app,
PointerEvent::Up(crate::events::PointerUpEvent {
pointer: 1,
..crate::events::PointerUpEvent::default()
}),
);
assert!(recognized.get());
erased.dispose(&mut app);
assert!(!app.contains(erased.id()));
}
#[test]
fn from_event_position_and_delta_and_arithmetic() {
let event = PointerEvent::Down(PointerDownEvent {
position: Offset::new(10.0, 20.0),
delta: Offset::new(1.0, 2.0),
..PointerDownEvent::default()
});
let position = OffsetPair::from_event_position(&event);
assert_eq!(position.global, Offset::new(10.0, 20.0));
assert_eq!(position.local, Offset::new(10.0, 20.0));
let delta = OffsetPair::from_event_delta(&event);
assert_eq!(delta.global, Offset::new(1.0, 2.0));
let sum = position + delta;
assert_eq!(sum.global, Offset::new(11.0, 22.0));
assert_eq!((sum - delta).global, position.global);
assert_eq!(OffsetPair::ZERO.global, Offset::ZERO);
}
}