Tldraw/packages/core/src/inputs.ts

402 wiersze
9.0 KiB
TypeScript

import type React from 'react'
import type { TLBounds, TLKeyboardInfo, TLPointerInfo } from './types'
import { Utils } from './utils'
import { Vec } from '@tldraw/vec'
const DOUBLE_CLICK_DURATION = 250
export class Inputs {
pointer?: TLPointerInfo<string>
keyboard?: TLKeyboardInfo
keys: Record<string, boolean> = {}
isPinching = false
bounds: TLBounds = {
minX: 0,
maxX: 640,
minY: 0,
maxY: 480,
width: 640,
height: 480,
}
zoom = 1
pointerUpTime = 0
activePointer?: number
pointerIsValid(e: TouchEvent | React.TouchEvent | PointerEvent | React.PointerEvent) {
if ('pointerId' in e) {
if (this.activePointer && this.activePointer !== e.pointerId) {
return false
}
}
if ('touches' in e) {
const touch = e.changedTouches[0]
if (this.activePointer && this.activePointer !== touch.identifier) {
return false
}
}
return true
}
touchStart<T extends string>(e: TouchEvent | React.TouchEvent, target: T): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e
const touch = e.changedTouches[0]
this.activePointer = touch.identifier
const info: TLPointerInfo<T> = {
target,
pointerId: touch.identifier,
origin: Inputs.getPoint(touch, this.bounds),
delta: [0, 0],
point: Inputs.getPoint(touch, this.bounds),
pressure: Inputs.getPressure(touch),
shiftKey,
ctrlKey,
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
altKey,
spaceKey: this.keys[' '],
}
this.pointer = info
return info
}
touchEnd<T extends string>(e: TouchEvent | React.TouchEvent, target: T): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e
const touch = e.changedTouches[0]
const info: TLPointerInfo<T> = {
target,
pointerId: touch.identifier,
origin: Inputs.getPoint(touch, this.bounds),
delta: [0, 0],
point: Inputs.getPoint(touch, this.bounds),
pressure: Inputs.getPressure(touch),
shiftKey,
ctrlKey,
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
altKey,
spaceKey: this.keys[' '],
}
this.pointer = info
this.activePointer = undefined
return info
}
touchMove<T extends string>(e: TouchEvent | React.TouchEvent, target: T): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e
const touch = e.changedTouches[0]
const prev = this.pointer
const point = Inputs.getPoint(touch, this.bounds)
const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0]
const info: TLPointerInfo<T> = {
origin: point,
...prev,
target,
pointerId: touch.identifier,
point,
delta,
pressure: Inputs.getPressure(touch),
shiftKey,
ctrlKey,
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
altKey,
spaceKey: this.keys[' '],
}
this.pointer = info
return info
}
pointerDown<T extends string>(e: PointerEvent | React.PointerEvent, target: T): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e
const point = Inputs.getPoint(e, this.bounds)
this.activePointer = e.pointerId
const info: TLPointerInfo<T> = {
target,
pointerId: e.pointerId,
origin: point,
point: point,
delta: [0, 0],
pressure: Inputs.getPressure(e),
shiftKey,
ctrlKey,
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
altKey,
spaceKey: this.keys[' '],
}
this.pointer = info
return info
}
pointerEnter<T extends string>(
e: PointerEvent | React.PointerEvent,
target: T
): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e
const point = Inputs.getPoint(e, this.bounds)
const info: TLPointerInfo<T> = {
target,
pointerId: e.pointerId,
origin: point,
delta: [0, 0],
point: point,
pressure: Inputs.getPressure(e),
shiftKey,
ctrlKey,
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
altKey,
spaceKey: this.keys[' '],
}
this.pointer = info
return info
}
pointerMove<T extends string>(e: PointerEvent | React.PointerEvent, target: T): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e
const prev = this.pointer
const point = Inputs.getPoint(e, this.bounds)
const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0]
const info: TLPointerInfo<T> = {
origin: point,
...prev,
target,
pointerId: e.pointerId,
point,
delta,
pressure: Inputs.getPressure(e),
shiftKey,
ctrlKey,
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
altKey,
spaceKey: this.keys[' '],
}
this.pointer = info
return info
}
pointerUp<T extends string>(e: PointerEvent | React.PointerEvent, target: T): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e
const prev = this.pointer
const point = Inputs.getPoint(e, this.bounds)
const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0]
this.activePointer = undefined
const info: TLPointerInfo<T> = {
origin: point,
...prev,
target,
pointerId: e.pointerId,
point,
delta,
pressure: Inputs.getPressure(e),
shiftKey,
ctrlKey,
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
altKey,
spaceKey: this.keys[' '],
}
this.pointer = info
this.pointerUpTime = Date.now()
return info
}
panStart = (e: WheelEvent): TLPointerInfo<'wheel'> => {
const { shiftKey, ctrlKey, metaKey, altKey } = e
const info: TLPointerInfo<'wheel'> = {
target: 'wheel',
pointerId: this.pointer?.pointerId || 0,
origin: this.pointer?.origin || [0, 0],
delta: [0, 0],
pressure: 0.5,
point: Inputs.getPoint(e, this.bounds),
shiftKey,
ctrlKey,
metaKey,
altKey,
spaceKey: this.keys[' '],
}
this.pointer = info
return info
}
pan = (delta: number[], e: WheelEvent): TLPointerInfo<'wheel'> => {
if (!this.pointer || this.pointer.target !== 'wheel') {
return this.panStart(e)
}
const { shiftKey, ctrlKey, metaKey, altKey } = e
const prev = this.pointer
const point = Inputs.getPoint(e, this.bounds)
const info: TLPointerInfo<'wheel'> = {
...prev,
target: 'wheel',
delta,
point,
shiftKey,
ctrlKey,
metaKey,
altKey,
spaceKey: this.keys[' '],
}
this.pointer = info
return info
}
isDoubleClick() {
if (!this.pointer) return false
const { origin, point } = this.pointer
const isDoubleClick =
Date.now() - this.pointerUpTime < DOUBLE_CLICK_DURATION && Vec.dist(origin, point) < 4
// Reset the active pointer, in case it got stuck
if (isDoubleClick) this.activePointer = undefined
return isDoubleClick
}
clear() {
this.pointer = undefined
}
resetDoubleClick() {
this.pointerUpTime = 0
}
keydown = (e: KeyboardEvent | React.KeyboardEvent): TLKeyboardInfo => {
const { shiftKey, ctrlKey, metaKey, altKey } = e
this.keys[e.key] = true
return {
point: this.pointer?.point || [0, 0],
origin: this.pointer?.origin || [0, 0],
key: e.key,
keys: Object.keys(this.keys),
shiftKey,
ctrlKey,
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
altKey,
}
}
keyup = (e: KeyboardEvent | React.KeyboardEvent): TLKeyboardInfo => {
const { shiftKey, ctrlKey, metaKey, altKey } = e
delete this.keys[e.key]
return {
point: this.pointer?.point || [0, 0],
origin: this.pointer?.origin || [0, 0],
key: e.key,
keys: Object.keys(this.keys),
shiftKey,
ctrlKey,
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
altKey,
}
}
pinch(point: number[], origin: number[]) {
const { shiftKey, ctrlKey, metaKey, altKey } = this.keys
const delta = Vec.sub(origin, point)
const info: TLPointerInfo<'pinch'> = {
pointerId: 0,
target: 'pinch',
origin,
delta: delta,
point: Vec.sub(Vec.toFixed(point), [this.bounds.minX, this.bounds.minY]),
pressure: 0.5,
shiftKey,
ctrlKey,
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
altKey,
spaceKey: this.keys[' '],
}
this.pointer = info
return info
}
reset() {
this.pointerUpTime = 0
this.pointer = undefined
this.keyboard = undefined
this.activePointer = undefined
this.keys = {}
}
static getPoint(
e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent,
bounds: TLBounds
): number[] {
return [+e.clientX.toFixed(2) - bounds.minX, +e.clientY.toFixed(2) - bounds.minY]
}
static getPressure(e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent) {
return 'pressure' in e ? +e.pressure.toFixed(2) || 0.5 : 0.5
}
static commandKey(): string {
return Utils.isDarwin() ? '⌘' : 'Ctrl'
}
}
export const inputs = new Inputs()