use std::{collections::HashMap, rc::Rc};
use incular_core::{Offset, PointerPhase, time::Instant};
use crate::arena_member::{ArenaLink, ArenaMember, arena_recognizer, compute_hit_slop};
use crate::{GestureDisposition, PointerDeviceKind, RawPointerEvent};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ForcePressDetails {
pub global_position: Offset,
pub local_position: Offset,
pub pressure: f32,
}
pub type ForceInterpolation = Rc<dyn Fn(f32, f32, f32) -> f32>;
type ForcePressCallback = Rc<dyn Fn(ForcePressDetails)>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ForceState {
Ready,
Possible,
Accepted,
Started,
Peaked,
}
pub struct ForcePressGestureRecognizer {
start_pressure: f32,
peak_pressure: f32,
interpolation: ForceInterpolation,
supported_devices: Option<Vec<PointerDeviceKind>>,
on_start: Option<ForcePressCallback>,
on_update: Option<ForcePressCallback>,
on_peak: Option<ForcePressCallback>,
on_end: Option<ForcePressCallback>,
state: ForceState,
last_position: (Offset, Offset),
last_pressure: f32,
tracked: Vec<u64>,
entries: Vec<u64>,
kinds: HashMap<u64, PointerDeviceKind>,
link: ArenaLink,
}
impl Default for ForcePressGestureRecognizer {
fn default() -> Self {
Self::new()
}
}
impl ForcePressGestureRecognizer {
#[must_use]
pub fn new() -> Self {
Self {
start_pressure: 0.4,
peak_pressure: 0.85,
interpolation: Rc::new(Self::inverse_lerp),
supported_devices: None,
on_start: None,
on_update: None,
on_peak: None,
on_end: None,
state: ForceState::Ready,
last_position: (Offset::ZERO, Offset::ZERO),
last_pressure: 0.0,
tracked: Vec::new(),
entries: Vec::new(),
kinds: HashMap::new(),
link: ArenaLink::default(),
}
}
#[must_use]
pub fn pressures(mut self, start: f32, peak: f32) -> Self {
assert!(peak > start, "the peak pressure exceeds the start pressure");
self.start_pressure = start;
self.peak_pressure = peak;
self
}
#[must_use]
pub fn interpolation(mut self, interpolation: impl Fn(f32, f32, f32) -> f32 + 'static) -> Self {
self.interpolation = Rc::new(interpolation);
self
}
#[must_use]
pub fn supported_devices(mut self, kinds: impl IntoIterator<Item = PointerDeviceKind>) -> Self {
self.supported_devices = Some(kinds.into_iter().collect());
self
}
#[must_use]
pub fn on_start(mut self, callback: impl Fn(ForcePressDetails) + 'static) -> Self {
self.on_start = Some(Rc::new(callback));
self
}
#[must_use]
pub fn on_update(mut self, callback: impl Fn(ForcePressDetails) + 'static) -> Self {
self.on_update = Some(Rc::new(callback));
self
}
#[must_use]
pub fn on_peak(mut self, callback: impl Fn(ForcePressDetails) + 'static) -> Self {
self.on_peak = Some(Rc::new(callback));
self
}
#[must_use]
pub fn on_end(mut self, callback: impl Fn(ForcePressDetails) + 'static) -> Self {
self.on_end = Some(Rc::new(callback));
self
}
#[must_use]
pub const fn start_pressure(&self) -> f32 {
self.start_pressure
}
#[must_use]
pub const fn peak_pressure(&self) -> f32 {
self.peak_pressure
}
#[must_use]
pub fn inverse_lerp(min: f32, max: f32, t: f32) -> f32 {
let value = (t - min) / (max - min);
if value.is_nan() {
value
} else {
value.clamp(0.0, 1.0)
}
}
pub fn add_pointer(&mut self, event: RawPointerEvent) {
self.kinds.insert(event.pointer, event.kind);
let allowed = self
.supported_devices
.as_ref()
.is_none_or(|kinds| kinds.contains(&event.kind));
if !allowed {
return;
}
if event.sample.pressure.is_none() {
self.resolve(GestureDisposition::Rejected);
return;
}
self.start_tracking_pointer(event.pointer);
if self.state == ForceState::Ready {
self.state = ForceState::Possible;
self.last_position = (event.position, event.local_position);
}
}
pub fn handle_event(&mut self, event: RawPointerEvent) {
let event = self.link.with_deltas(event);
if !self.tracked.contains(&event.pointer) {
return;
}
if matches!(event.phase, PointerPhase::Move | PointerPhase::Down) {
let pressure = (self.interpolation)(
0.0,
1.0,
event
.sample
.pressure
.map_or(f32::NAN, |pressure| pressure.get()),
);
self.last_position = (event.position, event.local_position);
self.last_pressure = pressure;
if self.state == ForceState::Possible {
if pressure > self.start_pressure {
self.state = ForceState::Started;
self.resolve(GestureDisposition::Accepted);
} else if event.delta.x * event.delta.x + event.delta.y * event.delta.y
> compute_hit_slop(event.kind)
{
self.resolve(GestureDisposition::Rejected);
}
}
if pressure > self.start_pressure && self.state == ForceState::Accepted {
self.state = ForceState::Started;
if let Some(callback) = &self.on_start {
callback(ForcePressDetails {
global_position: self.last_position.0,
local_position: self.last_position.1,
pressure,
});
}
}
let details = ForcePressDetails {
global_position: event.position,
local_position: event.local_position,
pressure,
};
if let Some(callback) = &self.on_peak
&& pressure > self.peak_pressure
&& self.state == ForceState::Started
{
self.state = ForceState::Peaked;
callback(details);
}
if let Some(callback) = &self.on_update
&& !pressure.is_nan()
&& matches!(self.state, ForceState::Started | ForceState::Peaked)
{
callback(details);
}
}
if matches!(event.phase, PointerPhase::Up | PointerPhase::Cancel) {
self.stop_tracking_pointer(event.pointer);
}
}
pub fn accept_gesture(&mut self, _pointer: u64) {
if self.state == ForceState::Possible {
self.state = ForceState::Accepted;
}
if let Some(callback) = &self.on_start
&& self.state == ForceState::Started
{
callback(ForcePressDetails {
global_position: self.last_position.0,
local_position: self.last_position.1,
pressure: self.last_pressure,
});
}
}
pub fn reject_gesture(&mut self, pointer: u64) {
self.stop_tracking_pointer(pointer);
self.did_stop_tracking_last_pointer();
}
pub fn poll(&mut self, _now: Instant) {}
#[must_use]
pub const fn next_deadline(&self) -> Option<Instant> {
None
}
fn start_tracking_pointer(&mut self, pointer: u64) {
if !self.tracked.contains(&pointer) {
self.tracked.push(pointer);
}
if !self.entries.contains(&pointer) {
self.entries.push(pointer);
}
self.link.add(pointer);
}
fn stop_tracking_pointer(&mut self, pointer: u64) {
if let Some(index) = self.tracked.iter().position(|p| *p == pointer) {
self.tracked.remove(index);
if self.tracked.is_empty() {
self.did_stop_tracking_last_pointer();
}
}
}
fn resolve(&mut self, disposition: GestureDisposition) {
for pointer in std::mem::take(&mut self.entries) {
self.settle(pointer, disposition);
}
}
fn did_stop_tracking_last_pointer(&mut self) {
let was_accepted = matches!(self.state, ForceState::Started | ForceState::Peaked);
if self.state == ForceState::Possible {
self.resolve(GestureDisposition::Rejected);
return;
}
if was_accepted && let Some(callback) = &self.on_end {
callback(ForcePressDetails {
global_position: self.last_position.0,
local_position: self.last_position.1,
pressure: 0.0,
});
}
self.state = ForceState::Ready;
}
}
arena_recognizer!(ForcePressGestureRecognizer);