mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 18:01:38 +03:00
Pan capturing (#10304)
Panning or zooming "captures" wheel events. While events are captured, further events of the same type will continue the pan/zoom action. The capture expires if no events are received for 250ms. A trackpad capture also expires if any pointer movement occurs.
This commit is contained in:
parent
ccfeac1c02
commit
3f70307a88
@ -662,6 +662,8 @@ const groupColors = computed(() => {
|
||||
@click="handleClick"
|
||||
@dragover.prevent
|
||||
@drop.prevent="handleFileDrop($event)"
|
||||
@pointermove.capture="graphNavigator.pointerEventsCapture.pointermove"
|
||||
@wheel.capture="graphNavigator.pointerEventsCapture.wheel"
|
||||
>
|
||||
<div class="layer" :style="{ transform: graphNavigator.transform }">
|
||||
<GraphNodes
|
||||
|
@ -1,5 +1,6 @@
|
||||
/** @file Vue composables for listening to DOM events. */
|
||||
|
||||
import type { KeyboardComposable } from '@/composables/keyboard.ts'
|
||||
import type { Opt } from '@/util/data/opt'
|
||||
import { Vec2 } from '@/util/data/vec2'
|
||||
import { type VueInstance } from '@vueuse/core'
|
||||
@ -528,3 +529,85 @@ export function useArrows(
|
||||
|
||||
return { events, moving }
|
||||
}
|
||||
|
||||
/** Supports panning or zooming "capturing" wheel events.
|
||||
*
|
||||
* While events are captured, further events of the same type will continue the pan/zoom action.
|
||||
* The capture expires if no events are received within the specified `captureDurationMs`.
|
||||
* A trackpad capture also expires if any pointer movement occurs.
|
||||
*/
|
||||
export function useWheelActions(
|
||||
keyboard: KeyboardComposable,
|
||||
captureDurationMs: number,
|
||||
onZoom: (e: WheelEvent, inputType: 'trackpad' | 'wheel') => boolean | void,
|
||||
onPan: (e: WheelEvent) => boolean | void,
|
||||
) {
|
||||
let prevEventPanInfo:
|
||||
| ({ expiration: number } & (
|
||||
| { type: 'trackpad-zoom' }
|
||||
| { type: 'wheel-zoom' }
|
||||
| { type: 'pan'; trackpad: boolean }
|
||||
))
|
||||
| undefined = undefined
|
||||
|
||||
type WheelEventType = 'trackpad-zoom' | 'wheel-zoom' | 'pan'
|
||||
function classifyEvent(e: WheelEvent): WheelEventType {
|
||||
if (e.ctrlKey) {
|
||||
// A pinch gesture is represented by setting `e.ctrlKey`. It can be distinguished from an actual Ctrl+wheel
|
||||
// combination because the real Ctrl key emits keyup/keydown events.
|
||||
const isGesture = !keyboard.ctrl
|
||||
return isGesture ? 'trackpad-zoom' : 'wheel-zoom'
|
||||
} else {
|
||||
return 'pan'
|
||||
}
|
||||
}
|
||||
|
||||
function handleWheel(e: WheelEvent) {
|
||||
const newType = classifyEvent(e)
|
||||
if (e.eventPhase === e.CAPTURING_PHASE) {
|
||||
if (newType !== prevEventPanInfo?.type || e.timeStamp > prevEventPanInfo.expiration) {
|
||||
prevEventPanInfo = undefined
|
||||
return
|
||||
}
|
||||
}
|
||||
const expiration = e.timeStamp + captureDurationMs
|
||||
if (newType === 'wheel-zoom') {
|
||||
prevEventPanInfo = { expiration, type: newType }
|
||||
onZoom(e, 'wheel')
|
||||
} else if (newType === 'trackpad-zoom') {
|
||||
prevEventPanInfo = { expiration, type: newType }
|
||||
onZoom(e, 'trackpad')
|
||||
} else if (newType === 'pan') {
|
||||
const alreadyKnownTrackpad = prevEventPanInfo?.type === 'pan' && prevEventPanInfo.trackpad
|
||||
prevEventPanInfo = {
|
||||
expiration,
|
||||
type: newType,
|
||||
// Heuristic: Trackpad panning is usually multi-axis; wheel panning is not.
|
||||
trackpad: alreadyKnownTrackpad || (e.deltaX !== 0 && e.deltaY !== 0),
|
||||
}
|
||||
onPan(e)
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
function pointermove() {
|
||||
// If a `pointermove` event occurs, any trackpad action has ended.
|
||||
if (
|
||||
prevEventPanInfo?.type === 'trackpad-zoom' ||
|
||||
(prevEventPanInfo?.type === 'pan' && prevEventPanInfo.trackpad)
|
||||
) {
|
||||
prevEventPanInfo = undefined
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
events: {
|
||||
wheel: handleWheel,
|
||||
},
|
||||
captureEvents: {
|
||||
pointermove,
|
||||
wheel: handleWheel,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
useEvent,
|
||||
usePointer,
|
||||
useResizeObserver,
|
||||
useWheelActions,
|
||||
} from '@/composables/events'
|
||||
import type { KeyboardComposable } from '@/composables/keyboard'
|
||||
import { Rect } from '@/util/data/rect'
|
||||
@ -24,6 +25,7 @@ const ZOOM_LEVELS_REVERSED = [...ZOOM_LEVELS].reverse()
|
||||
* If we are that close to next zoom level, we should choose the next one instead
|
||||
* to avoid small unnoticeable changes to zoom. */
|
||||
const ZOOM_SKIP_THRESHOLD = 0.05
|
||||
const WHEEL_CAPTURE_DURATION_MS = 250
|
||||
|
||||
function elemRect(target: Element | undefined): Rect {
|
||||
if (target != null && target instanceof Element)
|
||||
@ -275,6 +277,24 @@ export function useNavigator(
|
||||
scale.skip()
|
||||
}
|
||||
|
||||
const { events: wheelEvents, captureEvents: wheelEventsCapture } = useWheelActions(
|
||||
keyboard,
|
||||
WHEEL_CAPTURE_DURATION_MS,
|
||||
(e, inputType) => {
|
||||
if (inputType === 'trackpad') {
|
||||
// OS X trackpad events provide usable rate-of-change information.
|
||||
updateScale((oldValue: number) => oldValue * Math.exp(-e.deltaY / 100))
|
||||
} else {
|
||||
// Mouse wheel rate information is unreliable. We just step in the direction of the sign.
|
||||
stepZoom(-Math.sign(e.deltaY))
|
||||
}
|
||||
},
|
||||
(e) => {
|
||||
const delta = new Vec2(e.deltaX, e.deltaY)
|
||||
scrollTo(center.value.addScaled(delta, 1 / scale.value))
|
||||
},
|
||||
)
|
||||
|
||||
return proxyRefs({
|
||||
pointerEvents: {
|
||||
dragover(e: DragEvent) {
|
||||
@ -301,28 +321,12 @@ export function useNavigator(
|
||||
panPointer.events.pointerdown(e)
|
||||
zoomPointer.events.pointerdown(e)
|
||||
},
|
||||
wheel(e: WheelEvent) {
|
||||
e.preventDefault()
|
||||
if (e.ctrlKey) {
|
||||
// A pinch gesture is represented by setting `e.ctrlKey`. It can be distinguished from an actual Ctrl+wheel
|
||||
// combination because the real Ctrl key emits keyup/keydown events.
|
||||
const isGesture = !keyboard.ctrl
|
||||
if (isGesture) {
|
||||
// OS X trackpad events provide usable rate-of-change information.
|
||||
updateScale((oldValue: number) => oldValue * Math.exp(-e.deltaY / 100))
|
||||
} else {
|
||||
// Mouse wheel rate information is unreliable. We just step in the direction of the sign.
|
||||
stepZoom(-Math.sign(e.deltaY))
|
||||
}
|
||||
} else {
|
||||
const delta = new Vec2(e.deltaX, e.deltaY)
|
||||
scrollTo(center.value.addScaled(delta, 1 / scale.value))
|
||||
}
|
||||
},
|
||||
contextmenu(e: Event) {
|
||||
e.preventDefault()
|
||||
},
|
||||
wheel: wheelEvents.wheel,
|
||||
},
|
||||
pointerEventsCapture: wheelEventsCapture,
|
||||
keyboardEvents: panArrows.events,
|
||||
translate,
|
||||
targetCenter: readonly(targetCenter),
|
||||
|
Loading…
Reference in New Issue
Block a user