Bläddra i källkod

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

main
Steve Ruiz 4 år sedan
förälder
incheckning
7d14791d00

+ 11
- 2
hooks/useHandleEvents.ts Visa fil

12
       if (!inputs.canAccept(e.pointerId)) return
12
       if (!inputs.canAccept(e.pointerId)) return
13
       e.stopPropagation()
13
       e.stopPropagation()
14
       rGroup.current.setPointerCapture(e.pointerId)
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
     [id]
19
     [id]
18
   )
20
   )
22
       if (!inputs.canAccept(e.pointerId)) return
24
       if (!inputs.canAccept(e.pointerId)) return
23
       e.stopPropagation()
25
       e.stopPropagation()
24
       rGroup.current.releasePointerCapture(e.pointerId)
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
     [id]
36
     [id]
28
   )
37
   )

+ 0
- 41
state/code/circle.ts Visa fil

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 Visa fil

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

+ 33
- 0
state/commands/double-point-handle.ts Visa fil

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 Visa fil

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

+ 5
- 3
state/inputs.tsx Visa fil

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

+ 143
- 141
state/shape-utils/arrow.tsx Visa fil

7
   translateBounds,
7
   translateBounds,
8
   pointsBetween,
8
   pointsBetween,
9
 } from 'utils/utils'
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
 import { circleFromThreePoints, isAngleBetween } from 'utils/utils'
17
 import { circleFromThreePoints, isAngleBetween } from 'utils/utils'
12
 import { pointInBounds } from 'utils/hitTests'
18
 import { pointInBounds } from 'utils/hitTests'
13
 import {
19
 import {
71
       handles,
77
       handles,
72
       decorations: {
78
       decorations: {
73
         start: null,
79
         start: null,
74
-        end: null,
75
         middle: null,
80
         middle: null,
81
+        end: Decoration.Arrow,
76
       },
82
       },
77
       ...props,
83
       ...props,
78
       style: {
84
       style: {
98
 
104
 
99
     const arrowDist = vec.dist(start.point, end.point)
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
     if (isStraightLine) {
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
       const { strokeDasharray, strokeDashoffset } =
121
       const { strokeDasharray, strokeDashoffset } =
110
         shape.style.dash === DashStyle.Solid
122
         shape.style.dash === DashStyle.Solid
119
               2
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
           <path
140
           <path
126
             d={path}
141
             d={path}
127
             stroke="transparent"
142
             stroke="transparent"
131
             strokeDashoffset="none"
146
             strokeDashoffset="none"
132
             strokeLinecap="round"
147
             strokeLinecap="round"
133
           />
148
           />
134
-          {/* Arrowshaft */}
135
           <path
149
           <path
136
             d={path}
150
             d={path}
137
             fill="none"
151
             fill="none"
141
             strokeDasharray={strokeDasharray}
155
             strokeDasharray={strokeDasharray}
142
             strokeDashoffset={strokeDashoffset}
156
             strokeDashoffset={strokeDashoffset}
143
             strokeLinecap="round"
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
         vec.angle([circle[0], circle[1]], end.point) -
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
     return (
217
     return (
191
       <g id={id}>
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
       </g>
238
       </g>
218
     )
239
     )
219
   },
240
   },
347
     return this
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
     for (const id in handles) {
394
     for (const id in handles) {
355
       const handle = handles[id]
395
       const handle = handles[id]
356
 
396
 
450
     : vec.add(midPoint, vec.mul(vec.per(u), bendDist))
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
   const { style, id } = shape
494
   const { style, id } = shape
455
   const { start, end } = shape.handles
495
   const { start, end } = shape.handles
456
 
496
 
458
 
498
 
459
   const strokeWidth = +getShapeStyle(style).strokeWidth * 2
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
   const m = vec.add(
501
   const m = vec.add(
471
     vec.lrp(start.point, end.point, 0.25 + Math.abs(getRandom()) / 2),
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
   pathCache.set(shape, getSvgPathFromStroke(stroke))
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
   const { start, end } = shape.handles
533
   const { start, end } = shape.handles
529
 
534
 
530
   const stroke = +getShapeStyle(shape.style).strokeWidth * 2
535
   const stroke = +getShapeStyle(shape.style).strokeWidth * 2
537
   const u = vec.uni(vec.vec(start.point, end.point))
542
   const u = vec.uni(vec.vec(start.point, end.point))
538
 
543
 
539
   // The end of the arrowhead wings
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
   // Use the shape's random seed to create minor offsets for the angles
547
   // Use the shape's random seed to create minor offsets for the angles
543
   const getRandom = rng(shape.id)
548
   const getRandom = rng(shape.id)
544
 
549
 
545
   return {
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
     right: vec.add(
552
     right: vec.add(
551
-      end.point,
553
+      point,
552
       vec.rot(v, -(Math.PI / 6) + (Math.PI / 8) * getRandom())
554
       vec.rot(v, -(Math.PI / 6) + (Math.PI / 8) * getRandom())
553
     ),
555
     ),
554
   }
556
   }

+ 0
- 120
state/shape-utils/circle.tsx Visa fil

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 Visa fil

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

82
       return this
82
       return this
83
     },
83
     },
84
 
84
 
85
+    onDoublePointHandle() {
86
+      return this
87
+    },
88
+
85
     onDoubleFocus() {
89
     onDoubleFocus() {
86
       return this
90
       return this
87
     },
91
     },

+ 13
- 40
state/state.ts Visa fil

26
   getSelectedIds,
26
   getSelectedIds,
27
   setSelectedIds,
27
   setSelectedIds,
28
   getPageState,
28
   getPageState,
29
+  setToArray,
29
 } from 'utils/utils'
30
 } from 'utils/utils'
30
 import {
31
 import {
31
   Data,
32
   Data,
161
         SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
162
         SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
162
         SELECTED_ARROW_TOOL: { unless: 'isReadOnly', to: 'arrow' },
163
         SELECTED_ARROW_TOOL: { unless: 'isReadOnly', to: 'arrow' },
163
         SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
164
         SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
164
-        SELECTED_CIRCLE_TOOL: { unless: 'isReadOnly', to: 'circle' },
165
         SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
165
         SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
166
         SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
166
         SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
167
         SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
167
         SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
408
                 PRESSED_SHIFT_KEY: 'keyUpdateHandleSession',
408
                 PRESSED_SHIFT_KEY: 'keyUpdateHandleSession',
409
                 RELEASED_SHIFT_KEY: 'keyUpdateHandleSession',
409
                 RELEASED_SHIFT_KEY: 'keyUpdateHandleSession',
410
                 STOPPED_POINTING: { to: 'selecting' },
410
                 STOPPED_POINTING: { to: 'selecting' },
411
+                DOUBLE_POINTED_HANDLE: {
412
+                  do: ['cancelSession', 'doublePointHandle'],
413
+                  to: 'selecting',
414
+                },
411
                 CANCELLED: { do: 'cancelSession', to: 'selecting' },
415
                 CANCELLED: { do: 'cancelSession', to: 'selecting' },
412
               },
416
               },
413
             },
417
             },
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
             ellipse: {
634
             ellipse: {
662
               onEnter: 'setActiveToolEllipse',
635
               onEnter: 'setActiveToolEllipse',
663
               initial: 'creating',
636
               initial: 'creating',
871
     newArrow() {
844
     newArrow() {
872
       return ShapeType.Arrow
845
       return ShapeType.Arrow
873
     },
846
     },
874
-    newCircle() {
875
-      return ShapeType.Circle
876
-    },
877
     newEllipse() {
847
     newEllipse() {
878
       return ShapeType.Ellipse
848
       return ShapeType.Ellipse
879
     },
849
     },
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
     // Dragging Handle
1078
     // Dragging Handle
1103
     startHandleSession(data, payload: PointerInfo) {
1079
     startHandleSession(data, payload: PointerInfo) {
1104
       const shapeId = Array.from(getSelectedIds(data).values())[0]
1080
       const shapeId = Array.from(getSelectedIds(data).values())[0]
1230
       )
1206
       )
1231
     },
1207
     },
1232
 
1208
 
1209
+    /* -------------------- Selection ------------------- */
1210
+
1233
     // Nudges
1211
     // Nudges
1234
     nudgeSelection(data, payload: { delta: number[]; shiftKey: boolean }) {
1212
     nudgeSelection(data, payload: { delta: number[]; shiftKey: boolean }) {
1235
       commands.nudge(
1213
       commands.nudge(
1243
       )
1221
       )
1244
     },
1222
     },
1245
 
1223
 
1246
-    /* -------------------- Selection ------------------- */
1247
-
1248
     clearInputs() {
1224
     clearInputs() {
1249
       inputs.clear()
1225
       inputs.clear()
1250
     },
1226
     },
1377
     setActiveToolRay(data) {
1353
     setActiveToolRay(data) {
1378
       data.activeTool = ShapeType.Ray
1354
       data.activeTool = ShapeType.Ray
1379
     },
1355
     },
1380
-    setActiveToolCircle(data) {
1381
-      data.activeTool = ShapeType.Circle
1382
-    },
1383
     setActiveToolLine(data) {
1356
     setActiveToolLine(data) {
1384
       data.activeTool = ShapeType.Line
1357
       data.activeTool = ShapeType.Line
1385
     },
1358
     },

+ 7
- 8
types.ts Visa fil

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

Laddar…
Avbryt
Spara