Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

draw.tsx 4.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import { v4 as uuid } from "uuid"
  2. import * as vec from "utils/vec"
  3. import { DrawShape, ShapeType } from "types"
  4. import { registerShapeUtils } from "./index"
  5. import { intersectPolylineBounds } from "utils/intersections"
  6. import { boundsContainPolygon } from "utils/bounds"
  7. import { getBoundsFromPoints, translateBounds } from "utils/utils"
  8. import { DotCircle } from "components/canvas/misc"
  9. const pathCache = new WeakMap<DrawShape, string>([])
  10. const draw = registerShapeUtils<DrawShape>({
  11. boundsCache: new WeakMap([]),
  12. create(props) {
  13. return {
  14. id: uuid(),
  15. type: ShapeType.Draw,
  16. isGenerated: false,
  17. name: "Draw",
  18. parentId: "page0",
  19. childIndex: 0,
  20. point: [0, 0],
  21. points: [[0, 0]],
  22. rotation: 0,
  23. ...props,
  24. style: {
  25. strokeWidth: 2,
  26. strokeLinecap: "round",
  27. strokeLinejoin: "round",
  28. ...props.style,
  29. fill: "transparent",
  30. },
  31. }
  32. },
  33. render(shape) {
  34. const { id, point, points } = shape
  35. if (points.length < 2) {
  36. return <DotCircle cx={point[0]} cy={point[1]} r={3} />
  37. }
  38. if (!pathCache.has(shape)) {
  39. pathCache.set(
  40. shape,
  41. points
  42. .reduce(
  43. (acc, [x0, y0], i, arr) => {
  44. if (i === points.length - 1) {
  45. acc.push("L", x0, y0)
  46. } else {
  47. const [x1, y1] = arr[i + 1]
  48. acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2)
  49. }
  50. return acc
  51. },
  52. ["M", ...points[0], "Q"]
  53. )
  54. .join(" ")
  55. )
  56. }
  57. return <path id={id} d={pathCache.get(shape)} />
  58. },
  59. applyStyles(shape, style) {
  60. Object.assign(shape.style, style)
  61. shape.style.fill = "transparent"
  62. return this
  63. },
  64. getBounds(shape) {
  65. if (!this.boundsCache.has(shape)) {
  66. const bounds = getBoundsFromPoints(shape.points)
  67. this.boundsCache.set(shape, bounds)
  68. }
  69. return translateBounds(this.boundsCache.get(shape), shape.point)
  70. },
  71. getRotatedBounds(shape) {
  72. return this.getBounds(shape)
  73. },
  74. getCenter(shape) {
  75. const bounds = this.getBounds(shape)
  76. return [bounds.minX + bounds.width / 2, bounds.minY + bounds.height / 2]
  77. },
  78. hitTest(shape, point) {
  79. let pt = vec.sub(point, shape.point)
  80. let prev = shape.points[0]
  81. for (let i = 1; i < shape.points.length; i++) {
  82. let curr = shape.points[i]
  83. if (vec.distanceToLineSegment(prev, curr, pt) < 4) {
  84. return true
  85. }
  86. prev = curr
  87. }
  88. return false
  89. },
  90. hitTestBounds(this, shape, brushBounds) {
  91. const b = this.getBounds(shape)
  92. const center = [b.minX + b.width / 2, b.minY + b.height / 2]
  93. const rotatedCorners = [
  94. [b.minX, b.minY],
  95. [b.maxX, b.minY],
  96. [b.maxX, b.maxY],
  97. [b.minX, b.maxY],
  98. ].map((point) => vec.rotWith(point, center, shape.rotation))
  99. return (
  100. boundsContainPolygon(brushBounds, rotatedCorners) ||
  101. intersectPolylineBounds(
  102. shape.points.map((point) => vec.add(point, shape.point)),
  103. brushBounds
  104. ).length > 0
  105. )
  106. },
  107. rotateTo(shape, rotation) {
  108. shape.rotation = rotation
  109. return this
  110. },
  111. translateTo(shape, point) {
  112. shape.point = point
  113. return this
  114. },
  115. transform(shape, bounds, { initialShape, scaleX, scaleY }) {
  116. const initialShapeBounds = this.boundsCache.get(initialShape)
  117. shape.points = initialShape.points.map(([x, y]) => {
  118. return [
  119. bounds.width *
  120. (scaleX < 0
  121. ? 1 - x / initialShapeBounds.width
  122. : x / initialShapeBounds.width),
  123. bounds.height *
  124. (scaleY < 0
  125. ? 1 - y / initialShapeBounds.height
  126. : y / initialShapeBounds.height),
  127. ]
  128. })
  129. const newBounds = getBoundsFromPoints(shape.points)
  130. shape.point = vec.sub(
  131. [bounds.minX, bounds.minY],
  132. [newBounds.minX, newBounds.minY]
  133. )
  134. return this
  135. },
  136. transformSingle(shape, bounds, info) {
  137. this.transform(shape, bounds, info)
  138. return this
  139. },
  140. setParent(shape, parentId) {
  141. shape.parentId = parentId
  142. return this
  143. },
  144. setChildIndex(shape, childIndex) {
  145. shape.childIndex = childIndex
  146. return this
  147. },
  148. setPoints(shape, points) {
  149. // const bounds = getBoundsFromPoints(points)
  150. // const corner = [bounds.minX, bounds.minY]
  151. // const nudged = points.map((point) => vec.sub(point, corner))
  152. // this.boundsCache.set(shape, translategetBoundsFromPoints(nudged))
  153. // shape.point = vec.add(shape.point, corner)
  154. shape.points = points
  155. return this
  156. },
  157. canTransform: true,
  158. canChangeAspectRatio: true,
  159. })
  160. export default draw