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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import {
  2. Bounds,
  3. Shape,
  4. ShapeType,
  5. Corner,
  6. Edge,
  7. ShapeStyles,
  8. ShapeHandle,
  9. ShapeBinding,
  10. } from 'types'
  11. import circle from './circle'
  12. import dot from './dot'
  13. import polyline from './polyline'
  14. import rectangle from './rectangle'
  15. import ellipse from './ellipse'
  16. import line from './line'
  17. import ray from './ray'
  18. import draw from './draw'
  19. import arrow from './arrow'
  20. /*
  21. Shape Utiliies
  22. A shape utility is an object containing utility methods for each type of shape
  23. in the application. While shapes may be very different, each shape must support
  24. a common set of utility methods, such as hit tests or translations, that
  25. Operations throughout the app will call these utility methods
  26. when performing tests (such as hit tests) or mutations, such as translations.
  27. */
  28. export interface ShapeUtility<K extends Readonly<Shape>> {
  29. // A cache for the computed bounds of this kind of shape.
  30. boundsCache: WeakMap<K, Bounds>
  31. // Whether to show transform controls when this shape is selected.
  32. canTransform: boolean
  33. // Whether the shape's aspect ratio can change
  34. canChangeAspectRatio: boolean
  35. // Whether the shape's style can be filled
  36. canStyleFill: boolean
  37. // Create a new shape.
  38. create(props: Partial<K>): K
  39. applyStyles(
  40. this: ShapeUtility<K>,
  41. shape: K,
  42. style: Partial<ShapeStyles>
  43. ): ShapeUtility<K>
  44. // Set the shape's point.
  45. translateTo(this: ShapeUtility<K>, shape: K, delta: number[]): ShapeUtility<K>
  46. // Set the shape's rotation.
  47. rotateTo(this: ShapeUtility<K>, shape: K, rotation: number): ShapeUtility<K>
  48. // Transform to fit a new bounding box when more than one shape is selected.
  49. transform(
  50. this: ShapeUtility<K>,
  51. shape: K,
  52. bounds: Bounds,
  53. info: {
  54. type: Edge | Corner
  55. initialShape: K
  56. scaleX: number
  57. scaleY: number
  58. transformOrigin: number[]
  59. }
  60. ): ShapeUtility<K>
  61. // Transform a single shape to fit a new bounding box.
  62. transformSingle(
  63. this: ShapeUtility<K>,
  64. shape: K,
  65. bounds: Bounds,
  66. info: {
  67. type: Edge | Corner
  68. initialShape: K
  69. scaleX: number
  70. scaleY: number
  71. transformOrigin: number[]
  72. }
  73. ): ShapeUtility<K>
  74. setProperty<P extends keyof K>(
  75. this: ShapeUtility<K>,
  76. shape: K,
  77. prop: P,
  78. value: K[P]
  79. ): ShapeUtility<K>
  80. // Respond when a user moves one of the shape's bound elements.
  81. onBindingMove?(
  82. this: ShapeUtility<K>,
  83. shape: K,
  84. bindings: Record<string, ShapeBinding>
  85. ): ShapeUtility<K>
  86. // Respond when a user moves one of the shape's handles.
  87. onHandleMove?(
  88. this: ShapeUtility<K>,
  89. shape: K,
  90. handle: Partial<K['handles']>
  91. ): ShapeUtility<K>
  92. // Render a shape to JSX.
  93. render(this: ShapeUtility<K>, shape: K): JSX.Element
  94. // Get the bounds of the a shape.
  95. getBounds(this: ShapeUtility<K>, shape: K): Bounds
  96. // Get the routated bounds of the a shape.
  97. getRotatedBounds(this: ShapeUtility<K>, shape: K): Bounds
  98. // Get the center of the shape
  99. getCenter(this: ShapeUtility<K>, shape: K): number[]
  100. // Test whether a point lies within a shape.
  101. hitTest(this: ShapeUtility<K>, shape: K, test: number[]): boolean
  102. // Test whether bounds collide with or contain a shape.
  103. hitTestBounds(this: ShapeUtility<K>, shape: K, bounds: Bounds): boolean
  104. }
  105. // A mapping of shape types to shape utilities.
  106. const shapeUtilityMap: Record<ShapeType, ShapeUtility<Shape>> = {
  107. [ShapeType.Circle]: circle,
  108. [ShapeType.Dot]: dot,
  109. [ShapeType.Polyline]: polyline,
  110. [ShapeType.Rectangle]: rectangle,
  111. [ShapeType.Ellipse]: ellipse,
  112. [ShapeType.Line]: line,
  113. [ShapeType.Ray]: ray,
  114. [ShapeType.Draw]: draw,
  115. [ShapeType.Arrow]: arrow,
  116. }
  117. /**
  118. * A helper to retrieve a shape utility based on a shape object.
  119. * @param shape
  120. * @returns
  121. */
  122. export function getShapeUtils<T extends Shape>(shape: T): ShapeUtility<T> {
  123. return shapeUtilityMap[shape.type] as ShapeUtility<T>
  124. }
  125. /**
  126. * A factory of shape utilities, with typing enforced.
  127. * @param shape
  128. * @returns
  129. */
  130. export function registerShapeUtils<T extends Shape>(
  131. shape: ShapeUtility<T>
  132. ): ShapeUtility<T> {
  133. return Object.freeze(shape)
  134. }
  135. export function createShape<T extends Shape>(
  136. type: T['type'],
  137. props: Partial<T>
  138. ) {
  139. return shapeUtilityMap[type].create(props) as T
  140. }
  141. export default shapeUtilityMap