@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
132 lines (118 loc) • 4.09 kB
text/typescript
import { Vec } from '../../../primitives/Vec'
import { EASINGS } from '../../../primitives/easings'
import { Editor } from '../../Editor'
/** @public */
export class EdgeScrollManager {
constructor(public editor: Editor) {}
private _isEdgeScrolling = false
private _edgeScrollDuration = -1
/**
* Update the camera position when the mouse is close to the edge of the screen.
* Run this on every tick when in a state where edge scrolling is enabled.
*
* @public
*/
updateEdgeScrolling(elapsed: number) {
const { editor } = this
const edgeScrollProximityFactor = this.getEdgeScroll()
if (edgeScrollProximityFactor.x === 0 && edgeScrollProximityFactor.y === 0) {
if (this._isEdgeScrolling) {
this._isEdgeScrolling = false
this._edgeScrollDuration = 0
}
} else {
if (!this._isEdgeScrolling) {
this._isEdgeScrolling = true
this._edgeScrollDuration = 0
}
this._edgeScrollDuration += elapsed
if (this._edgeScrollDuration > editor.options.edgeScrollDelay) {
const eased =
editor.options.edgeScrollEaseDuration > 0
? EASINGS.easeInCubic(
Math.min(
1,
this._edgeScrollDuration /
(editor.options.edgeScrollDelay + editor.options.edgeScrollEaseDuration)
)
)
: 1
this.moveCameraWhenCloseToEdge({
x: edgeScrollProximityFactor.x * eased,
y: edgeScrollProximityFactor.y * eased,
})
}
}
}
/**
* Helper function to get the scroll proximity factor for a given position.
* @param position - The mouse position on the axis.
* @param dimension - The component dimension on the axis.
* @param isCoarse - Whether the pointer is coarse.
* @param insetStart - Whether the pointer is inset at the start of the axis.
* @param insetEnd - Whether the pointer is inset at the end of the axis.
* @internal
*/
private getEdgeProximityFactors(
position: number,
dimension: number,
isCoarse: boolean,
insetStart: boolean,
insetEnd: boolean
) {
const { editor } = this
const dist = editor.options.edgeScrollDistance
const pw = isCoarse ? editor.options.coarsePointerWidth : 0 // pointer width
const pMin = position - pw
const pMax = position + pw
const min = insetStart ? 0 : dist
const max = insetEnd ? dimension : dimension - dist
if (pMin < min) {
return Math.min(1, (min - pMin) / dist)
} else if (pMax > max) {
return -Math.min(1, (pMax - max) / dist)
}
return 0
}
private getEdgeScroll() {
const { editor } = this
const {
inputs: {
currentScreenPoint: { x, y },
},
} = editor
const screenBounds = editor.getViewportScreenBounds()
const {
isCoarsePointer,
insets: [t, r, b, l],
} = editor.getInstanceState()
const proximityFactorX = this.getEdgeProximityFactors(x, screenBounds.w, isCoarsePointer, l, r)
const proximityFactorY = this.getEdgeProximityFactors(y, screenBounds.h, isCoarsePointer, t, b)
return {
x: proximityFactorX,
y: proximityFactorY,
}
}
/**
* Moves the camera when the mouse is close to the edge of the screen.
* @public
*/
private moveCameraWhenCloseToEdge(proximityFactor: { x: number; y: number }) {
const { editor } = this
if (!editor.inputs.isDragging || editor.inputs.isPanning || editor.getCameraOptions().isLocked)
return
if (proximityFactor.x === 0 && proximityFactor.y === 0) return
const screenBounds = editor.getViewportScreenBounds()
// Determines how much the speed is affected by the screen size
const screenSizeFactorX = screenBounds.w < 1000 ? 0.612 : 1
const screenSizeFactorY = screenBounds.h < 1000 ? 0.612 : 1
// Determines the base speed of the scroll
const zoomLevel = editor.getZoomLevel()
const pxSpeed = editor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed
const scrollDeltaX = (pxSpeed * proximityFactor.x * screenSizeFactorX) / zoomLevel
const scrollDeltaY = (pxSpeed * proximityFactor.y * screenSizeFactorY) / zoomLevel
// update the camera
const { x, y, z } = editor.getCamera()
editor.setCamera(new Vec(x + scrollDeltaX, y + scrollDeltaY, z))
}
}