瀏覽代碼

Improves shape props types, improves circle

main
Steve Ruiz 4 年之前
父節點
當前提交
602bd67a74

+ 6
- 13
components/canvas/shapes/circle.tsx 查看文件

@@ -1,25 +1,18 @@
1 1
 import { useSelector } from "state"
2
-import { CircleShape } from "types"
2
+import { CircleShape, ShapeProps } from "types"
3 3
 import ShapeGroup from "./shape-g"
4 4
 
5
-interface BaseCircleProps extends Pick<CircleShape, "radius"> {
6
-  radius: number
7
-  fill?: string
8
-  stroke?: string
9
-  strokeWidth?: number
10
-}
11
-
12 5
 function BaseCircle({
13 6
   radius,
14
-  fill = "#ccc",
7
+  fill = "#999",
15 8
   stroke = "none",
16 9
   strokeWidth = 0,
17
-}: BaseCircleProps) {
10
+}: ShapeProps<CircleShape>) {
18 11
   return (
19 12
     <circle
20
-      cx={strokeWidth}
21
-      cy={strokeWidth}
22
-      r={radius - strokeWidth}
13
+      cx={radius}
14
+      cy={radius}
15
+      r={radius}
23 16
       fill={fill}
24 17
       stroke={stroke}
25 18
       strokeWidth={strokeWidth}

+ 3
- 9
components/canvas/shapes/dot.tsx 查看文件

@@ -1,18 +1,12 @@
1 1
 import { useSelector } from "state"
2
-import { DotShape } from "types"
2
+import { DotShape, ShapeProps } from "types"
3 3
 import ShapeGroup from "./shape-g"
4 4
 
5
-interface BaseCircleProps {
6
-  fill?: string
7
-  stroke?: string
8
-  strokeWidth?: number
9
-}
10
-
11 5
 function BaseDot({
12
-  fill = "#ccc",
6
+  fill = "#999",
13 7
   stroke = "none",
14 8
   strokeWidth = 0,
15
-}: BaseCircleProps) {
9
+}: ShapeProps<DotShape>) {
16 10
   return (
17 11
     <>
18 12
       <circle

+ 18
- 16
components/canvas/shapes/polyline.tsx 查看文件

@@ -1,26 +1,28 @@
1 1
 import { useSelector } from "state"
2
-import { PolylineShape } from "types"
2
+import { PolylineShape, ShapeProps } from "types"
3 3
 import ShapeGroup from "./shape-g"
4 4
 
5
-interface BasePolylineProps extends Pick<PolylineShape, "points"> {
6
-  fill?: string
7
-  stroke?: string
8
-  strokeWidth?: number
9
-}
10
-
11 5
 function BasePolyline({
12 6
   points,
13 7
   fill = "none",
14
-  stroke = "#ccc",
15
-  strokeWidth = 2,
16
-}: BasePolylineProps) {
8
+  stroke = "#999",
9
+  strokeWidth = 1,
10
+}: ShapeProps<PolylineShape>) {
17 11
   return (
18
-    <polyline
19
-      points={points.toString()}
20
-      fill={fill}
21
-      stroke={stroke}
22
-      strokeWidth={strokeWidth}
23
-    />
12
+    <>
13
+      <polyline
14
+        points={points.toString()}
15
+        fill="none"
16
+        stroke="transparent"
17
+        strokeWidth={12}
18
+      />
19
+      <polyline
20
+        points={points.toString()}
21
+        fill={fill}
22
+        stroke={stroke}
23
+        strokeWidth={strokeWidth}
24
+      />
25
+    </>
24 26
   )
25 27
 }
26 28
 

+ 3
- 10
components/canvas/shapes/rectangle.tsx 查看文件

@@ -1,20 +1,13 @@
1 1
 import { useSelector } from "state"
2
-import { RectangleShape } from "types"
2
+import { RectangleShape, ShapeProps } from "types"
3 3
 import ShapeGroup from "./shape-g"
4 4
 
5
-interface BaseRectangleProps extends Pick<RectangleShape, "size"> {
6
-  size: number[]
7
-  fill?: string
8
-  stroke?: string
9
-  strokeWidth?: number
10
-}
11
-
12 5
 function BaseRectangle({
13 6
   size,
14
-  fill = "#ccc",
7
+  fill = "#999",
15 8
   stroke = "none",
16 9
   strokeWidth = 0,
17
-}: BaseRectangleProps) {
10
+}: ShapeProps<RectangleShape>) {
18 11
   return (
19 12
     <rect
20 13
       x={strokeWidth}

+ 38
- 19
components/canvas/shapes/shape-g.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import React from "react"
2 1
 import state from "state"
2
+import React, { useCallback, useRef } from "react"
3 3
 import { getPointerEventInfo } from "utils/utils"
4 4
 
5 5
 export default function ShapeGroup({
@@ -11,27 +11,46 @@ export default function ShapeGroup({
11 11
   children: React.ReactNode
12 12
   point: number[]
13 13
 }) {
14
+  const rGroup = useRef<SVGGElement>(null)
15
+
16
+  const handlePointerDown = useCallback(
17
+    (e: React.PointerEvent) => {
18
+      e.stopPropagation()
19
+      rGroup.current.setPointerCapture(e.pointerId)
20
+      state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
21
+    },
22
+    [id]
23
+  )
24
+
25
+  const handlePointerUp = useCallback(
26
+    (e: React.PointerEvent) => {
27
+      e.stopPropagation()
28
+      rGroup.current.releasePointerCapture(e.pointerId)
29
+      state.send("STOPPED_POINTING_SHAPE", { id, ...getPointerEventInfo(e) })
30
+    },
31
+    [id]
32
+  )
33
+
34
+  const handlePointerEnter = useCallback(
35
+    (e: React.PointerEvent) =>
36
+      state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
37
+    [id]
38
+  )
39
+
40
+  const handlePointerLeave = useCallback(
41
+    (e: React.PointerEvent) =>
42
+      state.send("UNHOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
43
+    [id]
44
+  )
45
+
14 46
   return (
15 47
     <g
48
+      ref={rGroup}
16 49
       transform={`translate(${point})`}
17
-      onPointerDown={(e) =>
18
-        state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
19
-      }
20
-      onPointerUp={(e) =>
21
-        state.send("STOPPED_POINTING_SHAPE", {
22
-          id,
23
-          ...getPointerEventInfo(e),
24
-        })
25
-      }
26
-      onPointerEnter={(e) =>
27
-        state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) })
28
-      }
29
-      onPointerLeave={(e) =>
30
-        state.send("UNHOVERED_SHAPE", {
31
-          id,
32
-          ...getPointerEventInfo(e),
33
-        })
34
-      }
50
+      onPointerDown={handlePointerDown}
51
+      onPointerUp={handlePointerUp}
52
+      onPointerEnter={handlePointerEnter}
53
+      onPointerLeave={handlePointerLeave}
35 54
     >
36 55
       {children}
37 56
     </g>

+ 5
- 2
state/sessions/brush-session.ts 查看文件

@@ -82,8 +82,11 @@ export default class BrushSession extends BaseSession {
82 82
                 shape,
83 83
                 test: (brushBounds: Bounds) =>
84 84
                   boundsContained(bounds, brushBounds) ||
85
-                  intersectCircleBounds(shape.point, shape.radius, brushBounds)
86
-                    .length > 0,
85
+                  intersectCircleBounds(
86
+                    vec.addScalar(shape.point, shape.radius),
87
+                    shape.radius,
88
+                    brushBounds
89
+                  ).length > 0,
87 90
               }
88 91
             }
89 92
             case ShapeType.Rectangle: {

+ 30
- 2
state/state.ts 查看文件

@@ -32,11 +32,23 @@ const state = createState({
32 32
     selecting: {
33 33
       on: {
34 34
         POINTED_CANVAS: { to: "brushSelecting" },
35
+        POINTED_SHAPE: [
36
+          "setPointedId",
37
+          {
38
+            if: "isPressingShiftKey",
39
+            then: {
40
+              if: "isPointedShapeSelected",
41
+              do: "pullPointedIdFromSelectedIds",
42
+              else: "pushPointedIdToSelectedIds",
43
+            },
44
+            else: ["clearSelectedIds", "pushPointedIdToSelectedIds"],
45
+          },
46
+        ],
35 47
       },
36 48
     },
37 49
     brushSelecting: {
38 50
       onEnter: [
39
-        { unless: "isPressingShiftKey", do: "clearSelection" },
51
+        { unless: "isPressingShiftKey", do: "clearSelectedIds" },
40 52
         "startBrushSession",
41 53
       ],
42 54
       on: {
@@ -48,6 +60,9 @@ const state = createState({
48 60
     },
49 61
   },
50 62
   conditions: {
63
+    isPointedShapeSelected(data) {
64
+      return data.selectedIds.includes(data.pointedId)
65
+    },
51 66
     isPressingShiftKey(data, payload: { shiftKey: boolean }) {
52 67
       return payload.shiftKey
53 68
     },
@@ -71,9 +86,22 @@ const state = createState({
71 86
       session.update(data, screenToWorld(payload.point, data))
72 87
     },
73 88
     // Selection
74
-    clearSelection(data) {
89
+    setPointedId(data, payload: { id: string }) {
90
+      data.pointedId = payload.id
91
+    },
92
+    clearPointedId(data) {
93
+      data.pointedId = undefined
94
+    },
95
+    clearSelectedIds(data) {
75 96
       data.selectedIds = []
76 97
     },
98
+    pullPointedIdFromSelectedIds(data) {
99
+      const { selectedIds, pointedId } = data
100
+      selectedIds.splice(selectedIds.indexOf(pointedId, 1))
101
+    },
102
+    pushPointedIdToSelectedIds(data) {
103
+      data.selectedIds.push(data.pointedId)
104
+    },
77 105
     // Camera
78 106
     zoomCamera(data, payload: { delta: number; point: number[] }) {
79 107
       const { camera } = data

+ 16
- 0
types.ts 查看文件

@@ -106,3 +106,19 @@ export interface Shapes extends Record<ShapeType, Shape> {
106 106
   [ShapeType.Polyline]: PolylineShape
107 107
   [ShapeType.Rectangle]: RectangleShape
108 108
 }
109
+
110
+export interface BaseShapeStyles {
111
+  fill: string
112
+  stroke: string
113
+  strokeWidth: number
114
+}
115
+
116
+export type Difference<A, B> = A extends B ? never : A
117
+
118
+export type ShapeSpecificProps<T extends Shape> = Pick<
119
+  T,
120
+  Difference<keyof T, keyof BaseShape>
121
+>
122
+
123
+export type ShapeProps<T extends Shape> = Partial<BaseShapeStyles> &
124
+  ShapeSpecificProps<T>

+ 18
- 0
utils/vec.ts 查看文件

@@ -26,6 +26,15 @@ export function add(A: number[], B: number[]) {
26 26
   return [A[0] + B[0], A[1] + B[1]]
27 27
 }
28 28
 
29
+/**
30
+ * Add scalar to vector.
31
+ * @param A
32
+ * @param B
33
+ */
34
+export function addScalar(A: number[], n: number) {
35
+  return [A[0] + n, A[1] + n]
36
+}
37
+
29 38
 /**
30 39
  * Subtract vectors.
31 40
  * @param A
@@ -35,6 +44,15 @@ export function sub(A: number[], B: number[]) {
35 44
   return [A[0] - B[0], A[1] - B[1]]
36 45
 }
37 46
 
47
+/**
48
+ * Subtract scalar from vector.
49
+ * @param A
50
+ * @param B
51
+ */
52
+export function subScalar(A: number[], n: number) {
53
+  return [A[0] - n, A[1] - n]
54
+}
55
+
38 56
 /**
39 57
  * Get the vector from vectors A to B.
40 58
  * @param A

Loading…
取消
儲存