Improves shape props types, improves circle

This commit is contained in:
Steve Ruiz 2021-05-12 10:48:35 +01:00
parent f5d555863c
commit 602bd67a74
9 changed files with 137 additions and 71 deletions

View file

@ -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}

View file

@ -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

View file

@ -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="none"
stroke="transparent"
strokeWidth={12}
/>
<polyline
points={points.toString()}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
/>
</>
)
}

View file

@ -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}

View file

@ -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>

View file

@ -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: {

View file

@ -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

View file

@ -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>

View file

@ -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