Przeglądaj źródła

improves arrow rotation

main
Steve Ruiz 4 lat temu
rodzic
commit
81141e7bb5

+ 1
- 10
components/canvas/bounds/handles.tsx Wyświetl plik

30
       {Object.values(shape.handles).map((handle) => (
30
       {Object.values(shape.handles).map((handle) => (
31
         <Handle
31
         <Handle
32
           key={handle.id}
32
           key={handle.id}
33
-          shapeId={shape.id}
34
           id={handle.id}
33
           id={handle.id}
35
           point={vec.add(handle.point, shape.point)}
34
           point={vec.add(handle.point, shape.point)}
36
         />
35
         />
39
   )
38
   )
40
 }
39
 }
41
 
40
 
42
-function Handle({
43
-  shapeId,
44
-  id,
45
-  point,
46
-}: {
47
-  shapeId: string
48
-  id: string
49
-  point: number[]
50
-}) {
41
+function Handle({ id, point }: { id: string; point: number[] }) {
51
   const rGroup = useRef<SVGGElement>(null)
42
   const rGroup = useRef<SVGGElement>(null)
52
   const events = useHandleEvents(id, rGroup)
43
   const events = useHandleEvents(id, rGroup)
53
 
44
 

+ 97
- 18
lib/shape-utils/arrow.tsx Wyświetl plik

3
 import * as svg from 'utils/svg'
3
 import * as svg from 'utils/svg'
4
 import {
4
 import {
5
   ArrowShape,
5
   ArrowShape,
6
+  Bounds,
6
   ColorStyle,
7
   ColorStyle,
7
   DashStyle,
8
   DashStyle,
8
   ShapeHandle,
9
   ShapeHandle,
10
   SizeStyle,
11
   SizeStyle,
11
 } from 'types'
12
 } from 'types'
12
 import { registerShapeUtils } from './index'
13
 import { registerShapeUtils } from './index'
13
-import { circleFromThreePoints, clamp, isAngleBetween } from 'utils/utils'
14
+import {
15
+  circleFromThreePoints,
16
+  clamp,
17
+  getBoundsCenter,
18
+  isAngleBetween,
19
+  rotateBounds,
20
+} from 'utils/utils'
14
 import { pointInBounds } from 'utils/bounds'
21
 import { pointInBounds } from 'utils/bounds'
15
 import {
22
 import {
16
   intersectArcBounds,
23
   intersectArcBounds,
170
     )
177
     )
171
   },
178
   },
172
 
179
 
173
-  rotateTo(shape, rotation, delta) {
180
+  rotateBy(shape, delta) {
174
     const { start, end, bend } = shape.handles
181
     const { start, end, bend } = shape.handles
175
-    // const mp = vec.med(start.point, end.point)
176
-    // start.point = vec.rotWith(start.point, mp, delta)
177
-    // end.point = vec.rotWith(end.point, mp, delta)
178
-    // bend.point = vec.rotWith(bend.point, mp, delta)
179
-    // this.onHandleChange(shape, shape.handles)
182
+    const mp = vec.med(start.point, end.point)
183
+    start.point = vec.rotWith(start.point, mp, delta)
184
+    end.point = vec.rotWith(end.point, mp, delta)
185
+    bend.point = vec.rotWith(bend.point, mp, delta)
180
 
186
 
181
-    // const bounds = this.getBounds(shape)
187
+    this.onHandleChange(shape, shape.handles)
182
 
188
 
183
-    // const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
189
+    return this
190
+  },
184
 
191
 
185
-    // this.translateTo(shape, vec.add(shape.point, offset))
192
+  rotateTo(shape, rotation, delta) {
193
+    const { start, end, bend } = shape.handles
194
+    const mp = vec.med(start.point, end.point)
195
+    start.point = vec.rotWith(start.point, mp, delta)
196
+    end.point = vec.rotWith(end.point, mp, delta)
197
+    bend.point = vec.rotWith(bend.point, mp, delta)
186
 
198
 
187
-    // start.point = vec.sub(start.point, offset)
188
-    // end.point = vec.sub(end.point, offset)
189
-    // bend.point = vec.sub(bend.point, offset)
199
+    this.onHandleChange(shape, shape.handles)
190
 
200
 
191
-    shape.rotation = rotation
192
     return this
201
     return this
193
   },
202
   },
194
 
203
 
202
   },
211
   },
203
 
212
 
204
   getRotatedBounds(shape) {
213
   getRotatedBounds(shape) {
205
-    if (!this.boundsCache.has(shape)) {
206
-      this.boundsCache.set(shape, getBoundsFromPoints(shape.points))
207
-    }
214
+    const { start, end } = shape.handles
215
+    return translateBounds(
216
+      getBoundsFromPoints([start.point, end.point], shape.rotation),
217
+      shape.point
218
+    )
219
+  },
208
 
220
 
209
-    return translateBounds(this.boundsCache.get(shape), shape.point)
221
+  getCenter(shape) {
222
+    const { start, end } = shape.handles
223
+    return vec.add(shape.point, vec.med(start.point, end.point))
210
   },
224
   },
211
 
225
 
212
   hitTest(shape, point) {
226
   hitTest(shape, point) {
281
   },
295
   },
282
 
296
 
283
   onHandleChange(shape, handles) {
297
   onHandleChange(shape, handles) {
298
+    // const oldBounds = this.getRotatedBounds(shape)
299
+    // const prevCenter = getBoundsCenter(oldBounds)
300
+
284
     for (let id in handles) {
301
     for (let id in handles) {
285
       const handle = handles[id]
302
       const handle = handles[id]
286
 
303
 
313
 
330
 
314
     shape.handles.bend.point = getBendPoint(shape)
331
     shape.handles.bend.point = getBendPoint(shape)
315
 
332
 
333
+    // const newBounds = this.getRotatedBounds(shape)
334
+    // const newCenter = getBoundsCenter(newBounds)
335
+
336
+    // shape.point = vec.add(shape.point, vec.neg(vec.sub(newCenter, prevCenter)))
337
+
338
+    return this
339
+  },
340
+
341
+  onSessionComplete(shape) {
342
+    const bounds = this.getBounds(shape)
343
+
344
+    const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
345
+
346
+    this.translateTo(shape, vec.add(shape.point, offset))
347
+
348
+    const { start, end, bend } = shape.handles
349
+
350
+    start.point = vec.sub(start.point, offset)
351
+    end.point = vec.sub(end.point, offset)
352
+    bend.point = vec.sub(bend.point, offset)
353
+
316
     return this
354
     return this
317
   },
355
   },
318
 
356
 
360
     ? midPoint
398
     ? midPoint
361
     : vec.add(midPoint, vec.mul(vec.per(u), bendDist))
399
     : vec.add(midPoint, vec.mul(vec.per(u), bendDist))
362
 }
400
 }
401
+
402
+function getResizeOffset(a: Bounds, b: Bounds) {
403
+  const { minX: x0, minY: y0, width: w0, height: h0 } = a
404
+  const { minX: x1, minY: y1, width: w1, height: h1 } = b
405
+
406
+  let delta: number[]
407
+
408
+  if (h0 === h1 && w0 !== w1) {
409
+    if (x0 !== x1) {
410
+      // moving left edge, pin right edge
411
+      delta = vec.sub([x1, y1 + h1 / 2], [x0, y0 + h0 / 2])
412
+    } else {
413
+      // moving right edge, pin left edge
414
+      delta = vec.sub([x1 + w1, y1 + h1 / 2], [x0 + w0, y0 + h0 / 2])
415
+    }
416
+  } else if (h0 !== h1 && w0 === w1) {
417
+    if (y0 !== y1) {
418
+      // moving top edge, pin bottom edge
419
+      delta = vec.sub([x1 + w1 / 2, y1], [x0 + w0 / 2, y0])
420
+    } else {
421
+      // moving bottom edge, pin top edge
422
+      delta = vec.sub([x1 + w1 / 2, y1 + h1], [x0 + w0 / 2, y0 + h0])
423
+    }
424
+  } else if (x0 !== x1) {
425
+    if (y0 !== y1) {
426
+      // moving top left, pin bottom right
427
+      delta = vec.sub([x1, y1], [x0, y0])
428
+    } else {
429
+      // moving bottom left, pin top right
430
+      delta = vec.sub([x1, y1 + h1], [x0, y0 + h0])
431
+    }
432
+  } else if (y0 !== y1) {
433
+    // moving top right, pin bottom left
434
+    delta = vec.sub([x1 + w1, y1], [x0 + w0, y0])
435
+  } else {
436
+    // moving bottom right, pin top left
437
+    delta = vec.sub([x1 + w1, y1 + h1], [x0 + w0, y0 + h0])
438
+  }
439
+
440
+  return delta
441
+}

+ 7
- 0
lib/shape-utils/index.tsx Wyświetl plik

147
     handle: Partial<K['handles']>
147
     handle: Partial<K['handles']>
148
   ): ShapeUtility<K>
148
   ): ShapeUtility<K>
149
 
149
 
150
+  // Clean up changes when a session ends.
151
+  onSessionComplete(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
152
+
150
   // Render a shape to JSX.
153
   // Render a shape to JSX.
151
   render(this: ShapeUtility<K>, shape: K): JSX.Element
154
   render(this: ShapeUtility<K>, shape: K): JSX.Element
152
 
155
 
258
       return this
261
       return this
259
     },
262
     },
260
 
263
 
264
+    onSessionComplete() {
265
+      return this
266
+    },
267
+
261
     getBounds(shape) {
268
     getBounds(shape) {
262
       const [x, y] = shape.point
269
       const [x, y] = shape.point
263
       return {
270
       return {

+ 12
- 11
state/commands/handle.ts Wyświetl plik

24
         const page = getPage(data, currentPageId)
24
         const page = getPage(data, currentPageId)
25
         const shape = page.shapes[initialShape.id]
25
         const shape = page.shapes[initialShape.id]
26
 
26
 
27
-        getShapeUtils(shape).onHandleChange(shape, initialShape.handles)
27
+        getShapeUtils(shape)
28
+          .onHandleChange(shape, initialShape.handles)
29
+          .onSessionComplete(shape)
28
 
30
 
29
-        const bounds = getShapeUtils(shape).getBounds(shape)
31
+        // const bounds = getShapeUtils(shape).getBounds(shape)
30
 
32
 
31
-        const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
33
+        // const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
32
 
34
 
33
-        getShapeUtils(shape).translateTo(shape, vec.add(shape.point, offset))
35
+        // getShapeUtils(shape).translateTo(shape, vec.add(shape.point, offset))
34
 
36
 
35
-        const { start, end, bend } = page.shapes[initialShape.id].handles
37
+        // const { start, end, bend } = page.shapes[initialShape.id].handles
36
 
38
 
37
-        start.point = vec.sub(start.point, offset)
38
-        end.point = vec.sub(end.point, offset)
39
-        bend.point = vec.sub(bend.point, offset)
39
+        // start.point = vec.sub(start.point, offset)
40
+        // end.point = vec.sub(end.point, offset)
41
+        // bend.point = vec.sub(bend.point, offset)
40
       },
42
       },
41
       undo(data) {
43
       undo(data) {
42
         const { initialShape, currentPageId } = before
44
         const { initialShape, currentPageId } = before
43
 
45
 
44
-        const shape = getPage(data, currentPageId).shapes[initialShape.id]
45
-
46
-        getShapeUtils(shape).onHandleChange(shape, initialShape.handles)
46
+        const page = getPage(data, currentPageId)
47
+        page.shapes[initialShape.id] = initialShape
47
       },
48
       },
48
     })
49
     })
49
   )
50
   )

+ 6
- 6
state/commands/rotate.ts Wyświetl plik

20
 
20
 
21
         for (let { id, point, rotation } of after.initialShapes) {
21
         for (let { id, point, rotation } of after.initialShapes) {
22
           const shape = shapes[id]
22
           const shape = shapes[id]
23
-          const utils = getShapeUtils(shape)
24
-          utils
25
-            .rotateTo(shape, rotation, rotation - shape.rotation)
23
+          getShapeUtils(shape)
24
+            .rotateBy(shape, rotation - shape.rotation)
26
             .translateTo(shape, point)
25
             .translateTo(shape, point)
26
+            .onSessionComplete(shape)
27
         }
27
         }
28
 
28
 
29
         data.boundsRotation = after.boundsRotation
29
         data.boundsRotation = after.boundsRotation
33
 
33
 
34
         for (let { id, point, rotation } of before.initialShapes) {
34
         for (let { id, point, rotation } of before.initialShapes) {
35
           const shape = shapes[id]
35
           const shape = shapes[id]
36
-          const utils = getShapeUtils(shape)
37
-          utils
38
-            .rotateTo(shape, rotation, rotation - shape.rotation)
36
+          getShapeUtils(shape)
37
+            .rotateBy(shape, rotation - shape.rotation)
39
             .translateTo(shape, point)
38
             .translateTo(shape, point)
39
+            .onSessionComplete(shape)
40
         }
40
         }
41
 
41
 
42
         data.boundsRotation = before.boundsRotation
42
         data.boundsRotation = before.boundsRotation

+ 25
- 25
state/data.ts Wyświetl plik

115
         //   },
115
         //   },
116
         // }),
116
         // }),
117
         // Groups Testing
117
         // Groups Testing
118
-        shapeA: shapeUtils[ShapeType.Rectangle].create({
119
-          id: 'shapeA',
120
-          name: 'Shape A',
121
-          childIndex: 1,
122
-          point: [0, 0],
123
-          size: [200, 200],
124
-          parentId: 'groupA',
125
-        }),
126
-        shapeB: shapeUtils[ShapeType.Rectangle].create({
127
-          id: 'shapeB',
128
-          name: 'Shape B',
129
-          childIndex: 2,
130
-          point: [220, 100],
131
-          size: [200, 200],
132
-          parentId: 'groupA',
133
-        }),
134
-        groupA: shapeUtils[ShapeType.Group].create({
135
-          id: 'groupA',
136
-          name: 'Group A',
137
-          childIndex: 2,
138
-          point: [0, 0],
139
-          size: [420, 300],
140
-          parentId: 'page1',
141
-          children: ['shapeA', 'shapeB'],
142
-        }),
118
+        // shapeA: shapeUtils[ShapeType.Rectangle].create({
119
+        //   id: 'shapeA',
120
+        //   name: 'Shape A',
121
+        //   childIndex: 1,
122
+        //   point: [0, 0],
123
+        //   size: [200, 200],
124
+        //   parentId: 'groupA',
125
+        // }),
126
+        // shapeB: shapeUtils[ShapeType.Rectangle].create({
127
+        //   id: 'shapeB',
128
+        //   name: 'Shape B',
129
+        //   childIndex: 2,
130
+        //   point: [220, 100],
131
+        //   size: [200, 200],
132
+        //   parentId: 'groupA',
133
+        // }),
134
+        // groupA: shapeUtils[ShapeType.Group].create({
135
+        //   id: 'groupA',
136
+        //   name: 'Group A',
137
+        //   childIndex: 2,
138
+        //   point: [0, 0],
139
+        //   size: [420, 300],
140
+        //   parentId: 'page1',
141
+        //   children: ['shapeA', 'shapeB'],
142
+        // }),
143
       },
143
       },
144
     },
144
     },
145
     page2: {
145
     page2: {

+ 6
- 2
state/sessions/handle-session.ts Wyświetl plik

33
 
33
 
34
     const handles = initialShape.handles
34
     const handles = initialShape.handles
35
 
35
 
36
+    // rotate the delta ?
37
+    // rotate the handle ?
38
+    // rotate the shape around the previous center point
39
+
36
     getShapeUtils(shape).onHandleChange(shape, {
40
     getShapeUtils(shape).onHandleChange(shape, {
37
       [handleId]: {
41
       [handleId]: {
38
         ...handles[handleId],
42
         ...handles[handleId],
39
-        point: vec.add(handles[handleId].point, delta),
43
+        point: vec.add(handles[handleId].point, delta), // vec.rot(delta, shape.rotation)),
40
       },
44
       },
41
     })
45
     })
42
   }
46
   }
43
 
47
 
44
   cancel(data: Data) {
48
   cancel(data: Data) {
45
     const { currentPageId, handleId, initialShape } = this.snapshot
49
     const { currentPageId, handleId, initialShape } = this.snapshot
46
-    const shape = getPage(data, currentPageId).shapes[initialShape.id]
50
+    getPage(data, currentPageId).shapes[initialShape.id] = initialShape
47
   }
51
   }
48
 
52
 
49
   complete(data: Data) {
53
   complete(data: Data) {

+ 10
- 3
state/state.ts Wyświetl plik

925
       payload: PointerInfo & { target: Corner | Edge }
925
       payload: PointerInfo & { target: Corner | Edge }
926
     ) {
926
     ) {
927
       const point = screenToWorld(inputs.pointer.origin, data)
927
       const point = screenToWorld(inputs.pointer.origin, data)
928
-      // session = new Sessions.TransformSession(data, payload.target, point)
928
+      session = new Sessions.TransformSession(data, payload.target, point)
929
       session =
929
       session =
930
         data.selectedIds.size === 1
930
         data.selectedIds.size === 1
931
           ? new Sessions.TransformSingleSession(data, payload.target, point)
931
           ? new Sessions.TransformSingleSession(data, payload.target, point)
1442
         return bounds
1442
         return bounds
1443
       }
1443
       }
1444
 
1444
 
1445
+      const uniqueSelectedShapeIds: string[] = Array.from(
1446
+        new Set(
1447
+          Array.from(selectedIds.values()).flatMap((id) =>
1448
+            getDocumentBranch(data, id)
1449
+          )
1450
+        ).values()
1451
+      )
1452
+
1445
       const commonBounds = getCommonBounds(
1453
       const commonBounds = getCommonBounds(
1446
-        ...shapes
1447
-          .flatMap((shape) => getDocumentBranch(data, shape.id))
1454
+        ...uniqueSelectedShapeIds
1448
           .map((id) => page.shapes[id])
1455
           .map((id) => page.shapes[id])
1449
           .filter((shape) => shape.type !== ShapeType.Group)
1456
           .filter((shape) => shape.type !== ShapeType.Group)
1450
           .map((shape) => {
1457
           .map((shape) => {

+ 13
- 9
todo.md Wyświetl plik

1
 # Todo
1
 # Todo
2
 
2
 
3
-## Done
3
+## Groups
4
 
4
 
5
-- fix select indicator placement for arrow
5
+- fix drift when moving children of rotated group
6
 
6
 
7
-## Todo
7
+## Select
8
 
8
 
9
 - Restore select highlight, fix for children of rotated groups
9
 - Restore select highlight, fix for children of rotated groups
10
-- Transforming on rotated shapes
11
-- Fix bounding box for rotated shapes
12
-- Allow single-selected groups to transform their children correctly
10
+
11
+# Transforms
12
+
13
 - (merge transform-session and transform-single-session)
13
 - (merge transform-session and transform-single-session)
14
-- fix drift when moving children of rotated group
15
-- shift dragging arrow handles should lock to directions
16
-- arrow rotation with handles
14
+- Allow single-selected groups to transform their children correctly
17
 - fix ellipse when scaleX < 0 or scaleY < 0
15
 - fix ellipse when scaleX < 0 or scaleY < 0
16
+- Transforming on rotated shapes
17
+
18
+## Arrows
19
+
20
+- shift dragging arrow handles should lock to directions
21
+- fix undo/redo on rotated arrows

Ładowanie…
Anuluj
Zapisz