Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

index.tsx 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import {
  2. Bounds,
  3. Shape,
  4. ShapeType,
  5. Corner,
  6. Edge,
  7. ShapeStyles,
  8. ShapeHandle,
  9. ShapeBinding,
  10. BaseShape,
  11. ShapeSpecificProps,
  12. Mutable,
  13. } from 'types'
  14. import { v4 as uuid } from 'uuid'
  15. import circle from './circle'
  16. import dot from './dot'
  17. import polyline from './polyline'
  18. import rectangle from './rectangle'
  19. import ellipse from './ellipse'
  20. import line from './line'
  21. import ray from './ray'
  22. import draw from './draw'
  23. import arrow from './arrow'
  24. import {
  25. getBoundsCenter,
  26. getBoundsFromPoints,
  27. getRotatedCorners,
  28. } from 'utils/utils'
  29. import shape from 'components/canvas/shape'
  30. import {
  31. boundsCollidePolygon,
  32. boundsContainPolygon,
  33. pointInBounds,
  34. } from 'utils/bounds'
  35. /*
  36. Shape Utiliies
  37. A shape utility is an object containing utility methods for each type of shape
  38. in the application. While shapes may be very different, each shape must support
  39. a common set of utility methods, such as hit tests or translations, that
  40. Operations throughout the app will call these utility methods
  41. when performing tests (such as hit tests) or mutations, such as translations.
  42. */
  43. export interface ShapeUtility<K extends Shape> {
  44. // A cache for the computed bounds of this kind of shape.
  45. boundsCache: WeakMap<K, Bounds>
  46. // Whether to show transform controls when this shape is selected.
  47. canTransform: boolean
  48. // Whether the shape's aspect ratio can change
  49. canChangeAspectRatio: boolean
  50. // Whether the shape's style can be filled
  51. canStyleFill: boolean
  52. // Create a new shape.
  53. create(props: Partial<K>): K
  54. applyStyles(
  55. this: ShapeUtility<K>,
  56. shape: Mutable<K>,
  57. style: Partial<ShapeStyles>
  58. ): ShapeUtility<K>
  59. // Transform to fit a new bounding box when more than one shape is selected.
  60. transform(
  61. this: ShapeUtility<K>,
  62. shape: Mutable<K>,
  63. bounds: Bounds,
  64. info: {
  65. type: Edge | Corner
  66. initialShape: K
  67. scaleX: number
  68. scaleY: number
  69. transformOrigin: number[]
  70. }
  71. ): ShapeUtility<K>
  72. // Transform a single shape to fit a new bounding box.
  73. transformSingle(
  74. this: ShapeUtility<K>,
  75. shape: Mutable<K>,
  76. bounds: Bounds,
  77. info: {
  78. type: Edge | Corner
  79. initialShape: K
  80. scaleX: number
  81. scaleY: number
  82. transformOrigin: number[]
  83. }
  84. ): ShapeUtility<K>
  85. setProperty<P extends keyof K>(
  86. this: ShapeUtility<K>,
  87. shape: Mutable<K>,
  88. prop: P,
  89. value: K[P]
  90. ): ShapeUtility<K>
  91. // Respond when a user moves one of the shape's bound elements.
  92. onBindingMove?(
  93. this: ShapeUtility<K>,
  94. shape: Mutable<K>,
  95. bindings: Record<string, ShapeBinding>
  96. ): ShapeUtility<K>
  97. // Respond when a user moves one of the shape's handles.
  98. onHandleMove?(
  99. this: ShapeUtility<K>,
  100. shape: Mutable<K>,
  101. handle: Partial<K['handles']>
  102. ): ShapeUtility<K>
  103. // Render a shape to JSX.
  104. render(this: ShapeUtility<K>, shape: K): JSX.Element
  105. // Get the bounds of the a shape.
  106. getBounds(this: ShapeUtility<K>, shape: K): Bounds
  107. // Get the routated bounds of the a shape.
  108. getRotatedBounds(this: ShapeUtility<K>, shape: K): Bounds
  109. // Get the center of the shape
  110. getCenter(this: ShapeUtility<K>, shape: K): number[]
  111. // Test whether a point lies within a shape.
  112. hitTest(this: ShapeUtility<K>, shape: K, test: number[]): boolean
  113. // Test whether bounds collide with or contain a shape.
  114. hitTestBounds(this: ShapeUtility<K>, shape: K, bounds: Bounds): boolean
  115. }
  116. // A mapping of shape types to shape utilities.
  117. const shapeUtilityMap: Record<ShapeType, ShapeUtility<Shape>> = {
  118. [ShapeType.Circle]: circle,
  119. [ShapeType.Dot]: dot,
  120. [ShapeType.Polyline]: polyline,
  121. [ShapeType.Rectangle]: rectangle,
  122. [ShapeType.Ellipse]: ellipse,
  123. [ShapeType.Line]: line,
  124. [ShapeType.Ray]: ray,
  125. [ShapeType.Draw]: draw,
  126. [ShapeType.Arrow]: arrow,
  127. }
  128. /**
  129. * A helper to retrieve a shape utility based on a shape object.
  130. * @param shape
  131. * @returns
  132. */
  133. export function getShapeUtils<T extends Shape>(shape: T): ShapeUtility<T> {
  134. return shapeUtilityMap[shape.type] as ShapeUtility<T>
  135. }
  136. function getDefaultShapeUtil<T extends Shape>(): ShapeUtility<T> {
  137. return {
  138. boundsCache: new WeakMap(),
  139. canTransform: true,
  140. canChangeAspectRatio: true,
  141. canStyleFill: true,
  142. create(props) {
  143. return {
  144. id: uuid(),
  145. isGenerated: false,
  146. point: [0, 0],
  147. name: 'Shape',
  148. parentId: 'page0',
  149. childIndex: 0,
  150. rotation: 0,
  151. isAspectRatioLocked: false,
  152. isLocked: false,
  153. isHidden: false,
  154. ...props,
  155. } as T
  156. },
  157. render(shape) {
  158. return <circle id={shape.id} />
  159. },
  160. transform(shape, bounds) {
  161. shape.point = [bounds.minX, bounds.minY]
  162. return this
  163. },
  164. transformSingle(shape, bounds, info) {
  165. return this.transform(shape, bounds, info)
  166. },
  167. onBindingMove() {
  168. return this
  169. },
  170. onHandleMove() {
  171. return this
  172. },
  173. getBounds(shape) {
  174. const [x, y] = shape.point
  175. return {
  176. minX: x,
  177. minY: y,
  178. maxX: x + 1,
  179. maxY: y + 1,
  180. width: 1,
  181. height: 1,
  182. }
  183. },
  184. getRotatedBounds(shape) {
  185. return getBoundsFromPoints(
  186. getRotatedCorners(this.getBounds(shape), shape.rotation)
  187. )
  188. },
  189. getCenter(shape) {
  190. return getBoundsCenter(this.getBounds(shape))
  191. },
  192. hitTest(shape, point) {
  193. return pointInBounds(point, this.getBounds(shape))
  194. },
  195. hitTestBounds(shape, brushBounds) {
  196. const rotatedCorners = getRotatedCorners(
  197. this.getBounds(shape),
  198. shape.rotation
  199. )
  200. return (
  201. boundsContainPolygon(brushBounds, rotatedCorners) ||
  202. boundsCollidePolygon(brushBounds, rotatedCorners)
  203. )
  204. },
  205. setProperty(shape, prop, value) {
  206. shape[prop] = value
  207. return this
  208. },
  209. applyStyles(shape, style) {
  210. Object.assign(shape.style, style)
  211. return this
  212. },
  213. }
  214. }
  215. /**
  216. * A factory of shape utilities, with typing enforced.
  217. * @param shape
  218. * @returns
  219. */
  220. export function registerShapeUtils<K extends Shape>(
  221. shapeUtil: Partial<ShapeUtility<K>>
  222. ): ShapeUtility<K> {
  223. return Object.freeze({ ...getDefaultShapeUtil<K>(), ...shapeUtil })
  224. }
  225. export function createShape<T extends Shape>(
  226. type: T['type'],
  227. props: Partial<T>
  228. ) {
  229. return shapeUtilityMap[type].create(props) as T
  230. }
  231. export default shapeUtilityMap