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.

index.ts 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. import {
  2. ColorStyle,
  3. DashStyle,
  4. Mutable,
  5. Shape,
  6. ShapeUtility,
  7. SizeStyle,
  8. } from 'types'
  9. import { createShape, getShapeUtils } from 'state/shape-utils'
  10. import { uniqueId } from 'utils'
  11. import Vec from 'utils/vec'
  12. export const codeShapes = new Set<CodeShape<Shape>>([])
  13. function getOrderedShapes() {
  14. return Array.from(codeShapes.values()).sort(
  15. (a, b) => a.shape.childIndex - b.shape.childIndex
  16. )
  17. }
  18. /**
  19. * A base class for code shapes. Note that creating a shape adds it to the
  20. * shape map, while deleting it removes it from the collected shapes set
  21. */
  22. /* ----------------- Start Copy Here ---------------- */
  23. export default class CodeShape<T extends Shape> {
  24. private _shape: Mutable<T>
  25. protected utils: ShapeUtility<T>
  26. constructor(props: T) {
  27. this._shape = createShape(props.type, props) as Mutable<T>
  28. this.utils = getShapeUtils<T>(this._shape)
  29. codeShapes.add(this)
  30. }
  31. /**
  32. * Destroy the shape.
  33. *
  34. * ```ts
  35. * shape.destroy()
  36. * ```
  37. */
  38. destroy = (): void => {
  39. codeShapes.delete(this)
  40. }
  41. /**
  42. * Move the shape to a point.
  43. *
  44. * ```ts
  45. * shape.moveTo(100,100)
  46. * ```
  47. */
  48. moveTo = (point: number[]): CodeShape<T> => {
  49. return this.translateTo(point)
  50. }
  51. /**
  52. * Move the shape to a point.
  53. *
  54. * ```ts
  55. * shape.translateTo([100,100])
  56. * ```
  57. */
  58. translateTo = (point: number[]): CodeShape<T> => {
  59. this.utils.translateTo(this._shape, point)
  60. return this
  61. }
  62. /**
  63. * Move the shape by a delta.
  64. *
  65. * ```ts
  66. * shape.translateBy([100,100])
  67. * ```
  68. */
  69. translateBy = (delta: number[]): CodeShape<T> => {
  70. this.utils.translateTo(this._shape, delta)
  71. return this
  72. }
  73. /**
  74. * Rotate the shape.
  75. *
  76. * ```ts
  77. * shape.rotateTo(Math.PI / 2)
  78. * ```
  79. */
  80. rotateTo = (rotation: number): CodeShape<T> => {
  81. this.utils.rotateTo(this._shape, rotation, this.shape.rotation - rotation)
  82. return this
  83. }
  84. /**
  85. * Rotate the shape by a delta.
  86. *
  87. * ```ts
  88. * shape.rotateBy(Math.PI / 2)
  89. * ```
  90. */
  91. rotateBy = (rotation: number): CodeShape<T> => {
  92. this.utils.rotateBy(this._shape, rotation)
  93. return this
  94. }
  95. /**
  96. * Get the shape's bounding box.
  97. *
  98. * ```ts
  99. * const bounds = shape.getBounds()
  100. * ```
  101. */
  102. getBounds = (): CodeShape<T> => {
  103. this.utils.getBounds(this.shape)
  104. return this
  105. }
  106. /**
  107. * Test whether a point is inside of the shape.
  108. *
  109. * ```ts
  110. * const isHit = shape.hitTest()
  111. * ```
  112. */
  113. hitTest = (point: number[]): CodeShape<T> => {
  114. this.utils.hitTest(this.shape, point)
  115. return this
  116. }
  117. /**
  118. * Duplicate this shape.
  119. *
  120. * ```ts
  121. * const shapeB = shape.duplicate()
  122. * ```
  123. */
  124. duplicate = (): CodeShape<T> => {
  125. const duplicate = Object.assign(
  126. Object.create(Object.getPrototypeOf(this)),
  127. this
  128. )
  129. duplicate._shape = createShape(this._shape.type, {
  130. ...this._shape,
  131. id: uniqueId(),
  132. } as any)
  133. codeShapes.add(duplicate)
  134. return duplicate
  135. }
  136. /**
  137. * Move the shape to the back of the painting order.
  138. *
  139. * ```ts
  140. * shape.moveToBack()
  141. * ```
  142. */
  143. moveToBack = (): CodeShape<T> => {
  144. const sorted = getOrderedShapes()
  145. if (sorted.length <= 1) return
  146. const first = sorted[0].childIndex
  147. sorted.forEach((shape) => shape.childIndex++)
  148. this.childIndex = first
  149. codeShapes.clear()
  150. sorted.forEach((shape) => codeShapes.add(shape))
  151. return this
  152. }
  153. /**
  154. * Move the shape to the top of the painting order.
  155. *
  156. * ```ts
  157. * shape.moveToFront()
  158. * ```
  159. */
  160. moveToFront = (): CodeShape<T> => {
  161. const sorted = getOrderedShapes()
  162. if (sorted.length <= 1) return
  163. const ahead = sorted.slice(sorted.indexOf(this))
  164. const last = ahead[ahead.length - 1].childIndex
  165. ahead.forEach((shape) => shape.childIndex--)
  166. this.childIndex = last
  167. codeShapes.clear()
  168. sorted.forEach((shape) => codeShapes.add(shape))
  169. return this
  170. }
  171. /**
  172. * Move the shape backward in the painting order.
  173. *
  174. * ```ts
  175. * shape.moveBackward()
  176. * ```
  177. */
  178. moveBackward = (): CodeShape<T> => {
  179. const sorted = getOrderedShapes()
  180. if (sorted.length <= 1) return
  181. const next = sorted[sorted.indexOf(this) - 1]
  182. if (!next) return
  183. const index = next.childIndex
  184. next.childIndex = this.childIndex
  185. this.childIndex = index
  186. codeShapes.clear()
  187. sorted.forEach((shape) => codeShapes.add(shape))
  188. return this
  189. }
  190. /**
  191. * Move the shape forward in the painting order.
  192. *
  193. * ```ts
  194. * shape.moveForward()
  195. * ```
  196. */
  197. moveForward = (): CodeShape<T> => {
  198. const sorted = getOrderedShapes()
  199. if (sorted.length <= 1) return
  200. const next = sorted[sorted.indexOf(this) + 1]
  201. if (!next) return
  202. const index = next.childIndex
  203. next.childIndex = this.childIndex
  204. this.childIndex = index
  205. codeShapes.clear()
  206. sorted.forEach((shape) => codeShapes.add(shape))
  207. return this
  208. }
  209. get id(): string {
  210. return this._shape.id
  211. }
  212. /**
  213. * The shape's underlying shape (readonly).
  214. *
  215. * ```ts
  216. * const underlyingShape = shape.shape
  217. * ```
  218. */
  219. get shape(): Readonly<T> {
  220. return this._shape
  221. }
  222. /**
  223. * The shape's current point.
  224. *
  225. * ```ts
  226. * const shapePoint = shape.point()
  227. * ```
  228. */
  229. get point(): number[] {
  230. return [...this.shape.point]
  231. }
  232. set point(point: number[]) {
  233. this.utils.translateTo(this._shape, point)
  234. }
  235. /**
  236. * The shape's current x position.
  237. *
  238. * ```ts
  239. * const shapeX = shape.x
  240. *
  241. * shape.x = 100
  242. * ```
  243. */
  244. get x(): number {
  245. return this.point[0]
  246. }
  247. set x(x: number) {
  248. this.utils.translateTo(this._shape, [x, this.y])
  249. }
  250. /**
  251. * The shape's current y position.
  252. *
  253. * ```ts
  254. * const shapeY = shape.y
  255. *
  256. * shape.y = 100
  257. * ```
  258. */
  259. get y(): number {
  260. return this.point[1]
  261. }
  262. set y(y: number) {
  263. this.utils.translateTo(this._shape, [this.x, y])
  264. }
  265. /**
  266. * The shape's rotation.
  267. *
  268. * ```ts
  269. * const shapeRotation = shape.rotation
  270. *
  271. * shape.rotation = Math.PI / 2
  272. * ```
  273. */
  274. get rotation(): number {
  275. return this.shape.rotation
  276. }
  277. set rotation(rotation: number) {
  278. this.utils.rotateTo(this._shape, rotation, rotation - this.shape.rotation)
  279. }
  280. /**
  281. * The shape's color style (ColorStyle).
  282. *
  283. * ```ts
  284. * const shapeColor = shape.color
  285. *
  286. * shape.color = ColorStyle.Red
  287. * ```
  288. */
  289. get color(): ColorStyle {
  290. return this.shape.style.color
  291. }
  292. set color(color: ColorStyle) {
  293. this.utils.applyStyles(this._shape, { color })
  294. }
  295. /**
  296. * The shape's dash style (DashStyle).
  297. *
  298. * ```ts
  299. * const shapeDash = shape.dash
  300. *
  301. * shape.dash = DashStyle.Dotted
  302. * ```
  303. */
  304. get dash(): DashStyle {
  305. return this.shape.style.dash
  306. }
  307. set dash(dash: DashStyle) {
  308. this.utils.applyStyles(this._shape, { dash })
  309. }
  310. /**
  311. * The shape's size (SizeStyle).
  312. *
  313. * ```ts
  314. * const shapeSize = shape.size
  315. *
  316. * shape.size = SizeStyle.Large
  317. * ```
  318. */
  319. get size(): SizeStyle {
  320. return this.shape.style.size
  321. }
  322. set size(size: SizeStyle) {
  323. this.utils.applyStyles(this._shape, { size })
  324. }
  325. /**
  326. * The shape's index in the painting order.
  327. *
  328. * ```ts
  329. * const shapeChildIndex = shape.childIndex
  330. *
  331. * shape.childIndex = 10
  332. * ```
  333. */
  334. get childIndex(): number {
  335. return this.shape.childIndex
  336. }
  337. set childIndex(childIndex: number) {
  338. this.utils.setProperty(this._shape, 'childIndex', childIndex)
  339. }
  340. /**
  341. * The shape's center.
  342. *
  343. * ```ts
  344. * const shapeCenter = shape.center
  345. *
  346. * shape.center = [100, 100]
  347. * ```
  348. */
  349. get center(): number[] {
  350. return this.utils.getCenter(this.shape)
  351. }
  352. set center(center: number[]) {
  353. const oldCenter = this.utils.getCenter(this.shape)
  354. const delta = Vec.sub(center, oldCenter)
  355. this.translateBy(delta)
  356. }
  357. }