Parcourir la source

Fixes more bugs with groups

main
Steve Ruiz il y a 4 ans
Parent
révision
d99cacd105

+ 4
- 2
components/canvas/bounds/center-handle.tsx Voir le fichier

@@ -10,8 +10,10 @@ export default function CenterHandle({
10 10
 }) {
11 11
   return (
12 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 17
       pointerEvents="none"
16 18
       isLocked={isLocked}
17 19
     />

+ 4
- 4
components/canvas/bounds/corner-handle.tsx Voir le fichier

@@ -20,15 +20,15 @@ export default function CornerHandle({
20 20
     <g>
21 21
       <StyledCorner
22 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 25
         width={size * 2}
26 26
         height={size * 2}
27 27
         {...events}
28 28
       />
29 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 32
         width={size}
33 33
         height={size}
34 34
         pointerEvents="none"

+ 6
- 4
components/canvas/bounds/edge-handle.tsx Voir le fichier

@@ -16,13 +16,15 @@ export default function EdgeHandle({
16 16
   const isHorizontal = edge === Edge.Top || edge === Edge.Bottom
17 17
   const isFarEdge = edge === Edge.Right || edge === Edge.Bottom
18 18
 
19
+  const { height, width } = bounds
20
+
19 21
   return (
20 22
     <StyledEdge
21 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 28
       {...events}
27 29
     />
28 30
   )

+ 33
- 23
components/canvas/shape.tsx Voir le fichier

@@ -40,30 +40,30 @@ function Shape({ id, isSelecting, parentPoint, parentRotation }: ShapeProps) {
40 40
   const style = getShapeStyle(shape.style)
41 41
 
42 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,15 +93,25 @@ const HoverIndicator = styled('path', {
93 93
 })
94 94
 
95 95
 const StyledGroup = styled('g', {
96
+  outline: 'none',
97
+  [`& *[data-shy="true"]`]: {
98
+    opacity: '0',
99
+  },
96 100
   [`& ${HoverIndicator}`]: {
97 101
     opacity: '0',
98 102
   },
99 103
   [`&:hover ${HoverIndicator}`]: {
100 104
     opacity: '0.16',
101 105
   },
106
+  [`&:hover *[data-shy="true"]`]: {
107
+    opacity: '1',
108
+  },
102 109
   variants: {
103 110
     isSelected: {
104 111
       true: {
112
+        [`& *[data-shy="true"]`]: {
113
+          opacity: '1',
114
+        },
105 115
         [`& ${HoverIndicator}`]: {
106 116
           opacity: '0.2',
107 117
         },

+ 6
- 3
lib/shape-utils/group.tsx Voir le fichier

@@ -48,9 +48,12 @@ const group = registerShapeUtils<GroupShape>({
48 48
     const { id, size } = shape
49 49
 
50 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 Voir le fichier

@@ -36,8 +36,10 @@ const rectangle = registerShapeUtils<RectangleShape>({
36 36
           id={id}
37 37
           rx={radius}
38 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 44
       </g>
43 45
     )

+ 8
- 3
state/commands/delete-selected.ts Voir le fichier

@@ -30,6 +30,11 @@ export default function deleteSelected(data: Data) {
30 30
 
31 31
         for (let id of selectedIds) {
32 32
           const shape = page.shapes[id]
33
+          if (!shape) {
34
+            console.error('no shape ' + id)
35
+            continue
36
+          }
37
+
33 38
           if (shape.parentId !== data.currentPageId) {
34 39
             const parent = page.shapes[shape.parentId]
35 40
             getShapeUtils(parent)
@@ -43,10 +48,10 @@ export default function deleteSelected(data: Data) {
43 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 57
         data.selectedIds.clear()

+ 10
- 8
state/sessions/brush-session.ts Voir le fichier

@@ -64,14 +64,16 @@ export function getBrushSnapshot(data: Data) {
64 64
     shapeHitTests: Object.fromEntries(
65 65
       getShapes(current(data))
66 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 Voir le fichier

@@ -1,4 +1,4 @@
1
-import { Data, Edge, Corner } from 'types'
1
+import { Data, Edge, Corner, Bounds } from 'types'
2 2
 import * as vec from 'utils/vec'
3 3
 import BaseSession from './base-session'
4 4
 import commands from 'state/commands'
@@ -8,6 +8,7 @@ import {
8 8
   getBoundsCenter,
9 9
   getBoundsFromPoints,
10 10
   getCommonBounds,
11
+  getDocumentBranch,
11 12
   getPage,
12 13
   getRelativeTransformedBoundingBox,
13 14
   getSelectedShapes,
@@ -115,10 +116,11 @@ export default class TransformSession extends BaseSession {
115 116
 export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
116 117
   const cData = current(data)
117 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 125
   const hasUnlockedShapes = initialShapes.length > 0
124 126
 
@@ -182,3 +184,27 @@ export type TransformSnapshot = ReturnType<typeof getTransformSnapshot>
182 184
 // }
183 185
 
184 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 Voir le fichier

@@ -24,6 +24,7 @@ import {
24 24
   getParentRotation,
25 25
   rotateBounds,
26 26
   getBoundsCenter,
27
+  getDocumentBranch,
27 28
 } from 'utils/utils'
28 29
 import {
29 30
   Data,
@@ -924,6 +925,7 @@ const state = createState({
924 925
       payload: PointerInfo & { target: Corner | Edge }
925 926
     ) {
926 927
       const point = screenToWorld(inputs.pointer.origin, data)
928
+      // session = new Sessions.TransformSession(data, payload.target, point)
927 929
       session =
928 930
         data.selectedIds.size === 1
929 931
           ? new Sessions.TransformSingleSession(data, payload.target, point)
@@ -1385,7 +1387,7 @@ const state = createState({
1385 1387
     },
1386 1388
 
1387 1389
     restoreSavedData(data) {
1388
-      // history.load(data)
1390
+      history.load(data)
1389 1391
     },
1390 1392
 
1391 1393
     clearBoundsRotation(data) {
@@ -1441,16 +1443,22 @@ const state = createState({
1441 1443
       }
1442 1444
 
1443 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 1464
       return commonBounds
@@ -1463,6 +1471,7 @@ const state = createState({
1463 1471
         return currentStyle
1464 1472
       }
1465 1473
       const page = getPage(data)
1474
+
1466 1475
       const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
1467 1476
 
1468 1477
       const commonStyle: ShapeStyles = {} as ShapeStyles

+ 3
- 3
styles/stitches.config.ts Voir le fichier

@@ -6,12 +6,12 @@ const { styled, global, css, theme, getCssString } = createCss({
6 6
   },
7 7
   theme: {
8 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 11
       hint: 'rgba(216, 226, 249, 1.000)',
12 12
       selected: 'rgba(66, 133, 244, 1.000)',
13 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 15
       border: '#aaa',
16 16
       canvas: '#fafafa',
17 17
       panel: '#fefefe',

+ 10
- 0
todo.md Voir le fichier

@@ -0,0 +1,10 @@
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 Voir le fichier

@@ -1623,6 +1623,7 @@ export function updateParents(data: Data, changedShapeIds: string[]) {
1623 1623
 
1624 1624
   for (const parentId of parentToUpdateIds) {
1625 1625
     const parent = shapes[parentId] as GroupShape
1626
+
1626 1627
     getShapeUtils(parent).onChildrenChange(
1627 1628
       parent,
1628 1629
       parent.children.map((id) => shapes[id])

Chargement…
Annuler
Enregistrer