Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

DrawSession.ts 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import { Utils } from '@tldraw/core'
  2. import { Vec } from '@tldraw/vec'
  3. import { SessionType, TDStatus, TldrawPatch, TldrawCommand, DrawShape } from '~types'
  4. import type { TldrawApp } from '../../internal'
  5. import { BaseSession } from '../BaseSession'
  6. export class DrawSession extends BaseSession {
  7. type = SessionType.Draw
  8. status = TDStatus.Creating
  9. topLeft: number[]
  10. points: number[][]
  11. lastAdjustedPoint: number[]
  12. shiftedPoints: number[][] = []
  13. shapeId: string
  14. isLocked?: boolean
  15. lockedDirection?: 'horizontal' | 'vertical'
  16. constructor(app: TldrawApp, id: string) {
  17. super(app)
  18. const { originPoint } = this.app
  19. this.topLeft = [...originPoint]
  20. this.shapeId = id
  21. // Add a first point but don't update the shape yet. We'll update
  22. // when the draw session ends; if the user hasn't added additional
  23. // points, this single point will be interpreted as a "dot" shape.
  24. this.points = [[0, 0, originPoint[2] || 0.5]]
  25. this.shiftedPoints = [...this.points]
  26. this.lastAdjustedPoint = [0, 0]
  27. }
  28. start = (): TldrawPatch | undefined => void null
  29. update = (): TldrawPatch | undefined => {
  30. const { shapeId } = this
  31. const { currentPoint, originPoint, shiftKey } = this.app
  32. // Even if we're not locked yet, we base the future locking direction
  33. // on the first dimension to reach a threshold, or the bigger dimension
  34. // once one or both dimensions have reached the threshold.
  35. if (!this.lockedDirection && this.points.length > 1) {
  36. const bounds = Utils.getBoundsFromPoints(this.points)
  37. if (bounds.width > 8 || bounds.height > 8) {
  38. this.lockedDirection = bounds.width > bounds.height ? 'horizontal' : 'vertical'
  39. }
  40. }
  41. // Drawing while holding shift will "lock" the pen to either the
  42. // x or y axis, depending on the locking direction.
  43. if (shiftKey) {
  44. if (!this.isLocked && this.points.length > 2) {
  45. // If we're locking before knowing what direction we're in, set it
  46. // early based on the bigger dimension.
  47. if (!this.lockedDirection) {
  48. const bounds = Utils.getBoundsFromPoints(this.points)
  49. this.lockedDirection = bounds.width > bounds.height ? 'horizontal' : 'vertical'
  50. }
  51. this.isLocked = true
  52. // Start locking
  53. const returning = [...this.lastAdjustedPoint]
  54. if (this.lockedDirection === 'vertical') {
  55. returning[0] = 0
  56. } else {
  57. returning[1] = 0
  58. }
  59. this.points.push(returning.concat(currentPoint[2]))
  60. }
  61. } else if (this.isLocked) {
  62. this.isLocked = false
  63. }
  64. if (this.isLocked) {
  65. if (this.lockedDirection === 'vertical') {
  66. currentPoint[0] = originPoint[0]
  67. } else {
  68. currentPoint[1] = originPoint[1]
  69. }
  70. }
  71. // The new adjusted point
  72. const newAdjustedPoint = Vec.toFixed(Vec.sub(currentPoint, originPoint)).concat(currentPoint[2])
  73. // Don't add duplicate points.
  74. if (Vec.isEqual(this.lastAdjustedPoint, newAdjustedPoint)) return
  75. // Add the new adjusted point to the points array
  76. this.points.push(newAdjustedPoint)
  77. // The new adjusted point is now the previous adjusted point.
  78. this.lastAdjustedPoint = newAdjustedPoint
  79. // Does the input point create a new top left?
  80. const prevTopLeft = [...this.topLeft]
  81. const topLeft = [
  82. Math.min(this.topLeft[0], currentPoint[0]),
  83. Math.min(this.topLeft[1], currentPoint[1]),
  84. ]
  85. const delta = Vec.sub(topLeft, originPoint)
  86. // Time to shift some points!
  87. let points: number[][]
  88. if (prevTopLeft[0] !== topLeft[0] || prevTopLeft[1] !== topLeft[1]) {
  89. this.topLeft = topLeft
  90. // If we have a new top left, then we need to iterate through
  91. // the "unshifted" points array and shift them based on the
  92. // offset between the new top left and the original top left.
  93. points = this.points.map((pt) => {
  94. return Vec.toFixed(Vec.sub(pt, delta)).concat(pt[2])
  95. })
  96. } else {
  97. // If the new top left is the same as the previous top left,
  98. // we don't need to shift anything: we just shift the new point
  99. // and add it to the shifted points array.
  100. points = [...this.shiftedPoints, Vec.sub(newAdjustedPoint, delta).concat(newAdjustedPoint[2])]
  101. }
  102. this.shiftedPoints = points
  103. return {
  104. document: {
  105. pages: {
  106. [this.app.currentPageId]: {
  107. shapes: {
  108. [shapeId]: {
  109. point: this.topLeft,
  110. points,
  111. },
  112. },
  113. },
  114. },
  115. pageStates: {
  116. [this.app.currentPageId]: {
  117. selectedIds: [shapeId],
  118. },
  119. },
  120. },
  121. }
  122. }
  123. cancel = (): TldrawPatch | undefined => {
  124. const { shapeId } = this
  125. const pageId = this.app.currentPageId
  126. return {
  127. document: {
  128. pages: {
  129. [pageId]: {
  130. shapes: {
  131. [shapeId]: undefined,
  132. },
  133. },
  134. },
  135. pageStates: {
  136. [pageId]: {
  137. selectedIds: [],
  138. },
  139. },
  140. },
  141. }
  142. }
  143. complete = (): TldrawPatch | TldrawCommand | undefined => {
  144. const { shapeId } = this
  145. const pageId = this.app.currentPageId
  146. const shape = this.app.getShape<DrawShape>(shapeId)
  147. return {
  148. id: 'create_draw',
  149. before: {
  150. document: {
  151. pages: {
  152. [pageId]: {
  153. shapes: {
  154. [shapeId]: undefined,
  155. },
  156. },
  157. },
  158. pageStates: {
  159. [pageId]: {
  160. selectedIds: [],
  161. },
  162. },
  163. },
  164. },
  165. after: {
  166. document: {
  167. pages: {
  168. [pageId]: {
  169. shapes: {
  170. [shapeId]: {
  171. ...shape,
  172. point: Vec.toFixed(shape.point),
  173. points: shape.points.map((pt) => Vec.toFixed(pt)),
  174. isComplete: true,
  175. },
  176. },
  177. },
  178. },
  179. pageStates: {
  180. [this.app.currentPageId]: {
  181. selectedIds: [],
  182. },
  183. },
  184. },
  185. },
  186. }
  187. }
  188. }