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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import { v4 as uuid } from 'uuid'
  2. import * as vec from 'utils/vec'
  3. import { EllipseShape, ShapeType } from 'types'
  4. import { registerShapeUtils } from './index'
  5. import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
  6. import { intersectEllipseBounds } from 'utils/intersections'
  7. import { pointInEllipse } from 'utils/hitTests'
  8. import {
  9. getBoundsFromPoints,
  10. getRotatedCorners,
  11. rotateBounds,
  12. translateBounds,
  13. } from 'utils/utils'
  14. import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
  15. const ellipse = registerShapeUtils<EllipseShape>({
  16. boundsCache: new WeakMap([]),
  17. create(props) {
  18. return {
  19. id: uuid(),
  20. type: ShapeType.Ellipse,
  21. isGenerated: false,
  22. name: 'Ellipse',
  23. parentId: 'page0',
  24. childIndex: 0,
  25. point: [0, 0],
  26. radiusX: 1,
  27. radiusY: 1,
  28. rotation: 0,
  29. isAspectRatioLocked: false,
  30. isLocked: false,
  31. isHidden: false,
  32. style: defaultStyle,
  33. ...props,
  34. }
  35. },
  36. render({ id, radiusX, radiusY, style }) {
  37. const styles = getShapeStyle(style)
  38. return (
  39. <ellipse
  40. id={id}
  41. cx={radiusX}
  42. cy={radiusY}
  43. rx={Math.max(0, radiusX - Number(styles.strokeWidth) / 2)}
  44. ry={Math.max(0, radiusY - Number(styles.strokeWidth) / 2)}
  45. />
  46. )
  47. },
  48. getBounds(shape) {
  49. if (!this.boundsCache.has(shape)) {
  50. const { radiusX, radiusY } = shape
  51. const bounds = {
  52. minX: 0,
  53. minY: 0,
  54. maxX: radiusX * 2,
  55. maxY: radiusY * 2,
  56. width: radiusX * 2,
  57. height: radiusY * 2,
  58. }
  59. this.boundsCache.set(shape, bounds)
  60. }
  61. return translateBounds(this.boundsCache.get(shape), shape.point)
  62. },
  63. getRotatedBounds(shape) {
  64. return getRotatedEllipseBounds(
  65. shape.point[0],
  66. shape.point[1],
  67. shape.radiusX,
  68. shape.radiusY,
  69. shape.rotation
  70. )
  71. },
  72. getCenter(shape) {
  73. return [shape.point[0] + shape.radiusX, shape.point[1] + shape.radiusY]
  74. },
  75. hitTest(shape, point) {
  76. return pointInEllipse(
  77. point,
  78. vec.add(shape.point, [shape.radiusX, shape.radiusY]),
  79. shape.radiusX,
  80. shape.radiusY,
  81. shape.rotation
  82. )
  83. },
  84. hitTestBounds(this, shape, brushBounds) {
  85. const shapeBounds = this.getBounds(shape)
  86. return (
  87. boundsContained(shapeBounds, brushBounds) ||
  88. intersectEllipseBounds(
  89. vec.add(shape.point, [shape.radiusX, shape.radiusY]),
  90. shape.radiusX,
  91. shape.radiusY,
  92. brushBounds,
  93. shape.rotation
  94. ).length > 0
  95. )
  96. },
  97. transform(shape, bounds, { scaleX, scaleY, initialShape }) {
  98. // TODO: Locked aspect ratio transform
  99. shape.point = [bounds.minX, bounds.minY]
  100. shape.radiusX = bounds.width / 2
  101. shape.radiusY = bounds.height / 2
  102. shape.rotation =
  103. (scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0)
  104. ? -initialShape.rotation
  105. : initialShape.rotation
  106. return this
  107. },
  108. transformSingle(shape, bounds, info) {
  109. return this.transform(shape, bounds, info)
  110. },
  111. })
  112. export default ellipse