Просмотр исходного кода

Adds double-pointing handles action, toggled arrowheads, removes circles.

main
Steve Ruiz 4 лет назад
Родитель
Сommit
7d14791d00

+ 11
- 2
hooks/useHandleEvents.ts Просмотреть файл

@@ -12,7 +12,9 @@ export default function useHandleEvents(
12 12
       if (!inputs.canAccept(e.pointerId)) return
13 13
       e.stopPropagation()
14 14
       rGroup.current.setPointerCapture(e.pointerId)
15
-      state.send('POINTED_HANDLE', inputs.pointerDown(e, id))
15
+      const info = inputs.pointerDown(e, id)
16
+
17
+      state.send('POINTED_HANDLE', info)
16 18
     },
17 19
     [id]
18 20
   )
@@ -22,7 +24,14 @@ export default function useHandleEvents(
22 24
       if (!inputs.canAccept(e.pointerId)) return
23 25
       e.stopPropagation()
24 26
       rGroup.current.releasePointerCapture(e.pointerId)
25
-      state.send('STOPPED_POINTING', inputs.pointerUp(e))
27
+      const isDoubleClick = inputs.isDoubleClick()
28
+      const info = inputs.pointerUp(e, id)
29
+
30
+      if (isDoubleClick && !(info.altKey || info.metaKey)) {
31
+        state.send('DOUBLE_POINTED_HANDLE', info)
32
+      } else {
33
+        state.send('STOPPED_POINTING', inputs.pointerUp(e))
34
+      }
26 35
     },
27 36
     [id]
28 37
   )

+ 0
- 41
state/code/circle.ts Просмотреть файл

@@ -1,41 +0,0 @@
1
-import CodeShape from './index'
2
-import { uniqueId } from 'utils/utils'
3
-import { CircleShape, ShapeType } from 'types'
4
-import Utils from './utils'
5
-import { defaultStyle } from 'state/shape-styles'
6
-
7
-export default class Circle extends CodeShape<CircleShape> {
8
-  constructor(props = {} as Partial<CircleShape>) {
9
-    props.point = Utils.vectorToPoint(props.point)
10
-
11
-    super({
12
-      id: uniqueId(),
13
-      seed: Math.random(),
14
-      parentId: (window as any).currentPageId,
15
-      type: ShapeType.Circle,
16
-      isGenerated: true,
17
-      name: 'Circle',
18
-      childIndex: 0,
19
-      point: [0, 0],
20
-      rotation: 0,
21
-      radius: 20,
22
-      isAspectRatioLocked: false,
23
-      isLocked: false,
24
-      isHidden: false,
25
-      ...props,
26
-      style: { ...defaultStyle, ...props.style },
27
-    })
28
-  }
29
-
30
-  export(): CircleShape {
31
-    const shape = { ...this.shape }
32
-
33
-    shape.point = Utils.vectorToPoint(shape.point)
34
-
35
-    return shape
36
-  }
37
-
38
-  get radius(): number {
39
-    return this.shape.radius
40
-  }
41
-}

+ 0
- 2
state/code/generate.ts Просмотреть файл

@@ -1,5 +1,4 @@
1 1
 import Rectangle from './rectangle'
2
-import Circle from './circle'
3 2
 import Ellipse from './ellipse'
4 3
 import Polyline from './polyline'
5 4
 import Dot from './dot'
@@ -13,7 +12,6 @@ import { CodeControl, Data, Shape } from 'types'
13 12
 
14 13
 const baseScope = {
15 14
   Dot,
16
-  Circle,
17 15
   Ellipse,
18 16
   Ray,
19 17
   Line,

+ 33
- 0
state/commands/double-point-handle.ts Просмотреть файл

@@ -0,0 +1,33 @@
1
+import Command from './command'
2
+import history from '../history'
3
+import { Data, PointerInfo } from 'types'
4
+import { getShapeUtils } from 'state/shape-utils'
5
+import { deepClone, getPage, getShape, updateParents } from 'utils/utils'
6
+
7
+export default function doublePointHandleCommand(
8
+  data: Data,
9
+  id: string,
10
+  payload: PointerInfo
11
+): void {
12
+  const initialShape = deepClone(getShape(data, id))
13
+
14
+  history.execute(
15
+    data,
16
+    new Command({
17
+      name: 'double_point_handle',
18
+      category: 'canvas',
19
+      do(data) {
20
+        const { shapes } = getPage(data)
21
+
22
+        const shape = shapes[id]
23
+        getShapeUtils(shape).onDoublePointHandle(shape, payload.target, payload)
24
+        updateParents(data, [id])
25
+      },
26
+      undo(data) {
27
+        const { shapes } = getPage(data)
28
+        shapes[id] = initialShape
29
+        updateParents(data, [id])
30
+      },
31
+    })
32
+  )
33
+}

+ 7
- 5
state/commands/index.ts Просмотреть файл

@@ -6,30 +6,30 @@ import deletePage from './delete-page'
6 6
 import deleteSelected from './delete-selected'
7 7
 import direct from './direct'
8 8
 import distribute from './distribute'
9
+import doublePointHandle from './double-point-handle'
9 10
 import draw from './draw'
10 11
 import duplicate from './duplicate'
12
+import edit from './edit'
11 13
 import generate from './generate'
12 14
 import group from './group'
13 15
 import handle from './handle'
14 16
 import move from './move'
15 17
 import moveToPage from './move-to-page'
18
+import mutate from './mutate'
16 19
 import nudge from './nudge'
17
-import rotate from './rotate'
18 20
 import paste from './paste'
21
+import resetBounds from './reset-bounds'
22
+import rotate from './rotate'
19 23
 import rotateCcw from './rotate-ccw'
20 24
 import stretch from './stretch'
21 25
 import style from './style'
22
-import mutate from './mutate'
23 26
 import toggle from './toggle'
24 27
 import transform from './transform'
25 28
 import transformSingle from './transform-single'
26 29
 import translate from './translate'
27 30
 import ungroup from './ungroup'
28
-import edit from './edit'
29
-import resetBounds from './reset-bounds'
30 31
 
31 32
 const commands = {
32
-  mutate,
33 33
   align,
34 34
   arrow,
35 35
   changePage,
@@ -38,6 +38,7 @@ const commands = {
38 38
   deleteSelected,
39 39
   direct,
40 40
   distribute,
41
+  doublePointHandle,
41 42
   draw,
42 43
   duplicate,
43 44
   edit,
@@ -46,6 +47,7 @@ const commands = {
46 47
   handle,
47 48
   move,
48 49
   moveToPage,
50
+  mutate,
49 51
   nudge,
50 52
   paste,
51 53
   resetBounds,

+ 5
- 3
state/inputs.tsx Просмотреть файл

@@ -3,7 +3,7 @@ import { PointerInfo } from 'types'
3 3
 import vec from 'utils/vec'
4 4
 import { isDarwin, getPoint } from 'utils/utils'
5 5
 
6
-const DOUBLE_CLICK_DURATION = 300
6
+const DOUBLE_CLICK_DURATION = 250
7 7
 
8 8
 class Inputs {
9 9
   activePointerId?: number
@@ -104,13 +104,14 @@ class Inputs {
104 104
     return info
105 105
   }
106 106
 
107
-  pointerMove(e: PointerEvent | React.PointerEvent) {
107
+  pointerMove(e: PointerEvent | React.PointerEvent, target = '') {
108 108
     const { shiftKey, ctrlKey, metaKey, altKey } = e
109 109
 
110 110
     const prev = this.points[e.pointerId]
111 111
 
112 112
     const info = {
113 113
       ...prev,
114
+      target,
114 115
       pointerId: e.pointerId,
115 116
       point: getPoint(e),
116 117
       pressure: e.pressure || 0.5,
@@ -129,13 +130,14 @@ class Inputs {
129 130
     return info
130 131
   }
131 132
 
132
-  pointerUp = (e: PointerEvent | React.PointerEvent) => {
133
+  pointerUp = (e: PointerEvent | React.PointerEvent, target = '') => {
133 134
     const { shiftKey, ctrlKey, metaKey, altKey } = e
134 135
 
135 136
     const prev = this.points[e.pointerId]
136 137
 
137 138
     const info = {
138 139
       ...prev,
140
+      target,
139 141
       origin: prev?.origin || getPoint(e),
140 142
       point: getPoint(e),
141 143
       pressure: e.pressure || 0.5,

+ 143
- 141
state/shape-utils/arrow.tsx Просмотреть файл

@@ -7,7 +7,13 @@ import {
7 7
   translateBounds,
8 8
   pointsBetween,
9 9
 } from 'utils/utils'
10
-import { ArrowShape, DashStyle, ShapeHandle, ShapeType } from 'types'
10
+import {
11
+  ArrowShape,
12
+  DashStyle,
13
+  Decoration,
14
+  ShapeHandle,
15
+  ShapeType,
16
+} from 'types'
11 17
 import { circleFromThreePoints, isAngleBetween } from 'utils/utils'
12 18
 import { pointInBounds } from 'utils/hitTests'
13 19
 import {
@@ -71,8 +77,8 @@ const arrow = registerShapeUtils<ArrowShape>({
71 77
       handles,
72 78
       decorations: {
73 79
         start: null,
74
-        end: null,
75 80
         middle: null,
81
+        end: Decoration.Arrow,
76 82
       },
77 83
       ...props,
78 84
       style: {
@@ -98,13 +104,19 @@ const arrow = registerShapeUtils<ArrowShape>({
98 104
 
99 105
     const arrowDist = vec.dist(start.point, end.point)
100 106
 
107
+    let shaftPath: JSX.Element
108
+    let startAngle: number
109
+    let endAngle: number
110
+
101 111
     if (isStraightLine) {
102
-      // Render a straight arrow as a freehand path.
103
-      if (!pathCache.has(shape)) {
104
-        renderPath(shape)
112
+      if (shape.style.dash === DashStyle.Solid && !pathCache.has(shape)) {
113
+        renderFreehandArrowShaft(shape)
105 114
       }
106 115
 
107
-      const path = pathCache.get(shape)
116
+      const path =
117
+        shape.style.dash === DashStyle.Solid
118
+          ? pathCache.get(shape)
119
+          : 'M' + start.point + 'L' + end.point
108 120
 
109 121
       const { strokeDasharray, strokeDashoffset } =
110 122
         shape.style.dash === DashStyle.Solid
@@ -119,9 +131,12 @@ const arrow = registerShapeUtils<ArrowShape>({
119 131
               2
120 132
             )
121 133
 
122
-      return (
123
-        <g id={id}>
124
-          {/* Improves hit testing */}
134
+      startAngle = Math.PI
135
+
136
+      endAngle = 0
137
+
138
+      shaftPath = (
139
+        <>
125 140
           <path
126 141
             d={path}
127 142
             stroke="transparent"
@@ -131,7 +146,6 @@ const arrow = registerShapeUtils<ArrowShape>({
131 146
             strokeDashoffset="none"
132 147
             strokeLinecap="round"
133 148
           />
134
-          {/* Arrowshaft */}
135 149
           <path
136 150
             d={path}
137 151
             fill="none"
@@ -141,79 +155,86 @@ const arrow = registerShapeUtils<ArrowShape>({
141 155
             strokeDasharray={strokeDasharray}
142 156
             strokeDashoffset={strokeDashoffset}
143 157
             strokeLinecap="round"
144
-          />
145
-          {/* Arrowhead */}
146
-          {style.dash !== DashStyle.Solid && (
147
-            <path
148
-              d={getArrowHeadPath(shape, 0)}
149
-              strokeWidth={strokeWidth * 1.618}
150
-              fill="none"
151
-              strokeDashoffset="none"
152
-              strokeDasharray="none"
153
-            />
154
-          )}
155
-        </g>
158
+          ></path>
159
+        </>
156 160
       )
157
-    }
161
+    } else {
162
+      const circle = getCtp(shape)
158 163
 
159
-    const circle = getCtp(shape)
164
+      const path = getArrowArcPath(start, end, circle, bend)
160 165
 
161
-    if (!pathCache.has(shape)) {
162
-      renderPath(
163
-        shape,
166
+      const { strokeDasharray, strokeDashoffset } =
167
+        shape.style.dash === DashStyle.Solid
168
+          ? {
169
+              strokeDasharray: 'none',
170
+              strokeDashoffset: '0',
171
+            }
172
+          : getPerfectDashProps(
173
+              getArcLength(
174
+                [circle[0], circle[1]],
175
+                circle[2],
176
+                start.point,
177
+                end.point
178
+              ) - 1,
179
+              strokeWidth * 1.618,
180
+              shape.style.dash === DashStyle.Dotted ? 'dotted' : 'dashed',
181
+              2
182
+            )
183
+
184
+      startAngle =
185
+        vec.angle([circle[0], circle[1]], start.point) -
186
+        vec.angle(end.point, start.point) +
187
+        (Math.PI / 2) * (bend > 0 ? 0.98 : -0.98)
188
+
189
+      endAngle =
164 190
         vec.angle([circle[0], circle[1]], end.point) -
165
-          vec.angle(start.point, end.point) +
166
-          (Math.PI / 2) * (bend > 0 ? 0.98 : -0.98)
191
+        vec.angle(start.point, end.point) +
192
+        (Math.PI / 2) * (bend > 0 ? 0.98 : -0.98)
193
+
194
+      shaftPath = (
195
+        <>
196
+          <path
197
+            d={path}
198
+            stroke="transparent"
199
+            fill="none"
200
+            strokeWidth={Math.max(8, strokeWidth * 2)}
201
+            strokeDasharray="none"
202
+            strokeDashoffset="none"
203
+            strokeLinecap="round"
204
+          />
205
+          <path
206
+            d={path}
207
+            fill="none"
208
+            strokeWidth={strokeWidth * 2}
209
+            strokeDasharray={strokeDasharray}
210
+            strokeDashoffset={strokeDashoffset}
211
+            strokeLinecap="round"
212
+          ></path>
213
+        </>
167 214
       )
168 215
     }
169 216
 
170
-    const path = getArrowArcPath(start, end, circle, bend)
171
-
172
-    const { strokeDasharray, strokeDashoffset } =
173
-      shape.style.dash === DashStyle.Solid
174
-        ? {
175
-            strokeDasharray: 'none',
176
-            strokeDashoffset: '0',
177
-          }
178
-        : getPerfectDashProps(
179
-            getArcLength(
180
-              [circle[0], circle[1]],
181
-              circle[2],
182
-              start.point,
183
-              end.point
184
-            ) - 1,
185
-            strokeWidth * 1.618,
186
-            shape.style.dash === DashStyle.Dotted ? 'dotted' : 'dashed',
187
-            2
188
-          )
189
-
190 217
     return (
191 218
       <g id={id}>
192
-        {/* Improves hit testing */}
193
-        <path
194
-          d={path}
195
-          stroke="transparent"
196
-          fill="none"
197
-          strokeWidth={Math.max(8, strokeWidth * 2)}
198
-          strokeLinecap="round"
199
-          strokeDasharray="none"
200
-        />
201
-        {/* Arrow Shaft */}
202
-        <path
203
-          d={path}
204
-          fill="none"
205
-          strokeWidth={strokeWidth * 1.618}
206
-          strokeLinecap="round"
207
-          strokeDasharray={strokeDasharray}
208
-          strokeDashoffset={strokeDashoffset}
209
-        />
210
-        {/* Arrowhead */}
211
-        <path
212
-          d={pathCache.get(shape)}
213
-          strokeWidth={strokeWidth * 1.618}
214
-          strokeDasharray="none"
215
-          fill="none"
216
-        />
219
+        {shaftPath}
220
+        {shape.decorations.start === Decoration.Arrow && (
221
+          <path
222
+            d={getArrowHeadPath(shape, start.point, startAngle)}
223
+            strokeWidth={strokeWidth * 1.618}
224
+            fill="none"
225
+            strokeDashoffset="none"
226
+            strokeDasharray="none"
227
+          />
228
+        )}
229
+        {shape.decorations.end === Decoration.Arrow && (
230
+          <path
231
+            d={getArrowHeadPath(shape, end.point, endAngle)}
232
+            strokeWidth={strokeWidth * 1.618}
233
+            fill="none"
234
+            strokeDashoffset="none"
235
+            strokeDasharray="none"
236
+          />
237
+        )}
217 238
       </g>
218 239
     )
219 240
   },
@@ -347,10 +368,29 @@ const arrow = registerShapeUtils<ArrowShape>({
347 368
     return this
348 369
   },
349 370
 
350
-  onHandleChange(shape, handles) {
351
-    // const oldBounds = this.getRotatedBounds(shape)
352
-    // const prevCenter = getBoundsCenter(oldBounds)
371
+  onDoublePointHandle(shape, handle) {
372
+    switch (handle) {
373
+      case 'bend': {
374
+        shape.bend = 0
375
+        shape.handles.bend.point = getBendPoint(shape)
376
+        break
377
+      }
378
+      case 'start': {
379
+        shape.decorations.start = shape.decorations.start
380
+          ? null
381
+          : Decoration.Arrow
382
+        break
383
+      }
384
+      case 'end': {
385
+        shape.decorations.end = shape.decorations.end ? null : Decoration.Arrow
386
+        break
387
+      }
388
+    }
353 389
 
390
+    return this
391
+  },
392
+
393
+  onHandleChange(shape, handles) {
354 394
     for (const id in handles) {
355 395
       const handle = handles[id]
356 396
 
@@ -450,7 +490,7 @@ function getBendPoint(shape: ArrowShape) {
450 490
     : vec.add(midPoint, vec.mul(vec.per(u), bendDist))
451 491
 }
452 492
 
453
-function renderPath(shape: ArrowShape, endAngle = 0) {
493
+function renderFreehandArrowShaft(shape: ArrowShape) {
454 494
   const { style, id } = shape
455 495
   const { start, end } = shape.handles
456 496
 
@@ -458,73 +498,38 @@ function renderPath(shape: ArrowShape, endAngle = 0) {
458 498
 
459 499
   const strokeWidth = +getShapeStyle(style).strokeWidth * 2
460 500
 
461
-  const sw = strokeWidth
462
-
463
-  // Start
464
-  const a = start.point
465
-
466
-  // End
467
-  const b = end.point
468
-
469
-  // Middle
470 501
   const m = vec.add(
471 502
     vec.lrp(start.point, end.point, 0.25 + Math.abs(getRandom()) / 2),
472
-    [getRandom() * sw, getRandom() * sw]
503
+    [getRandom() * strokeWidth, getRandom() * strokeWidth]
473 504
   )
474 505
 
475
-  // Left and right sides of the arrowhead
476
-  let { left: c, right: d } = getArrowHeadPoints(shape, endAngle)
477
-
478
-  // Switch which side of the arrow is drawn first
479
-  if (getRandom() > 0) [c, d] = [d, c]
480
-
481
-  if (style.dash !== DashStyle.Solid) {
482
-    pathCache.set(
483
-      shape,
484
-      (endAngle ? ['M', c, 'L', b, d] : ['M', a, 'L', b]).join(' ')
485
-    )
486
-    return
487
-  }
488
-
489
-  const points = endAngle
490
-    ? [
491
-        // Just the arrowhead
492
-        ...pointsBetween(b, c),
493
-        ...pointsBetween(c, b),
494
-        ...pointsBetween(b, d),
495
-        ...pointsBetween(d, b),
496
-      ]
497
-    : [
498
-        // The arrow shaft
499
-        b,
500
-        a,
501
-        ...pointsBetween(a, m),
502
-        ...pointsBetween(m, b),
503
-        ...pointsBetween(b, c),
504
-        ...pointsBetween(c, b),
505
-        ...pointsBetween(b, d),
506
-        ...pointsBetween(d, b),
507
-      ]
508
-
509
-  const stroke = getStroke(points, {
510
-    size: 1 + strokeWidth,
511
-    thinning: 0.6,
512
-    easing: (t) => t * t * t * t,
513
-    end: { taper: strokeWidth * 20 },
514
-    start: { taper: strokeWidth * 20 },
515
-    simulatePressure: false,
516
-  })
506
+  const stroke = getStroke(
507
+    [
508
+      ...pointsBetween(start.point, m),
509
+      ...pointsBetween(m, end.point),
510
+      end.point,
511
+      end.point,
512
+      end.point,
513
+    ],
514
+    {
515
+      size: 1 + strokeWidth,
516
+      thinning: 0.6,
517
+      easing: (t) => t * t * t * t,
518
+      end: { taper: strokeWidth * 2 },
519
+      start: { taper: strokeWidth * 2 },
520
+      simulatePressure: false,
521
+    }
522
+  )
517 523
 
518 524
   pathCache.set(shape, getSvgPathFromStroke(stroke))
519 525
 }
520 526
 
521
-function getArrowHeadPath(shape: ArrowShape, endAngle = 0) {
522
-  const { end } = shape.handles
523
-  const { left, right } = getArrowHeadPoints(shape, endAngle)
524
-  return ['M', left, 'L', end.point, right].join(' ')
527
+function getArrowHeadPath(shape: ArrowShape, point: number[], angle = 0) {
528
+  const { left, right } = getArrowHeadPoints(shape, point, angle)
529
+  return ['M', left, 'L', point, right].join(' ')
525 530
 }
526 531
 
527
-function getArrowHeadPoints(shape: ArrowShape, endAngle = 0) {
532
+function getArrowHeadPoints(shape: ArrowShape, point: number[], angle = 0) {
528 533
   const { start, end } = shape.handles
529 534
 
530 535
   const stroke = +getShapeStyle(shape.style).strokeWidth * 2
@@ -537,18 +542,15 @@ function getArrowHeadPoints(shape: ArrowShape, endAngle = 0) {
537 542
   const u = vec.uni(vec.vec(start.point, end.point))
538 543
 
539 544
   // The end of the arrowhead wings
540
-  const v = vec.rot(vec.mul(vec.neg(u), arrowHeadlength), endAngle)
545
+  const v = vec.rot(vec.mul(vec.neg(u), arrowHeadlength), angle)
541 546
 
542 547
   // Use the shape's random seed to create minor offsets for the angles
543 548
   const getRandom = rng(shape.id)
544 549
 
545 550
   return {
546
-    left: vec.add(
547
-      end.point,
548
-      vec.rot(v, Math.PI / 6 + (Math.PI / 8) * getRandom())
549
-    ),
551
+    left: vec.add(point, vec.rot(v, Math.PI / 6 + (Math.PI / 8) * getRandom())),
550 552
     right: vec.add(
551
-      end.point,
553
+      point,
552 554
       vec.rot(v, -(Math.PI / 6) + (Math.PI / 8) * getRandom())
553 555
     ),
554 556
   }

+ 0
- 120
state/shape-utils/circle.tsx Просмотреть файл

@@ -1,120 +0,0 @@
1
-import { uniqueId } from 'utils/utils'
2
-import vec from 'utils/vec'
3
-import { CircleShape, ShapeType } from 'types'
4
-import { boundsContained } from 'utils/bounds'
5
-import { intersectCircleBounds } from 'utils/intersections'
6
-import { pointInCircle } from 'utils/hitTests'
7
-import { translateBounds } from 'utils/utils'
8
-import { defaultStyle, getShapeStyle } from 'state/shape-styles'
9
-import { registerShapeUtils } from './register'
10
-
11
-const circle = registerShapeUtils<CircleShape>({
12
-  boundsCache: new WeakMap([]),
13
-
14
-  create(props) {
15
-    return {
16
-      id: uniqueId(),
17
-      seed: Math.random(),
18
-      type: ShapeType.Circle,
19
-      isGenerated: false,
20
-      name: 'Circle',
21
-      parentId: 'page1',
22
-      childIndex: 0,
23
-      point: [0, 0],
24
-      rotation: 0,
25
-      radius: 1,
26
-      isAspectRatioLocked: false,
27
-      isLocked: false,
28
-      isHidden: false,
29
-      style: defaultStyle,
30
-      ...props,
31
-    }
32
-  },
33
-
34
-  render({ id, radius, style }) {
35
-    const styles = getShapeStyle(style)
36
-
37
-    return (
38
-      <circle
39
-        id={id}
40
-        cx={radius}
41
-        cy={radius}
42
-        r={Math.max(0, radius - Number(styles.strokeWidth) / 2)}
43
-      />
44
-    )
45
-  },
46
-
47
-  getBounds(shape) {
48
-    if (!this.boundsCache.has(shape)) {
49
-      const { radius } = shape
50
-
51
-      const bounds = {
52
-        minX: 0,
53
-        maxX: radius * 2,
54
-        minY: 0,
55
-        maxY: radius * 2,
56
-        width: radius * 2,
57
-        height: radius * 2,
58
-      }
59
-
60
-      this.boundsCache.set(shape, bounds)
61
-    }
62
-
63
-    return translateBounds(this.boundsCache.get(shape), shape.point)
64
-  },
65
-
66
-  getRotatedBounds(shape) {
67
-    return this.getBounds(shape)
68
-  },
69
-
70
-  getCenter(shape) {
71
-    return [shape.point[0] + shape.radius, shape.point[1] + shape.radius]
72
-  },
73
-
74
-  hitTest(shape, point) {
75
-    return pointInCircle(
76
-      point,
77
-      vec.addScalar(shape.point, shape.radius),
78
-      shape.radius
79
-    )
80
-  },
81
-
82
-  hitTestBounds(shape, bounds) {
83
-    const shapeBounds = this.getBounds(shape)
84
-
85
-    return (
86
-      boundsContained(shapeBounds, bounds) ||
87
-      intersectCircleBounds(
88
-        vec.addScalar(shape.point, shape.radius),
89
-        shape.radius,
90
-        bounds
91
-      ).length > 0
92
-    )
93
-  },
94
-
95
-  transform(shape, bounds, { initialShape, transformOrigin, scaleX, scaleY }) {
96
-    shape.radius =
97
-      initialShape.radius * Math.min(Math.abs(scaleX), Math.abs(scaleY))
98
-
99
-    shape.point = [
100
-      bounds.minX +
101
-        (bounds.width - shape.radius * 2) *
102
-          (scaleX < 0 ? 1 - transformOrigin[0] : transformOrigin[0]),
103
-      bounds.minY +
104
-        (bounds.height - shape.radius * 2) *
105
-          (scaleY < 0 ? 1 - transformOrigin[1] : transformOrigin[1]),
106
-    ]
107
-
108
-    return this
109
-  },
110
-
111
-  transformSingle(shape, bounds) {
112
-    shape.radius = Math.min(bounds.width, bounds.height) / 2
113
-    shape.point = [bounds.minX, bounds.minY]
114
-    return this
115
-  },
116
-
117
-  canChangeAspectRatio: false,
118
-})
119
-
120
-export default circle

+ 4
- 6
state/shape-utils/index.tsx Просмотреть файл

@@ -1,5 +1,4 @@
1 1
 import { Shape, ShapeType, ShapeByType, ShapeUtility } from 'types'
2
-import circle from './circle'
3 2
 import dot from './dot'
4 3
 import polyline from './polyline'
5 4
 import rectangle from './rectangle'
@@ -13,17 +12,16 @@ import text from './text'
13 12
 
14 13
 // A mapping of shape types to shape utilities.
15 14
 const shapeUtilityMap: Record<ShapeType, ShapeUtility<Shape>> = {
16
-  [ShapeType.Circle]: circle,
17
-  [ShapeType.Dot]: dot,
18
-  [ShapeType.Polyline]: polyline,
19 15
   [ShapeType.Rectangle]: rectangle,
20 16
   [ShapeType.Ellipse]: ellipse,
21
-  [ShapeType.Line]: line,
22
-  [ShapeType.Ray]: ray,
23 17
   [ShapeType.Draw]: draw,
24 18
   [ShapeType.Arrow]: arrow,
25 19
   [ShapeType.Text]: text,
26 20
   [ShapeType.Group]: group,
21
+  [ShapeType.Dot]: dot,
22
+  [ShapeType.Polyline]: polyline,
23
+  [ShapeType.Line]: line,
24
+  [ShapeType.Ray]: ray,
27 25
 }
28 26
 
29 27
 /**

+ 4
- 0
state/shape-utils/register.tsx Просмотреть файл

@@ -82,6 +82,10 @@ function getDefaultShapeUtil<T extends Shape>(): ShapeUtility<T> {
82 82
       return this
83 83
     },
84 84
 
85
+    onDoublePointHandle() {
86
+      return this
87
+    },
88
+
85 89
     onDoubleFocus() {
86 90
       return this
87 91
     },

+ 13
- 40
state/state.ts Просмотреть файл

@@ -26,6 +26,7 @@ import {
26 26
   getSelectedIds,
27 27
   setSelectedIds,
28 28
   getPageState,
29
+  setToArray,
29 30
 } from 'utils/utils'
30 31
 import {
31 32
   Data,
@@ -161,7 +162,6 @@ const state = createState({
161 162
         SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
162 163
         SELECTED_ARROW_TOOL: { unless: 'isReadOnly', to: 'arrow' },
163 164
         SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
164
-        SELECTED_CIRCLE_TOOL: { unless: 'isReadOnly', to: 'circle' },
165 165
         SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
166 166
         SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
167 167
         SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
@@ -408,6 +408,10 @@ const state = createState({
408 408
                 PRESSED_SHIFT_KEY: 'keyUpdateHandleSession',
409 409
                 RELEASED_SHIFT_KEY: 'keyUpdateHandleSession',
410 410
                 STOPPED_POINTING: { to: 'selecting' },
411
+                DOUBLE_POINTED_HANDLE: {
412
+                  do: ['cancelSession', 'doublePointHandle'],
413
+                  to: 'selecting',
414
+                },
411 415
                 CANCELLED: { do: 'cancelSession', to: 'selecting' },
412 416
               },
413 417
             },
@@ -627,37 +631,6 @@ const state = createState({
627 631
                 },
628 632
               },
629 633
             },
630
-            circle: {
631
-              onEnter: 'setActiveToolCircle',
632
-              initial: 'creating',
633
-              states: {
634
-                creating: {
635
-                  on: {
636
-                    CANCELLED: { to: 'selecting' },
637
-                    POINTED_SHAPE: {
638
-                      to: 'circle.editing',
639
-                    },
640
-                    POINTED_CANVAS: {
641
-                      to: 'circle.editing',
642
-                    },
643
-                  },
644
-                },
645
-                editing: {
646
-                  on: {
647
-                    STOPPED_POINTING: { to: 'selecting' },
648
-                    CANCELLED: { to: 'selecting' },
649
-                    MOVED_POINTER: {
650
-                      if: 'distanceImpliesDrag',
651
-                      then: {
652
-                        get: 'newCircle',
653
-                        do: 'createShape',
654
-                        to: 'drawingShape.bounds',
655
-                      },
656
-                    },
657
-                  },
658
-                },
659
-              },
660
-            },
661 634
             ellipse: {
662 635
               onEnter: 'setActiveToolEllipse',
663 636
               initial: 'creating',
@@ -871,9 +844,6 @@ const state = createState({
871 844
     newArrow() {
872 845
       return ShapeType.Arrow
873 846
     },
874
-    newCircle() {
875
-      return ShapeType.Circle
876
-    },
877 847
     newEllipse() {
878 848
       return ShapeType.Ellipse
879 849
     },
@@ -1099,6 +1069,12 @@ const state = createState({
1099 1069
       )
1100 1070
     },
1101 1071
 
1072
+    // Handles
1073
+    doublePointHandle(data, payload: PointerInfo) {
1074
+      const id = setToArray(getSelectedIds(data))[0]
1075
+      commands.doublePointHandle(data, id, payload)
1076
+    },
1077
+
1102 1078
     // Dragging Handle
1103 1079
     startHandleSession(data, payload: PointerInfo) {
1104 1080
       const shapeId = Array.from(getSelectedIds(data).values())[0]
@@ -1230,6 +1206,8 @@ const state = createState({
1230 1206
       )
1231 1207
     },
1232 1208
 
1209
+    /* -------------------- Selection ------------------- */
1210
+
1233 1211
     // Nudges
1234 1212
     nudgeSelection(data, payload: { delta: number[]; shiftKey: boolean }) {
1235 1213
       commands.nudge(
@@ -1243,8 +1221,6 @@ const state = createState({
1243 1221
       )
1244 1222
     },
1245 1223
 
1246
-    /* -------------------- Selection ------------------- */
1247
-
1248 1224
     clearInputs() {
1249 1225
       inputs.clear()
1250 1226
     },
@@ -1377,9 +1353,6 @@ const state = createState({
1377 1353
     setActiveToolRay(data) {
1378 1354
       data.activeTool = ShapeType.Ray
1379 1355
     },
1380
-    setActiveToolCircle(data) {
1381
-      data.activeTool = ShapeType.Circle
1382
-    },
1383 1356
     setActiveToolLine(data) {
1384 1357
       data.activeTool = ShapeType.Line
1385 1358
     },

+ 7
- 8
types.ts Просмотреть файл

@@ -61,7 +61,6 @@ export interface PageState {
61 61
 
62 62
 export enum ShapeType {
63 63
   Dot = 'dot',
64
-  Circle = 'circle',
65 64
   Ellipse = 'ellipse',
66 65
   Line = 'line',
67 66
   Ray = 'ray',
@@ -137,11 +136,6 @@ export interface DotShape extends BaseShape {
137 136
   type: ShapeType.Dot
138 137
 }
139 138
 
140
-export interface CircleShape extends BaseShape {
141
-  type: ShapeType.Circle
142
-  radius: number
143
-}
144
-
145 139
 export interface EllipseShape extends BaseShape {
146 140
   type: ShapeType.Ellipse
147 141
   radiusX: number
@@ -201,7 +195,6 @@ export interface GroupShape extends BaseShape {
201 195
 
202 196
 export type MutableShape =
203 197
   | DotShape
204
-  | CircleShape
205 198
   | EllipseShape
206 199
   | LineShape
207 200
   | RayShape
@@ -214,7 +207,6 @@ export type MutableShape =
214 207
 
215 208
 export interface Shapes {
216 209
   [ShapeType.Dot]: Readonly<DotShape>
217
-  [ShapeType.Circle]: Readonly<CircleShape>
218 210
   [ShapeType.Ellipse]: Readonly<EllipseShape>
219 211
   [ShapeType.Line]: Readonly<LineShape>
220 212
   [ShapeType.Ray]: Readonly<RayShape>
@@ -538,6 +530,13 @@ export interface ShapeUtility<K extends Shape> {
538 530
     handle: Partial<K['handles']>
539 531
   ): ShapeUtility<K>
540 532
 
533
+  onDoublePointHandle(
534
+    this: ShapeUtility<K>,
535
+    shape: Mutable<K>,
536
+    handle: keyof K['handles'],
537
+    info: PointerInfo
538
+  ): ShapeUtility<K>
539
+
541 540
   // Respond when a user double clicks the shape's bounds.
542 541
   onBoundsReset(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
543 542
 

Загрузка…
Отмена
Сохранить