您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

index.tsx 6.3KB

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