You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

draw-session.ts 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { Data, DrawShape } from 'types'
  2. import BaseSession from './base-session'
  3. import { getShapeUtils } from 'state/shape-utils'
  4. import { deepClone, getBoundsFromPoints } from 'utils'
  5. import tld from 'utils/tld'
  6. import vec from 'utils/vec'
  7. import commands from 'state/commands'
  8. export default class DrawSession extends BaseSession {
  9. origin: number[]
  10. previous: number[]
  11. last: number[]
  12. points: number[][]
  13. snapshot: DrawSnapshot
  14. isLocked: boolean
  15. lockedDirection: 'horizontal' | 'vertical'
  16. constructor(data: Data, id: string, point: number[]) {
  17. super(data)
  18. this.origin = point
  19. this.previous = point
  20. this.last = point
  21. this.snapshot = getDrawSnapshot(data, id)
  22. // Add a first point but don't update the shape yet. We'll update
  23. // when the draw session ends; if the user hasn't added additional
  24. // points, this single point will be interpreted as a "dot" shape.
  25. this.points = [[0, 0, 0.5]]
  26. const shape = tld.getPage(data).shapes[id]
  27. getShapeUtils(shape).translateTo(shape, point)
  28. tld.updateParents(data, [shape.id])
  29. }
  30. update = (
  31. data: Data,
  32. point: number[],
  33. pressure: number,
  34. isLocked = false
  35. ): void => {
  36. const { snapshot } = this
  37. // Drawing while holding shift will "lock" the pen to either the
  38. // x or y axis, depending on which direction has the greater
  39. // delta. Pressing shift will also add more points to "return"
  40. // the pen to the axis.
  41. if (isLocked) {
  42. if (!this.isLocked && this.points.length > 1) {
  43. const bounds = getBoundsFromPoints(this.points)
  44. if (bounds.width > 8 || bounds.height > 8) {
  45. this.isLocked = true
  46. const returning = [...this.previous]
  47. const isVertical = bounds.height > 8
  48. if (isVertical) {
  49. this.lockedDirection = 'vertical'
  50. returning[0] = this.origin[0]
  51. } else {
  52. this.lockedDirection = 'horizontal'
  53. returning[1] = this.origin[1]
  54. }
  55. this.previous = returning
  56. this.points.push(vec.sub(returning, this.origin))
  57. }
  58. }
  59. } else if (this.isLocked) {
  60. this.isLocked = false
  61. }
  62. if (this.isLocked) {
  63. if (this.lockedDirection === 'vertical') {
  64. point[0] = this.origin[0]
  65. } else {
  66. point[1] = this.origin[1]
  67. }
  68. }
  69. // Low pass the current input point against the previous one
  70. const nextPrev = vec.med(this.previous, point)
  71. this.previous = nextPrev
  72. // Don't add duplicate points. It's important to test against the
  73. // adjusted (low-passed) point rather than the input point.
  74. const newPoint = vec.round([
  75. ...vec.sub(this.previous, this.origin),
  76. pressure,
  77. ])
  78. if (vec.isEqual(this.last, newPoint)) return
  79. this.last = newPoint
  80. this.points.push(newPoint)
  81. // We draw a dot when the number of points is 1 or 2, so this guard
  82. // prevents a "flash" of a dot when a user begins drawing a line.
  83. if (this.points.length <= 2) return
  84. // Update the points and update the shape's parents.
  85. const shape = tld.getShape(data, snapshot.id) as DrawShape
  86. // Note: Normally we would want to spread the points to create a new
  87. // array, however we create the new array in hacks/fastDrawUpdate.
  88. getShapeUtils(shape).setProperty(shape, 'points', this.points)
  89. tld.updateParents(data, [shape.id])
  90. }
  91. cancel = (data: Data): void => {
  92. const { snapshot } = this
  93. const shape = tld.getShape(data, snapshot.id) as DrawShape
  94. getShapeUtils(shape).translateTo(shape, snapshot.point)
  95. getShapeUtils(shape).setProperty(shape, 'points', snapshot.points)
  96. tld.updateParents(data, [shape.id])
  97. }
  98. complete = (data: Data): void => {
  99. const { snapshot } = this
  100. const page = tld.getPage(data)
  101. const shape = page.shapes[snapshot.id] as DrawShape
  102. if (shape.points.length < this.points.length) {
  103. getShapeUtils(shape).setProperty(shape, 'points', this.points)
  104. }
  105. getShapeUtils(shape).onSessionComplete(shape)
  106. tld.updateParents(data, [shape.id])
  107. commands.draw(data, this.snapshot.id)
  108. }
  109. }
  110. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  111. export function getDrawSnapshot(data: Data, shapeId: string) {
  112. const page = tld.getPage(data)
  113. const { points, point } = deepClone(page.shapes[shapeId]) as DrawShape
  114. return {
  115. id: shapeId,
  116. point,
  117. points,
  118. }
  119. }
  120. export type DrawSnapshot = ReturnType<typeof getDrawSnapshot>