Browse Source

expand arrow bounds based on interpolated points (#90)

main
Steve Ruiz 4 years ago
parent
commit
fc9b9fa3e3
No account linked to committer's email address

+ 1
- 1
packages/core/src/components/container/container.tsx View File

@@ -15,7 +15,7 @@ export const Container = React.memo(
15 15
     const rBounds = usePosition(bounds, rotation)
16 16
 
17 17
     return (
18
-      <div id={id} ref={rBounds} className={className + ' tl-positioned'}>
18
+      <div id={id} ref={rBounds} className={['tl-positioned', className || ''].join(' ')}>
19 19
         {children}
20 20
       </div>
21 21
     )

+ 10
- 14
packages/core/src/components/handles/handle.tsx View File

@@ -12,26 +12,22 @@ interface HandleProps {
12 12
 export const Handle = React.memo(({ id, point }: HandleProps) => {
13 13
   const events = useHandleEvents(id)
14 14
 
15
-  const bounds = React.useMemo(
16
-    () =>
17
-      Utils.translateBounds(
15
+  return (
16
+    <Container
17
+      bounds={Utils.translateBounds(
18 18
         {
19 19
           minX: 0,
20 20
           minY: 0,
21
-          maxX: 32,
22
-          maxY: 32,
23
-          width: 32,
24
-          height: 32,
21
+          maxX: 0,
22
+          maxY: 0,
23
+          width: 0,
24
+          height: 0,
25 25
         },
26 26
         point
27
-      ),
28
-    [point]
29
-  )
30
-
31
-  return (
32
-    <Container bounds={bounds}>
27
+      )}
28
+    >
33 29
       <SVGContainer>
34
-        <g className="tl-handles" {...events}>
30
+        <g className="tl-handle" {...events}>
35 31
           <circle className="tl-handle-bg" pointerEvents="all" />
36 32
           <circle className="tl-counter-scaled tl-handle" pointerEvents="none" r={4} />
37 33
         </g>

+ 1
- 6
packages/core/src/components/shape/shape.tsx View File

@@ -25,12 +25,7 @@ export const Shape = <T extends TLShape, E extends Element, M>({
25 25
   const events = useShapeEvents(shape.id, isCurrentParent)
26 26
 
27 27
   return (
28
-    <Container
29
-      id={shape.id}
30
-      className={'tl-shape' + (isCurrentParent ? 'tl-current-parent' : '')}
31
-      bounds={bounds}
32
-      rotation={shape.rotation}
33
-    >
28
+    <Container id={shape.id} className="tl-shape" bounds={bounds} rotation={shape.rotation}>
34 29
       <RenderedShape
35 30
         shape={shape}
36 31
         isBinding={isBinding}

+ 7
- 7
packages/core/src/hooks/useStyle.tsx View File

@@ -113,7 +113,7 @@ const tlcss = css`
113 113
     --tl-scale: calc(1 / var(--tl-zoom));
114 114
     --tl-camera-x: 0px;
115 115
     --tl-camera-y: 0px;
116
-    --tl-padding: calc(64px * var(--tl-scale));
116
+    --tl-padding: calc(64px * max(1, var(--tl-scale)));
117 117
     position: relative;
118 118
     top: 0px;
119 119
     left: 0px;
@@ -279,23 +279,23 @@ const tlcss = css`
279 279
     stroke-width: 2px;
280 280
   }
281 281
 
282
-  .tl-handles {
282
+  .tl-handle {
283 283
     pointer-events: all;
284 284
   }
285 285
 
286
-  .tl-handles:hover > .tl-handle-bg {
286
+  .tl-handle:hover .tl-handle-bg {
287 287
     fill: var(--tl-selectFill);
288 288
   }
289 289
 
290
-  .tl-handles:hover > .tl-handle-bg > * {
290
+  .tl-handle:hover .tl-handle-bg > * {
291 291
     stroke: var(--tl-selectFill);
292 292
   }
293 293
 
294
-  .tl-handles:active > .tl-handle-bg {
294
+  .tl-handle:active .tl-handle-bg {
295 295
     fill: var(--tl-selectFill);
296 296
   }
297 297
 
298
-  .tl-handles:active > .tl-handle-bg > * {
298
+  .tl-handle:active .tl-handle-bg > * {
299 299
     stroke: var(--tl-selectFill);
300 300
   }
301 301
 
@@ -309,7 +309,7 @@ const tlcss = css`
309 309
     fill: transparent;
310 310
     stroke: none;
311 311
     pointer-events: all;
312
-    r: calc(20 / max(1, var(--tl-zoom)));
312
+    r: calc(20px / max(1, var(--tl-zoom)));
313 313
   }
314 314
 
315 315
   .tl-binding-indicator {

+ 7
- 2
packages/core/src/shapes/createShape.tsx View File

@@ -206,8 +206,13 @@ export const ShapeUtil = function <T extends TLShape, E extends Element, M = any
206 206
   Object.assign(this, fn.call(this))
207 207
   Object.assign(this, fn.call(this))
208 208
 
209
-  this.getBounds = this.getBounds.bind(this)
210
-  this.Component = this.Component.bind(this)
209
+  // Make sure all functions are bound to this
210
+  for (const entry of Object.entries(this)) {
211
+    if (entry[1] instanceof Function) {
212
+      this[entry[0] as keyof typeof this] = this[entry[0]].bind(this)
213
+    }
214
+  }
215
+
211 216
   this._Component = React.forwardRef(this.Component)
212 217
 
213 218
   return this

+ 0
- 1
packages/core/tsconfig.json View File

@@ -6,7 +6,6 @@
6 6
     "outDir": "./dist/types",
7 7
     "rootDir": "src",
8 8
     "baseUrl": "src",
9
-    "emitDeclarationOnly": false,
10 9
     "paths": {
11 10
       "+*": ["./*"],
12 11
       "@tldraw/vec": ["../vec"],

+ 0
- 1
packages/intersect/tsconfig.json View File

@@ -6,7 +6,6 @@
6 6
     "outDir": "./dist/types",
7 7
     "rootDir": "src",
8 8
     "baseUrl": "src",
9
-    "emitDeclarationOnly": false,
10 9
     "paths": {
11 10
       "@tldraw/vec": ["../vec"]
12 11
     }

+ 37
- 12
packages/tldraw/src/shape/shapes/arrow/arrow.tsx View File

@@ -213,6 +213,8 @@ export const Arrow = new ShapeUtil<ArrowShape, SVGSVGElement, TLDrawMeta>(() =>
213 213
 
214 214
     const sw = strokeWidth * 1.618
215 215
 
216
+    const dots = getArcPoints(shape)
217
+
216 218
     return (
217 219
       <SVGContainer ref={ref} {...events}>
218 220
         <g pointerEvents="none">
@@ -249,7 +251,7 @@ export const Arrow = new ShapeUtil<ArrowShape, SVGSVGElement, TLDrawMeta>(() =>
249 251
   },
250 252
 
251 253
   Indicator({ shape }) {
252
-    const path = Utils.getFromCache(simplePathCache, shape.handles, () => getArrowPath(shape))
254
+    const path = getArrowPath(shape)
253 255
 
254 256
     return <path d={path} />
255 257
   },
@@ -260,20 +262,25 @@ export const Arrow = new ShapeUtil<ArrowShape, SVGSVGElement, TLDrawMeta>(() =>
260 262
 
261 263
   getBounds(shape) {
262 264
     const bounds = Utils.getFromCache(this.boundsCache, shape, () => {
263
-      const { start, bend, end } = shape.handles
264
-      return Utils.getBoundsFromPoints([start.point, bend.point, end.point])
265
+      const points = getArcPoints(shape)
266
+      return Utils.getBoundsFromPoints(points)
265 267
     })
266 268
 
267 269
     return Utils.translateBounds(bounds, shape.point)
268 270
   },
269 271
 
270 272
   getRotatedBounds(shape) {
271
-    const { start, bend, end } = shape.handles
273
+    let points = getArcPoints(shape)
272 274
 
273
-    return Utils.translateBounds(
274
-      Utils.getBoundsFromPoints([start.point, bend.point, end.point], shape.rotation),
275
-      shape.point
276
-    )
275
+    const { minX, minY, maxX, maxY } = Utils.getBoundsFromPoints(points)
276
+
277
+    if (shape.rotation !== 0) {
278
+      points = points.map((pt) =>
279
+        Vec.rotWith(pt, [(minX + maxX) / 2, (minY + maxY) / 2], shape.rotation || 0)
280
+      )
281
+    }
282
+
283
+    return Utils.translateBounds(Utils.getBoundsFromPoints(points), shape.point)
277 284
   },
278 285
 
279 286
   getCenter(shape) {
@@ -544,11 +551,10 @@ export const Arrow = new ShapeUtil<ArrowShape, SVGSVGElement, TLDrawMeta>(() =>
544 551
     // Zero out the handles to prevent handles with negative points. If a handle's x or y
545 552
     // is below zero, we need to move the shape left or up to make it zero.
546 553
 
547
-    const bounds = Utils.getBoundsFromPoints(
548
-      Object.values(nextShape.handles).map((handle) => handle.point)
549
-    )
554
+    const topLeft = shape.point
555
+    const nextBounds = this.getBounds({ ...nextShape } as ArrowShape)
550 556
 
551
-    const offset = [bounds.minX, bounds.minY]
557
+    const offset = Vec.sub([nextBounds.minX, nextBounds.minY], topLeft)
552 558
 
553 559
     if (!Vec.isEqual(offset, [0, 0])) {
554 560
       Object.values(nextShape.handles).forEach((handle) => {
@@ -766,3 +772,22 @@ function getArrowPath(shape: ArrowShape) {
766 772
 
767 773
   return path.join(' ')
768 774
 }
775
+
776
+function getArcPoints(shape: ArrowShape) {
777
+  const { start, bend, end } = shape.handles
778
+  const points: number[][] = [start.point, end.point]
779
+
780
+  if (Vec.dist2(bend.point, Vec.med(start.point, end.point)) > 4) {
781
+    // We're an arc, calculate points along the arc
782
+    const { center, radius } = getArrowArc(shape)
783
+    const startAngle = Vec.angle(center, start.point)
784
+    const endAngle = Vec.angle(center, end.point)
785
+
786
+    for (let i = 1 / 20; i < 1; i += 1 / 20) {
787
+      const angle = Utils.lerpAngles(startAngle, endAngle, i)
788
+      points.push(Vec.nudgeAtAngle(center, angle, radius))
789
+    }
790
+  }
791
+
792
+  return points
793
+}

+ 6
- 1
packages/tldraw/src/state/session/sessions/arrow/arrow.session.ts View File

@@ -10,6 +10,7 @@ import {
10 10
 import { Vec } from '@tldraw/vec'
11 11
 import { Utils } from '@tldraw/core'
12 12
 import { TLDR } from '~state/tldr'
13
+import { ThickArrowDownIcon } from '@radix-ui/react-icons'
13 14
 
14 15
 export class ArrowSession implements Session {
15 16
   id = 'transform_single'
@@ -18,6 +19,7 @@ export class ArrowSession implements Session {
18 19
   delta = [0, 0]
19 20
   offset = [0, 0]
20 21
   origin: number[]
22
+  topLeft: number[]
21 23
   initialShape: ArrowShape
22 24
   handleId: 'start' | 'end'
23 25
   bindableShapeIds: string[]
@@ -33,6 +35,7 @@ export class ArrowSession implements Session {
33 35
     this.origin = point
34 36
     this.handleId = handleId
35 37
     this.initialShape = TLDR.getShape<ArrowShape>(data, shapeId, data.appState.currentPageId)
38
+    this.topLeft = this.initialShape.point
36 39
     this.bindableShapeIds = TLDR.getBindableShapeIds(data)
37 40
 
38 41
     const initialBindingId = this.initialShape.handles[this.handleId].bindingId
@@ -66,7 +69,9 @@ export class ArrowSession implements Session {
66 69
     }
67 70
 
68 71
     // First update the handle's next point
69
-    const change = TLDR.getShapeUtils<ArrowShape>(shape.type).onHandleChange(
72
+    const utils = TLDR.getShapeUtils<ArrowShape>(shape.type)
73
+
74
+    const change = utils.onHandleChange(
70 75
       shape,
71 76
       {
72 77
         [handleId]: handle,

+ 3
- 0
packages/tldraw/src/state/session/sessions/handle/handle.session.ts View File

@@ -9,6 +9,7 @@ export class HandleSession implements Session {
9 9
   status = TLDrawStatus.TranslatingHandle
10 10
   commandId: string
11 11
   delta = [0, 0]
12
+  topLeft: number[]
12 13
   origin: number[]
13 14
   shiftKey = false
14 15
   initialShape: ShapesWithProp<'handles'>
@@ -17,6 +18,7 @@ export class HandleSession implements Session {
17 18
   constructor(data: Data, handleId: string, point: number[], commandId = 'move_handle') {
18 19
     const { currentPageId } = data.appState
19 20
     const shapeId = TLDR.getSelectedIds(data, currentPageId)[0]
21
+    this.topLeft = point
20 22
     this.origin = point
21 23
     this.handleId = handleId
22 24
     this.initialShape = TLDR.getShape(data, shapeId, currentPageId)
@@ -43,6 +45,7 @@ export class HandleSession implements Session {
43 45
     }
44 46
 
45 47
     // First update the handle's next point
48
+
46 49
     const change = TLDR.getShapeUtils(shape).onHandleChange(
47 50
       shape,
48 51
       {

+ 0
- 1
packages/tldraw/tsconfig.json View File

@@ -6,7 +6,6 @@
6 6
     "outDir": "./dist/types",
7 7
     "rootDir": "src",
8 8
     "baseUrl": "src",
9
-    "emitDeclarationOnly": false,
10 9
     "paths": {
11 10
       "~*": ["./*"],
12 11
       "@tldraw/core": ["../core"],

Loading…
Cancel
Save