source: imaps-frontend/node_modules/@use-gesture/core/src/engines/PinchEngine.ts

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 8.8 KB
Line 
1import { Engine } from './Engine'
2import { touchDistanceAngle, distanceAngle, wheelValues } from '../utils/events'
3import { V } from '../utils/maths'
4import { Vector2, WebKitGestureEvent } from '../types'
5import { clampStateInternalMovementToBounds } from '../utils/state'
6
7const SCALE_ANGLE_RATIO_INTENT_DEG = 30
8const PINCH_WHEEL_RATIO = 100
9
10export class PinchEngine extends Engine<'pinch'> {
11 ingKey = 'pinching' as const
12 aliasKey = 'da'
13
14 init() {
15 this.state.offset = [1, 0]
16 this.state.lastOffset = [1, 0]
17 this.state._pointerEvents = new Map()
18 }
19
20 // superseeds generic Engine reset call
21 reset() {
22 super.reset()
23 const state = this.state
24 state._touchIds = []
25 state.canceled = false
26 state.cancel = this.cancel.bind(this)
27 state.turns = 0
28 }
29
30 computeOffset() {
31 const { type, movement, lastOffset } = this.state
32 if (type === 'wheel') {
33 this.state.offset = V.add(movement, lastOffset)
34 } else {
35 this.state.offset = [(1 + movement[0]) * lastOffset[0], movement[1] + lastOffset[1]]
36 }
37 }
38
39 computeMovement() {
40 const { offset, lastOffset } = this.state
41 this.state.movement = [offset[0] / lastOffset[0], offset[1] - lastOffset[1]]
42 }
43
44 axisIntent() {
45 const state = this.state
46 const [_m0, _m1] = state._movement
47 if (!state.axis) {
48 const axisMovementDifference = Math.abs(_m0) * SCALE_ANGLE_RATIO_INTENT_DEG - Math.abs(_m1)
49 if (axisMovementDifference < 0) state.axis = 'angle'
50 else if (axisMovementDifference > 0) state.axis = 'scale'
51 }
52 }
53
54 restrictToAxis(v: Vector2) {
55 if (this.config.lockDirection) {
56 if (this.state.axis === 'scale') v[1] = 0
57 else if (this.state.axis === 'angle') v[0] = 0
58 }
59 }
60
61 cancel() {
62 const state = this.state
63 if (state.canceled) return
64 setTimeout(() => {
65 state.canceled = true
66 state._active = false
67 // we run compute with no event so that kinematics won't be computed
68 this.compute()
69 this.emit()
70 }, 0)
71 }
72
73 touchStart(event: TouchEvent) {
74 this.ctrl.setEventIds(event)
75 const state = this.state
76 const ctrlTouchIds = this.ctrl.touchIds
77
78 if (state._active) {
79 // check that the touchIds that initiated the gesture are still enabled
80 // This is useful for when the page loses track of the pointers (minifying
81 // gesture on iPad).
82 if (state._touchIds.every((id) => ctrlTouchIds.has(id))) return
83 // The gesture is still active, but probably didn't have the opportunity to
84 // end properly, so we restart the pinch.
85 }
86
87 if (ctrlTouchIds.size < 2) return
88
89 this.start(event)
90 state._touchIds = Array.from(ctrlTouchIds).slice(0, 2) as [number, number]
91
92 const payload = touchDistanceAngle(event, state._touchIds)
93
94 if (!payload) return
95 this.pinchStart(event, payload)
96 }
97
98 pointerStart(event: PointerEvent) {
99 if (event.buttons != null && event.buttons % 2 !== 1) return
100 this.ctrl.setEventIds(event)
101 ;(event.target as HTMLElement).setPointerCapture(event.pointerId)
102 const state = this.state
103 const _pointerEvents = state._pointerEvents
104 const ctrlPointerIds = this.ctrl.pointerIds
105
106 if (state._active) {
107 // see touchStart comment
108 if (Array.from(_pointerEvents.keys()).every((id) => ctrlPointerIds.has(id))) return
109 }
110
111 if (_pointerEvents.size < 2) {
112 _pointerEvents.set(event.pointerId, event)
113 }
114
115 if (state._pointerEvents.size < 2) return
116
117 this.start(event)
118
119 // @ts-ignore
120 const payload = distanceAngle(...Array.from(_pointerEvents.values()))
121
122 if (!payload) return
123 this.pinchStart(event, payload)
124 }
125
126 pinchStart(event: PointerEvent | TouchEvent, payload: { distance: number; angle: number; origin: Vector2 }) {
127 const state = this.state
128 state.origin = payload.origin
129 this.computeValues([payload.distance, payload.angle])
130 this.computeInitial()
131
132 this.compute(event)
133 this.emit()
134 }
135
136 touchMove(event: TouchEvent) {
137 if (!this.state._active) return
138 const payload = touchDistanceAngle(event, this.state._touchIds)
139
140 if (!payload) return
141 this.pinchMove(event, payload)
142 }
143
144 pointerMove(event: PointerEvent) {
145 const _pointerEvents = this.state._pointerEvents
146 if (_pointerEvents.has(event.pointerId)) {
147 _pointerEvents.set(event.pointerId, event)
148 }
149 if (!this.state._active) return
150 // @ts-ignore
151 const payload = distanceAngle(...Array.from(_pointerEvents.values()))
152
153 if (!payload) return
154 this.pinchMove(event, payload)
155 }
156
157 pinchMove(event: PointerEvent | TouchEvent, payload: { distance: number; angle: number; origin: Vector2 }) {
158 const state = this.state
159 const prev_a = state._values[1]
160 const delta_a = payload.angle - prev_a
161
162 let delta_turns = 0
163 if (Math.abs(delta_a) > 270) delta_turns += Math.sign(delta_a)
164
165 this.computeValues([payload.distance, payload.angle - 360 * delta_turns])
166
167 state.origin = payload.origin
168 state.turns = delta_turns
169 state._movement = [state._values[0] / state._initial[0] - 1, state._values[1] - state._initial[1]]
170
171 this.compute(event)
172 this.emit()
173 }
174
175 touchEnd(event: TouchEvent) {
176 this.ctrl.setEventIds(event)
177 if (!this.state._active) return
178
179 if (this.state._touchIds.some((id) => !this.ctrl.touchIds.has(id))) {
180 this.state._active = false
181
182 this.compute(event)
183 this.emit()
184 }
185 }
186
187 pointerEnd(event: PointerEvent) {
188 const state = this.state
189 this.ctrl.setEventIds(event)
190 try {
191 // @ts-ignore r3f
192 event.target.releasePointerCapture(event.pointerId)
193 } catch {}
194
195 if (state._pointerEvents.has(event.pointerId)) {
196 state._pointerEvents.delete(event.pointerId)
197 }
198
199 if (!state._active) return
200
201 if (state._pointerEvents.size < 2) {
202 state._active = false
203 this.compute(event)
204 this.emit()
205 }
206 }
207
208 gestureStart(event: WebKitGestureEvent) {
209 if (event.cancelable) event.preventDefault()
210 const state = this.state
211
212 if (state._active) return
213
214 this.start(event)
215 this.computeValues([event.scale, event.rotation])
216 state.origin = [event.clientX, event.clientY]
217 this.compute(event)
218
219 this.emit()
220 }
221
222 gestureMove(event: WebKitGestureEvent) {
223 if (event.cancelable) event.preventDefault()
224
225 if (!this.state._active) return
226
227 const state = this.state
228
229 this.computeValues([event.scale, event.rotation])
230 state.origin = [event.clientX, event.clientY]
231 const _previousMovement = state._movement
232 state._movement = [event.scale - 1, event.rotation]
233 state._delta = V.sub(state._movement, _previousMovement)
234 this.compute(event)
235 this.emit()
236 }
237
238 gestureEnd(event: WebKitGestureEvent) {
239 if (!this.state._active) return
240
241 this.state._active = false
242
243 this.compute(event)
244 this.emit()
245 }
246
247 wheel(event: WheelEvent) {
248 const modifierKey = this.config.modifierKey
249 if (modifierKey && (Array.isArray(modifierKey) ? !modifierKey.find((k) => event[k]) : !event[modifierKey])) return
250 if (!this.state._active) this.wheelStart(event)
251 else this.wheelChange(event)
252 this.timeoutStore.add('wheelEnd', this.wheelEnd.bind(this))
253 }
254
255 wheelStart(event: WheelEvent) {
256 this.start(event)
257 this.wheelChange(event)
258 }
259
260 wheelChange(event: WheelEvent) {
261 const isR3f = 'uv' in event
262 if (!isR3f) {
263 if (event.cancelable) {
264 event.preventDefault()
265 }
266 if (process.env.NODE_ENV === 'development' && !event.defaultPrevented) {
267 // eslint-disable-next-line no-console
268 console.warn(
269 `[@use-gesture]: To properly support zoom on trackpads, try using the \`target\` option.\n\nThis message will only appear in development mode.`
270 )
271 }
272 }
273 const state = this.state
274 state._delta = [(-wheelValues(event)[1] / PINCH_WHEEL_RATIO) * state.offset[0], 0]
275 V.addTo(state._movement, state._delta)
276
277 // _movement rolls back to when it passed the bounds.
278 clampStateInternalMovementToBounds(state)
279
280 this.state.origin = [event.clientX, event.clientY]
281
282 this.compute(event)
283 this.emit()
284 }
285
286 wheelEnd() {
287 if (!this.state._active) return
288 this.state._active = false
289 this.compute()
290 this.emit()
291 }
292
293 bind(bindFunction: any) {
294 const device = this.config.device
295 if (!!device) {
296 // @ts-ignore
297 bindFunction(device, 'start', this[device + 'Start'].bind(this))
298 // @ts-ignore
299 bindFunction(device, 'change', this[device + 'Move'].bind(this))
300 // @ts-ignore
301 bindFunction(device, 'end', this[device + 'End'].bind(this))
302 // @ts-ignore
303 bindFunction(device, 'cancel', this[device + 'End'].bind(this))
304 // @ts-ignore
305 bindFunction('lostPointerCapture', '', this[device + 'End'].bind(this))
306 }
307 // we try to set a passive listener, knowing that in any case React will
308 // ignore it.
309 if (this.config.pinchOnWheel) {
310 bindFunction('wheel', '', this.wheel.bind(this), { passive: false })
311 }
312 }
313}
Note: See TracBrowser for help on using the repository browser.