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

Improves rotation? I think?

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

+ 36
- 5
components/style-panel/style-panel.tsx Просмотреть файл

14
 
14
 
15
 import ColorPicker from './color-picker'
15
 import ColorPicker from './color-picker'
16
 import AlignDistribute from './align-distribute'
16
 import AlignDistribute from './align-distribute'
17
-import { ShapeStyles } from 'types'
17
+import { MoveType, ShapeStyles } from 'types'
18
 import WidthPicker from './width-picker'
18
 import WidthPicker from './width-picker'
19
 import {
19
 import {
20
+  AlignTopIcon,
21
+  ArrowDownIcon,
22
+  ArrowUpIcon,
20
   AspectRatioIcon,
23
   AspectRatioIcon,
21
   BoxIcon,
24
   BoxIcon,
22
   CopyIcon,
25
   CopyIcon,
24
   EyeOpenIcon,
27
   EyeOpenIcon,
25
   LockClosedIcon,
28
   LockClosedIcon,
26
   LockOpen1Icon,
29
   LockOpen1Icon,
30
+  PinBottomIcon,
31
+  PinTopIcon,
27
   RotateCounterClockwiseIcon,
32
   RotateCounterClockwiseIcon,
28
   TrashIcon,
33
   TrashIcon,
29
 } from '@radix-ui/react-icons'
34
 } from '@radix-ui/react-icons'
128
           <label htmlFor="width">Width</label>
133
           <label htmlFor="width">Width</label>
129
           <WidthPicker strokeWidth={Number(commonStyle.strokeWidth)} />
134
           <WidthPicker strokeWidth={Number(commonStyle.strokeWidth)} />
130
         </Row>
135
         </Row>
131
-        <AlignDistribute
132
-          hasTwoOrMore={selectedIds.length > 1}
133
-          hasThreeOrMore={selectedIds.length > 2}
134
-        />
135
         <ButtonsRow>
136
         <ButtonsRow>
136
           <IconButton
137
           <IconButton
137
             disabled={!hasSelection}
138
             disabled={!hasSelection}
163
           >
164
           >
164
             {isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
165
             {isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
165
           </IconButton>
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
           <IconButton
193
           <IconButton
167
             disabled={!hasSelection}
194
             disabled={!hasSelection}
168
             onClick={() => state.send('DELETED')}
195
             onClick={() => state.send('DELETED')}
170
             <TrashIcon />
197
             <TrashIcon />
171
           </IconButton>
198
           </IconButton>
172
         </ButtonsRow>
199
         </ButtonsRow>
200
+        <AlignDistribute
201
+          hasTwoOrMore={selectedIds.length > 1}
202
+          hasThreeOrMore={selectedIds.length > 2}
203
+        />
173
       </Content>
204
       </Content>
174
     </Panel.Layout>
205
     </Panel.Layout>
175
   )
206
   )

+ 14
- 2
lib/shape-utils/draw.tsx Просмотреть файл

6
 import { boundsContainPolygon } from 'utils/bounds'
6
 import { boundsContainPolygon } from 'utils/bounds'
7
 import getStroke from 'perfect-freehand'
7
 import getStroke from 'perfect-freehand'
8
 import {
8
 import {
9
+  getBoundsCenter,
9
   getBoundsFromPoints,
10
   getBoundsFromPoints,
10
   getRotatedCorners,
11
   getRotatedCorners,
11
   getSvgPathFromStroke,
12
   getSvgPathFromStroke,
77
   },
78
   },
78
 
79
 
79
   getRotatedBounds(shape) {
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
   getCenter(shape) {
97
   getCenter(shape) {

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

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

+ 91
- 0
state/commands/rotate-ccw.ts Просмотреть файл

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 Просмотреть файл

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
 export default function rotateCommand(
8
 export default function rotateCommand(
9
   data: Data,
9
   data: Data,
13
   history.execute(
13
   history.execute(
14
     data,
14
     data,
15
     new Command({
15
     new Command({
16
-      name: "translate_shapes",
17
-      category: "canvas",
16
+      name: 'translate_shapes',
17
+      category: 'canvas',
18
       do(data) {
18
       do(data) {
19
         const { shapes } = getPage(data)
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
           const shape = shapes[id]
22
           const shape = shapes[id]
23
           const utils = getShapeUtils(shape)
23
           const utils = getShapeUtils(shape)
24
           utils.rotateTo(shape, rotation).translateTo(shape, point)
24
           utils.rotateTo(shape, rotation).translateTo(shape, point)
29
       undo(data) {
29
       undo(data) {
30
         const { shapes } = getPage(data, before.currentPageId)
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
           const shape = shapes[id]
33
           const shape = shapes[id]
34
           const utils = getShapeUtils(shape)
34
           const utils = getShapeUtils(shape)
35
           utils.rotateTo(shape, rotation).translateTo(shape, point)
35
           utils.rotateTo(shape, rotation).translateTo(shape, point)

+ 25
- 0
state/sessions/draw-session.ts Просмотреть файл

33
 
33
 
34
     const page = getPage(data)
34
     const page = getPage(data)
35
     const shape = page.shapes[snapshot.id] as DrawShape
35
     const shape = page.shapes[snapshot.id] as DrawShape
36
+
36
     getShapeUtils(shape).setProperty(shape, 'points', [...this.points])
37
     getShapeUtils(shape).setProperty(shape, 'points', [...this.points])
37
   }
38
   }
38
 
39
 
44
   }
45
   }
45
 
46
 
46
   complete = (data: Data) => {
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
     commands.draw(data, this.snapshot.id, this.points)
72
     commands.draw(data, this.snapshot.id, this.points)
48
   }
73
   }
49
 }
74
 }

+ 26
- 24
state/sessions/rotate-session.ts Просмотреть файл

9
   getCommonBounds,
9
   getCommonBounds,
10
   getPage,
10
   getPage,
11
   getSelectedShapes,
11
   getSelectedShapes,
12
+  getRotatedBounds,
12
   getShapeBounds,
13
   getShapeBounds,
13
 } from 'utils/utils'
14
 } from 'utils/utils'
14
 import { getShapeUtils } from 'lib/shape-utils'
15
 import { getShapeUtils } from 'lib/shape-utils'
27
   }
28
   }
28
 
29
 
29
   update(data: Data, point: number[], isLocked: boolean) {
30
   update(data: Data, point: number[], isLocked: boolean) {
30
-    const { boundsCenter, shapes } = this.snapshot
31
+    const { commonBoundsCenter, initialShapes } = this.snapshot
31
 
32
 
32
     const page = getPage(data)
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
     let rot = a2 - a1
37
     let rot = a2 - a1
37
 
38
 
41
 
42
 
42
     data.boundsRotation = (PI2 + (this.snapshot.boundsRotation + rot)) % PI2
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
       const shape = page.shapes[id]
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
       getShapeUtils(shape)
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
   cancel(data: Data) {
63
   cancel(data: Data) {
64
     const page = getPage(data, this.snapshot.currentPageId)
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
       const shape = page.shapes[id]
67
       const shape = page.shapes[id]
68
       getShapeUtils(shape).rotateTo(shape, rotation).translateTo(shape, point)
68
       getShapeUtils(shape).rotateTo(shape, rotation).translateTo(shape, point)
69
     }
69
     }
88
 
88
 
89
   const bounds = getCommonBounds(...Object.values(shapesBounds))
89
   const bounds = getCommonBounds(...Object.values(shapesBounds))
90
 
90
 
91
+  const commonBoundsCenter = getBoundsCenter(bounds)
92
+
91
   return {
93
   return {
92
     hasUnlockedShapes,
94
     hasUnlockedShapes,
93
     currentPageId: data.currentPageId,
95
     currentPageId: data.currentPageId,
94
     boundsRotation: data.boundsRotation,
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
       const center = getBoundsCenter(bounds)
100
       const center = getBoundsCenter(bounds)
101
+      const offset = vec.sub(center, shape.point)
100
 
102
 
101
       return {
103
       return {
102
-        id,
103
-        point,
104
-        rotation,
104
+        id: shape.id,
105
+        point: shape.point,
106
+        rotation: shape.rotation,
105
         offset,
107
         offset,
106
         center,
108
         center,
107
       }
109
       }

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

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

+ 14
- 1
utils/utils.ts Просмотреть файл

972
   return point
972
   return point
973
 }
973
 }
974
 
974
 
975
-export function getBoundsFromPoints(points: number[][]): Bounds {
975
+export function getBoundsFromPoints(points: number[][], rotation = 0): Bounds {
976
   let minX = Infinity
976
   let minX = Infinity
977
   let minY = Infinity
977
   let minY = Infinity
978
   let maxX = -Infinity
978
   let maxX = -Infinity
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
   return {
1004
   return {
996
     minX,
1005
     minX,
997
     minY,
1006
     minY,
1374
   return _isMobile()
1383
   return _isMobile()
1375
 }
1384
 }
1376
 
1385
 
1386
+export function getRotatedBounds(shape: Shape) {
1387
+  return getShapeUtils(shape).getRotatedBounds(shape)
1388
+}
1389
+
1377
 export function getShapeBounds(shape: Shape) {
1390
 export function getShapeBounds(shape: Shape) {
1378
   return getShapeUtils(shape).getBounds(shape)
1391
   return getShapeUtils(shape).getBounds(shape)
1379
 }
1392
 }

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