///|
pub(all) struct TouchId {
value : Int
} derive(Eq, Hash, Debug)
///|
pub fn TouchId::TouchId(value : Int) -> TouchId {
{ value, }
}
///|
pub fn TouchId::value(self : TouchId) -> Int {
self.value
}
///|
pub(all) enum TouchPhase {
Started
Moved
Ended
Cancelled
} derive(Eq, Debug)
///|
pub(all) struct TouchPoint {
id : TouchId
source : PointerSource
position : @math.Vec2
previous_position : @math.Vec2
delta : @math.Vec2
primary : Bool
}
///|
pub(all) struct TouchEvent {
id : TouchId
source : PointerSource
phase : TouchPhase
position : @math.Vec2
previous_position : @math.Vec2
delta : @math.Vec2
primary : Bool
}
///|
pub(all) enum PointerSource {
PointerMouse
PointerTouch
PointerPen
} derive(Eq, Hash, Debug)
///|
pub(all) enum PointerPhase {
Down
Move
Up
Cancel
} derive(Eq, Debug)
///|
pub(all) struct PointerId {
source : PointerSource
value : Int
} derive(Eq, Hash, Debug)
///|
pub(all) struct PointerEvent {
id : PointerId
source : PointerSource
phase : PointerPhase
position : @math.Vec2
previous_position : @math.Vec2
delta : @math.Vec2
primary : Bool
}
///|
pub(all) enum TouchGestureKind {
Tap
LongPress
PanStart
PanMove
PanEnd
PanCancel
PinchStart
PinchMove
PinchEnd
PinchCancel
} derive(Eq, Debug)
///|
pub(all) struct TouchGestureEvent {
kind : TouchGestureKind
primary_id : TouchId?
secondary_id : TouchId?
position : @math.Vec2
delta : @math.Vec2
scale : Double
distance : Double
duration : Double
}
///|
priv struct GestureTouchState {
id : TouchId
start_position : @math.Vec2
mut position : @math.Vec2
mut duration : Double
mut pan_active : Bool
mut long_press_sent : Bool
mut cancelled : Bool
}
///|
priv struct PinchState {
first : TouchId
second : TouchId
mut previous_center : @math.Vec2
mut previous_distance : Double
mut active : Bool
}
///|
const TAP_MAX_DURATION : Double = 0.25
///|
const TAP_MAX_MOVEMENT : Double = 10.0
///|
const LONG_PRESS_DURATION : Double = 0.5
///|
const PAN_THRESHOLD : Double = 8.0
///|
const PINCH_THRESHOLD : Double = 4.0
///|
let touches : Map[TouchId, TouchPoint] = Map([])
///|
let touch_changes : Array[TouchEvent] = []
///|
let pointer_changes : Array[PointerEvent] = []
///|
let gesture_touches : Map[TouchId, GestureTouchState] = Map([])
///|
let touch_gesture_changes : Array[TouchGestureEvent] = []
///|
let pinch_state : Ref[PinchState?] = Ref(None)
///|
pub let touch_event_bus : @event.Events[TouchEvent] = Events()
///|
pub let touch_gesture_event_bus : @event.Events[TouchGestureEvent] = Events()
///|
pub let pointer_event_bus : @event.Events[PointerEvent] = Events()
///|
pub fn active_touches() -> Map[TouchId, TouchPoint] {
touches
}
///|
pub fn touch_changes_this_frame() -> Array[TouchEvent] {
touch_changes
}
///|
pub fn pointer_changes_this_frame() -> Array[PointerEvent] {
pointer_changes
}
///|
pub fn touch_gesture_changes_this_frame() -> Array[TouchGestureEvent] {
touch_gesture_changes
}
///|
pub fn touch_point(id : TouchId) -> TouchPoint? {
touches.get(id)
}
///|
pub fn is_touch_active(id : TouchId) -> Bool {
touches.contains(id)
}
///|
pub fn primary_touch() -> TouchPoint? {
for _, touch in touches {
if touch.primary {
return Some(touch)
}
}
None
}
///|
pub fn first_touch_change(phase : TouchPhase) -> TouchEvent? {
for event in touch_changes {
if event.phase == phase {
return Some(event)
}
}
None
}
///|
pub fn replace_touch_state(
active : Map[TouchId, TouchPoint],
changes : Array[TouchEvent],
) -> Unit {
touches.clear()
for id, touch in active {
touches.set(id, touch)
}
touch_changes.clear()
for event in changes {
touch_changes.push(event)
}
}
///|
pub fn clear_touch_state() -> Unit {
touches.clear()
touch_changes.clear()
pointer_changes.clear()
gesture_touches.clear()
touch_gesture_changes.clear()
pinch_state.val = None
}
///|
fn touch_phase_to_pointer_phase(phase : TouchPhase) -> PointerPhase {
match phase {
Started => Down
Moved => Move
Ended => Up
Cancelled => Cancel
}
}
///|
pub fn pointer_event_from_touch(event : TouchEvent) -> PointerEvent {
{
id: { source: event.source, value: event.id.value },
source: event.source,
phase: touch_phase_to_pointer_phase(event.phase),
position: event.position,
previous_position: event.previous_position,
delta: event.delta,
primary: event.primary,
}
}
///|
pub fn pointer_id_from_touch(touch : TouchPoint) -> PointerId {
{ source: touch.source, value: touch.id.value }
}
///|
pub fn is_pointer_active(id : PointerId) -> Bool {
match id.source {
PointerMouse =>
id.value == 0 && primary_touch() is None && mouse.left_button
PointerTouch | PointerPen =>
match touches.get(TouchId(id.value)) {
Some(touch) => touch.source == id.source
None => false
}
}
}
///|
pub fn primary_pointer_position() -> @math.Vec2 {
match primary_touch() {
Some(touch) => touch.position
None => mouse.pos
}
}
///|
pub fn primary_pointer_down() -> Bool {
match primary_touch() {
Some(_) => true
None => mouse.left_button
}
}
///|
fn push_pointer_change(event : PointerEvent) -> Unit {
pointer_changes.push(event)
pointer_event_bus.send(event)
}
///|
pub fn advanced_pointer_system(_delta : Double) -> Unit {
pointer_changes.clear()
let mut contact_owns_primary = primary_touch() is Some(_)
for event in touch_changes {
push_pointer_change(pointer_event_from_touch(event))
if event.primary {
contact_owns_primary = true
}
}
if contact_owns_primary {
return
}
let id : PointerId = { source: PointerMouse, value: 0 }
if is_mouse_just_pressed(Left) {
push_pointer_change({
id,
source: PointerMouse,
phase: Down,
position: mouse.pos,
previous_position: mouse.pos,
delta: @math.Vec2::zero(),
primary: true,
})
}
if mouse_movement.movement[X] != 0.0 || mouse_movement.movement[Y] != 0.0 {
push_pointer_change({
id,
source: PointerMouse,
phase: Move,
position: mouse.pos,
previous_position: mouse.pos - mouse_movement.movement,
delta: mouse_movement.movement,
primary: true,
})
}
if is_mouse_just_released(Left) {
push_pointer_change({
id,
source: PointerMouse,
phase: Up,
position: mouse.pos,
previous_position: mouse.pos,
delta: @math.Vec2::zero(),
primary: true,
})
}
}
///|
fn abs_double(value : Double) -> Double {
if value < 0.0 {
-value
} else {
value
}
}
///|
fn distance_between(a : @math.Vec2, b : @math.Vec2) -> Double {
(a - b).distance()
}
///|
fn midpoint(a : @math.Vec2, b : @math.Vec2) -> @math.Vec2 {
(a + b).scalar_mul(0.5)
}
///|
fn push_gesture(
kind : TouchGestureKind,
primary_id? : TouchId,
secondary_id? : TouchId,
position~ : @math.Vec2,
delta? : @math.Vec2 = @math.Vec2::zero(),
scale? : Double = 1.0,
distance? : Double = 0.0,
duration? : Double = 0.0,
) -> Unit {
touch_gesture_changes.push({
kind,
primary_id,
secondary_id,
position,
delta,
scale,
distance,
duration,
})
}
///|
fn make_gesture_touch_state(event : TouchEvent) -> GestureTouchState {
{
id: event.id,
start_position: event.position,
position: event.position,
duration: 0.0,
pan_active: false,
long_press_sent: false,
cancelled: false,
}
}
///|
fn update_long_press(delta : Double) -> Unit {
for _, state in gesture_touches {
state.duration += delta
let movement = distance_between(state.start_position, state.position)
if !state.long_press_sent &&
!state.pan_active &&
!state.cancelled &&
movement <= TAP_MAX_MOVEMENT &&
state.duration >= LONG_PRESS_DURATION {
state.long_press_sent = true
push_gesture(
LongPress,
primary_id=state.id,
position=state.position,
duration=state.duration,
)
}
}
}
///|
fn process_started_touch(event : TouchEvent) -> Unit {
gesture_touches.set(event.id, make_gesture_touch_state(event))
}
///|
fn active_touch_count() -> Int {
let mut count = 0
for _, _ in touches {
count += 1
}
count
}
///|
fn process_moved_touch(event : TouchEvent) -> Unit {
guard gesture_touches.get(event.id) is Some(state) else { return }
state.position = event.position
if active_touch_count() > 1 {
return
}
let movement = distance_between(state.start_position, state.position)
if !state.pan_active && movement >= PAN_THRESHOLD {
state.pan_active = true
push_gesture(
PanStart,
primary_id=event.id,
position=event.position,
delta=event.delta,
distance=movement,
duration=state.duration,
)
} else if state.pan_active {
push_gesture(
PanMove,
primary_id=event.id,
position=event.position,
delta=event.delta,
distance=movement,
duration=state.duration,
)
}
}
///|
fn process_ended_touch(event : TouchEvent) -> Unit {
guard gesture_touches.get(event.id) is Some(state) else { return }
state.position = event.position
let movement = distance_between(state.start_position, state.position)
if state.pan_active {
push_gesture(
PanEnd,
primary_id=event.id,
position=event.position,
delta=event.delta,
distance=movement,
duration=state.duration,
)
} else if !state.long_press_sent &&
state.duration <= TAP_MAX_DURATION &&
movement <= TAP_MAX_MOVEMENT {
push_gesture(
Tap,
primary_id=event.id,
position=event.position,
distance=movement,
duration=state.duration,
)
}
ignore(gesture_touches.remove(event.id))
}
///|
fn process_cancelled_touch(event : TouchEvent) -> Unit {
guard gesture_touches.get(event.id) is Some(state) else { return }
state.cancelled = true
if state.pan_active {
push_gesture(
PanCancel,
primary_id=event.id,
position=event.position,
delta=event.delta,
duration=state.duration,
)
}
ignore(gesture_touches.remove(event.id))
}
///|
fn process_touch_changes_for_gestures() -> Unit {
for event in touch_changes {
match event.phase {
Started => process_started_touch(event)
Moved => process_moved_touch(event)
Ended => process_ended_touch(event)
Cancelled => process_cancelled_touch(event)
}
}
}
///|
fn first_two_gesture_touches() -> (GestureTouchState, GestureTouchState)? {
let active : Array[GestureTouchState] = []
for id, state in gesture_touches {
if touches.contains(id) && !state.cancelled {
active.push(state)
}
}
if active.length() < 2 {
return None
}
active.sort_by(fn(lhs, rhs) { lhs.id.value.compare(rhs.id.value) })
guard active.get(0) is Some(first) else { return None }
guard active.get(1) is Some(second) else { return None }
Some((first, second))
}
///|
fn same_pinch_pair(
state : PinchState,
first : TouchId,
second : TouchId,
) -> Bool {
(state.first == first && state.second == second) ||
(state.first == second && state.second == first)
}
///|
fn end_current_pinch(cancelled~ : Bool) -> Unit {
match pinch_state.val {
Some(state) => {
if state.active {
push_gesture(
if cancelled {
PinchCancel
} else {
PinchEnd
},
primary_id=state.first,
secondary_id=state.second,
position=state.previous_center,
distance=state.previous_distance,
)
}
pinch_state.val = None
}
None => ()
}
}
///|
fn update_pinch(cancelled : Bool) -> Unit {
match first_two_gesture_touches() {
Some((first, second)) => {
let center = midpoint(first.position, second.position)
let distance = distance_between(first.position, second.position)
match pinch_state.val {
Some(state) => {
if !same_pinch_pair(state, first.id, second.id) {
end_current_pinch(cancelled=false)
pinch_state.val = Some({
first: first.id,
second: second.id,
previous_center: center,
previous_distance: distance,
active: false,
})
return
}
let delta_distance = distance - state.previous_distance
let center_delta = center - state.previous_center
let scale = if state.previous_distance <= 0.000001 {
1.0
} else {
distance / state.previous_distance
}
if !state.active && abs_double(delta_distance) >= PINCH_THRESHOLD {
state.active = true
push_gesture(
PinchStart,
primary_id=first.id,
secondary_id=second.id,
position=center,
delta=center_delta,
scale~,
distance~,
)
} else if state.active &&
(
abs_double(delta_distance) > 0.000001 ||
center_delta[X] != 0.0 ||
center_delta[Y] != 0.0
) {
push_gesture(
PinchMove,
primary_id=first.id,
secondary_id=second.id,
position=center,
delta=center_delta,
scale~,
distance~,
)
}
state.previous_center = center
state.previous_distance = distance
}
None =>
pinch_state.val = Some({
first: first.id,
second: second.id,
previous_center: center,
previous_distance: distance,
active: false,
})
}
}
None => end_current_pinch(cancelled~)
}
}
///|
pub fn advanced_touch_system(_delta : Double) -> Unit {
touch_gesture_changes.clear()
update_long_press(_delta)
let mut cancelled = false
for event in touch_changes {
if event.phase == Cancelled {
cancelled = true
}
touch_event_bus.send(event)
}
process_touch_changes_for_gestures()
update_pinch(cancelled)
for event in touch_gesture_changes {
touch_gesture_event_bus.send(event)
}
}