Improves shape props types, improves circle
This commit is contained in:
parent
f5d555863c
commit
602bd67a74
9 changed files with 137 additions and 71 deletions
|
@ -1,25 +1,18 @@
|
|||
import { useSelector } from "state"
|
||||
import { CircleShape } from "types"
|
||||
import { CircleShape, ShapeProps } from "types"
|
||||
import ShapeGroup from "./shape-g"
|
||||
|
||||
interface BaseCircleProps extends Pick<CircleShape, "radius"> {
|
||||
radius: number
|
||||
fill?: string
|
||||
stroke?: string
|
||||
strokeWidth?: number
|
||||
}
|
||||
|
||||
function BaseCircle({
|
||||
radius,
|
||||
fill = "#ccc",
|
||||
fill = "#999",
|
||||
stroke = "none",
|
||||
strokeWidth = 0,
|
||||
}: BaseCircleProps) {
|
||||
}: ShapeProps<CircleShape>) {
|
||||
return (
|
||||
<circle
|
||||
cx={strokeWidth}
|
||||
cy={strokeWidth}
|
||||
r={radius - strokeWidth}
|
||||
cx={radius}
|
||||
cy={radius}
|
||||
r={radius}
|
||||
fill={fill}
|
||||
stroke={stroke}
|
||||
strokeWidth={strokeWidth}
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
import { useSelector } from "state"
|
||||
import { DotShape } from "types"
|
||||
import { DotShape, ShapeProps } from "types"
|
||||
import ShapeGroup from "./shape-g"
|
||||
|
||||
interface BaseCircleProps {
|
||||
fill?: string
|
||||
stroke?: string
|
||||
strokeWidth?: number
|
||||
}
|
||||
|
||||
function BaseDot({
|
||||
fill = "#ccc",
|
||||
fill = "#999",
|
||||
stroke = "none",
|
||||
strokeWidth = 0,
|
||||
}: BaseCircleProps) {
|
||||
}: ShapeProps<DotShape>) {
|
||||
return (
|
||||
<>
|
||||
<circle
|
||||
|
|
|
@ -1,26 +1,28 @@
|
|||
import { useSelector } from "state"
|
||||
import { PolylineShape } from "types"
|
||||
import { PolylineShape, ShapeProps } from "types"
|
||||
import ShapeGroup from "./shape-g"
|
||||
|
||||
interface BasePolylineProps extends Pick<PolylineShape, "points"> {
|
||||
fill?: string
|
||||
stroke?: string
|
||||
strokeWidth?: number
|
||||
}
|
||||
|
||||
function BasePolyline({
|
||||
points,
|
||||
fill = "none",
|
||||
stroke = "#ccc",
|
||||
strokeWidth = 2,
|
||||
}: BasePolylineProps) {
|
||||
stroke = "#999",
|
||||
strokeWidth = 1,
|
||||
}: ShapeProps<PolylineShape>) {
|
||||
return (
|
||||
<polyline
|
||||
points={points.toString()}
|
||||
fill={fill}
|
||||
stroke={stroke}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
<>
|
||||
<polyline
|
||||
points={points.toString()}
|
||||
fill="none"
|
||||
stroke="transparent"
|
||||
strokeWidth={12}
|
||||
/>
|
||||
<polyline
|
||||
points={points.toString()}
|
||||
fill={fill}
|
||||
stroke={stroke}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,13 @@
|
|||
import { useSelector } from "state"
|
||||
import { RectangleShape } from "types"
|
||||
import { RectangleShape, ShapeProps } from "types"
|
||||
import ShapeGroup from "./shape-g"
|
||||
|
||||
interface BaseRectangleProps extends Pick<RectangleShape, "size"> {
|
||||
size: number[]
|
||||
fill?: string
|
||||
stroke?: string
|
||||
strokeWidth?: number
|
||||
}
|
||||
|
||||
function BaseRectangle({
|
||||
size,
|
||||
fill = "#ccc",
|
||||
fill = "#999",
|
||||
stroke = "none",
|
||||
strokeWidth = 0,
|
||||
}: BaseRectangleProps) {
|
||||
}: ShapeProps<RectangleShape>) {
|
||||
return (
|
||||
<rect
|
||||
x={strokeWidth}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react"
|
||||
import state from "state"
|
||||
import React, { useCallback, useRef } from "react"
|
||||
import { getPointerEventInfo } from "utils/utils"
|
||||
|
||||
export default function ShapeGroup({
|
||||
|
@ -11,27 +11,46 @@ export default function ShapeGroup({
|
|||
children: React.ReactNode
|
||||
point: number[]
|
||||
}) {
|
||||
const rGroup = useRef<SVGGElement>(null)
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
e.stopPropagation()
|
||||
rGroup.current.setPointerCapture(e.pointerId)
|
||||
state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
|
||||
},
|
||||
[id]
|
||||
)
|
||||
|
||||
const handlePointerUp = useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
e.stopPropagation()
|
||||
rGroup.current.releasePointerCapture(e.pointerId)
|
||||
state.send("STOPPED_POINTING_SHAPE", { id, ...getPointerEventInfo(e) })
|
||||
},
|
||||
[id]
|
||||
)
|
||||
|
||||
const handlePointerEnter = useCallback(
|
||||
(e: React.PointerEvent) =>
|
||||
state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
|
||||
[id]
|
||||
)
|
||||
|
||||
const handlePointerLeave = useCallback(
|
||||
(e: React.PointerEvent) =>
|
||||
state.send("UNHOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
|
||||
[id]
|
||||
)
|
||||
|
||||
return (
|
||||
<g
|
||||
ref={rGroup}
|
||||
transform={`translate(${point})`}
|
||||
onPointerDown={(e) =>
|
||||
state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
|
||||
}
|
||||
onPointerUp={(e) =>
|
||||
state.send("STOPPED_POINTING_SHAPE", {
|
||||
id,
|
||||
...getPointerEventInfo(e),
|
||||
})
|
||||
}
|
||||
onPointerEnter={(e) =>
|
||||
state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) })
|
||||
}
|
||||
onPointerLeave={(e) =>
|
||||
state.send("UNHOVERED_SHAPE", {
|
||||
id,
|
||||
...getPointerEventInfo(e),
|
||||
})
|
||||
}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerUp={handlePointerUp}
|
||||
onPointerEnter={handlePointerEnter}
|
||||
onPointerLeave={handlePointerLeave}
|
||||
>
|
||||
{children}
|
||||
</g>
|
||||
|
|
|
@ -82,8 +82,11 @@ export default class BrushSession extends BaseSession {
|
|||
shape,
|
||||
test: (brushBounds: Bounds) =>
|
||||
boundsContained(bounds, brushBounds) ||
|
||||
intersectCircleBounds(shape.point, shape.radius, brushBounds)
|
||||
.length > 0,
|
||||
intersectCircleBounds(
|
||||
vec.addScalar(shape.point, shape.radius),
|
||||
shape.radius,
|
||||
brushBounds
|
||||
).length > 0,
|
||||
}
|
||||
}
|
||||
case ShapeType.Rectangle: {
|
||||
|
|
|
@ -32,11 +32,23 @@ const state = createState({
|
|||
selecting: {
|
||||
on: {
|
||||
POINTED_CANVAS: { to: "brushSelecting" },
|
||||
POINTED_SHAPE: [
|
||||
"setPointedId",
|
||||
{
|
||||
if: "isPressingShiftKey",
|
||||
then: {
|
||||
if: "isPointedShapeSelected",
|
||||
do: "pullPointedIdFromSelectedIds",
|
||||
else: "pushPointedIdToSelectedIds",
|
||||
},
|
||||
else: ["clearSelectedIds", "pushPointedIdToSelectedIds"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
brushSelecting: {
|
||||
onEnter: [
|
||||
{ unless: "isPressingShiftKey", do: "clearSelection" },
|
||||
{ unless: "isPressingShiftKey", do: "clearSelectedIds" },
|
||||
"startBrushSession",
|
||||
],
|
||||
on: {
|
||||
|
@ -48,6 +60,9 @@ const state = createState({
|
|||
},
|
||||
},
|
||||
conditions: {
|
||||
isPointedShapeSelected(data) {
|
||||
return data.selectedIds.includes(data.pointedId)
|
||||
},
|
||||
isPressingShiftKey(data, payload: { shiftKey: boolean }) {
|
||||
return payload.shiftKey
|
||||
},
|
||||
|
@ -71,9 +86,22 @@ const state = createState({
|
|||
session.update(data, screenToWorld(payload.point, data))
|
||||
},
|
||||
// Selection
|
||||
clearSelection(data) {
|
||||
setPointedId(data, payload: { id: string }) {
|
||||
data.pointedId = payload.id
|
||||
},
|
||||
clearPointedId(data) {
|
||||
data.pointedId = undefined
|
||||
},
|
||||
clearSelectedIds(data) {
|
||||
data.selectedIds = []
|
||||
},
|
||||
pullPointedIdFromSelectedIds(data) {
|
||||
const { selectedIds, pointedId } = data
|
||||
selectedIds.splice(selectedIds.indexOf(pointedId, 1))
|
||||
},
|
||||
pushPointedIdToSelectedIds(data) {
|
||||
data.selectedIds.push(data.pointedId)
|
||||
},
|
||||
// Camera
|
||||
zoomCamera(data, payload: { delta: number; point: number[] }) {
|
||||
const { camera } = data
|
||||
|
|
16
types.ts
16
types.ts
|
@ -106,3 +106,19 @@ export interface Shapes extends Record<ShapeType, Shape> {
|
|||
[ShapeType.Polyline]: PolylineShape
|
||||
[ShapeType.Rectangle]: RectangleShape
|
||||
}
|
||||
|
||||
export interface BaseShapeStyles {
|
||||
fill: string
|
||||
stroke: string
|
||||
strokeWidth: number
|
||||
}
|
||||
|
||||
export type Difference<A, B> = A extends B ? never : A
|
||||
|
||||
export type ShapeSpecificProps<T extends Shape> = Pick<
|
||||
T,
|
||||
Difference<keyof T, keyof BaseShape>
|
||||
>
|
||||
|
||||
export type ShapeProps<T extends Shape> = Partial<BaseShapeStyles> &
|
||||
ShapeSpecificProps<T>
|
||||
|
|
18
utils/vec.ts
18
utils/vec.ts
|
@ -26,6 +26,15 @@ export function add(A: number[], B: number[]) {
|
|||
return [A[0] + B[0], A[1] + B[1]]
|
||||
}
|
||||
|
||||
/**
|
||||
* Add scalar to vector.
|
||||
* @param A
|
||||
* @param B
|
||||
*/
|
||||
export function addScalar(A: number[], n: number) {
|
||||
return [A[0] + n, A[1] + n]
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract vectors.
|
||||
* @param A
|
||||
|
@ -35,6 +44,15 @@ export function sub(A: number[], B: number[]) {
|
|||
return [A[0] - B[0], A[1] - B[1]]
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract scalar from vector.
|
||||
* @param A
|
||||
* @param B
|
||||
*/
|
||||
export function subScalar(A: number[], n: number) {
|
||||
return [A[0] - n, A[1] - n]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the vector from vectors A to B.
|
||||
* @param A
|
||||
|
|
Loading…
Reference in a new issue