-
e.preventDefault()}>
- e.stopPropagation()}
- />
-
-
+ return (
+
+
+
e.preventDefault()}>
+ e.stopPropagation()}
+ />
+
-
- )
- }
- )
+
+
+ )
+ },
- renderIndicator(shape: PostItShape) {
+ Indicator({ shape }) {
const {
style,
size: [width, height],
@@ -82,162 +79,13 @@ export class PostIt extends TLDrawShapeUtil
{
height={Math.max(1, height - sw)}
/>
)
- }
+ },
- getBounds(shape: PostItShape) {
- const bounds = Utils.getFromCache(this.boundsCache, shape, () => {
- const [width, height] = shape.size
- return {
- minX: 0,
- maxX: width,
- minY: 0,
- maxY: height,
- width,
- height,
- }
- })
+ getBounds(shape) {
+ return getBoundsRectangle(shape, this.boundsCache)
+ },
- return Utils.translateBounds(bounds, shape.point)
- }
+ transform: transformRectangle,
- getRotatedBounds(shape: PostItShape) {
- return Utils.getBoundsFromPoints(Utils.getRotatedCorners(this.getBounds(shape), shape.rotation))
- }
-
- getCenter(shape: PostItShape): number[] {
- return Utils.getBoundsCenter(this.getBounds(shape))
- }
-
- getBindingPoint(
- shape: PostItShape,
- fromShape: ArrowShape,
- point: number[],
- origin: number[],
- direction: number[],
- padding: number,
- anywhere: boolean
- ) {
- const bounds = this.getBounds(shape)
-
- const expandedBounds = Utils.expandBounds(bounds, padding)
-
- let bindingPoint: number[]
- let distance: number
-
- // The point must be inside of the expanded bounding box
- if (!Utils.pointInBounds(point, expandedBounds)) return
-
- // The point is inside of the shape, so we'll assume the user is
- // indicating a specific point inside of the shape.
- if (anywhere) {
- if (Vec.dist(point, this.getCenter(shape)) < 12) {
- bindingPoint = [0.5, 0.5]
- } else {
- bindingPoint = Vec.divV(Vec.sub(point, [expandedBounds.minX, expandedBounds.minY]), [
- expandedBounds.width,
- expandedBounds.height,
- ])
- }
-
- distance = 0
- } else {
- // TODO: What if the shape has a curve? In that case, should we
- // intersect the circle-from-three-points instead?
-
- // Find furthest intersection between ray from
- // origin through point and expanded bounds.
-
- // TODO: Make this a ray vs rounded rect intersection
- const intersection = intersectRayBounds(origin, direction, expandedBounds)
- .filter((int) => int.didIntersect)
- .map((int) => int.points[0])
- .sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0]
- // The anchor is a point between the handle and the intersection
- const anchor = Vec.med(point, intersection)
-
- // If we're close to the center, snap to the center
- if (Vec.distanceToLineSegment(point, anchor, this.getCenter(shape)) < 12) {
- bindingPoint = [0.5, 0.5]
- } else {
- // Or else calculate a normalized point
- bindingPoint = Vec.divV(Vec.sub(anchor, [expandedBounds.minX, expandedBounds.minY]), [
- expandedBounds.width,
- expandedBounds.height,
- ])
- }
-
- if (Utils.pointInBounds(point, bounds)) {
- distance = 16
- } else {
- // If the binding point was close to the shape's center, snap to the center
- // Find the distance between the point and the real bounds of the shape
- distance = Math.max(
- 16,
- Utils.getBoundsSides(bounds)
- .map((side) => Vec.distanceToLineSegment(side[1][0], side[1][1], point))
- .sort((a, b) => a - b)[0]
- )
- }
- }
-
- return {
- point: Vec.clampV(bindingPoint, 0, 1),
- distance,
- }
- }
-
- hitTestBounds(shape: PostItShape, bounds: TLBounds) {
- const rotatedCorners = Utils.getRotatedCorners(this.getBounds(shape), shape.rotation)
-
- return (
- rotatedCorners.every((point) => Utils.pointInBounds(point, bounds)) ||
- intersectPolylineBounds(rotatedCorners, bounds).length > 0
- )
- }
-
- transform(
- shape: PostItShape,
- bounds: TLBounds,
- { initialShape, transformOrigin, scaleX, scaleY }: TLTransformInfo
- ) {
- if (!shape.rotation && !shape.isAspectRatioLocked) {
- return {
- point: Vec.round([bounds.minX, bounds.minY]),
- size: Vec.round([bounds.width, bounds.height]),
- }
- } else {
- const size = Vec.round(
- Vec.mul(initialShape.size, Math.min(Math.abs(scaleX), Math.abs(scaleY)))
- )
-
- const point = Vec.round([
- bounds.minX +
- (bounds.width - shape.size[0]) *
- (scaleX < 0 ? 1 - transformOrigin[0] : transformOrigin[0]),
- bounds.minY +
- (bounds.height - shape.size[1]) *
- (scaleY < 0 ? 1 - transformOrigin[1] : transformOrigin[1]),
- ])
-
- const rotation =
- (scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0)
- ? initialShape.rotation
- ? -initialShape.rotation
- : 0
- : initialShape.rotation
-
- return {
- size,
- point,
- rotation,
- }
- }
- }
-
- transformSingle(_shape: PostItShape, bounds: TLBounds) {
- return {
- size: Vec.round([bounds.width, bounds.height]),
- point: Vec.round([bounds.minX, bounds.minY]),
- }
- }
-}
+ transformSingle: transformSingleRectangle,
+}))
diff --git a/packages/tldraw/src/shape/shapes/rectangle/__snapshots__/rectangle.spec.tsx.snap b/packages/tldraw/src/shape/shapes/rectangle/__snapshots__/rectangle.spec.tsx.snap
new file mode 100644
index 000000000..3c5e294d7
--- /dev/null
+++ b/packages/tldraw/src/shape/shapes/rectangle/__snapshots__/rectangle.spec.tsx.snap
@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Rectangle shape Creates a shape: rectangle 1`] = `
+Object {
+ "childIndex": 1,
+ "id": "rectangle",
+ "name": "Rectangle",
+ "parentId": "page",
+ "point": Array [
+ 0,
+ 0,
+ ],
+ "rotation": 0,
+ "size": Array [
+ 1,
+ 1,
+ ],
+ "style": Object {
+ "color": "Black",
+ "dash": "Draw",
+ "isFilled": false,
+ "size": "Medium",
+ },
+ "type": "rectangle",
+}
+`;
diff --git a/packages/tldraw/src/shape/shapes/rectangle/rectangle.spec.tsx b/packages/tldraw/src/shape/shapes/rectangle/rectangle.spec.tsx
index 9df18e8c9..ddb3bdffc 100644
--- a/packages/tldraw/src/shape/shapes/rectangle/rectangle.spec.tsx
+++ b/packages/tldraw/src/shape/shapes/rectangle/rectangle.spec.tsx
@@ -1,7 +1,7 @@
import { Rectangle } from './rectangle'
describe('Rectangle shape', () => {
- it('Creates an instance', () => {
- new Rectangle()
+ it('Creates a shape', () => {
+ expect(Rectangle.create({ id: 'rectangle' })).toMatchSnapshot('rectangle')
})
})
diff --git a/packages/tldraw/src/shape/shapes/rectangle/rectangle.tsx b/packages/tldraw/src/shape/shapes/rectangle/rectangle.tsx
index f9e7d0cf1..b2311048b 100644
--- a/packages/tldraw/src/shape/shapes/rectangle/rectangle.tsx
+++ b/packages/tldraw/src/shape/shapes/rectangle/rectangle.tsx
@@ -1,30 +1,23 @@
import * as React from 'react'
-import { TLBounds, Utils, TLTransformInfo, TLShapeProps, SVGContainer } from '@tldraw/core'
-import { intersectRayBounds } from '@tldraw/intersect'
+import { Utils, SVGContainer, ShapeUtil } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import getStroke from 'perfect-freehand'
import { getPerfectDashProps, defaultStyle, getShapeStyle } from '~shape/shape-styles'
-import {
- RectangleShape,
- DashStyle,
- TLDrawShapeUtil,
- TLDrawShapeType,
- TLDrawToolType,
- ArrowShape,
-} from '~types'
+import { RectangleShape, DashStyle, TLDrawShapeType, TLDrawToolType, TLDrawMeta } from '~types'
+import { getBoundsRectangle, transformRectangle, transformSingleRectangle } from '../shared'
-// TODO
-// [ ] - Make sure that fill does not extend drawn shape at corners
+const pathCache = new WeakMap([])
-export class Rectangle extends TLDrawShapeUtil {
- type = TLDrawShapeType.Rectangle as const
- toolType = TLDrawToolType.Bounds
- canBind = true
- pathCache = new WeakMap([])
+export const Rectangle = new ShapeUtil(() => ({
+ type: TLDrawShapeType.Rectangle,
- defaultProps: RectangleShape = {
+ toolType: TLDrawToolType.Bounds,
+
+ canBind: true,
+
+ defaultProps: {
id: 'id',
- type: TLDrawShapeType.Rectangle as const,
+ type: TLDrawShapeType.Rectangle,
name: 'Rectangle',
parentId: 'page',
childIndex: 1,
@@ -32,115 +25,116 @@ export class Rectangle extends TLDrawShapeUtil {
size: [1, 1],
rotation: 0,
style: defaultStyle,
- }
+ },
- shouldRender(prev: RectangleShape, next: RectangleShape) {
+ shouldRender(prev, next) {
return next.size !== prev.size || next.style !== prev.style
- }
+ },
- render = React.forwardRef>(
- ({ shape, isBinding, meta, events }, ref) => {
- const { id, size, style } = shape
- const styles = getShapeStyle(style, meta.isDarkMode)
- const strokeWidth = +styles.strokeWidth
+ Component({ shape, isBinding, meta, events }, ref) {
+ const { id, size, style } = shape
+ const styles = getShapeStyle(style, meta.isDarkMode)
+ const strokeWidth = +styles.strokeWidth
- if (style.dash === DashStyle.Draw) {
- const pathData = Utils.getFromCache(this.pathCache, shape.size, () => renderPath(shape))
+ this
- return (
-
- {isBinding && (
-