Bläddra i källkod

Improves rotation? I think?

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

+ 36
- 5
components/style-panel/style-panel.tsx Visa fil

@@ -14,9 +14,12 @@ import { shades, fills, strokes } from 'lib/colors'
14 14
 
15 15
 import ColorPicker from './color-picker'
16 16
 import AlignDistribute from './align-distribute'
17
-import { ShapeStyles } from 'types'
17
+import { MoveType, ShapeStyles } from 'types'
18 18
 import WidthPicker from './width-picker'
19 19
 import {
20
+  AlignTopIcon,
21
+  ArrowDownIcon,
22
+  ArrowUpIcon,
20 23
   AspectRatioIcon,
21 24
   BoxIcon,
22 25
   CopyIcon,
@@ -24,6 +27,8 @@ import {
24 27
   EyeOpenIcon,
25 28
   LockClosedIcon,
26 29
   LockOpen1Icon,
30
+  PinBottomIcon,
31
+  PinTopIcon,
27 32
   RotateCounterClockwiseIcon,
28 33
   TrashIcon,
29 34
 } from '@radix-ui/react-icons'
@@ -128,10 +133,6 @@ function SelectedShapeStyles({}: {}) {
128 133
           <label htmlFor="width">Width</label>
129 134
           <WidthPicker strokeWidth={Number(commonStyle.strokeWidth)} />
130 135
         </Row>
131
-        <AlignDistribute
132
-          hasTwoOrMore={selectedIds.length > 1}
133
-          hasThreeOrMore={selectedIds.length > 2}
134
-        />
135 136
         <ButtonsRow>
136 137
           <IconButton
137 138
             disabled={!hasSelection}
@@ -163,6 +164,32 @@ function SelectedShapeStyles({}: {}) {
163 164
           >
164 165
             {isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
165 166
           </IconButton>
167
+        </ButtonsRow>
168
+        <ButtonsRow>
169
+          <IconButton
170
+            disabled={!hasSelection}
171
+            onClick={() => state.send('MOVED', { type: MoveType.ToBack })}
172
+          >
173
+            <PinBottomIcon />
174
+          </IconButton>
175
+          <IconButton
176
+            disabled={!hasSelection}
177
+            onClick={() => state.send('MOVED', { type: MoveType.Backward })}
178
+          >
179
+            <ArrowDownIcon />
180
+          </IconButton>
181
+          <IconButton
182
+            disabled={!hasSelection}
183
+            onClick={() => state.send('MOVED', { type: MoveType.Forward })}
184
+          >
185
+            <ArrowUpIcon />
186
+          </IconButton>
187
+          <IconButton
188
+            disabled={!hasSelection}
189
+            onClick={() => state.send('MOVED', { type: MoveType.ToFront })}
190
+          >
191
+            <PinTopIcon />
192
+          </IconButton>
166 193
           <IconButton
167 194
             disabled={!hasSelection}
168 195
             onClick={() => state.send('DELETED')}
@@ -170,6 +197,10 @@ function SelectedShapeStyles({}: {}) {
170 197
             <TrashIcon />
171 198
           </IconButton>
172 199
         </ButtonsRow>
200
+        <AlignDistribute
201
+          hasTwoOrMore={selectedIds.length > 1}
202
+          hasThreeOrMore={selectedIds.length > 2}
203
+        />
173 204
       </Content>
174 205
     </Panel.Layout>
175 206
   )

+ 14
- 2
lib/shape-utils/draw.tsx Visa fil

@@ -6,6 +6,7 @@ import { intersectPolylineBounds } from 'utils/intersections'
6 6
 import { boundsContainPolygon } from 'utils/bounds'
7 7
 import getStroke from 'perfect-freehand'
8 8
 import {
9
+  getBoundsCenter,
9 10
   getBoundsFromPoints,
10 11
   getRotatedCorners,
11 12
   getSvgPathFromStroke,
@@ -77,9 +78,20 @@ const draw = registerShapeUtils<DrawShape>({
77 78
   },
78 79
 
79 80
   getRotatedBounds(shape) {
80
-    return getBoundsFromPoints(
81
-      getRotatedCorners(this.getBounds(shape), shape.rotation)
81
+    const bounds =
82
+      this.boundsCache.get(shape) || getBoundsFromPoints(shape.points)
83
+
84
+    const center = getBoundsCenter(bounds)
85
+
86
+    const rotatedPts = shape.points.map((pt) =>
87
+      vec.rotWith(pt, center, shape.rotation)
82 88
     )
89
+    const rotatedBounds = translateBounds(
90
+      getBoundsFromPoints(rotatedPts),
91
+      shape.point
92
+    )
93
+
94
+    return rotatedBounds
83 95
   },
84 96
 
85 97
   getCenter(shape) {

+ 2
- 0
state/commands/index.ts Visa fil

@@ -14,6 +14,7 @@ import transformSingle from './transform-single'
14 14
 import translate from './translate'
15 15
 import nudge from './nudge'
16 16
 import toggle from './toggle'
17
+import rotateCcw from './rotate-ccw'
17 18
 
18 19
 const commands = {
19 20
   align,
@@ -32,6 +33,7 @@ const commands = {
32 33
   translate,
33 34
   nudge,
34 35
   toggle,
36
+  rotateCcw,
35 37
 }
36 38
 
37 39
 export default commands

+ 91
- 0
state/commands/rotate-ccw.ts Visa fil

@@ -0,0 +1,91 @@
1
+import Command from './command'
2
+import history from '../history'
3
+import { Data } from 'types'
4
+import {
5
+  getBoundsCenter,
6
+  getCommonBounds,
7
+  getPage,
8
+  getSelectedShapes,
9
+} from 'utils/utils'
10
+import * as vec from 'utils/vec'
11
+import { getShapeUtils } from 'lib/shape-utils'
12
+
13
+const PI2 = Math.PI * 2
14
+
15
+export default function rotateCcwCommand(data: Data) {
16
+  const { currentPageId, boundsRotation } = data
17
+
18
+  const page = getPage(data)
19
+
20
+  const initialShapes = Object.fromEntries(
21
+    getSelectedShapes(data).map((shape) => {
22
+      const bounds = getShapeUtils(shape).getBounds(shape)
23
+      return [
24
+        shape.id,
25
+        {
26
+          rotation: shape.rotation,
27
+          point: [...shape.point],
28
+          center: getBoundsCenter(bounds),
29
+          bounds,
30
+        },
31
+      ]
32
+    })
33
+  )
34
+
35
+  const commonBoundsCenter = getBoundsCenter(
36
+    getCommonBounds(...Object.values(initialShapes).map((b) => b.bounds))
37
+  )
38
+
39
+  const nextShapes = Object.fromEntries(
40
+    Object.entries(initialShapes).map(([id, { point, center }]) => {
41
+      const shape = { ...page.shapes[id] }
42
+      const offset = vec.sub(center, point)
43
+      const nextPoint = vec.sub(
44
+        vec.rotWith(center, commonBoundsCenter, -(PI2 / 4)),
45
+        offset
46
+      )
47
+
48
+      const rot = (PI2 + (shape.rotation - PI2 / 4)) % PI2
49
+
50
+      getShapeUtils(shape).rotateTo(shape, rot).translateTo(shape, nextPoint)
51
+
52
+      return [id, shape]
53
+    })
54
+  )
55
+
56
+  const nextboundsRotation = (PI2 + (data.boundsRotation - PI2 / 4)) % PI2
57
+
58
+  history.execute(
59
+    data,
60
+    new Command({
61
+      name: 'translate_shapes',
62
+      category: 'canvas',
63
+      do(data) {
64
+        const { shapes } = getPage(data, currentPageId)
65
+
66
+        for (let id in nextShapes) {
67
+          const shape = shapes[id]
68
+
69
+          getShapeUtils(shape)
70
+            .rotateTo(shape, nextShapes[id].rotation)
71
+            .translateTo(shape, nextShapes[id].point)
72
+        }
73
+
74
+        data.boundsRotation = nextboundsRotation
75
+      },
76
+      undo(data) {
77
+        const { shapes } = getPage(data, currentPageId)
78
+
79
+        for (let id in initialShapes) {
80
+          const { point, rotation } = initialShapes[id]
81
+
82
+          const shape = shapes[id]
83
+          const utils = getShapeUtils(shape)
84
+          utils.rotateTo(shape, rotation).translateTo(shape, point)
85
+        }
86
+
87
+        data.boundsRotation = boundsRotation
88
+      },
89
+    })
90
+  )
91
+}

+ 10
- 10
state/commands/rotate.ts Visa fil

@@ -1,9 +1,9 @@
1
-import Command from "./command"
2
-import history from "../history"
3
-import { Data } from "types"
4
-import { RotateSnapshot } from "state/sessions/rotate-session"
5
-import { getPage } from "utils/utils"
6
-import { getShapeUtils } from "lib/shape-utils"
1
+import Command from './command'
2
+import history from '../history'
3
+import { Data } from 'types'
4
+import { RotateSnapshot } from 'state/sessions/rotate-session'
5
+import { getPage } from 'utils/utils'
6
+import { getShapeUtils } from 'lib/shape-utils'
7 7
 
8 8
 export default function rotateCommand(
9 9
   data: Data,
@@ -13,12 +13,12 @@ export default function rotateCommand(
13 13
   history.execute(
14 14
     data,
15 15
     new Command({
16
-      name: "translate_shapes",
17
-      category: "canvas",
16
+      name: 'translate_shapes',
17
+      category: 'canvas',
18 18
       do(data) {
19 19
         const { shapes } = getPage(data)
20 20
 
21
-        for (let { id, point, rotation } of after.shapes) {
21
+        for (let { id, point, rotation } of after.initialShapes) {
22 22
           const shape = shapes[id]
23 23
           const utils = getShapeUtils(shape)
24 24
           utils.rotateTo(shape, rotation).translateTo(shape, point)
@@ -29,7 +29,7 @@ export default function rotateCommand(
29 29
       undo(data) {
30 30
         const { shapes } = getPage(data, before.currentPageId)
31 31
 
32
-        for (let { id, point, rotation } of before.shapes) {
32
+        for (let { id, point, rotation } of before.initialShapes) {
33 33
           const shape = shapes[id]
34 34
           const utils = getShapeUtils(shape)
35 35
           utils.rotateTo(shape, rotation).translateTo(shape, point)

+ 25
- 0
state/sessions/draw-session.ts Visa fil

@@ -33,6 +33,7 @@ export default class BrushSession extends BaseSession {
33 33
 
34 34
     const page = getPage(data)
35 35
     const shape = page.shapes[snapshot.id] as DrawShape
36
+
36 37
     getShapeUtils(shape).setProperty(shape, 'points', [...this.points])
37 38
   }
38 39
 
@@ -44,6 +45,30 @@ export default class BrushSession extends BaseSession {
44 45
   }
45 46
 
46 47
   complete = (data: Data) => {
48
+    if (this.points.length > 1) {
49
+      let minX = Infinity
50
+      let minY = Infinity
51
+      const pts = [...this.points]
52
+
53
+      for (let pt of pts) {
54
+        minX = Math.min(pt[0], minX)
55
+        minY = Math.min(pt[1], minY)
56
+      }
57
+
58
+      for (let pt of pts) {
59
+        pt[0] -= minX
60
+        pt[1] -= minY
61
+      }
62
+
63
+      const { snapshot } = this
64
+      const page = getPage(data)
65
+      const shape = page.shapes[snapshot.id] as DrawShape
66
+
67
+      getShapeUtils(shape)
68
+        .setProperty(shape, 'points', pts)
69
+        .translateTo(shape, vec.add(shape.point, [minX, minY]))
70
+    }
71
+
47 72
     commands.draw(data, this.snapshot.id, this.points)
48 73
   }
49 74
 }

+ 26
- 24
state/sessions/rotate-session.ts Visa fil

@@ -9,6 +9,7 @@ import {
9 9
   getCommonBounds,
10 10
   getPage,
11 11
   getSelectedShapes,
12
+  getRotatedBounds,
12 13
   getShapeBounds,
13 14
 } from 'utils/utils'
14 15
 import { getShapeUtils } from 'lib/shape-utils'
@@ -27,11 +28,11 @@ export default class RotateSession extends BaseSession {
27 28
   }
28 29
 
29 30
   update(data: Data, point: number[], isLocked: boolean) {
30
-    const { boundsCenter, shapes } = this.snapshot
31
+    const { commonBoundsCenter, initialShapes } = this.snapshot
31 32
 
32 33
     const page = getPage(data)
33
-    const a1 = vec.angle(boundsCenter, this.origin)
34
-    const a2 = vec.angle(boundsCenter, point)
34
+    const a1 = vec.angle(commonBoundsCenter, this.origin)
35
+    const a2 = vec.angle(commonBoundsCenter, point)
35 36
 
36 37
     let rot = a2 - a1
37 38
 
@@ -41,29 +42,28 @@ export default class RotateSession extends BaseSession {
41 42
 
42 43
     data.boundsRotation = (PI2 + (this.snapshot.boundsRotation + rot)) % PI2
43 44
 
44
-    for (let { id, center, offset, rotation } of shapes) {
45
+    for (let { id, center, offset, rotation } of initialShapes) {
45 46
       const shape = page.shapes[id]
46 47
 
48
+      const nextRotation = isLocked
49
+        ? clampToRotationToSegments(rotation + rot, 24)
50
+        : rotation + rot
51
+
52
+      const nextPoint = vec.sub(
53
+        vec.rotWith(center, commonBoundsCenter, rot),
54
+        offset
55
+      )
56
+
47 57
       getShapeUtils(shape)
48
-        .rotateTo(
49
-          shape,
50
-          (PI2 +
51
-            (isLocked
52
-              ? clampToRotationToSegments(rotation + rot, 24)
53
-              : rotation + rot)) %
54
-            PI2
55
-        )
56
-        .translateTo(
57
-          shape,
58
-          vec.sub(vec.rotWith(center, boundsCenter, rot % PI2), offset)
59
-        )
58
+        .rotateTo(shape, (PI2 + nextRotation) % PI2)
59
+        .translateTo(shape, nextPoint)
60 60
     }
61 61
   }
62 62
 
63 63
   cancel(data: Data) {
64 64
     const page = getPage(data, this.snapshot.currentPageId)
65 65
 
66
-    for (let { id, point, rotation } of this.snapshot.shapes) {
66
+    for (let { id, point, rotation } of this.snapshot.initialShapes) {
67 67
       const shape = page.shapes[id]
68 68
       getShapeUtils(shape).rotateTo(shape, rotation).translateTo(shape, point)
69 69
     }
@@ -88,20 +88,22 @@ export function getRotateSnapshot(data: Data) {
88 88
 
89 89
   const bounds = getCommonBounds(...Object.values(shapesBounds))
90 90
 
91
+  const commonBoundsCenter = getBoundsCenter(bounds)
92
+
91 93
   return {
92 94
     hasUnlockedShapes,
93 95
     currentPageId: data.currentPageId,
94 96
     boundsRotation: data.boundsRotation,
95
-    boundsCenter: getBoundsCenter(bounds),
96
-    shapes: initialShapes.map(({ id, point, rotation }) => {
97
-      const bounds = shapesBounds[id]
98
-      const offset = [bounds.width / 2, bounds.height / 2]
97
+    commonBoundsCenter,
98
+    initialShapes: initialShapes.map((shape) => {
99
+      const bounds = shapesBounds[shape.id]
99 100
       const center = getBoundsCenter(bounds)
101
+      const offset = vec.sub(center, shape.point)
100 102
 
101 103
       return {
102
-        id,
103
-        point,
104
-        rotation,
104
+        id: shape.id,
105
+        point: shape.point,
106
+        rotation: shape.rotation,
105 107
         offset,
106 108
         center,
107 109
       }

+ 4
- 0
state/state.ts Visa fil

@@ -157,6 +157,7 @@ const state = createState({
157 157
             STRETCHED: { if: 'hasSelection', do: 'stretchSelection' },
158 158
             DISTRIBUTED: { if: 'hasSelection', do: 'distributeSelection' },
159 159
             DUPLICATED: { if: 'hasSelection', do: 'duplicateSelection' },
160
+            ROTATED_CCW: { if: 'hasSelection', do: 'rotateSelectionCcw' },
160 161
           },
161 162
           initial: 'notPointing',
162 163
           states: {
@@ -859,6 +860,9 @@ const state = createState({
859 860
     deleteSelection(data) {
860 861
       commands.deleteSelected(data)
861 862
     },
863
+    rotateSelectionCcw(data) {
864
+      commands.rotateCcw(data)
865
+    },
862 866
 
863 867
     /* --------------------- Camera --------------------- */
864 868
 

+ 14
- 1
utils/utils.ts Visa fil

@@ -972,7 +972,7 @@ export function vectorToPoint(point: number[] | Vector | undefined) {
972 972
   return point
973 973
 }
974 974
 
975
-export function getBoundsFromPoints(points: number[][]): Bounds {
975
+export function getBoundsFromPoints(points: number[][], rotation = 0): Bounds {
976 976
   let minX = Infinity
977 977
   let minY = Infinity
978 978
   let maxX = -Infinity
@@ -992,6 +992,15 @@ export function getBoundsFromPoints(points: number[][]): Bounds {
992 992
     }
993 993
   }
994 994
 
995
+  if (rotation !== 0) {
996
+    console.log('returning rotated bounds')
997
+    return getBoundsFromPoints(
998
+      points.map((pt) =>
999
+        vec.rotWith(pt, [(minX + maxX) / 2, (minY + maxY) / 2], rotation)
1000
+      )
1001
+    )
1002
+  }
1003
+
995 1004
   return {
996 1005
     minX,
997 1006
     minY,
@@ -1374,6 +1383,10 @@ export function isMobile() {
1374 1383
   return _isMobile()
1375 1384
 }
1376 1385
 
1386
+export function getRotatedBounds(shape: Shape) {
1387
+  return getShapeUtils(shape).getRotatedBounds(shape)
1388
+}
1389
+
1377 1390
 export function getShapeBounds(shape: Shape) {
1378 1391
   return getShapeUtils(shape).getBounds(shape)
1379 1392
 }

Laddar…
Avbryt
Spara