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.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import { current } from 'immer'
  2. import { Data, DrawShape } from 'types'
  3. import BaseSession from './base-session'
  4. import { getShapeUtils } from 'lib/shape-utils'
  5. import { getPage, getShape, isMobile, updateParents } from 'utils/utils'
  6. import * as vec from 'utils/vec'
  7. import commands from 'state/commands'
  8. export default class BrushSession 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[], isLocked = false) {
  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]]
  26. const shape = getPage(data).shapes[id]
  27. getShapeUtils(shape).translateTo(shape, point)
  28. updateParents(data, [shape.id])
  29. }
  30. update = (
  31. data: Data,
  32. point: number[],
  33. pressure: number,
  34. isLocked = false
  35. ) => {
  36. const { snapshot } = this
  37. const delta = vec.vec(this.origin, point)
  38. // Drawing while holding shift will "lock" the pen to either the
  39. // x or y axis, depending on which direction has the greater
  40. // delta. Pressing shift will also add more points to "return"
  41. // the pen to the axis.
  42. if (isLocked) {
  43. if (!this.isLocked && this.points.length > 1) {
  44. this.isLocked = true
  45. const returning = [...this.previous]
  46. const isVertical = Math.abs(delta[0]) < Math.abs(delta[1])
  47. if (isVertical) {
  48. this.lockedDirection = 'vertical'
  49. returning[0] = this.origin[0]
  50. } else {
  51. this.lockedDirection = 'horizontal'
  52. returning[1] = this.origin[1]
  53. }
  54. this.previous = returning
  55. this.points.push(vec.sub(returning, this.origin))
  56. }
  57. } else if (this.isLocked) {
  58. this.isLocked = false
  59. }
  60. if (this.isLocked) {
  61. if (this.lockedDirection === 'vertical') {
  62. point[0] = this.origin[0]
  63. } else {
  64. point[1] = this.origin[1]
  65. }
  66. }
  67. // Low pass the current input point against the previous one
  68. const nextPrev = vec.med(this.previous, point)
  69. this.previous = nextPrev
  70. // Don't add duplicate points. It's important to test against the
  71. // adjusted (low-passed) point rather than the input point.
  72. const newPoint = vec.round([
  73. ...vec.sub(this.previous, this.origin),
  74. pressure,
  75. ])
  76. if (vec.isEqual(this.last, newPoint)) return
  77. this.last = newPoint
  78. this.points.push(newPoint)
  79. // We draw a dot when the number of points is 1 or 2, so this guard
  80. // prevents a "flash" of a dot when a user begins drawing a line.
  81. if (this.points.length <= 2) return
  82. // Update the points and update the shape's parents.
  83. const shape = getShape(data, snapshot.id) as DrawShape
  84. getShapeUtils(shape).setProperty(shape, 'points', [...this.points])
  85. updateParents(data, [shape.id])
  86. }
  87. cancel = (data: Data) => {
  88. const { snapshot } = this
  89. const shape = getShape(data, snapshot.id) as DrawShape
  90. getShapeUtils(shape).setProperty(shape, 'points', snapshot.points)
  91. updateParents(data, [shape.id])
  92. }
  93. complete = (data: Data) => {
  94. const { snapshot } = this
  95. const page = getPage(data)
  96. const shape = page.shapes[snapshot.id] as DrawShape
  97. if (shape.points.length < this.points.length) {
  98. getShapeUtils(shape).setProperty(shape, 'points', this.points)
  99. }
  100. getShapeUtils(shape).onSessionComplete(shape)
  101. updateParents(data, [shape.id])
  102. commands.draw(data, this.snapshot.id)
  103. }
  104. }
  105. export function getDrawSnapshot(data: Data, shapeId: string) {
  106. const page = getPage(current(data))
  107. const { points } = page.shapes[shapeId] as DrawShape
  108. return {
  109. id: shapeId,
  110. points,
  111. }
  112. }
  113. export type DrawSnapshot = ReturnType<typeof getDrawSnapshot>