Browse Source

Fixes more bugs with groups

main
Steve Ruiz 4 years ago
parent
commit
d99cacd105

+ 4
- 2
components/canvas/bounds/center-handle.tsx View File

10
 }) {
10
 }) {
11
   return (
11
   return (
12
     <StyledBounds
12
     <StyledBounds
13
-      width={bounds.width}
14
-      height={bounds.height}
13
+      x={-1}
14
+      y={-1}
15
+      width={bounds.width + 2}
16
+      height={bounds.height + 2}
15
       pointerEvents="none"
17
       pointerEvents="none"
16
       isLocked={isLocked}
18
       isLocked={isLocked}
17
     />
19
     />

+ 4
- 4
components/canvas/bounds/corner-handle.tsx View File

20
     <g>
20
     <g>
21
       <StyledCorner
21
       <StyledCorner
22
         corner={corner}
22
         corner={corner}
23
-        x={(isLeft ? 0 : bounds.width) - size}
24
-        y={(isTop ? 0 : bounds.height) - size}
23
+        x={(isLeft ? -1 : bounds.width + 1) - size}
24
+        y={(isTop ? -1 : bounds.height + 1) - size}
25
         width={size * 2}
25
         width={size * 2}
26
         height={size * 2}
26
         height={size * 2}
27
         {...events}
27
         {...events}
28
       />
28
       />
29
       <StyledCornerInner
29
       <StyledCornerInner
30
-        x={(isLeft ? 0 : bounds.width) - size / 2}
31
-        y={(isTop ? 0 : bounds.height) - size / 2}
30
+        x={(isLeft ? -1 : bounds.width + 1) - size / 2}
31
+        y={(isTop ? -1 : bounds.height + 1) - size / 2}
32
         width={size}
32
         width={size}
33
         height={size}
33
         height={size}
34
         pointerEvents="none"
34
         pointerEvents="none"

+ 6
- 4
components/canvas/bounds/edge-handle.tsx View File

16
   const isHorizontal = edge === Edge.Top || edge === Edge.Bottom
16
   const isHorizontal = edge === Edge.Top || edge === Edge.Bottom
17
   const isFarEdge = edge === Edge.Right || edge === Edge.Bottom
17
   const isFarEdge = edge === Edge.Right || edge === Edge.Bottom
18
 
18
 
19
+  const { height, width } = bounds
20
+
19
   return (
21
   return (
20
     <StyledEdge
22
     <StyledEdge
21
       edge={edge}
23
       edge={edge}
22
-      x={isHorizontal ? size / 2 : (isFarEdge ? bounds.width : 0) - size / 2}
23
-      y={isHorizontal ? (isFarEdge ? bounds.height : 0) - size / 2 : size / 2}
24
-      width={isHorizontal ? Math.max(0, bounds.width - size) : size}
25
-      height={isHorizontal ? size : Math.max(0, bounds.height - size)}
24
+      x={isHorizontal ? size / 2 : (isFarEdge ? width + 1 : -1) - size / 2}
25
+      y={isHorizontal ? (isFarEdge ? height + 1 : -1) - size / 2 : size / 2}
26
+      width={isHorizontal ? Math.max(0, width + 1 - size) : size}
27
+      height={isHorizontal ? size : Math.max(0, height + 1 - size)}
26
       {...events}
28
       {...events}
27
     />
29
     />
28
   )
30
   )

+ 33
- 23
components/canvas/shape.tsx View File

40
   const style = getShapeStyle(shape.style)
40
   const style = getShapeStyle(shape.style)
41
 
41
 
42
   return (
42
   return (
43
-    <>
44
-      <StyledGroup ref={rGroup} transform={transform}>
45
-        {isSelecting && !isGroup && (
46
-          <HoverIndicator
47
-            as="use"
48
-            href={'#' + id}
49
-            strokeWidth={+style.strokeWidth + 4}
50
-            variant={getShapeUtils(shape).canStyleFill ? 'filled' : 'hollow'}
51
-            {...events}
43
+    <StyledGroup ref={rGroup} transform={transform}>
44
+      {isSelecting && !isGroup && (
45
+        <HoverIndicator
46
+          as="use"
47
+          href={'#' + id}
48
+          strokeWidth={+style.strokeWidth + 4}
49
+          variant={getShapeUtils(shape).canStyleFill ? 'filled' : 'hollow'}
50
+          {...events}
51
+        />
52
+      )}
53
+      {!shape.isHidden && (
54
+        <StyledShape as="use" data-shy={isGroup} href={'#' + id} {...style} />
55
+      )}
56
+      {isGroup &&
57
+        shape.children.map((shapeId) => (
58
+          <Shape
59
+            key={shapeId}
60
+            id={shapeId}
61
+            isSelecting={isSelecting}
62
+            parentPoint={shape.point}
63
+            parentRotation={shape.rotation}
52
           />
64
           />
53
-        )}
54
-        {!shape.isHidden && <StyledShape as="use" href={'#' + id} {...style} />}
55
-        {isGroup &&
56
-          shape.children.map((shapeId) => (
57
-            <Shape
58
-              key={shapeId}
59
-              id={shapeId}
60
-              isSelecting={isSelecting}
61
-              parentPoint={shape.point}
62
-              parentRotation={shape.rotation}
63
-            />
64
-          ))}
65
-      </StyledGroup>
66
-    </>
65
+        ))}
66
+    </StyledGroup>
67
   )
67
   )
68
 }
68
 }
69
 
69
 
93
 })
93
 })
94
 
94
 
95
 const StyledGroup = styled('g', {
95
 const StyledGroup = styled('g', {
96
+  outline: 'none',
97
+  [`& *[data-shy="true"]`]: {
98
+    opacity: '0',
99
+  },
96
   [`& ${HoverIndicator}`]: {
100
   [`& ${HoverIndicator}`]: {
97
     opacity: '0',
101
     opacity: '0',
98
   },
102
   },
99
   [`&:hover ${HoverIndicator}`]: {
103
   [`&:hover ${HoverIndicator}`]: {
100
     opacity: '0.16',
104
     opacity: '0.16',
101
   },
105
   },
106
+  [`&:hover *[data-shy="true"]`]: {
107
+    opacity: '1',
108
+  },
102
   variants: {
109
   variants: {
103
     isSelected: {
110
     isSelected: {
104
       true: {
111
       true: {
112
+        [`& *[data-shy="true"]`]: {
113
+          opacity: '1',
114
+        },
105
         [`& ${HoverIndicator}`]: {
115
         [`& ${HoverIndicator}`]: {
106
           opacity: '0.2',
116
           opacity: '0.2',
107
         },
117
         },

+ 6
- 3
lib/shape-utils/group.tsx View File

48
     const { id, size } = shape
48
     const { id, size } = shape
49
 
49
 
50
     return (
50
     return (
51
-      <g id={id}>
52
-        <StyledGroupShape id={id} width={size[0]} height={size[1]} />
53
-      </g>
51
+      <StyledGroupShape
52
+        id={id}
53
+        width={size[0]}
54
+        height={size[1]}
55
+        data-shy={true}
56
+      />
54
     )
57
     )
55
   },
58
   },
56
 
59
 

+ 4
- 2
lib/shape-utils/rectangle.tsx View File

36
           id={id}
36
           id={id}
37
           rx={radius}
37
           rx={radius}
38
           ry={radius}
38
           ry={radius}
39
-          width={Math.max(0, size[0] - Number(styles.strokeWidth) / 2)}
40
-          height={Math.max(0, size[1] - Number(styles.strokeWidth) / 2)}
39
+          x={+styles.strokeWidth / 2}
40
+          y={+styles.strokeWidth / 2}
41
+          width={Math.max(0, size[0] + -styles.strokeWidth)}
42
+          height={Math.max(0, size[1] + -styles.strokeWidth)}
41
         />
43
         />
42
       </g>
44
       </g>
43
     )
45
     )

+ 8
- 3
state/commands/delete-selected.ts View File

30
 
30
 
31
         for (let id of selectedIds) {
31
         for (let id of selectedIds) {
32
           const shape = page.shapes[id]
32
           const shape = page.shapes[id]
33
+          if (!shape) {
34
+            console.error('no shape ' + id)
35
+            continue
36
+          }
37
+
33
           if (shape.parentId !== data.currentPageId) {
38
           if (shape.parentId !== data.currentPageId) {
34
             const parent = page.shapes[shape.parentId]
39
             const parent = page.shapes[shape.parentId]
35
             getShapeUtils(parent)
40
             getShapeUtils(parent)
43
                 parent.children.map((id) => page.shapes[id])
48
                 parent.children.map((id) => page.shapes[id])
44
               )
49
               )
45
           }
50
           }
51
+        }
46
 
52
 
47
-          for (let shape of childrenToDelete) {
48
-            delete page.shapes[shape.id]
49
-          }
53
+        for (let shape of childrenToDelete) {
54
+          delete page.shapes[shape.id]
50
         }
55
         }
51
 
56
 
52
         data.selectedIds.clear()
57
         data.selectedIds.clear()

+ 10
- 8
state/sessions/brush-session.ts View File

64
     shapeHitTests: Object.fromEntries(
64
     shapeHitTests: Object.fromEntries(
65
       getShapes(current(data))
65
       getShapes(current(data))
66
         .filter((shape) => shape.type !== ShapeType.Group)
66
         .filter((shape) => shape.type !== ShapeType.Group)
67
-        .map((shape) => [
68
-          shape.id,
69
-          {
70
-            selectId: getTopParentId(data, shape.id),
71
-            test: (bounds: Bounds) =>
72
-              getShapeUtils(shape).hitTestBounds(shape, bounds),
73
-          },
74
-        ])
67
+        .map((shape) => {
68
+          return [
69
+            shape.id,
70
+            {
71
+              selectId: getTopParentId(data, shape.id),
72
+              test: (bounds: Bounds) =>
73
+                getShapeUtils(shape).hitTestBounds(shape, bounds),
74
+            },
75
+          ]
76
+        })
75
     ),
77
     ),
76
   }
78
   }
77
 }
79
 }

+ 30
- 4
state/sessions/transform-session.ts View File

1
-import { Data, Edge, Corner } from 'types'
1
+import { Data, Edge, Corner, Bounds } from 'types'
2
 import * as vec from 'utils/vec'
2
 import * as vec from 'utils/vec'
3
 import BaseSession from './base-session'
3
 import BaseSession from './base-session'
4
 import commands from 'state/commands'
4
 import commands from 'state/commands'
8
   getBoundsCenter,
8
   getBoundsCenter,
9
   getBoundsFromPoints,
9
   getBoundsFromPoints,
10
   getCommonBounds,
10
   getCommonBounds,
11
+  getDocumentBranch,
11
   getPage,
12
   getPage,
12
   getRelativeTransformedBoundingBox,
13
   getRelativeTransformedBoundingBox,
13
   getSelectedShapes,
14
   getSelectedShapes,
115
 export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
116
 export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
116
   const cData = current(data)
117
   const cData = current(data)
117
   const { currentPageId } = cData
118
   const { currentPageId } = cData
119
+  const page = getPage(cData)
118
 
120
 
119
-  const initialShapes = getSelectedShapes(cData).filter(
120
-    (shape) => !shape.isLocked
121
-  )
121
+  const initialShapes = Array.from(cData.selectedIds.values())
122
+    .flatMap((id) => getDocumentBranch(cData, id).map((id) => page.shapes[id]))
123
+    .filter((shape) => !shape.isLocked)
122
 
124
 
123
   const hasUnlockedShapes = initialShapes.length > 0
125
   const hasUnlockedShapes = initialShapes.length > 0
124
 
126
 
182
 // }
184
 // }
183
 
185
 
184
 // const origin = transformOrigins[this.transformType]
186
 // const origin = transformOrigins[this.transformType]
187
+
188
+// function resizeDescendants(data: Data, shapeId: string, bounds: Bounds) {
189
+
190
+//   const { initialShape, initialShapeBounds, transformOrigin } =
191
+//     shapeBounds[id]
192
+
193
+//   const newShapeBounds = getRelativeTransformedBoundingBox(
194
+//     newBoundingBox,
195
+//     initialBounds,
196
+//     initialShapeBounds,
197
+//     this.scaleX < 0,
198
+//     this.scaleY < 0
199
+//   )
200
+
201
+//   const shape = shapes[id]
202
+
203
+//   getShapeUtils(shape).transform(shape, newShapeBounds, {
204
+//     type: this.transformType,
205
+//     initialShape,
206
+//     scaleX: this.scaleX,
207
+//     scaleY: this.scaleY,
208
+//     transformOrigin,
209
+//   })
210
+// }

+ 20
- 11
state/state.ts View File

24
   getParentRotation,
24
   getParentRotation,
25
   rotateBounds,
25
   rotateBounds,
26
   getBoundsCenter,
26
   getBoundsCenter,
27
+  getDocumentBranch,
27
 } from 'utils/utils'
28
 } from 'utils/utils'
28
 import {
29
 import {
29
   Data,
30
   Data,
924
       payload: PointerInfo & { target: Corner | Edge }
925
       payload: PointerInfo & { target: Corner | Edge }
925
     ) {
926
     ) {
926
       const point = screenToWorld(inputs.pointer.origin, data)
927
       const point = screenToWorld(inputs.pointer.origin, data)
928
+      // session = new Sessions.TransformSession(data, payload.target, point)
927
       session =
929
       session =
928
         data.selectedIds.size === 1
930
         data.selectedIds.size === 1
929
           ? new Sessions.TransformSingleSession(data, payload.target, point)
931
           ? new Sessions.TransformSingleSession(data, payload.target, point)
1385
     },
1387
     },
1386
 
1388
 
1387
     restoreSavedData(data) {
1389
     restoreSavedData(data) {
1388
-      // history.load(data)
1390
+      history.load(data)
1389
     },
1391
     },
1390
 
1392
 
1391
     clearBoundsRotation(data) {
1393
     clearBoundsRotation(data) {
1441
       }
1443
       }
1442
 
1444
 
1443
       const commonBounds = getCommonBounds(
1445
       const commonBounds = getCommonBounds(
1444
-        ...shapes.map((shape) => {
1445
-          const parentOffset = getParentOffset(data, shape.id)
1446
-          const parentRotation = getParentRotation(data, shape.id)
1447
-          const bounds = getShapeUtils(shape).getRotatedBounds(shape)
1448
-
1449
-          return translateBounds(
1450
-            rotateBounds(bounds, getBoundsCenter(bounds), parentRotation),
1451
-            vec.neg(parentOffset)
1452
-          )
1453
-        })
1446
+        ...shapes
1447
+          .flatMap((shape) => getDocumentBranch(data, shape.id))
1448
+          .map((id) => page.shapes[id])
1449
+          .filter((shape) => shape.type !== ShapeType.Group)
1450
+          .map((shape) => {
1451
+            const parentOffset = getParentOffset(data, shape.id)
1452
+            const parentRotation = getParentRotation(data, shape.id)
1453
+            const bounds = getShapeUtils(shape).getRotatedBounds(shape)
1454
+
1455
+            return bounds
1456
+
1457
+            return translateBounds(
1458
+              rotateBounds(bounds, getBoundsCenter(bounds), parentRotation),
1459
+              vec.neg(parentOffset)
1460
+            )
1461
+          })
1454
       )
1462
       )
1455
 
1463
 
1456
       return commonBounds
1464
       return commonBounds
1463
         return currentStyle
1471
         return currentStyle
1464
       }
1472
       }
1465
       const page = getPage(data)
1473
       const page = getPage(data)
1474
+
1466
       const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
1475
       const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
1467
 
1476
 
1468
       const commonStyle: ShapeStyles = {} as ShapeStyles
1477
       const commonStyle: ShapeStyles = {} as ShapeStyles

+ 3
- 3
styles/stitches.config.ts View File

6
   },
6
   },
7
   theme: {
7
   theme: {
8
     colors: {
8
     colors: {
9
-      brushFill: 'rgba(0,0,0,.1)',
10
-      brushStroke: 'rgba(0,0,0,.5)',
9
+      brushFill: 'rgba(0,0,0,.05)',
10
+      brushStroke: 'rgba(0,0,0,.25)',
11
       hint: 'rgba(216, 226, 249, 1.000)',
11
       hint: 'rgba(216, 226, 249, 1.000)',
12
       selected: 'rgba(66, 133, 244, 1.000)',
12
       selected: 'rgba(66, 133, 244, 1.000)',
13
       bounds: 'rgba(65, 132, 244, 1.000)',
13
       bounds: 'rgba(65, 132, 244, 1.000)',
14
-      boundsBg: 'rgba(65, 132, 244, 0.100)',
14
+      boundsBg: 'rgba(65, 132, 244, 0.050)',
15
       border: '#aaa',
15
       border: '#aaa',
16
       canvas: '#fafafa',
16
       canvas: '#fafafa',
17
       panel: '#fefefe',
17
       panel: '#fefefe',

+ 10
- 0
todo.md View File

1
+# Todo
2
+
3
+## Groups
4
+
5
+- Restore select highlight, fix for children of rotated groups
6
+- Transforming on rotated shapes
7
+- Fix bounding box for rotated shapes
8
+- Allow single-selected groups to transform their children correctly
9
+- (merge transform-session and transform-single-session)
10
+- fix drift when moving children of rotated group

+ 1
- 0
utils/utils.ts View File

1623
 
1623
 
1624
   for (const parentId of parentToUpdateIds) {
1624
   for (const parentId of parentToUpdateIds) {
1625
     const parent = shapes[parentId] as GroupShape
1625
     const parent = shapes[parentId] as GroupShape
1626
+
1626
     getShapeUtils(parent).onChildrenChange(
1627
     getShapeUtils(parent).onChildrenChange(
1627
       parent,
1628
       parent,
1628
       parent.children.map((id) => shapes[id])
1629
       parent.children.map((id) => shapes[id])

Loading…
Cancel
Save