123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- 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'
-
- export class DrawSession extends BaseSession {
- type = SessionType.Draw
- status = TDStatus.Creating
- topLeft: number[]
- points: number[][]
- lastAdjustedPoint: number[]
- shiftedPoints: number[][] = []
- shapeId: string
- isLocked?: boolean
- lockedDirection?: 'horizontal' | 'vertical'
-
- constructor(app: TldrawApp, id: string) {
- super(app)
- const { originPoint } = this.app
- this.topLeft = [...originPoint]
- this.shapeId = id
-
- // 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]]
- this.shiftedPoints = [...this.points]
- this.lastAdjustedPoint = [0, 0]
- }
-
- start = (): TldrawPatch | undefined => void null
-
- update = (): TldrawPatch | undefined => {
- const { shapeId } = this
- const { currentPoint, originPoint, shiftKey } = this.app
-
- // 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'
- }
- }
-
- // Drawing while holding shift will "lock" the pen to either the
- // x or y axis, depending on the locking direction.
- if (shiftKey) {
- if (!this.isLocked && this.points.length > 2) {
- // 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]
-
- if (this.lockedDirection === 'vertical') {
- returning[0] = 0
- } else {
- returning[1] = 0
- }
-
- this.points.push(returning.concat(currentPoint[2]))
- }
- } 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])
-
- // Don't add duplicate points.
- if (Vec.isEqual(this.lastAdjustedPoint, newAdjustedPoint)) return
-
- // 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
-
- // 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]),
- ]
-
- const delta = Vec.sub(topLeft, originPoint)
-
- // Time to shift some points!
- let points: number[][]
-
- 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.
-
- points = this.points.map((pt) => {
- return Vec.toFixed(Vec.sub(pt, delta)).concat(pt[2])
- })
- } 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
-
- return {
- document: {
- pages: {
- [this.app.currentPageId]: {
- shapes: {
- [shapeId]: {
- point: this.topLeft,
- points,
- },
- },
- },
- },
- pageStates: {
- [this.app.currentPageId]: {
- selectedIds: [shapeId],
- },
- },
- },
- }
- }
-
- cancel = (): TldrawPatch | undefined => {
- const { shapeId } = this
- const pageId = this.app.currentPageId
-
- return {
- document: {
- pages: {
- [pageId]: {
- shapes: {
- [shapeId]: undefined,
- },
- },
- },
- pageStates: {
- [pageId]: {
- selectedIds: [],
- },
- },
- },
- }
- }
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const { shapeId } = this
- const pageId = this.app.currentPageId
-
- const shape = this.app.getShape<DrawShape>(shapeId)
-
- return {
- id: 'create_draw',
- before: {
- document: {
- pages: {
- [pageId]: {
- shapes: {
- [shapeId]: undefined,
- },
- },
- },
- pageStates: {
- [pageId]: {
- selectedIds: [],
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [pageId]: {
- shapes: {
- [shapeId]: {
- ...shape,
- point: Vec.toFixed(shape.point),
- points: shape.points.map((pt) => Vec.toFixed(pt)),
- isComplete: true,
- },
- },
- },
- },
- pageStates: {
- [this.app.currentPageId]: {
- selectedIds: [],
- },
- },
- },
- },
- }
- }
- }
|