Procházet zdrojové kódy

Improves transforms

main
Steve Ruiz před 4 roky
rodič
revize
c3740cacdd

+ 2
- 1
components/canvas/bounds-bg.tsx Zobrazit soubor

@@ -6,7 +6,7 @@ import styled from "styles"
6 6
 export default function BoundsBg() {
7 7
   const rBounds = useRef<SVGRectElement>(null)
8 8
   const bounds = useSelector((state) => state.values.selectedBounds)
9
-
9
+  const isSelecting = useSelector((s) => s.isIn("selecting"))
10 10
   const rotation = useSelector((s) => {
11 11
     if (s.data.selectedIds.size === 1) {
12 12
       const { shapes } = s.data.document.pages[s.data.currentPageId]
@@ -18,6 +18,7 @@ export default function BoundsBg() {
18 18
   })
19 19
 
20 20
   if (!bounds) return null
21
+  if (!isSelecting) return null
21 22
 
22 23
   const { minX, minY, width, height } = bounds
23 24
 

+ 2
- 1
components/canvas/bounds.tsx Zobrazit soubor

@@ -7,9 +7,9 @@ import { lerp } from "utils/utils"
7 7
 
8 8
 export default function Bounds() {
9 9
   const isBrushing = useSelector((s) => s.isIn("brushSelecting"))
10
+  const isSelecting = useSelector((s) => s.isIn("selecting"))
10 11
   const zoom = useSelector((s) => s.data.camera.zoom)
11 12
   const bounds = useSelector((s) => s.values.selectedBounds)
12
-
13 13
   const rotation = useSelector((s) => {
14 14
     if (s.data.selectedIds.size === 1) {
15 15
       const { shapes } = s.data.document.pages[s.data.currentPageId]
@@ -21,6 +21,7 @@ export default function Bounds() {
21 21
   })
22 22
 
23 23
   if (!bounds) return null
24
+  if (!isSelecting) return null
24 25
 
25 26
   let { minX, minY, maxX, maxY, width, height } = bounds
26 27
 

+ 1
- 1
lib/code/circle.ts Zobrazit soubor

@@ -18,7 +18,7 @@ export default class Circle extends CodeShape<CircleShape> {
18 18
       rotation: 0,
19 19
       radius: 20,
20 20
       style: {
21
-        fill: "rgba(142, 143, 142, 1.000)",
21
+        fill: "#c6cacb",
22 22
         stroke: "#000",
23 23
         strokeWidth: 1,
24 24
       },

+ 1
- 1
lib/code/dot.ts Zobrazit soubor

@@ -17,7 +17,7 @@ export default class Dot extends CodeShape<DotShape> {
17 17
       point: [0, 0],
18 18
       rotation: 0,
19 19
       style: {
20
-        fill: "rgba(142, 143, 142, 1.000)",
20
+        fill: "#c6cacb",
21 21
         stroke: "#000",
22 22
         strokeWidth: 1,
23 23
       },

+ 1
- 1
lib/code/ellipse.ts Zobrazit soubor

@@ -19,7 +19,7 @@ export default class Ellipse extends CodeShape<EllipseShape> {
19 19
       radiusY: 20,
20 20
       rotation: 0,
21 21
       style: {
22
-        fill: "rgba(142, 143, 142, 1.000)",
22
+        fill: "#c6cacb",
23 23
         stroke: "#000",
24 24
         strokeWidth: 1,
25 25
       },

+ 1
- 1
lib/code/line.ts Zobrazit soubor

@@ -19,7 +19,7 @@ export default class Line extends CodeShape<LineShape> {
19 19
       direction: [-0.5, 0.5],
20 20
       rotation: 0,
21 21
       style: {
22
-        fill: "rgba(142, 143, 142, 1.000)",
22
+        fill: "#c6cacb",
23 23
         stroke: "#000",
24 24
         strokeWidth: 1,
25 25
       },

+ 1
- 1
lib/code/ray.ts Zobrazit soubor

@@ -19,7 +19,7 @@ export default class Ray extends CodeShape<RayShape> {
19 19
       direction: [0, 1],
20 20
       rotation: 0,
21 21
       style: {
22
-        fill: "rgba(142, 143, 142, 1.000)",
22
+        fill: "#c6cacb",
23 23
         stroke: "#000",
24 24
         strokeWidth: 1,
25 25
       },

+ 1
- 1
lib/code/rectangle.ts Zobrazit soubor

@@ -19,7 +19,7 @@ export default class Rectangle extends CodeShape<RectangleShape> {
19 19
       size: [100, 100],
20 20
       rotation: 0,
21 21
       style: {
22
-        fill: "rgba(142, 143, 142, 1.000)",
22
+        fill: "#c6cacb",
23 23
         stroke: "#000",
24 24
         strokeWidth: 1,
25 25
       },

+ 5
- 1
lib/shapes/circle.tsx Zobrazit soubor

@@ -22,7 +22,7 @@ const circle = createShape<CircleShape>({
22 22
       rotation: 0,
23 23
       radius: 20,
24 24
       style: {
25
-        fill: "rgba(142, 143, 142, 1.000)",
25
+        fill: "#c6cacb",
26 26
         stroke: "#000",
27 27
       },
28 28
       ...props,
@@ -157,6 +157,10 @@ const circle = createShape<CircleShape>({
157 157
     return shape
158 158
   },
159 159
 
160
+  transformSingle(shape, bounds, info) {
161
+    return this.transform(shape, bounds, info)
162
+  },
163
+
160 164
   canTransform: true,
161 165
 })
162 166
 

+ 5
- 1
lib/shapes/dot.tsx Zobrazit soubor

@@ -22,7 +22,7 @@ const dot = createShape<DotShape>({
22 22
       point: [0, 0],
23 23
       rotation: 0,
24 24
       style: {
25
-        fill: "rgba(142, 143, 142, 1.000)",
25
+        fill: "#c6cacb",
26 26
         stroke: "#000",
27 27
       },
28 28
       ...props,
@@ -89,6 +89,10 @@ const dot = createShape<DotShape>({
89 89
     return shape
90 90
   },
91 91
 
92
+  transformSingle(shape, bounds, info) {
93
+    return this.transform(shape, bounds, info)
94
+  },
95
+
92 96
   canTransform: false,
93 97
 })
94 98
 

+ 16
- 5
lib/shapes/ellipse.tsx Zobrazit soubor

@@ -5,7 +5,12 @@ import { createShape } from "./index"
5 5
 import { boundsContained } from "utils/bounds"
6 6
 import { intersectEllipseBounds } from "utils/intersections"
7 7
 import { pointInEllipse } from "utils/hitTests"
8
-import { translateBounds } from "utils/utils"
8
+import {
9
+  getBoundsFromPoints,
10
+  getRotatedCorners,
11
+  rotateBounds,
12
+  translateBounds,
13
+} from "utils/utils"
9 14
 
10 15
 const ellipse = createShape<EllipseShape>({
11 16
   boundsCache: new WeakMap([]),
@@ -23,7 +28,7 @@ const ellipse = createShape<EllipseShape>({
23 28
       radiusY: 20,
24 29
       rotation: 0,
25 30
       style: {
26
-        fill: "rgba(142, 143, 142, 1.000)",
31
+        fill: "#c6cacb",
27 32
         stroke: "#000",
28 33
       },
29 34
       ...props,
@@ -56,7 +61,7 @@ const ellipse = createShape<EllipseShape>({
56 61
   },
57 62
 
58 63
   getRotatedBounds(shape) {
59
-    return this.getBounds(shape)
64
+    return getBoundsFromPoints(getRotatedCorners(shape))
60 65
   },
61 66
 
62 67
   getCenter(shape) {
@@ -68,7 +73,8 @@ const ellipse = createShape<EllipseShape>({
68 73
       point,
69 74
       vec.add(shape.point, [shape.radiusX, shape.radiusY]),
70 75
       shape.radiusX,
71
-      shape.radiusY
76
+      shape.radiusY,
77
+      shape.rotation
72 78
     )
73 79
   },
74 80
 
@@ -83,7 +89,8 @@ const ellipse = createShape<EllipseShape>({
83 89
         vec.add(shape.point, [shape.radiusX, shape.radiusY]),
84 90
         shape.radiusX,
85 91
         shape.radiusY,
86
-        brushBounds
92
+        brushBounds,
93
+        shape.rotation
87 94
       ).length > 0
88 95
     )
89 96
   },
@@ -109,6 +116,10 @@ const ellipse = createShape<EllipseShape>({
109 116
     return shape
110 117
   },
111 118
 
119
+  transformSingle(shape, bounds, info) {
120
+    return this.transform(shape, bounds, info)
121
+  },
122
+
112 123
   canTransform: true,
113 124
 })
114 125
 

+ 17
- 0
lib/shapes/index.tsx Zobrazit soubor

@@ -72,6 +72,23 @@ export interface ShapeUtility<K extends Shape> {
72 72
     }
73 73
   ): K
74 74
 
75
+  transformSingle(
76
+    this: ShapeUtility<K>,
77
+    shape: K,
78
+    bounds: Bounds,
79
+    info: {
80
+      type: TransformEdge | TransformCorner
81
+      boundsRotation: number
82
+      initialShape: K
83
+      initialShapeBounds: BoundsSnapshot
84
+      initialBounds: Bounds
85
+      isFlippedX: boolean
86
+      isFlippedY: boolean
87
+      isSingle: boolean
88
+      anchor: TransformEdge | TransformCorner
89
+    }
90
+  ): K
91
+
75 92
   // Apply a scale to a shape.
76 93
   scale(this: ShapeUtility<K>, shape: K, scale: number): K
77 94
 

+ 5
- 1
lib/shapes/line.tsx Zobrazit soubor

@@ -22,7 +22,7 @@ const line = createShape<LineShape>({
22 22
       direction: [0, 0],
23 23
       rotation: 0,
24 24
       style: {
25
-        fill: "rgba(142, 143, 142, 1.000)",
25
+        fill: "#c6cacb",
26 26
         stroke: "#000",
27 27
       },
28 28
       ...props,
@@ -97,6 +97,10 @@ const line = createShape<LineShape>({
97 97
     return shape
98 98
   },
99 99
 
100
+  transformSingle(shape, bounds, info) {
101
+    return this.transform(shape, bounds, info)
102
+  },
103
+
100 104
   canTransform: false,
101 105
 })
102 106
 

+ 4
- 0
lib/shapes/polyline.tsx Zobrazit soubor

@@ -123,6 +123,10 @@ const polyline = createShape<PolylineShape>({
123 123
     return shape
124 124
   },
125 125
 
126
+  transformSingle(shape, bounds, info) {
127
+    return this.transform(shape, bounds, info)
128
+  },
129
+
126 130
   canTransform: true,
127 131
 })
128 132
 

+ 1
- 1
lib/shapes/ray.tsx Zobrazit soubor

@@ -22,7 +22,7 @@ const ray = createShape<RayShape>({
22 22
       direction: [0, 1],
23 23
       rotation: 0,
24 24
       style: {
25
-        fill: "rgba(142, 143, 142, 1.000)",
25
+        fill: "#c6cacb",
26 26
         stroke: "#000",
27 27
         strokeWidth: 1,
28 28
       },

+ 119
- 26
lib/shapes/rectangle.tsx Zobrazit soubor

@@ -1,9 +1,19 @@
1 1
 import { v4 as uuid } from "uuid"
2 2
 import * as vec from "utils/vec"
3
-import { RectangleShape, ShapeType } from "types"
3
+import {
4
+  RectangleShape,
5
+  ShapeType,
6
+  TransformCorner,
7
+  TransformEdge,
8
+} from "types"
4 9
 import { createShape } from "./index"
5 10
 import { boundsCollidePolygon, boundsContainPolygon } from "utils/bounds"
6
-import { getBoundsFromPoints, rotateBounds, translateBounds } from "utils/utils"
11
+import {
12
+  getBoundsFromPoints,
13
+  getRotatedCorners,
14
+  rotateBounds,
15
+  translateBounds,
16
+} from "utils/utils"
7 17
 
8 18
 const rectangle = createShape<RectangleShape>({
9 19
   boundsCache: new WeakMap([]),
@@ -20,7 +30,7 @@ const rectangle = createShape<RectangleShape>({
20 30
       size: [1, 1],
21 31
       rotation: 0,
22 32
       style: {
23
-        fill: "rgba(142, 143, 142, 1.000)",
33
+        fill: "#c6cacb",
24 34
         stroke: "#000",
25 35
       },
26 36
       ...props,
@@ -50,18 +60,9 @@ const rectangle = createShape<RectangleShape>({
50 60
   },
51 61
 
52 62
   getRotatedBounds(shape) {
53
-    const b = this.getBounds(shape)
54
-    const center = [b.minX + b.width / 2, b.minY + b.height / 2]
55
-
56
-    // Rotate corners of the shape, then find the minimum among those points.
57
-    const rotatedCorners = [
58
-      [b.minX, b.minY],
59
-      [b.maxX, b.minY],
60
-      [b.maxX, b.maxY],
61
-      [b.minX, b.maxY],
62
-    ].map((point) => vec.rotWith(point, center, shape.rotation))
63
-
64
-    return getBoundsFromPoints(rotatedCorners)
63
+    return getBoundsFromPoints(
64
+      getRotatedCorners(this.getBounds(shape), shape.rotation)
65
+    )
65 66
   },
66 67
 
67 68
   getCenter(shape) {
@@ -74,15 +75,10 @@ const rectangle = createShape<RectangleShape>({
74 75
   },
75 76
 
76 77
   hitTestBounds(shape, brushBounds) {
77
-    const b = this.getBounds(shape)
78
-    const center = [b.minX + b.width / 2, b.minY + b.height / 2]
79
-
80
-    const rotatedCorners = [
81
-      [b.minX, b.minY],
82
-      [b.maxX, b.minY],
83
-      [b.maxX, b.maxY],
84
-      [b.minX, b.maxY],
85
-    ].map((point) => vec.rotWith(point, center, shape.rotation))
78
+    const rotatedCorners = getRotatedCorners(
79
+      this.getBounds(shape),
80
+      shape.rotation
81
+    )
86 82
 
87 83
     return (
88 84
       boundsContainPolygon(brushBounds, rotatedCorners) ||
@@ -108,8 +104,6 @@ const rectangle = createShape<RectangleShape>({
108 104
     shapeBounds,
109 105
     { initialShape, isSingle, initialShapeBounds, isFlippedX, isFlippedY }
110 106
   ) {
111
-    // TODO: Apply rotation to single-selection items
112
-
113 107
     if (shape.rotation === 0 || isSingle) {
114 108
       shape.size = [shapeBounds.width, shapeBounds.height]
115 109
       shape.point = [shapeBounds.minX, shapeBounds.minY]
@@ -145,6 +139,105 @@ const rectangle = createShape<RectangleShape>({
145 139
     return shape
146 140
   },
147 141
 
142
+  transformSingle(
143
+    shape,
144
+    bounds,
145
+    { initialShape, initialShapeBounds, anchor, isFlippedY, isFlippedX }
146
+  ) {
147
+    shape.size = [bounds.width, bounds.height]
148
+    shape.point = [bounds.minX, bounds.minY]
149
+
150
+    // const prevCorners = getRotatedCorners(
151
+    //   initialShapeBounds,
152
+    //   initialShape.rotation
153
+    // )
154
+
155
+    // let currCorners = getRotatedCorners(this.getBounds(shape), shape.rotation)
156
+
157
+    // if (isFlippedX) {
158
+    //   let t = currCorners[3]
159
+    //   currCorners[3] = currCorners[2]
160
+    //   currCorners[2] = t
161
+
162
+    //   t = currCorners[0]
163
+    //   currCorners[0] = currCorners[1]
164
+    //   currCorners[1] = t
165
+    // }
166
+
167
+    // if (isFlippedY) {
168
+    //   let t = currCorners[3]
169
+    //   currCorners[3] = currCorners[0]
170
+    //   currCorners[0] = t
171
+
172
+    //   t = currCorners[2]
173
+    //   currCorners[2] = currCorners[1]
174
+    //   currCorners[1] = t
175
+    // }
176
+
177
+    // switch (anchor) {
178
+    //   case TransformCorner.TopLeft: {
179
+    //     shape.point = vec.sub(
180
+    //       shape.point,
181
+    //       vec.sub(currCorners[2], prevCorners[2])
182
+    //     )
183
+    //     break
184
+    //   }
185
+    //   case TransformCorner.TopRight: {
186
+    //     shape.point = vec.sub(
187
+    //       shape.point,
188
+    //       vec.sub(currCorners[3], prevCorners[3])
189
+    //     )
190
+    //     break
191
+    //   }
192
+    //   case TransformCorner.BottomRight: {
193
+    //     shape.point = vec.sub(
194
+    //       shape.point,
195
+    //       vec.sub(currCorners[0], prevCorners[0])
196
+    //     )
197
+    //     break
198
+    //   }
199
+    //   case TransformCorner.BottomLeft: {
200
+    //     shape.point = vec.sub(
201
+    //       shape.point,
202
+    //       vec.sub(currCorners[1], prevCorners[1])
203
+    //     )
204
+    //     break
205
+    //   }
206
+    //   case TransformEdge.Top: {
207
+    //     shape.point = vec.sub(
208
+    //       shape.point,
209
+    //       vec.sub(currCorners[3], prevCorners[3])
210
+    //     )
211
+    //     break
212
+    //   }
213
+    //   case TransformEdge.Right: {
214
+    //     shape.point = vec.sub(
215
+    //       shape.point,
216
+    //       vec.sub(currCorners[3], prevCorners[3])
217
+    //     )
218
+    //     break
219
+    //   }
220
+    //   case TransformEdge.Bottom: {
221
+    //     shape.point = vec.sub(
222
+    //       shape.point,
223
+    //       vec.sub(currCorners[0], prevCorners[0])
224
+    //     )
225
+    //     break
226
+    //   }
227
+    //   case TransformEdge.Left: {
228
+    //     shape.point = vec.sub(
229
+    //       shape.point,
230
+    //       vec.sub(currCorners[2], prevCorners[2])
231
+    //     )
232
+    //     break
233
+    //   }
234
+    // }
235
+
236
+    // console.log(shape.point, shape.size)
237
+
238
+    return shape
239
+  },
240
+
148 241
   canTransform: true,
149 242
 })
150 243
 

+ 1
- 1
state/commands/create-shape.ts Zobrazit soubor

@@ -2,7 +2,7 @@ import Command from "./command"
2 2
 import history from "../history"
3 3
 import { Data, Shape } from "types"
4 4
 
5
-export default function createShape(data: Data, shape: Shape) {
5
+export default function createShapeCommand(data: Data, shape: Shape) {
6 6
   const { currentPageId } = data
7 7
 
8 8
   history.execute(

+ 1
- 1
state/commands/direct.ts Zobrazit soubor

@@ -3,7 +3,7 @@ import history from "../history"
3 3
 import { DirectionSnapshot } from "state/sessions/direction-session"
4 4
 import { Data, LineShape, RayShape } from "types"
5 5
 
6
-export default function translateCommand(
6
+export default function directCommand(
7 7
   data: Data,
8 8
   before: DirectionSnapshot,
9 9
   after: DirectionSnapshot

+ 1
- 1
state/commands/generate.ts Zobrazit soubor

@@ -3,7 +3,7 @@ import history from "../history"
3 3
 import { CodeControl, Data, Shape } from "types"
4 4
 import { current } from "immer"
5 5
 
6
-export default function setGeneratedShapes(
6
+export default function generateCommand(
7 7
   data: Data,
8 8
   currentPageId: string,
9 9
   generatedShapes: Shape[]

+ 2
- 0
state/commands/index.ts Zobrazit soubor

@@ -1,5 +1,6 @@
1 1
 import translate from "./translate"
2 2
 import transform from "./transform"
3
+import transformSingle from "./transform-single"
3 4
 import generate from "./generate"
4 5
 import createShape from "./create-shape"
5 6
 import direct from "./direct"
@@ -8,6 +9,7 @@ import rotate from "./rotate"
8 9
 const commands = {
9 10
   translate,
10 11
   transform,
12
+  transformSingle,
11 13
   generate,
12 14
   createShape,
13 15
   direct,

+ 1
- 1
state/commands/rotate.ts Zobrazit soubor

@@ -3,7 +3,7 @@ import history from "../history"
3 3
 import { Data } from "types"
4 4
 import { RotateSnapshot } from "state/sessions/rotate-session"
5 5
 
6
-export default function translateCommand(
6
+export default function rotateCommand(
7 7
   data: Data,
8 8
   before: RotateSnapshot,
9 9
   after: RotateSnapshot

+ 72
- 0
state/commands/transform-single.ts Zobrazit soubor

@@ -0,0 +1,72 @@
1
+import Command from "./command"
2
+import history from "../history"
3
+import { Data, TransformCorner, TransformEdge } from "types"
4
+import { getShapeUtils } from "lib/shapes"
5
+import { TransformSingleSnapshot } from "state/sessions/transform-single-session"
6
+
7
+export default function transformSingleCommand(
8
+  data: Data,
9
+  before: TransformSingleSnapshot,
10
+  after: TransformSingleSnapshot,
11
+  anchor: TransformCorner | TransformEdge
12
+) {
13
+  history.execute(
14
+    data,
15
+    new Command({
16
+      name: "translate_shapes",
17
+      category: "canvas",
18
+      do(data) {
19
+        const {
20
+          type,
21
+          initialShape,
22
+          initialShapeBounds,
23
+          currentPageId,
24
+          id,
25
+          boundsRotation,
26
+        } = after
27
+
28
+        const { shapes } = data.document.pages[currentPageId]
29
+
30
+        const shape = shapes[id]
31
+
32
+        getShapeUtils(shape).transform(shape, initialShapeBounds, {
33
+          type,
34
+          initialShape,
35
+          initialShapeBounds,
36
+          initialBounds: initialShapeBounds,
37
+          boundsRotation,
38
+          isFlippedX: false,
39
+          isFlippedY: false,
40
+          isSingle: false,
41
+          anchor,
42
+        })
43
+      },
44
+      undo(data) {
45
+        const {
46
+          type,
47
+          initialShape,
48
+          initialShapeBounds,
49
+          currentPageId,
50
+          id,
51
+          boundsRotation,
52
+        } = before
53
+
54
+        const { shapes } = data.document.pages[currentPageId]
55
+
56
+        const shape = shapes[id]
57
+
58
+        getShapeUtils(shape).transform(shape, initialShapeBounds, {
59
+          type,
60
+          initialShape,
61
+          initialShapeBounds,
62
+          initialBounds: initialShapeBounds,
63
+          boundsRotation,
64
+          isFlippedX: false,
65
+          isFlippedY: false,
66
+          isSingle: false,
67
+          anchor,
68
+        })
69
+      },
70
+    })
71
+  )
72
+}

+ 3
- 5
state/commands/transform.ts Zobrazit soubor

@@ -4,7 +4,7 @@ import { Data, TransformCorner, TransformEdge } from "types"
4 4
 import { TransformSnapshot } from "state/sessions/transform-session"
5 5
 import { getShapeUtils } from "lib/shapes"
6 6
 
7
-export default function translateCommand(
7
+export default function transformCommand(
8 8
   data: Data,
9 9
   before: TransformSnapshot,
10 10
   after: TransformSnapshot,
@@ -22,7 +22,6 @@ export default function translateCommand(
22 22
           initialBounds,
23 23
           currentPageId,
24 24
           selectedIds,
25
-          isSingle,
26 25
           boundsRotation,
27 26
         } = after
28 27
 
@@ -40,7 +39,7 @@ export default function translateCommand(
40 39
             boundsRotation,
41 40
             isFlippedX: false,
42 41
             isFlippedY: false,
43
-            isSingle,
42
+            isSingle: false,
44 43
             anchor,
45 44
           })
46 45
         })
@@ -52,7 +51,6 @@ export default function translateCommand(
52 51
           initialBounds,
53 52
           currentPageId,
54 53
           selectedIds,
55
-          isSingle,
56 54
           boundsRotation,
57 55
         } = before
58 56
 
@@ -70,7 +68,7 @@ export default function translateCommand(
70 68
             boundsRotation,
71 69
             isFlippedX: false,
72 70
             isFlippedY: false,
73
-            isSingle,
71
+            isSingle: false,
74 72
             anchor: type,
75 73
           })
76 74
         })

+ 8
- 8
state/data.ts Zobrazit soubor

@@ -17,7 +17,7 @@ export const defaultDocument: Data["document"] = {
17 17
           direction: [0.5, 0.5],
18 18
           style: {
19 19
             fill: "#AAA",
20
-            stroke: "rgba(142, 143, 142, 1.000)",
20
+            stroke: "#c6cacb",
21 21
             strokeWidth: 1,
22 22
           },
23 23
         }),
@@ -28,7 +28,7 @@ export const defaultDocument: Data["document"] = {
28 28
         //   point: [400, 500],
29 29
         //   style: {
30 30
         //     fill: "#AAA",
31
-        //     stroke: "rgba(142, 143, 142, 1.000)",
31
+        //     stroke: "#c6cacb",
32 32
         //     strokeWidth: 1,
33 33
         //   },
34 34
         // }),
@@ -40,7 +40,7 @@ export const defaultDocument: Data["document"] = {
40 40
           radius: 50,
41 41
           style: {
42 42
             fill: "#AAA",
43
-            stroke: "rgba(142, 143, 142, 1.000)",
43
+            stroke: "#c6cacb",
44 44
             strokeWidth: 1,
45 45
           },
46 46
         }),
@@ -53,7 +53,7 @@ export const defaultDocument: Data["document"] = {
53 53
           radiusY: 30,
54 54
           style: {
55 55
             fill: "#AAA",
56
-            stroke: "rgba(142, 143, 142, 1.000)",
56
+            stroke: "#c6cacb",
57 57
             strokeWidth: 1,
58 58
           },
59 59
         }),
@@ -66,7 +66,7 @@ export const defaultDocument: Data["document"] = {
66 66
         //   radiusY: 30,
67 67
         //   style: {
68 68
         //     fill: "#AAA",
69
-        //     stroke: "rgba(142, 143, 142, 1.000)",
69
+        //     stroke: "#c6cacb",
70 70
         //     strokeWidth: 1,
71 71
         //   },
72 72
         // }),
@@ -82,7 +82,7 @@ export const defaultDocument: Data["document"] = {
82 82
         //   ],
83 83
         //   style: {
84 84
         //     fill: "none",
85
-        //     stroke: "rgba(142, 143, 142, 1.000)",
85
+        //     stroke: "#c6cacb",
86 86
         //     strokeWidth: 2,
87 87
         //     strokeLinecap: "round",
88 88
         //     strokeLinejoin: "round",
@@ -96,7 +96,7 @@ export const defaultDocument: Data["document"] = {
96 96
           size: [200, 200],
97 97
           style: {
98 98
             fill: "#AAA",
99
-            stroke: "rgba(142, 143, 142, 1.000)",
99
+            stroke: "#c6cacb",
100 100
             strokeWidth: 1,
101 101
           },
102 102
         }),
@@ -108,7 +108,7 @@ export const defaultDocument: Data["document"] = {
108 108
         //   direction: [0.2, 0.2],
109 109
         //   style: {
110 110
         //     fill: "#AAA",
111
-        //     stroke: "rgba(142, 143, 142, 1.000)",
111
+        //     stroke: "#c6cacb",
112 112
         //     strokeWidth: 1,
113 113
         //   },
114 114
         // }),

+ 2
- 0
state/sessions/index.ts Zobrazit soubor

@@ -2,6 +2,7 @@ import BaseSession from "./base-session"
2 2
 import BrushSession from "./brush-session"
3 3
 import TranslateSession from "./translate-session"
4 4
 import TransformSession from "./transform-session"
5
+import TransformSingleSession from "./transform-single-session"
5 6
 import DirectionSession from "./direction-session"
6 7
 import RotateSession from "./rotate-session"
7 8
 
@@ -10,6 +11,7 @@ export {
10 11
   BaseSession,
11 12
   TranslateSession,
12 13
   TransformSession,
14
+  TransformSingleSession,
13 15
   DirectionSession,
14 16
   RotateSession,
15 17
 }

+ 17
- 6
state/sessions/transform-session.ts Zobrazit soubor

@@ -66,12 +66,25 @@ export default class TransformSession extends BaseSession {
66 66
       initialBounds,
67 67
       currentPageId,
68 68
       selectedIds,
69
-      isSingle,
70 69
     } = this.snapshot
71 70
 
72 71
     const { shapes } = data.document.pages[currentPageId]
73 72
 
74
-    const delta = vec.vec(this.origin, point)
73
+    let delta = vec.vec(this.origin, point)
74
+
75
+    // if (isSingle) {
76
+    //   const center = [
77
+    //     initialBounds.minX + initialBounds.width / 2,
78
+    //     initialBounds.minY + initialBounds.height / 2,
79
+    //   ]
80
+
81
+    //   const rotation = shapes[Array.from(selectedIds.values())[0]].rotation
82
+
83
+    //   const rotatedOrigin = vec.rotWith(this.origin, center, -rotation)
84
+    //   const rotatedPoint = vec.rotWith(point, center, -rotation)
85
+
86
+    //   delta = vec.vec(rotatedOrigin, rotatedPoint)
87
+    // }
75 88
 
76 89
     /*
77 90
     Transforms
@@ -173,7 +186,7 @@ export default class TransformSession extends BaseSession {
173 186
         boundsRotation,
174 187
         isFlippedX: this.isFlippedX,
175 188
         isFlippedY: this.isFlippedY,
176
-        isSingle,
189
+        isSingle: false,
177 190
         anchor: getTransformAnchor(
178 191
           this.transformType,
179 192
           this.isFlippedX,
@@ -190,7 +203,6 @@ export default class TransformSession extends BaseSession {
190 203
       initialBounds,
191 204
       currentPageId,
192 205
       selectedIds,
193
-      isSingle,
194 206
     } = this.snapshot
195 207
 
196 208
     const { shapes } = data.document.pages[currentPageId]
@@ -208,7 +220,7 @@ export default class TransformSession extends BaseSession {
208 220
         boundsRotation,
209 221
         isFlippedX: false,
210 222
         isFlippedY: false,
211
-        isSingle,
223
+        isSingle: false,
212 224
         anchor: getTransformAnchor(this.transformType, false, false),
213 225
       })
214 226
     })
@@ -255,7 +267,6 @@ export function getTransformSnapshot(
255 267
     type: transformType,
256 268
     initialBounds: bounds,
257 269
     boundsRotation,
258
-    isSingle: selectedIds.size === 1,
259 270
     selectedIds: new Set(selectedIds),
260 271
     shapeBounds: Object.fromEntries(
261 272
       Array.from(selectedIds.values()).map((id) => {

+ 247
- 0
state/sessions/transform-single-session.ts Zobrazit soubor

@@ -0,0 +1,247 @@
1
+import { Data, TransformEdge, TransformCorner } from "types"
2
+import * as vec from "utils/vec"
3
+import BaseSession from "./base-session"
4
+import commands from "state/commands"
5
+import { current } from "immer"
6
+import { getShapeUtils } from "lib/shapes"
7
+import {
8
+  getTransformedBoundingBox,
9
+  getCommonBounds,
10
+  getRotatedCorners,
11
+  getTransformAnchor,
12
+} from "utils/utils"
13
+
14
+export default class TransformSingleSession extends BaseSession {
15
+  delta = [0, 0]
16
+  isFlippedX = false
17
+  isFlippedY = false
18
+  transformType: TransformEdge | TransformCorner
19
+  origin: number[]
20
+  center: number[]
21
+  snapshot: TransformSingleSnapshot
22
+  corners: {
23
+    a: number[]
24
+    b: number[]
25
+  }
26
+  rotatedCorners: number[][]
27
+
28
+  constructor(
29
+    data: Data,
30
+    transformType: TransformCorner | TransformEdge,
31
+    point: number[]
32
+  ) {
33
+    super(data)
34
+    this.origin = point
35
+    this.transformType = transformType
36
+
37
+    this.snapshot = getTransformSingleSnapshot(data, transformType)
38
+
39
+    const { minX, minY, maxX, maxY } = this.snapshot.initialShapeBounds
40
+
41
+    this.center = [(minX + maxX) / 2, (minY + maxY) / 2]
42
+
43
+    this.corners = {
44
+      a: [minX, minY],
45
+      b: [maxX, maxY],
46
+    }
47
+
48
+    this.rotatedCorners = getRotatedCorners(
49
+      this.snapshot.initialShapeBounds,
50
+      this.snapshot.initialShape.rotation
51
+    )
52
+  }
53
+
54
+  update(data: Data, point: number[]) {
55
+    const {
56
+      corners: { a, b },
57
+      transformType,
58
+    } = this
59
+
60
+    const {
61
+      boundsRotation,
62
+      initialShapeBounds,
63
+      currentPageId,
64
+      initialShape,
65
+      id,
66
+    } = this.snapshot
67
+
68
+    const { shapes } = data.document.pages[currentPageId]
69
+
70
+    const shape = shapes[id]
71
+    const rotation = shape.rotation
72
+
73
+    // 1. Create a new bounding box.
74
+    // Counter rotate the delta and apply this to the original bounding box.
75
+
76
+    const delta = vec.vec(this.origin, point)
77
+
78
+    /*
79
+    Transforms
80
+    
81
+    Corners a and b are the original top-left and bottom-right corners of the
82
+    bounding box. Depending on what the user is dragging, change one or both
83
+    points. To keep things smooth, calculate based by adding the delta (the 
84
+    vector between the current point and its original point) to the original
85
+    bounding box values.
86
+    */
87
+
88
+    const newBoundingBox = getTransformedBoundingBox(
89
+      initialShapeBounds,
90
+      transformType,
91
+      delta,
92
+      shape.rotation
93
+    )
94
+
95
+    // console.log(newBoundingBox)
96
+
97
+    switch (transformType) {
98
+      case TransformEdge.Top: {
99
+        a[1] = initialShapeBounds.minY + delta[1]
100
+        break
101
+      }
102
+      case TransformEdge.Right: {
103
+        b[0] = initialShapeBounds.maxX + delta[0]
104
+        break
105
+      }
106
+      case TransformEdge.Bottom: {
107
+        b[1] = initialShapeBounds.maxY + delta[1]
108
+        break
109
+      }
110
+      case TransformEdge.Left: {
111
+        a[0] = initialShapeBounds.minX + delta[0]
112
+        break
113
+      }
114
+      case TransformCorner.TopLeft: {
115
+        a[0] = initialShapeBounds.minX + delta[0]
116
+        a[1] = initialShapeBounds.minY + delta[1]
117
+        break
118
+      }
119
+      case TransformCorner.TopRight: {
120
+        a[1] = initialShapeBounds.minY + delta[1]
121
+        b[0] = initialShapeBounds.maxX + delta[0]
122
+        break
123
+      }
124
+      case TransformCorner.BottomRight: {
125
+        b[0] = initialShapeBounds.maxX + delta[0]
126
+        b[1] = initialShapeBounds.maxY + delta[1]
127
+        break
128
+      }
129
+      case TransformCorner.BottomLeft: {
130
+        a[0] = initialShapeBounds.minX + delta[0]
131
+        b[1] = initialShapeBounds.maxY + delta[1]
132
+        break
133
+      }
134
+    }
135
+
136
+    // Calculate new common (externior) bounding box
137
+    const newBounds = {
138
+      minX: Math.min(a[0], b[0]),
139
+      minY: Math.min(a[1], b[1]),
140
+      maxX: Math.max(a[0], b[0]),
141
+      maxY: Math.max(a[1], b[1]),
142
+      width: Math.abs(b[0] - a[0]),
143
+      height: Math.abs(b[1] - a[1]),
144
+    }
145
+
146
+    this.isFlippedX = b[0] < a[0]
147
+    this.isFlippedY = b[1] < a[1]
148
+
149
+    const anchor = this.transformType
150
+
151
+    // Pass the new data to the shape's transform utility for mutation.
152
+    // Most shapes should be able to transform using only the bounding box,
153
+    // however some shapes (e.g. those with internal points) will need more
154
+    // data here too.
155
+
156
+    getShapeUtils(shape).transformSingle(shape, newBoundingBox, {
157
+      type: this.transformType,
158
+      initialShape,
159
+      initialShapeBounds,
160
+      initialBounds: initialShapeBounds,
161
+      boundsRotation,
162
+      isFlippedX: this.isFlippedX,
163
+      isFlippedY: this.isFlippedY,
164
+      isSingle: true,
165
+      anchor,
166
+    })
167
+  }
168
+
169
+  cancel(data: Data) {
170
+    const {
171
+      id,
172
+      boundsRotation,
173
+      initialShape,
174
+      initialShapeBounds,
175
+      currentPageId,
176
+      isSingle,
177
+    } = this.snapshot
178
+
179
+    const { shapes } = data.document.pages[currentPageId]
180
+
181
+    // selectedIds.forEach((id) => {
182
+    //   const shape = shapes[id]
183
+
184
+    //   const { initialShape, initialShapeBounds } = shapeBounds[id]
185
+
186
+    //   getShapeUtils(shape).transform(shape, initialShapeBounds, {
187
+    //     type: this.transformType,
188
+    //     initialShape,
189
+    //     initialShapeBounds,
190
+    //     initialBounds,
191
+    //     boundsRotation,
192
+    //     isFlippedX: false,
193
+    //     isFlippedY: false,
194
+    //     isSingle,
195
+    //     anchor: getTransformAnchor(this.transformType, false, false),
196
+    //   })
197
+    // })
198
+  }
199
+
200
+  complete(data: Data) {
201
+    commands.transformSingle(
202
+      data,
203
+      this.snapshot,
204
+      getTransformSingleSnapshot(data, this.transformType),
205
+      getTransformAnchor(this.transformType, false, false)
206
+    )
207
+  }
208
+}
209
+
210
+export function getTransformSingleSnapshot(
211
+  data: Data,
212
+  transformType: TransformEdge | TransformCorner
213
+) {
214
+  const {
215
+    document: { pages },
216
+    selectedIds,
217
+    currentPageId,
218
+  } = current(data)
219
+
220
+  const pageShapes = pages[currentPageId].shapes
221
+
222
+  const id = Array.from(selectedIds)[0]
223
+  const shape = pageShapes[id]
224
+  const bounds = getShapeUtils(shape).getBounds(shape)
225
+
226
+  return {
227
+    id,
228
+    currentPageId,
229
+    type: transformType,
230
+    initialShape: shape,
231
+    initialShapeBounds: {
232
+      ...bounds,
233
+      nx: 0,
234
+      ny: 0,
235
+      nmx: 1,
236
+      nmy: 1,
237
+      nw: 1,
238
+      nh: 1,
239
+    },
240
+    boundsRotation: shape.rotation,
241
+    isSingle: true,
242
+  }
243
+}
244
+
245
+export type TransformSingleSnapshot = ReturnType<
246
+  typeof getTransformSingleSnapshot
247
+>

+ 12
- 5
state/state.ts Zobrazit soubor

@@ -527,11 +527,18 @@ const state = createState({
527 527
       data,
528 528
       payload: PointerInfo & { target: TransformCorner | TransformEdge }
529 529
     ) {
530
-      session = new Sessions.TransformSession(
531
-        data,
532
-        payload.target,
533
-        screenToWorld(payload.point, data)
534
-      )
530
+      session =
531
+        data.selectedIds.size === 1
532
+          ? new Sessions.TransformSingleSession(
533
+              data,
534
+              payload.target,
535
+              screenToWorld(payload.point, data)
536
+            )
537
+          : new Sessions.TransformSession(
538
+              data,
539
+              payload.target,
540
+              screenToWorld(payload.point, data)
541
+            )
535 542
     },
536 543
     startDrawTransformSession(data, payload: PointerInfo) {
537 544
       session = new Sessions.TransformSession(

+ 151
- 1
utils/utils.ts Zobrazit soubor

@@ -1,6 +1,7 @@
1 1
 import Vector from "lib/code/vector"
2
+import { getShapeUtils } from "lib/shapes"
2 3
 import React from "react"
3
-import { Data, Bounds, TransformEdge, TransformCorner } from "types"
4
+import { Data, Bounds, TransformEdge, TransformCorner, Shape } from "types"
4 5
 import * as svg from "./svg"
5 6
 import * as vec from "./vec"
6 7
 
@@ -1020,3 +1021,152 @@ export function rotateBounds(
1020 1021
     height: bounds.height,
1021 1022
   }
1022 1023
 }
1024
+
1025
+export function getRotatedCorners(b: Bounds, rotation: number) {
1026
+  const center = [b.minX + b.width / 2, b.minY + b.height / 2]
1027
+
1028
+  return [
1029
+    [b.minX, b.minY],
1030
+    [b.maxX, b.minY],
1031
+    [b.maxX, b.maxY],
1032
+    [b.minX, b.maxY],
1033
+  ].map((point) => vec.rotWith(point, center, rotation))
1034
+}
1035
+
1036
+export function getTransformedBoundingBox(
1037
+  bounds: Bounds,
1038
+  handle: TransformCorner | TransformEdge,
1039
+  delta: number[],
1040
+  rotation = 0
1041
+) {
1042
+  // Create top left and bottom right corners.
1043
+  let [ax0, ay0] = [bounds.minX, bounds.minY]
1044
+  let [ax1, ay1] = [bounds.maxX, bounds.maxY]
1045
+
1046
+  // Create a second set of corners for the result.
1047
+  let [bx0, by0] = [bounds.minX, bounds.minY]
1048
+  let [bx1, by1] = [bounds.maxX, bounds.maxY]
1049
+
1050
+  // Counter rotate the delta. This lets us make changes as if
1051
+  // the (possibly rotated) boxes were axis aligned.
1052
+  const [dx, dy] = vec.rot(delta, -rotation)
1053
+
1054
+  // Depending on the dragging handle (an edge or corner of
1055
+  // the bounding box), find the anchor corner and use the delta
1056
+  // to adjust the result's corners.
1057
+
1058
+  let anchor: TransformCorner | TransformEdge
1059
+
1060
+  switch (handle) {
1061
+    case TransformEdge.Top: {
1062
+      anchor = TransformCorner.BottomRight
1063
+      by0 += dy
1064
+      break
1065
+    }
1066
+    case TransformEdge.Right: {
1067
+      anchor = TransformCorner.TopLeft
1068
+      bx1 += dx
1069
+      break
1070
+    }
1071
+    case TransformEdge.Bottom: {
1072
+      anchor = TransformCorner.TopLeft
1073
+      by1 += dy
1074
+      break
1075
+    }
1076
+    case TransformEdge.Left: {
1077
+      anchor = TransformCorner.BottomRight
1078
+      bx0 += dx
1079
+      break
1080
+    }
1081
+    case TransformCorner.TopLeft: {
1082
+      anchor = TransformCorner.BottomRight
1083
+      bx0 += dx
1084
+      by0 += dy
1085
+      break
1086
+    }
1087
+    case TransformCorner.TopRight: {
1088
+      anchor = TransformCorner.BottomLeft
1089
+      bx1 += dx
1090
+      by0 += dy
1091
+      break
1092
+    }
1093
+    case TransformCorner.BottomRight: {
1094
+      anchor = TransformCorner.TopLeft
1095
+      bx1 += dx
1096
+      by1 += dy
1097
+      break
1098
+    }
1099
+    case TransformCorner.BottomLeft: {
1100
+      anchor = TransformCorner.TopRight
1101
+      bx0 += dx
1102
+      by1 += dy
1103
+      break
1104
+    }
1105
+  }
1106
+
1107
+  // If the bounds are rotated, get a vector from the rotated anchor
1108
+  // corner in the inital bounds to the rotated anchor corner in the
1109
+  // result's bounds. Subtract this vector from the result's corners,
1110
+  // so that the two anchor points (initial and result) will be equal.
1111
+
1112
+  if (rotation % (Math.PI * 2) !== 0) {
1113
+    let cv = [0, 0]
1114
+
1115
+    const c0 = vec.med([ax0, ay0], [ax1, ay1])
1116
+    const c1 = vec.med([bx0, by0], [bx1, by1])
1117
+
1118
+    switch (anchor) {
1119
+      case TransformCorner.TopLeft: {
1120
+        cv = vec.sub(
1121
+          vec.rotWith([bx0, by0], c1, rotation),
1122
+          vec.rotWith([ax0, ay0], c0, rotation)
1123
+        )
1124
+        break
1125
+      }
1126
+      case TransformCorner.TopRight: {
1127
+        cv = vec.sub(
1128
+          vec.rotWith([bx1, by0], c1, rotation),
1129
+          vec.rotWith([ax1, ay0], c0, rotation)
1130
+        )
1131
+        break
1132
+      }
1133
+      case TransformCorner.BottomRight: {
1134
+        cv = vec.sub(
1135
+          vec.rotWith([bx1, by1], c1, rotation),
1136
+          vec.rotWith([ax1, ay1], c0, rotation)
1137
+        )
1138
+        break
1139
+      }
1140
+      case TransformCorner.BottomLeft: {
1141
+        cv = vec.sub(
1142
+          vec.rotWith([bx0, by1], c1, rotation),
1143
+          vec.rotWith([ax0, ay1], c0, rotation)
1144
+        )
1145
+        break
1146
+      }
1147
+    }
1148
+
1149
+    ;[bx0, by0] = vec.sub([bx0, by0], cv)
1150
+    ;[bx1, by1] = vec.sub([bx1, by1], cv)
1151
+  }
1152
+
1153
+  // If the axes are flipped (e.g. if the right edge has been dragged
1154
+  // left past the initial left edge) then swap points on that axis.
1155
+
1156
+  if (bx1 < bx0) {
1157
+    ;[bx1, bx0] = [bx0, bx1]
1158
+  }
1159
+
1160
+  if (by1 < by0) {
1161
+    ;[by1, by0] = [by0, by1]
1162
+  }
1163
+
1164
+  return {
1165
+    minX: bx0,
1166
+    minY: by0,
1167
+    maxX: bx1,
1168
+    maxY: by1,
1169
+    width: bx1 - bx0,
1170
+    height: by1 - by0,
1171
+  }
1172
+}

Načítá se…
Zrušit
Uložit