tldraw/state/sessions/draw-session.ts

159 lines
4.5 KiB
TypeScript
Raw Normal View History

2021-05-28 14:37:23 +00:00
import { current } from 'immer'
import { Data, DrawShape } from 'types'
import BaseSession from './base-session'
import { getShapeUtils } from 'lib/shape-utils'
2021-06-12 16:46:36 +00:00
import { getPage, getShape, isMobile, updateParents } from 'utils/utils'
import vec from 'utils/vec'
2021-05-28 14:37:23 +00:00
import commands from 'state/commands'
2021-05-27 17:59:40 +00:00
export default class BrushSession extends BaseSession {
origin: number[]
previous: number[]
2021-06-06 07:33:30 +00:00
last: number[]
2021-05-27 17:59:40 +00:00
points: number[][]
snapshot: DrawSnapshot
isLocked: boolean
lockedDirection: 'horizontal' | 'vertical'
2021-05-27 17:59:40 +00:00
constructor(data: Data, id: string, point: number[], isLocked = false) {
2021-05-27 17:59:40 +00:00
super(data)
this.origin = point
this.previous = point
2021-06-06 07:33:30 +00:00
this.last = point
2021-05-27 17:59:40 +00:00
this.snapshot = getDrawSnapshot(data, id)
2021-06-12 08:25:04 +00:00
// Add a first point but don't update the shape yet. We'll update
// when the draw session ends; if the user hasn't added additional
// points, this single point will be interpreted as a "dot" shape.
2021-06-13 13:55:37 +00:00
this.points = [[0, 0, 0.5]]
2021-06-12 08:25:04 +00:00
const shape = getPage(data).shapes[id]
2021-06-04 16:08:43 +00:00
getShapeUtils(shape).translateTo(shape, point)
2021-06-12 08:10:12 +00:00
updateParents(data, [shape.id])
2021-05-27 17:59:40 +00:00
}
2021-06-06 07:33:30 +00:00
update = (
data: Data,
point: number[],
pressure: number,
isLocked = false
) => {
2021-05-29 12:54:33 +00:00
const { snapshot } = this
2021-05-27 17:59:40 +00:00
const delta = vec.vec(this.origin, point)
2021-06-12 08:25:04 +00:00
// Drawing while holding shift will "lock" the pen to either the
// x or y axis, depending on which direction has the greater
// delta. Pressing shift will also add more points to "return"
// the pen to the axis.
if (isLocked) {
if (!this.isLocked && this.points.length > 1) {
this.isLocked = true
const returning = [...this.previous]
2021-06-12 08:25:04 +00:00
const isVertical = Math.abs(delta[0]) < Math.abs(delta[1])
if (isVertical) {
this.lockedDirection = 'vertical'
returning[0] = this.origin[0]
} else {
this.lockedDirection = 'horizontal'
returning[1] = this.origin[1]
}
this.previous = returning
this.points.push(vec.sub(returning, this.origin))
}
2021-06-12 08:25:04 +00:00
} else if (this.isLocked) {
this.isLocked = false
}
if (this.isLocked) {
if (this.lockedDirection === 'vertical') {
point[0] = this.origin[0]
} else {
point[1] = this.origin[1]
}
}
2021-06-12 16:34:38 +00:00
// Low pass the current input point against the previous one
const nextPrev = vec.med(this.previous, point)
this.previous = nextPrev
2021-06-12 08:25:04 +00:00
// Don't add duplicate points. It's important to test against the
// adjusted (low-passed) point rather than the input point.
2021-06-12 16:04:11 +00:00
const newPoint = vec.round([
...vec.sub(this.previous, this.origin),
pressure,
])
if (vec.isEqual(this.last, newPoint)) return
2021-06-12 08:25:04 +00:00
2021-06-12 16:04:11 +00:00
this.last = newPoint
this.points.push(newPoint)
2021-06-06 07:33:30 +00:00
2021-06-12 08:25:04 +00:00
// We draw a dot when the number of points is 1 or 2, so this guard
// prevents a "flash" of a dot when a user begins drawing a line.
if (this.points.length <= 2) return
2021-05-27 17:59:40 +00:00
2021-06-12 16:04:11 +00:00
// Update the points and update the shape's parents.
2021-06-04 16:08:43 +00:00
const shape = getShape(data, snapshot.id) as DrawShape
2021-06-12 16:04:11 +00:00
2021-06-13 13:55:37 +00:00
// Offset the points and shapes to avoid negative numbers
const ox = Math.min(newPoint[0], 0)
const oy = Math.min(newPoint[1], 0)
2021-06-13 13:24:03 +00:00
2021-06-13 13:55:37 +00:00
if (ox < 0 || oy < 0) {
const offset = [ox, oy]
this.points = this.points.map((pt) => [...vec.sub(pt, offset), pt[2]])
this.origin = vec.add(this.origin, offset)
2021-06-13 13:24:03 +00:00
getShapeUtils(shape).translateBy(shape, offset)
}
getShapeUtils(shape).setProperty(shape, 'points', [...this.points])
2021-06-12 16:04:11 +00:00
2021-06-04 16:14:01 +00:00
updateParents(data, [shape.id])
2021-05-27 17:59:40 +00:00
}
cancel = (data: Data) => {
2021-05-29 12:54:33 +00:00
const { snapshot } = this
2021-06-04 16:08:43 +00:00
const shape = getShape(data, snapshot.id) as DrawShape
2021-06-13 13:55:37 +00:00
getShapeUtils(shape).translateTo(shape, snapshot.point)
2021-05-29 12:54:33 +00:00
getShapeUtils(shape).setProperty(shape, 'points', snapshot.points)
2021-06-04 16:14:01 +00:00
updateParents(data, [shape.id])
2021-05-27 17:59:40 +00:00
}
complete = (data: Data) => {
const { snapshot } = this
const page = getPage(data)
const shape = page.shapes[snapshot.id] as DrawShape
2021-05-29 22:27:19 +00:00
2021-06-12 16:04:11 +00:00
if (shape.points.length < this.points.length) {
getShapeUtils(shape).setProperty(shape, 'points', this.points)
}
getShapeUtils(shape).onSessionComplete(shape)
2021-06-12 08:10:12 +00:00
updateParents(data, [shape.id])
commands.draw(data, this.snapshot.id)
2021-05-27 17:59:40 +00:00
}
}
export function getDrawSnapshot(data: Data, shapeId: string) {
const page = getPage(current(data))
2021-06-13 13:55:37 +00:00
const { points, point } = page.shapes[shapeId] as DrawShape
2021-05-27 17:59:40 +00:00
return {
2021-05-29 12:54:33 +00:00
id: shapeId,
2021-06-13 13:55:37 +00:00
point,
2021-05-27 17:59:40 +00:00
points,
}
}
export type DrawSnapshot = ReturnType<typeof getDrawSnapshot>