Selaa lähdekoodia

Greatly simplifies shapes

main
Steve Ruiz 4 vuotta sitten
vanhempi
commit
3d52d9e9d2

+ 106
- 18
components/canvas/shape.tsx Näytä tiedosto

@@ -1,35 +1,123 @@
1
+import React, { useCallback, useRef } from "react"
2
+import state, { useSelector } from "state"
3
+import styled from "styles"
4
+import { getPointerEventInfo } from "utils/utils"
1 5
 import { memo } from "react"
2
-import { useSelector } from "state"
3
-import { ShapeType } from "types"
4
-import Circle from "./shapes/circle"
5
-import Dot from "./shapes/dot"
6
-import Polyline from "./shapes/polyline"
7
-import Rectangle from "./shapes/rectangle"
6
+import Shapes from "lib/shapes"
8 7
 
9 8
 /*
10 9
 Gets the shape from the current page's shapes, using the
11 10
 provided ID. Depending on the shape's type, return the
12 11
 component for that type.
12
+
13
+This component takes an SVG shape as its children. It handles
14
+events for the shape as well as provides indicators for hover
15
+ and selected status
13 16
 */
14 17
 
15 18
 function Shape({ id }: { id: string }) {
19
+  const rGroup = useRef<SVGGElement>(null)
20
+
16 21
   const shape = useSelector((state) => {
17 22
     const { currentPageId, document } = state.data
18 23
     return document.pages[currentPageId].shapes[id]
19 24
   })
20 25
 
21
-  switch (shape.type) {
22
-    case ShapeType.Dot:
23
-      return <Dot {...shape} />
24
-    case ShapeType.Circle:
25
-      return <Circle {...shape} />
26
-    case ShapeType.Rectangle:
27
-      return <Rectangle {...shape} />
28
-    case ShapeType.Polyline:
29
-      return <Polyline {...shape} />
30
-    default:
31
-      return null
32
-  }
26
+  const isSelected = useSelector((state) => state.values.selectedIds.has(id))
27
+
28
+  const handlePointerDown = useCallback(
29
+    (e: React.PointerEvent) => {
30
+      e.stopPropagation()
31
+      rGroup.current.setPointerCapture(e.pointerId)
32
+      state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
33
+    },
34
+    [id]
35
+  )
36
+
37
+  const handlePointerUp = useCallback(
38
+    (e: React.PointerEvent) => {
39
+      e.stopPropagation()
40
+      rGroup.current.releasePointerCapture(e.pointerId)
41
+      state.send("STOPPED_POINTING_SHAPE", { id, ...getPointerEventInfo(e) })
42
+    },
43
+    [id]
44
+  )
45
+
46
+  const handlePointerEnter = useCallback(
47
+    (e: React.PointerEvent) =>
48
+      state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
49
+    [id]
50
+  )
51
+
52
+  const handlePointerLeave = useCallback(
53
+    (e: React.PointerEvent) =>
54
+      state.send("UNHOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
55
+    [id]
56
+  )
57
+
58
+  return (
59
+    <StyledGroup
60
+      ref={rGroup}
61
+      isSelected={isSelected}
62
+      transform={`translate(${shape.point})`}
63
+      onPointerDown={handlePointerDown}
64
+      onPointerUp={handlePointerUp}
65
+      onPointerEnter={handlePointerEnter}
66
+      onPointerLeave={handlePointerLeave}
67
+    >
68
+      <defs>
69
+        {Shapes[shape.type] ? Shapes[shape.type].render(shape) : null}
70
+      </defs>
71
+      <HoverIndicator as="use" xlinkHref={"#" + id} />
72
+      <use xlinkHref={"#" + id} {...shape.style} />
73
+      <Indicator as="use" xlinkHref={"#" + id} />
74
+    </StyledGroup>
75
+  )
33 76
 }
34 77
 
78
+const Indicator = styled("path", {
79
+  fill: "none",
80
+  stroke: "transparent",
81
+  strokeWidth: "max(1, calc(2 / var(--camera-zoom)))",
82
+  pointerEvents: "none",
83
+  strokeLineCap: "round",
84
+  strokeLinejoin: "round",
85
+})
86
+
87
+const HoverIndicator = styled("path", {
88
+  fill: "none",
89
+  stroke: "transparent",
90
+  strokeWidth: "max(1, calc(8 / var(--camera-zoom)))",
91
+  pointerEvents: "all",
92
+  strokeLinecap: "round",
93
+  strokeLinejoin: "round",
94
+})
95
+
96
+const StyledGroup = styled("g", {
97
+  [`& ${HoverIndicator}`]: {
98
+    opacity: "0",
99
+  },
100
+  variants: {
101
+    isSelected: {
102
+      true: {
103
+        [`& ${Indicator}`]: {
104
+          stroke: "$selected",
105
+        },
106
+        [`&:hover ${HoverIndicator}`]: {
107
+          opacity: "1",
108
+          stroke: "$hint",
109
+        },
110
+      },
111
+      false: {
112
+        [`&:hover ${HoverIndicator}`]: {
113
+          opacity: "1",
114
+          stroke: "$hint",
115
+        },
116
+      },
117
+    },
118
+  },
119
+})
120
+
121
+export { Indicator, HoverIndicator }
122
+
35 123
 export default memo(Shape)

+ 0
- 33
components/canvas/shapes/circle.tsx Näytä tiedosto

@@ -1,33 +0,0 @@
1
-import { CircleShape, ShapeProps } from "types"
2
-import { Indicator, HoverIndicator } from "./indicator"
3
-import ShapeGroup from "./shape-group"
4
-
5
-function BaseCircle({
6
-  radius,
7
-  fill = "#999",
8
-  stroke = "none",
9
-  strokeWidth = 0,
10
-}: ShapeProps<CircleShape>) {
11
-  return (
12
-    <>
13
-      <HoverIndicator as="circle" cx={radius} cy={radius} r={radius} />
14
-      <circle
15
-        cx={radius}
16
-        cy={radius}
17
-        r={radius}
18
-        fill={fill}
19
-        stroke={stroke}
20
-        strokeWidth={strokeWidth}
21
-      />
22
-      <Indicator as="circle" cx={radius} cy={radius} r={radius} />
23
-    </>
24
-  )
25
-}
26
-
27
-export default function Circle({ id, point, radius }: CircleShape) {
28
-  return (
29
-    <ShapeGroup id={id} point={point}>
30
-      <BaseCircle radius={radius} />
31
-    </ShapeGroup>
32
-  )
33
-}

+ 0
- 34
components/canvas/shapes/dot.tsx Näytä tiedosto

@@ -1,34 +0,0 @@
1
-import { Indicator, HoverIndicator } from "./indicator"
2
-import { DotShape, ShapeProps } from "types"
3
-import ShapeGroup from "./shape-group"
4
-
5
-const dotRadius = 4
6
-
7
-function BaseDot({
8
-  fill = "#999",
9
-  stroke = "none",
10
-  strokeWidth = 0,
11
-}: ShapeProps<DotShape>) {
12
-  return (
13
-    <>
14
-      <HoverIndicator as="circle" cx={dotRadius} cy={dotRadius} r={dotRadius} />
15
-      <circle
16
-        cx={dotRadius}
17
-        cy={dotRadius}
18
-        r={dotRadius}
19
-        fill={fill}
20
-        stroke={stroke}
21
-        strokeWidth={strokeWidth}
22
-      />
23
-      <Indicator as="circle" cx={dotRadius} cy={dotRadius} r={dotRadius} />
24
-    </>
25
-  )
26
-}
27
-
28
-export default function Dot({ id, point }: DotShape) {
29
-  return (
30
-    <ShapeGroup id={id} point={point}>
31
-      <BaseDot />
32
-    </ShapeGroup>
33
-  )
34
-}

+ 0
- 21
components/canvas/shapes/indicator.tsx Näytä tiedosto

@@ -1,21 +0,0 @@
1
-import styled from "styles"
2
-
3
-const Indicator = styled("path", {
4
-  fill: "none",
5
-  stroke: "transparent",
6
-  strokeWidth: "max(1, calc(2 / var(--camera-zoom)))",
7
-  pointerEvents: "none",
8
-  strokeLineCap: "round",
9
-  strokeLinejoin: "round",
10
-})
11
-
12
-const HoverIndicator = styled("path", {
13
-  fill: "none",
14
-  stroke: "transparent",
15
-  strokeWidth: "max(1, calc(8 / var(--camera-zoom)))",
16
-  pointerEvents: "all",
17
-  strokeLinecap: "round",
18
-  strokeLinejoin: "round",
19
-})
20
-
21
-export { Indicator, HoverIndicator }

+ 0
- 33
components/canvas/shapes/polyline.tsx Näytä tiedosto

@@ -1,33 +0,0 @@
1
-import { PolylineShape, ShapeProps } from "types"
2
-import { Indicator, HoverIndicator } from "./indicator"
3
-import ShapeGroup from "./shape-group"
4
-
5
-function BasePolyline({
6
-  points,
7
-  fill = "none",
8
-  stroke = "#999",
9
-  strokeWidth = 1,
10
-}: ShapeProps<PolylineShape>) {
11
-  return (
12
-    <>
13
-      <HoverIndicator as="polyline" points={points.toString()} />
14
-      <polyline
15
-        points={points.toString()}
16
-        fill={fill}
17
-        stroke={stroke}
18
-        strokeWidth={strokeWidth}
19
-        strokeLinecap="round"
20
-        strokeLinejoin="round"
21
-      />
22
-      <Indicator as="polyline" points={points.toString()} />
23
-    </>
24
-  )
25
-}
26
-
27
-export default function Polyline({ id, point, points }: PolylineShape) {
28
-  return (
29
-    <ShapeGroup id={id} point={point}>
30
-      <BasePolyline points={points} />
31
-    </ShapeGroup>
32
-  )
33
-}

+ 0
- 32
components/canvas/shapes/rectangle.tsx Näytä tiedosto

@@ -1,32 +0,0 @@
1
-import { RectangleShape, ShapeProps } from "types"
2
-import { HoverIndicator, Indicator } from "./indicator"
3
-import ShapeGroup from "./shape-group"
4
-
5
-function BaseRectangle({
6
-  size,
7
-  fill = "#999",
8
-  stroke = "none",
9
-  strokeWidth = 0,
10
-}: ShapeProps<RectangleShape>) {
11
-  return (
12
-    <>
13
-      <HoverIndicator as="rect" width={size[0]} height={size[1]} />
14
-      <rect
15
-        width={size[0]}
16
-        height={size[1]}
17
-        fill={fill}
18
-        stroke={stroke}
19
-        strokeWidth={strokeWidth}
20
-      />
21
-      <Indicator as="rect" width={size[0]} height={size[1]} />
22
-    </>
23
-  )
24
-}
25
-
26
-export default function Rectangle({ id, point, size }: RectangleShape) {
27
-  return (
28
-    <ShapeGroup id={id} point={point}>
29
-      <BaseRectangle size={size} />
30
-    </ShapeGroup>
31
-  )
32
-}

+ 0
- 87
components/canvas/shapes/shape-group.tsx Näytä tiedosto

@@ -1,87 +0,0 @@
1
-import state, { useSelector } from "state"
2
-import React, { useCallback, useRef } from "react"
3
-import { getPointerEventInfo } from "utils/utils"
4
-import { Indicator, HoverIndicator } from "./indicator"
5
-import styled from "styles"
6
-
7
-export default function ShapeGroup({
8
-  id,
9
-  children,
10
-  point,
11
-}: {
12
-  id: string
13
-  children: React.ReactNode
14
-  point: number[]
15
-}) {
16
-  const rGroup = useRef<SVGGElement>(null)
17
-  const isSelected = useSelector((state) => state.values.selectedIds.has(id))
18
-
19
-  const handlePointerDown = useCallback(
20
-    (e: React.PointerEvent) => {
21
-      e.stopPropagation()
22
-      rGroup.current.setPointerCapture(e.pointerId)
23
-      state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
24
-    },
25
-    [id]
26
-  )
27
-
28
-  const handlePointerUp = useCallback(
29
-    (e: React.PointerEvent) => {
30
-      e.stopPropagation()
31
-      rGroup.current.releasePointerCapture(e.pointerId)
32
-      state.send("STOPPED_POINTING_SHAPE", { id, ...getPointerEventInfo(e) })
33
-    },
34
-    [id]
35
-  )
36
-
37
-  const handlePointerEnter = useCallback(
38
-    (e: React.PointerEvent) =>
39
-      state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
40
-    [id]
41
-  )
42
-
43
-  const handlePointerLeave = useCallback(
44
-    (e: React.PointerEvent) =>
45
-      state.send("UNHOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
46
-    [id]
47
-  )
48
-
49
-  return (
50
-    <StyledGroup
51
-      ref={rGroup}
52
-      isSelected={isSelected}
53
-      transform={`translate(${point})`}
54
-      onPointerDown={handlePointerDown}
55
-      onPointerUp={handlePointerUp}
56
-      onPointerEnter={handlePointerEnter}
57
-      onPointerLeave={handlePointerLeave}
58
-    >
59
-      {children}
60
-    </StyledGroup>
61
-  )
62
-}
63
-
64
-const StyledGroup = styled("g", {
65
-  [`& ${HoverIndicator}`]: {
66
-    opacity: "0",
67
-  },
68
-  variants: {
69
-    isSelected: {
70
-      true: {
71
-        [`& ${Indicator}`]: {
72
-          stroke: "$selected",
73
-        },
74
-        [`&:hover ${HoverIndicator}`]: {
75
-          opacity: "1",
76
-          stroke: "$hint",
77
-        },
78
-      },
79
-      false: {
80
-        [`&:hover ${HoverIndicator}`]: {
81
-          opacity: "1",
82
-          stroke: "$hint",
83
-        },
84
-      },
85
-    },
86
-  },
87
-})

+ 1
- 3
components/status-bar.tsx Näytä tiedosto

@@ -12,7 +12,7 @@ export default function StatusBar() {
12 12
 
13 13
   return (
14 14
     <StatusBarContainer>
15
-      <States>{active.join(" | ")}</States>
15
+      <Section>{active.join(" | ")}</Section>
16 16
       <Section>| {log}</Section>
17 17
       <Section title="Renders | Time">
18 18
         {count} | {time.toString().padStart(3, "0")}
@@ -45,8 +45,6 @@ const Section = styled("div", {
45 45
   overflow: "hidden",
46 46
 })
47 47
 
48
-const States = styled("div", {})
49
-
50 48
 function useRenderCount() {
51 49
   const rTime = useRef(Date.now())
52 50
   const rCounter = useRef(0)

+ 64
- 0
lib/shapes/circle.tsx Näytä tiedosto

@@ -0,0 +1,64 @@
1
+import { v4 as uuid } from "uuid"
2
+import * as vec from "utils/vec"
3
+import { BaseLibShape, CircleShape, ShapeType } from "types"
4
+
5
+const Circle: BaseLibShape<ShapeType.Circle> = {
6
+  create(props): CircleShape {
7
+    return {
8
+      id: uuid(),
9
+      type: ShapeType.Circle,
10
+      name: "Circle",
11
+      parentId: "page0",
12
+      childIndex: 0,
13
+      point: [0, 0],
14
+      radius: 20,
15
+      rotation: 0,
16
+      style: {},
17
+      ...props,
18
+    }
19
+  },
20
+
21
+  render({ id, radius }) {
22
+    return <circle id={id} cx={radius} cy={radius} r={radius} />
23
+  },
24
+
25
+  getBounds(shape) {
26
+    const {
27
+      point: [cx, cy],
28
+      radius,
29
+    } = shape
30
+
31
+    return {
32
+      minX: cx,
33
+      maxX: cx + radius * 2,
34
+      minY: cy,
35
+      maxY: cy + radius * 2,
36
+      width: radius * 2,
37
+      height: radius * 2,
38
+    }
39
+  },
40
+
41
+  hitTest(shape, test) {
42
+    return (
43
+      vec.dist(vec.addScalar(shape.point, shape.radius), test) < shape.radius
44
+    )
45
+  },
46
+
47
+  rotate(shape) {
48
+    return shape
49
+  },
50
+
51
+  translate(shape) {
52
+    return shape
53
+  },
54
+
55
+  scale(shape, scale: number) {
56
+    return shape
57
+  },
58
+
59
+  stretch(shape, scaleX: number, scaleY: number) {
60
+    return shape
61
+  },
62
+}
63
+
64
+export default Circle

+ 60
- 0
lib/shapes/dot.tsx Näytä tiedosto

@@ -0,0 +1,60 @@
1
+import { v4 as uuid } from "uuid"
2
+import * as vec from "utils/vec"
3
+import { BaseLibShape, DotShape, ShapeType } from "types"
4
+
5
+const Dot: BaseLibShape<ShapeType.Dot> = {
6
+  create(props): DotShape {
7
+    return {
8
+      id: uuid(),
9
+      type: ShapeType.Dot,
10
+      name: "Dot",
11
+      parentId: "page0",
12
+      childIndex: 0,
13
+      point: [0, 0],
14
+      rotation: 0,
15
+      style: {},
16
+      ...props,
17
+    }
18
+  },
19
+
20
+  render({ id }) {
21
+    return <circle id={id} cx={4} cy={4} r={4} />
22
+  },
23
+
24
+  getBounds(shape) {
25
+    const {
26
+      point: [cx, cy],
27
+    } = shape
28
+
29
+    return {
30
+      minX: cx,
31
+      maxX: cx + 4,
32
+      minY: cy,
33
+      maxY: cy + 4,
34
+      width: 4,
35
+      height: 4,
36
+    }
37
+  },
38
+
39
+  hitTest(shape, test) {
40
+    return vec.dist(shape.point, test) < 4
41
+  },
42
+
43
+  rotate(shape) {
44
+    return shape
45
+  },
46
+
47
+  translate(shape) {
48
+    return shape
49
+  },
50
+
51
+  scale(shape, scale: number) {
52
+    return shape
53
+  },
54
+
55
+  stretch(shape, scaleX: number, scaleY: number) {
56
+    return shape
57
+  },
58
+}
59
+
60
+export default Dot

+ 13
- 0
lib/shapes/index.tsx Näytä tiedosto

@@ -0,0 +1,13 @@
1
+import Circle from "./circle"
2
+import Dot from "./dot"
3
+import Polyline from "./polyline"
4
+import Rectangle from "./rectangle"
5
+
6
+import { ShapeType } from "types"
7
+
8
+export default {
9
+  [ShapeType.Circle]: Circle,
10
+  [ShapeType.Dot]: Dot,
11
+  [ShapeType.Polyline]: Polyline,
12
+  [ShapeType.Rectangle]: Rectangle,
13
+}

+ 69
- 0
lib/shapes/polyline.tsx Näytä tiedosto

@@ -0,0 +1,69 @@
1
+import { v4 as uuid } from "uuid"
2
+import * as vec from "utils/vec"
3
+import { BaseLibShape, PolylineShape, ShapeType } from "types"
4
+
5
+const Polyline: BaseLibShape<ShapeType.Polyline> = {
6
+  create(props): PolylineShape {
7
+    return {
8
+      id: uuid(),
9
+      type: ShapeType.Polyline,
10
+      name: "Polyline",
11
+      parentId: "page0",
12
+      childIndex: 0,
13
+      point: [0, 0],
14
+      points: [[0, 0]],
15
+      rotation: 0,
16
+      style: {},
17
+      ...props,
18
+    }
19
+  },
20
+
21
+  render({ id, points }) {
22
+    return <polyline id={id} points={points.toString()} />
23
+  },
24
+
25
+  getBounds(shape) {
26
+    let minX = 0
27
+    let minY = 0
28
+    let maxX = 0
29
+    let maxY = 0
30
+
31
+    for (let [x, y] of shape.points) {
32
+      minX = Math.min(x, minX)
33
+      minY = Math.min(y, minY)
34
+      maxX = Math.max(x, maxX)
35
+      maxY = Math.max(y, maxY)
36
+    }
37
+
38
+    return {
39
+      minX: minX + shape.point[0],
40
+      minY: minY + shape.point[1],
41
+      maxX: maxX + shape.point[0],
42
+      maxY: maxY + shape.point[1],
43
+      width: maxX - minX,
44
+      height: maxY - minY,
45
+    }
46
+  },
47
+
48
+  hitTest(shape) {
49
+    return true
50
+  },
51
+
52
+  rotate(shape) {
53
+    return shape
54
+  },
55
+
56
+  translate(shape) {
57
+    return shape
58
+  },
59
+
60
+  scale(shape, scale: number) {
61
+    return shape
62
+  },
63
+
64
+  stretch(shape, scaleX: number, scaleY: number) {
65
+    return shape
66
+  },
67
+}
68
+
69
+export default Polyline

+ 62
- 0
lib/shapes/rectangle.tsx Näytä tiedosto

@@ -0,0 +1,62 @@
1
+import { v4 as uuid } from "uuid"
2
+import * as vec from "utils/vec"
3
+import { BaseLibShape, RectangleShape, ShapeType } from "types"
4
+
5
+const Rectangle: BaseLibShape<ShapeType.Rectangle> = {
6
+  create(props): RectangleShape {
7
+    return {
8
+      id: uuid(),
9
+      type: ShapeType.Rectangle,
10
+      name: "Rectangle",
11
+      parentId: "page0",
12
+      childIndex: 0,
13
+      point: [0, 0],
14
+      size: [1, 1],
15
+      rotation: 0,
16
+      style: {},
17
+      ...props,
18
+    }
19
+  },
20
+
21
+  render({ id, size }) {
22
+    return <rect id={id} width={size[0]} height={size[1]} />
23
+  },
24
+
25
+  getBounds(shape) {
26
+    const {
27
+      point: [x, y],
28
+      size: [width, height],
29
+    } = shape
30
+
31
+    return {
32
+      minX: x,
33
+      maxX: x + width,
34
+      minY: y,
35
+      maxY: y + height,
36
+      width,
37
+      height,
38
+    }
39
+  },
40
+
41
+  hitTest(shape) {
42
+    return true
43
+  },
44
+
45
+  rotate(shape) {
46
+    return shape
47
+  },
48
+
49
+  translate(shape) {
50
+    return shape
51
+  },
52
+
53
+  scale(shape, scale: number) {
54
+    return shape
55
+  },
56
+
57
+  stretch(shape, scaleX: number, scaleY: number) {
58
+    return shape
59
+  },
60
+}
61
+
62
+export default Rectangle

+ 38
- 29
state/data.ts Näytä tiedosto

@@ -1,4 +1,5 @@
1 1
 import { Data, ShapeType } from "types"
2
+import Shapes from "lib/shapes"
2 3
 
3 4
 export const defaultDocument: Data["document"] = {
4 5
   pages: {
@@ -8,31 +9,32 @@ export const defaultDocument: Data["document"] = {
8 9
       name: "Page 0",
9 10
       childIndex: 0,
10 11
       shapes: {
11
-        shape0: {
12
+        shape3: Shapes[ShapeType.Dot].create({
13
+          id: "shape3",
14
+          name: "Shape 3",
15
+          childIndex: 3,
16
+          point: [500, 100],
17
+          style: {
18
+            fill: "#aaa",
19
+            stroke: "#777",
20
+            strokeWidth: 1,
21
+          },
22
+        }),
23
+        shape0: Shapes[ShapeType.Circle].create({
12 24
           id: "shape0",
13
-          type: ShapeType.Circle,
14 25
           name: "Shape 0",
15
-          parentId: "page0",
16 26
           childIndex: 1,
17 27
           point: [100, 100],
18 28
           radius: 50,
19
-          rotation: 0,
20
-        },
21
-        shape1: {
22
-          id: "shape1",
23
-          type: ShapeType.Rectangle,
24
-          name: "Shape 1",
25
-          parentId: "page0",
26
-          childIndex: 1,
27
-          point: [300, 300],
28
-          size: [200, 200],
29
-          rotation: 0,
30
-        },
31
-        shape2: {
29
+          style: {
30
+            fill: "#aaa",
31
+            stroke: "#777",
32
+            strokeWidth: 1,
33
+          },
34
+        }),
35
+        shape2: Shapes[ShapeType.Polyline].create({
32 36
           id: "shape2",
33
-          type: ShapeType.Polyline,
34 37
           name: "Shape 2",
35
-          parentId: "page0",
36 38
           childIndex: 2,
37 39
           point: [200, 600],
38 40
           points: [
@@ -40,17 +42,24 @@ export const defaultDocument: Data["document"] = {
40 42
             [75, 200],
41 43
             [100, 50],
42 44
           ],
43
-          rotation: 0,
44
-        },
45
-        shape3: {
46
-          id: "shape3",
47
-          type: ShapeType.Dot,
48
-          name: "Shape 3",
49
-          parentId: "page0",
50
-          childIndex: 3,
51
-          point: [500, 100],
52
-          rotation: 0,
53
-        },
45
+          style: {
46
+            fill: "none",
47
+            stroke: "#777",
48
+            strokeWidth: 2,
49
+          },
50
+        }),
51
+        shape1: Shapes[ShapeType.Rectangle].create({
52
+          id: "shape1",
53
+          name: "Shape 1",
54
+          childIndex: 1,
55
+          point: [300, 300],
56
+          size: [200, 200],
57
+          style: {
58
+            fill: "#aaa",
59
+            stroke: "#777",
60
+            strokeWidth: 1,
61
+          },
62
+        }),
54 63
       },
55 64
     },
56 65
   },

+ 6
- 6
state/sessions/brush-session.ts Näytä tiedosto

@@ -1,7 +1,7 @@
1 1
 import { current } from "immer"
2
-import { Bounds, Data, Shape, ShapeType } from "types"
2
+import { Bounds, Data, ShapeType } from "types"
3 3
 import BaseSession from "./base-session"
4
-import shapeUtils from "utils/shape-utils"
4
+import Shapes from "lib/shapes"
5 5
 import { getBoundsFromPoints } from "utils/utils"
6 6
 import * as vec from "utils/vec"
7 7
 import {
@@ -72,7 +72,7 @@ export default class BrushSession extends BaseSession {
72 72
         .map((shape) => {
73 73
           switch (shape.type) {
74 74
             case ShapeType.Dot: {
75
-              const bounds = shapeUtils[shape.type].getBounds(shape)
75
+              const bounds = Shapes[shape.type].getBounds(shape)
76 76
 
77 77
               return {
78 78
                 id: shape.id,
@@ -82,7 +82,7 @@ export default class BrushSession extends BaseSession {
82 82
               }
83 83
             }
84 84
             case ShapeType.Circle: {
85
-              const bounds = shapeUtils[shape.type].getBounds(shape)
85
+              const bounds = Shapes[shape.type].getBounds(shape)
86 86
 
87 87
               return {
88 88
                 id: shape.id,
@@ -96,7 +96,7 @@ export default class BrushSession extends BaseSession {
96 96
               }
97 97
             }
98 98
             case ShapeType.Rectangle: {
99
-              const bounds = shapeUtils[shape.type].getBounds(shape)
99
+              const bounds = Shapes[shape.type].getBounds(shape)
100 100
 
101 101
               return {
102 102
                 id: shape.id,
@@ -106,7 +106,7 @@ export default class BrushSession extends BaseSession {
106 106
               }
107 107
             }
108 108
             case ShapeType.Polyline: {
109
-              const bounds = shapeUtils[shape.type].getBounds(shape)
109
+              const bounds = Shapes[shape.type].getBounds(shape)
110 110
               const points = shape.points.map((point) =>
111 111
                 vec.add(point, shape.point)
112 112
               )

+ 15
- 10
types.ts Näytä tiedosto

@@ -1,3 +1,5 @@
1
+import React from "react"
2
+
1 3
 export interface Data {
2 4
   camera: {
3 5
     point: number[]
@@ -26,7 +28,7 @@ export enum ShapeType {
26 28
   Ellipse = "ellipse",
27 29
   Line = "line",
28 30
   Ray = "ray",
29
-  Polyline = "Polyline",
31
+  Polyline = "polyline",
30 32
   Rectangle = "rectangle",
31 33
   // Glob = "glob",
32 34
   // Spline = "spline",
@@ -42,6 +44,7 @@ export interface BaseShape {
42 44
   name: string
43 45
   point: number[]
44 46
   rotation: 0
47
+  style: Partial<React.SVGProps<SVGUseElement>>
45 48
 }
46 49
 
47 50
 export interface DotShape extends BaseShape {
@@ -107,12 +110,6 @@ export interface Shapes extends Record<ShapeType, Shape> {
107 110
   [ShapeType.Rectangle]: RectangleShape
108 111
 }
109 112
 
110
-export interface BaseShapeStyles {
111
-  fill: string
112
-  stroke: string
113
-  strokeWidth: number
114
-}
115
-
116 113
 export type Difference<A, B> = A extends B ? never : A
117 114
 
118 115
 export type ShapeSpecificProps<T extends Shape> = Pick<
@@ -120,7 +117,15 @@ export type ShapeSpecificProps<T extends Shape> = Pick<
120 117
   Difference<keyof T, keyof BaseShape>
121 118
 >
122 119
 
123
-export type ShapeProps<T extends Shape> = Partial<BaseShapeStyles> &
124
-  ShapeSpecificProps<T> & { id?: Shape["id"] }
125
-
126 120
 export type ShapeIndicatorProps<T extends Shape> = ShapeSpecificProps<T>
121
+
122
+export type BaseLibShape<K extends ShapeType> = {
123
+  create(props: Partial<Shapes[K]>): Shapes[K]
124
+  getBounds(shape: Shapes[K]): Bounds
125
+  hitTest(shape: Shapes[K], test: number[]): boolean
126
+  rotate(shape: Shapes[K]): Shapes[K]
127
+  translate(shape: Shapes[K]): Shapes[K]
128
+  scale(shape: Shapes[K], scale: number): Shapes[K]
129
+  stretch(shape: Shapes[K], scaleX: number, scaleY: number): Shapes[K]
130
+  render(shape: Shapes[K]): JSX.Element
131
+}

+ 0
- 302
utils/shape-utils.ts Näytä tiedosto

@@ -1,302 +0,0 @@
1
-import {
2
-  boundsCollide,
3
-  boundsContain,
4
-  pointInBounds,
5
-} from "state/sessions/brush-session"
6
-import { Bounds, ShapeType, Shapes } from "types"
7
-import { intersectCircleBounds } from "./intersections"
8
-import * as vec from "./vec"
9
-
10
-type BaseShapeUtils<K extends ShapeType> = {
11
-  getBounds(shape: Shapes[K]): Bounds
12
-  hitTest(shape: Shapes[K], test: number[]): boolean
13
-  rotate(shape: Shapes[K]): Shapes[K]
14
-  translate(shape: Shapes[K]): Shapes[K]
15
-  scale(shape: Shapes[K], scale: number): Shapes[K]
16
-  stretch(shape: Shapes[K], scaleX: number, scaleY: number): Shapes[K]
17
-}
18
-
19
-/* ----------------------- Dot ---------------------- */
20
-
21
-const DotUtils: BaseShapeUtils<ShapeType.Dot> = {
22
-  getBounds(shape) {
23
-    const {
24
-      point: [cx, cy],
25
-    } = shape
26
-
27
-    return {
28
-      minX: cx,
29
-      maxX: cx + 4,
30
-      minY: cy,
31
-      maxY: cy + 4,
32
-      width: 4,
33
-      height: 4,
34
-    }
35
-  },
36
-
37
-  hitTest(shape, test) {
38
-    return vec.dist(shape.point, test) < 4
39
-  },
40
-
41
-  rotate(shape) {
42
-    return shape
43
-  },
44
-
45
-  translate(shape) {
46
-    return shape
47
-  },
48
-
49
-  scale(shape, scale: number) {
50
-    return shape
51
-  },
52
-
53
-  stretch(shape, scaleX: number, scaleY: number) {
54
-    return shape
55
-  },
56
-}
57
-
58
-/* --------------------- Circle --------------------- */
59
-
60
-const CircleUtils: BaseShapeUtils<ShapeType.Circle> = {
61
-  getBounds(shape) {
62
-    const {
63
-      point: [cx, cy],
64
-      radius,
65
-    } = shape
66
-
67
-    return {
68
-      minX: cx,
69
-      maxX: cx + radius * 2,
70
-      minY: cy,
71
-      maxY: cy + radius * 2,
72
-      width: radius * 2,
73
-      height: radius * 2,
74
-    }
75
-  },
76
-
77
-  hitTest(shape, test) {
78
-    return (
79
-      vec.dist(vec.addScalar(shape.point, shape.radius), test) < shape.radius
80
-    )
81
-  },
82
-
83
-  rotate(shape) {
84
-    return shape
85
-  },
86
-
87
-  translate(shape) {
88
-    return shape
89
-  },
90
-
91
-  scale(shape, scale: number) {
92
-    return shape
93
-  },
94
-
95
-  stretch(shape, scaleX: number, scaleY: number) {
96
-    return shape
97
-  },
98
-}
99
-
100
-/* --------------------- Ellipse -------------------- */
101
-
102
-const EllipseUtils: BaseShapeUtils<ShapeType.Ellipse> = {
103
-  getBounds(shape) {
104
-    return {
105
-      minX: 0,
106
-      minY: 0,
107
-      maxX: 0,
108
-      maxY: 0,
109
-      width: 0,
110
-      height: 0,
111
-    }
112
-  },
113
-
114
-  hitTest(shape) {
115
-    return true
116
-  },
117
-
118
-  rotate(shape) {
119
-    return shape
120
-  },
121
-
122
-  translate(shape) {
123
-    return shape
124
-  },
125
-
126
-  scale(shape, scale: number) {
127
-    return shape
128
-  },
129
-
130
-  stretch(shape, scaleX: number, scaleY: number) {
131
-    return shape
132
-  },
133
-}
134
-
135
-/* ---------------------- Line ---------------------- */
136
-
137
-const LineUtils: BaseShapeUtils<ShapeType.Line> = {
138
-  getBounds(shape) {
139
-    return {
140
-      minX: 0,
141
-      minY: 0,
142
-      maxX: 0,
143
-      maxY: 0,
144
-      width: 0,
145
-      height: 0,
146
-    }
147
-  },
148
-
149
-  hitTest(shape) {
150
-    return true
151
-  },
152
-
153
-  rotate(shape) {
154
-    return shape
155
-  },
156
-
157
-  translate(shape) {
158
-    return shape
159
-  },
160
-
161
-  scale(shape, scale: number) {
162
-    return shape
163
-  },
164
-
165
-  stretch(shape, scaleX: number, scaleY: number) {
166
-    return shape
167
-  },
168
-}
169
-
170
-/* ----------------------- Ray ---------------------- */
171
-
172
-const RayUtils: BaseShapeUtils<ShapeType.Ray> = {
173
-  getBounds(shape) {
174
-    return {
175
-      minX: Infinity,
176
-      minY: Infinity,
177
-      maxX: Infinity,
178
-      maxY: Infinity,
179
-      width: Infinity,
180
-      height: Infinity,
181
-    }
182
-  },
183
-
184
-  hitTest(shape) {
185
-    return true
186
-  },
187
-
188
-  rotate(shape) {
189
-    return shape
190
-  },
191
-
192
-  translate(shape) {
193
-    return shape
194
-  },
195
-
196
-  scale(shape, scale: number) {
197
-    return shape
198
-  },
199
-
200
-  stretch(shape, scaleX: number, scaleY: number) {
201
-    return shape
202
-  },
203
-}
204
-
205
-/* ------------------ Line Segment ------------------ */
206
-
207
-const PolylineUtils: BaseShapeUtils<ShapeType.Polyline> = {
208
-  getBounds(shape) {
209
-    let minX = 0
210
-    let minY = 0
211
-    let maxX = 0
212
-    let maxY = 0
213
-
214
-    for (let [x, y] of shape.points) {
215
-      minX = Math.min(x, minX)
216
-      minY = Math.min(y, minY)
217
-      maxX = Math.max(x, maxX)
218
-      maxY = Math.max(y, maxY)
219
-    }
220
-
221
-    return {
222
-      minX: minX + shape.point[0],
223
-      minY: minY + shape.point[1],
224
-      maxX: maxX + shape.point[0],
225
-      maxY: maxY + shape.point[1],
226
-      width: maxX - minX,
227
-      height: maxY - minY,
228
-    }
229
-  },
230
-
231
-  hitTest(shape) {
232
-    return true
233
-  },
234
-
235
-  rotate(shape) {
236
-    return shape
237
-  },
238
-
239
-  translate(shape) {
240
-    return shape
241
-  },
242
-
243
-  scale(shape, scale: number) {
244
-    return shape
245
-  },
246
-
247
-  stretch(shape, scaleX: number, scaleY: number) {
248
-    return shape
249
-  },
250
-}
251
-
252
-/* -------------------- Rectangle ------------------- */
253
-
254
-const RectangleUtils: BaseShapeUtils<ShapeType.Rectangle> = {
255
-  getBounds(shape) {
256
-    const {
257
-      point: [x, y],
258
-      size: [width, height],
259
-    } = shape
260
-
261
-    return {
262
-      minX: x,
263
-      maxX: x + width,
264
-      minY: y,
265
-      maxY: y + height,
266
-      width,
267
-      height,
268
-    }
269
-  },
270
-
271
-  hitTest(shape) {
272
-    return true
273
-  },
274
-
275
-  rotate(shape) {
276
-    return shape
277
-  },
278
-
279
-  translate(shape) {
280
-    return shape
281
-  },
282
-
283
-  scale(shape, scale: number) {
284
-    return shape
285
-  },
286
-
287
-  stretch(shape, scaleX: number, scaleY: number) {
288
-    return shape
289
-  },
290
-}
291
-
292
-const shapeUtils: { [K in ShapeType]: BaseShapeUtils<K> } = {
293
-  [ShapeType.Dot]: DotUtils,
294
-  [ShapeType.Circle]: CircleUtils,
295
-  [ShapeType.Ellipse]: EllipseUtils,
296
-  [ShapeType.Line]: LineUtils,
297
-  [ShapeType.Ray]: RayUtils,
298
-  [ShapeType.Polyline]: PolylineUtils,
299
-  [ShapeType.Rectangle]: RectangleUtils,
300
-}
301
-
302
-export default shapeUtils

+ 4
- 2
utils/utils.ts Näytä tiedosto

@@ -844,12 +844,14 @@ export async function postJsonToEndpoint(
844 844
   return await d.json()
845 845
 }
846 846
 
847
-export function getPointerEventInfo(e: React.PointerEvent | WheelEvent) {
847
+export function getPointerEventInfo(
848
+  e: PointerEvent | React.PointerEvent | WheelEvent
849
+) {
848 850
   const { shiftKey, ctrlKey, metaKey, altKey } = e
849 851
   return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
850 852
 }
851 853
 
852
-export function getKeyboardEventInfo(e: React.KeyboardEvent | KeyboardEvent) {
854
+export function getKeyboardEventInfo(e: KeyboardEvent | React.KeyboardEvent) {
853 855
   const { shiftKey, ctrlKey, metaKey, altKey } = e
854 856
   return { key: e.key, shiftKey, ctrlKey, metaKey, altKey }
855 857
 }

Loading…
Peruuta
Tallenna