Tldraw/packages/tldraw/src/state/sessions/DrawSession/DrawSession.ts

217 wiersze
6.1 KiB
TypeScript
Czysty Zwykły widok Historia

import { Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { SessionType, TDStatus, TldrawPatch, TldrawCommand, DrawShape } from '~types'
import type { TldrawApp } from '../../internal'
import { BaseSession } from '../BaseSession'
2021-08-10 16:12:55 +00:00
export class DrawSession extends BaseSession {
type = SessionType.Draw
status = TDStatus.Creating
topLeft: number[]
2021-05-27 17:59:40 +00:00
points: number[][]
lastAdjustedPoint: number[]
shiftedPoints: number[][] = []
shapeId: string
2021-08-10 16:12:55 +00:00
isLocked?: boolean
lockedDirection?: 'horizontal' | 'vertical'
2021-05-27 17:59:40 +00:00
constructor(app: TldrawApp, id: string) {
super(app)
const { originPoint } = this.app
this.topLeft = [...originPoint]
this.shapeId = id
2021-05-27 17:59:40 +00:00
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.
this.points = [[0, 0, originPoint[2] || 0.5]]
2021-11-12 22:19:50 +00:00
this.shiftedPoints = [...this.points]
this.lastAdjustedPoint = [0, 0]
2021-05-27 17:59:40 +00:00
}
start = (): TldrawPatch | undefined => void null
2021-08-10 16:12:55 +00:00
update = (): TldrawPatch | undefined => {
const { shapeId } = this
const { currentPoint, originPoint, shiftKey } = this.app
2021-05-27 17:59:40 +00:00
2021-09-14 11:17:49 +00:00
// Even if we're not locked yet, we base the future locking direction
// on the first dimension to reach a threshold, or the bigger dimension
// once one or both dimensions have reached the threshold.
if (!this.lockedDirection && this.points.length > 1) {
const bounds = Utils.getBoundsFromPoints(this.points)
if (bounds.width > 8 || bounds.height > 8) {
this.lockedDirection = bounds.width > bounds.height ? 'horizontal' : 'vertical'
}
}
2021-06-12 08:25:04 +00:00
// Drawing while holding shift will "lock" the pen to either the
2021-09-14 11:17:49 +00:00
// x or y axis, depending on the locking direction.
if (shiftKey) {
if (!this.isLocked && this.points.length > 2) {
2021-09-14 11:17:49 +00:00
// If we're locking before knowing what direction we're in, set it
// early based on the bigger dimension.
if (!this.lockedDirection) {
const bounds = Utils.getBoundsFromPoints(this.points)
this.lockedDirection = bounds.width > bounds.height ? 'horizontal' : 'vertical'
}
this.isLocked = true
// Start locking
const returning = [...this.lastAdjustedPoint]
2021-09-14 11:17:49 +00:00
if (this.lockedDirection === 'vertical') {
returning[0] = 0
} else {
returning[1] = 0
}
2021-09-14 11:17:49 +00:00
this.points.push(returning.concat(currentPoint[2]))
}
2021-06-12 08:25:04 +00:00
} else if (this.isLocked) {
this.isLocked = false
}
if (this.isLocked) {
if (this.lockedDirection === 'vertical') {
currentPoint[0] = originPoint[0]
} else {
currentPoint[1] = originPoint[1]
}
}
// The new adjusted point
const newAdjustedPoint = Vec.toFixed(Vec.sub(currentPoint, originPoint)).concat(currentPoint[2])
2021-06-12 16:04:11 +00:00
2021-09-14 11:17:49 +00:00
// Don't add duplicate points.
if (Vec.isEqual(this.lastAdjustedPoint, newAdjustedPoint)) return
2021-06-12 08:25:04 +00:00
// Add the new adjusted point to the points array
this.points.push(newAdjustedPoint)
// The new adjusted point is now the previous adjusted point.
this.lastAdjustedPoint = newAdjustedPoint
2021-06-12 16:04:11 +00:00
2021-09-11 23:34:15 +00:00
// Does the input point create a new top left?
const prevTopLeft = [...this.topLeft]
const topLeft = [
Math.min(this.topLeft[0], currentPoint[0]),
Math.min(this.topLeft[1], currentPoint[1]),
]
2021-09-11 23:34:15 +00:00
const delta = Vec.sub(topLeft, originPoint)
// Time to shift some points!
2021-09-11 23:34:15 +00:00
let points: number[][]
2021-09-14 11:17:49 +00:00
if (prevTopLeft[0] !== topLeft[0] || prevTopLeft[1] !== topLeft[1]) {
this.topLeft = topLeft
// If we have a new top left, then we need to iterate through
// the "unshifted" points array and shift them based on the
// offset between the new top left and the original top left.
2021-09-14 11:17:49 +00:00
points = this.points.map((pt) => {
return Vec.toFixed(Vec.sub(pt, delta)).concat(pt[2])
2021-09-14 11:17:49 +00:00
})
} else {
// If the new top left is the same as the previous top left,
// we don't need to shift anything: we just shift the new point
// and add it to the shifted points array.
points = [...this.shiftedPoints, Vec.sub(newAdjustedPoint, delta).concat(newAdjustedPoint[2])]
}
this.shiftedPoints = points
2021-08-10 16:12:55 +00:00
return {
2021-08-16 21:52:03 +00:00
document: {
pages: {
[this.app.currentPageId]: {
2021-08-16 21:52:03 +00:00
shapes: {
[shapeId]: {
point: this.topLeft,
points,
2021-08-16 21:52:03 +00:00
},
},
},
},
pageStates: {
[this.app.currentPageId]: {
selectedIds: [shapeId],
2021-08-10 16:12:55 +00:00
},
},
},
}
2021-05-27 17:59:40 +00:00
}
cancel = (): TldrawPatch | undefined => {
const { shapeId } = this
const pageId = this.app.currentPageId
2021-08-16 21:52:03 +00:00
2021-08-10 16:12:55 +00:00
return {
2021-08-16 21:52:03 +00:00
document: {
pages: {
2021-08-17 21:38:37 +00:00
[pageId]: {
2021-08-16 21:52:03 +00:00
shapes: {
[shapeId]: undefined,
2021-08-16 21:52:03 +00:00
},
},
},
pageStates: {
2021-08-17 21:38:37 +00:00
[pageId]: {
2021-08-16 21:52:03 +00:00
selectedIds: [],
},
2021-08-10 16:12:55 +00:00
},
},
}
2021-05-27 17:59:40 +00:00
}
complete = (): TldrawPatch | TldrawCommand | undefined => {
const { shapeId } = this
const pageId = this.app.currentPageId
const shape = this.app.getShape<DrawShape>(shapeId)
2021-08-10 16:12:55 +00:00
return {
id: 'create_draw',
before: {
2021-08-16 21:52:03 +00:00
document: {
pages: {
2021-08-17 21:38:37 +00:00
[pageId]: {
2021-08-16 21:52:03 +00:00
shapes: {
[shapeId]: undefined,
2021-08-16 21:52:03 +00:00
},
},
},
pageStates: {
2021-08-17 21:38:37 +00:00
[pageId]: {
2021-08-16 21:52:03 +00:00
selectedIds: [],
},
2021-08-10 16:12:55 +00:00
},
},
},
after: {
2021-08-16 21:52:03 +00:00
document: {
pages: {
2021-08-17 21:38:37 +00:00
[pageId]: {
2021-08-16 21:52:03 +00:00
shapes: {
[shapeId]: {
...shape,
point: Vec.toFixed(shape.point),
points: shape.points.map((pt) => Vec.toFixed(pt)),
isComplete: true,
},
2021-08-16 21:52:03 +00:00
},
},
},
pageStates: {
[this.app.currentPageId]: {
2021-08-16 21:52:03 +00:00
selectedIds: [],
},
2021-08-10 16:12:55 +00:00
},
},
},
2021-06-12 16:04:11 +00:00
}
2021-05-27 17:59:40 +00:00
}
}