Greatly simplifies shapes

canvas-rendering
Steve Ruiz 2021-05-12 22:11:17 +01:00
rodzic 32082492d1
commit 3d52d9e9d2
18 zmienionych plików z 438 dodań i 610 usunięć

Wyświetl plik

@ -1,35 +1,123 @@
import React, { useCallback, useRef } from "react"
import state, { useSelector } from "state"
import styled from "styles"
import { getPointerEventInfo } from "utils/utils"
import { memo } from "react"
import { useSelector } from "state"
import { ShapeType } from "types"
import Circle from "./shapes/circle"
import Dot from "./shapes/dot"
import Polyline from "./shapes/polyline"
import Rectangle from "./shapes/rectangle"
import Shapes from "lib/shapes"
/*
Gets the shape from the current page's shapes, using the
provided ID. Depending on the shape's type, return the
component for that type.
This component takes an SVG shape as its children. It handles
events for the shape as well as provides indicators for hover
and selected status
*/
function Shape({ id }: { id: string }) {
const rGroup = useRef<SVGGElement>(null)
const shape = useSelector((state) => {
const { currentPageId, document } = state.data
return document.pages[currentPageId].shapes[id]
})
switch (shape.type) {
case ShapeType.Dot:
return <Dot {...shape} />
case ShapeType.Circle:
return <Circle {...shape} />
case ShapeType.Rectangle:
return <Rectangle {...shape} />
case ShapeType.Polyline:
return <Polyline {...shape} />
default:
return null
}
const isSelected = useSelector((state) => state.values.selectedIds.has(id))
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 (
<StyledGroup
ref={rGroup}
isSelected={isSelected}
transform={`translate(${shape.point})`}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerEnter={handlePointerEnter}
onPointerLeave={handlePointerLeave}
>
<defs>
{Shapes[shape.type] ? Shapes[shape.type].render(shape) : null}
</defs>
<HoverIndicator as="use" xlinkHref={"#" + id} />
<use xlinkHref={"#" + id} {...shape.style} />
<Indicator as="use" xlinkHref={"#" + id} />
</StyledGroup>
)
}
const Indicator = styled("path", {
fill: "none",
stroke: "transparent",
strokeWidth: "max(1, calc(2 / var(--camera-zoom)))",
pointerEvents: "none",
strokeLineCap: "round",
strokeLinejoin: "round",
})
const HoverIndicator = styled("path", {
fill: "none",
stroke: "transparent",
strokeWidth: "max(1, calc(8 / var(--camera-zoom)))",
pointerEvents: "all",
strokeLinecap: "round",
strokeLinejoin: "round",
})
const StyledGroup = styled("g", {
[`& ${HoverIndicator}`]: {
opacity: "0",
},
variants: {
isSelected: {
true: {
[`& ${Indicator}`]: {
stroke: "$selected",
},
[`&:hover ${HoverIndicator}`]: {
opacity: "1",
stroke: "$hint",
},
},
false: {
[`&:hover ${HoverIndicator}`]: {
opacity: "1",
stroke: "$hint",
},
},
},
},
})
export { Indicator, HoverIndicator }
export default memo(Shape)

Wyświetl plik

@ -1,33 +0,0 @@
import { CircleShape, ShapeProps } from "types"
import { Indicator, HoverIndicator } from "./indicator"
import ShapeGroup from "./shape-group"
function BaseCircle({
radius,
fill = "#999",
stroke = "none",
strokeWidth = 0,
}: ShapeProps<CircleShape>) {
return (
<>
<HoverIndicator as="circle" cx={radius} cy={radius} r={radius} />
<circle
cx={radius}
cy={radius}
r={radius}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
/>
<Indicator as="circle" cx={radius} cy={radius} r={radius} />
</>
)
}
export default function Circle({ id, point, radius }: CircleShape) {
return (
<ShapeGroup id={id} point={point}>
<BaseCircle radius={radius} />
</ShapeGroup>
)
}

Wyświetl plik

@ -1,34 +0,0 @@
import { Indicator, HoverIndicator } from "./indicator"
import { DotShape, ShapeProps } from "types"
import ShapeGroup from "./shape-group"
const dotRadius = 4
function BaseDot({
fill = "#999",
stroke = "none",
strokeWidth = 0,
}: ShapeProps<DotShape>) {
return (
<>
<HoverIndicator as="circle" cx={dotRadius} cy={dotRadius} r={dotRadius} />
<circle
cx={dotRadius}
cy={dotRadius}
r={dotRadius}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
/>
<Indicator as="circle" cx={dotRadius} cy={dotRadius} r={dotRadius} />
</>
)
}
export default function Dot({ id, point }: DotShape) {
return (
<ShapeGroup id={id} point={point}>
<BaseDot />
</ShapeGroup>
)
}

Wyświetl plik

@ -1,21 +0,0 @@
import styled from "styles"
const Indicator = styled("path", {
fill: "none",
stroke: "transparent",
strokeWidth: "max(1, calc(2 / var(--camera-zoom)))",
pointerEvents: "none",
strokeLineCap: "round",
strokeLinejoin: "round",
})
const HoverIndicator = styled("path", {
fill: "none",
stroke: "transparent",
strokeWidth: "max(1, calc(8 / var(--camera-zoom)))",
pointerEvents: "all",
strokeLinecap: "round",
strokeLinejoin: "round",
})
export { Indicator, HoverIndicator }

Wyświetl plik

@ -1,33 +0,0 @@
import { PolylineShape, ShapeProps } from "types"
import { Indicator, HoverIndicator } from "./indicator"
import ShapeGroup from "./shape-group"
function BasePolyline({
points,
fill = "none",
stroke = "#999",
strokeWidth = 1,
}: ShapeProps<PolylineShape>) {
return (
<>
<HoverIndicator as="polyline" points={points.toString()} />
<polyline
points={points.toString()}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Indicator as="polyline" points={points.toString()} />
</>
)
}
export default function Polyline({ id, point, points }: PolylineShape) {
return (
<ShapeGroup id={id} point={point}>
<BasePolyline points={points} />
</ShapeGroup>
)
}

Wyświetl plik

@ -1,32 +0,0 @@
import { RectangleShape, ShapeProps } from "types"
import { HoverIndicator, Indicator } from "./indicator"
import ShapeGroup from "./shape-group"
function BaseRectangle({
size,
fill = "#999",
stroke = "none",
strokeWidth = 0,
}: ShapeProps<RectangleShape>) {
return (
<>
<HoverIndicator as="rect" width={size[0]} height={size[1]} />
<rect
width={size[0]}
height={size[1]}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
/>
<Indicator as="rect" width={size[0]} height={size[1]} />
</>
)
}
export default function Rectangle({ id, point, size }: RectangleShape) {
return (
<ShapeGroup id={id} point={point}>
<BaseRectangle size={size} />
</ShapeGroup>
)
}

Wyświetl plik

@ -1,87 +0,0 @@
import state, { useSelector } from "state"
import React, { useCallback, useRef } from "react"
import { getPointerEventInfo } from "utils/utils"
import { Indicator, HoverIndicator } from "./indicator"
import styled from "styles"
export default function ShapeGroup({
id,
children,
point,
}: {
id: string
children: React.ReactNode
point: number[]
}) {
const rGroup = useRef<SVGGElement>(null)
const isSelected = useSelector((state) => state.values.selectedIds.has(id))
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 (
<StyledGroup
ref={rGroup}
isSelected={isSelected}
transform={`translate(${point})`}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerEnter={handlePointerEnter}
onPointerLeave={handlePointerLeave}
>
{children}
</StyledGroup>
)
}
const StyledGroup = styled("g", {
[`& ${HoverIndicator}`]: {
opacity: "0",
},
variants: {
isSelected: {
true: {
[`& ${Indicator}`]: {
stroke: "$selected",
},
[`&:hover ${HoverIndicator}`]: {
opacity: "1",
stroke: "$hint",
},
},
false: {
[`&:hover ${HoverIndicator}`]: {
opacity: "1",
stroke: "$hint",
},
},
},
},
})

Wyświetl plik

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

Wyświetl plik

@ -0,0 +1,64 @@
import { v4 as uuid } from "uuid"
import * as vec from "utils/vec"
import { BaseLibShape, CircleShape, ShapeType } from "types"
const Circle: BaseLibShape<ShapeType.Circle> = {
create(props): CircleShape {
return {
id: uuid(),
type: ShapeType.Circle,
name: "Circle",
parentId: "page0",
childIndex: 0,
point: [0, 0],
radius: 20,
rotation: 0,
style: {},
...props,
}
},
render({ id, radius }) {
return <circle id={id} cx={radius} cy={radius} r={radius} />
},
getBounds(shape) {
const {
point: [cx, cy],
radius,
} = shape
return {
minX: cx,
maxX: cx + radius * 2,
minY: cy,
maxY: cy + radius * 2,
width: radius * 2,
height: radius * 2,
}
},
hitTest(shape, test) {
return (
vec.dist(vec.addScalar(shape.point, shape.radius), test) < shape.radius
)
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
export default Circle

60
lib/shapes/dot.tsx 100644
Wyświetl plik

@ -0,0 +1,60 @@
import { v4 as uuid } from "uuid"
import * as vec from "utils/vec"
import { BaseLibShape, DotShape, ShapeType } from "types"
const Dot: BaseLibShape<ShapeType.Dot> = {
create(props): DotShape {
return {
id: uuid(),
type: ShapeType.Dot,
name: "Dot",
parentId: "page0",
childIndex: 0,
point: [0, 0],
rotation: 0,
style: {},
...props,
}
},
render({ id }) {
return <circle id={id} cx={4} cy={4} r={4} />
},
getBounds(shape) {
const {
point: [cx, cy],
} = shape
return {
minX: cx,
maxX: cx + 4,
minY: cy,
maxY: cy + 4,
width: 4,
height: 4,
}
},
hitTest(shape, test) {
return vec.dist(shape.point, test) < 4
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
export default Dot

Wyświetl plik

@ -0,0 +1,13 @@
import Circle from "./circle"
import Dot from "./dot"
import Polyline from "./polyline"
import Rectangle from "./rectangle"
import { ShapeType } from "types"
export default {
[ShapeType.Circle]: Circle,
[ShapeType.Dot]: Dot,
[ShapeType.Polyline]: Polyline,
[ShapeType.Rectangle]: Rectangle,
}

Wyświetl plik

@ -0,0 +1,69 @@
import { v4 as uuid } from "uuid"
import * as vec from "utils/vec"
import { BaseLibShape, PolylineShape, ShapeType } from "types"
const Polyline: BaseLibShape<ShapeType.Polyline> = {
create(props): PolylineShape {
return {
id: uuid(),
type: ShapeType.Polyline,
name: "Polyline",
parentId: "page0",
childIndex: 0,
point: [0, 0],
points: [[0, 0]],
rotation: 0,
style: {},
...props,
}
},
render({ id, points }) {
return <polyline id={id} points={points.toString()} />
},
getBounds(shape) {
let minX = 0
let minY = 0
let maxX = 0
let maxY = 0
for (let [x, y] of shape.points) {
minX = Math.min(x, minX)
minY = Math.min(y, minY)
maxX = Math.max(x, maxX)
maxY = Math.max(y, maxY)
}
return {
minX: minX + shape.point[0],
minY: minY + shape.point[1],
maxX: maxX + shape.point[0],
maxY: maxY + shape.point[1],
width: maxX - minX,
height: maxY - minY,
}
},
hitTest(shape) {
return true
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
export default Polyline

Wyświetl plik

@ -0,0 +1,62 @@
import { v4 as uuid } from "uuid"
import * as vec from "utils/vec"
import { BaseLibShape, RectangleShape, ShapeType } from "types"
const Rectangle: BaseLibShape<ShapeType.Rectangle> = {
create(props): RectangleShape {
return {
id: uuid(),
type: ShapeType.Rectangle,
name: "Rectangle",
parentId: "page0",
childIndex: 0,
point: [0, 0],
size: [1, 1],
rotation: 0,
style: {},
...props,
}
},
render({ id, size }) {
return <rect id={id} width={size[0]} height={size[1]} />
},
getBounds(shape) {
const {
point: [x, y],
size: [width, height],
} = shape
return {
minX: x,
maxX: x + width,
minY: y,
maxY: y + height,
width,
height,
}
},
hitTest(shape) {
return true
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
export default Rectangle

Wyświetl plik

@ -1,4 +1,5 @@
import { Data, ShapeType } from "types"
import Shapes from "lib/shapes"
export const defaultDocument: Data["document"] = {
pages: {
@ -8,31 +9,32 @@ export const defaultDocument: Data["document"] = {
name: "Page 0",
childIndex: 0,
shapes: {
shape0: {
shape3: Shapes[ShapeType.Dot].create({
id: "shape3",
name: "Shape 3",
childIndex: 3,
point: [500, 100],
style: {
fill: "#aaa",
stroke: "#777",
strokeWidth: 1,
},
}),
shape0: Shapes[ShapeType.Circle].create({
id: "shape0",
type: ShapeType.Circle,
name: "Shape 0",
parentId: "page0",
childIndex: 1,
point: [100, 100],
radius: 50,
rotation: 0,
},
shape1: {
id: "shape1",
type: ShapeType.Rectangle,
name: "Shape 1",
parentId: "page0",
childIndex: 1,
point: [300, 300],
size: [200, 200],
rotation: 0,
},
shape2: {
style: {
fill: "#aaa",
stroke: "#777",
strokeWidth: 1,
},
}),
shape2: Shapes[ShapeType.Polyline].create({
id: "shape2",
type: ShapeType.Polyline,
name: "Shape 2",
parentId: "page0",
childIndex: 2,
point: [200, 600],
points: [
@ -40,17 +42,24 @@ export const defaultDocument: Data["document"] = {
[75, 200],
[100, 50],
],
rotation: 0,
},
shape3: {
id: "shape3",
type: ShapeType.Dot,
name: "Shape 3",
parentId: "page0",
childIndex: 3,
point: [500, 100],
rotation: 0,
},
style: {
fill: "none",
stroke: "#777",
strokeWidth: 2,
},
}),
shape1: Shapes[ShapeType.Rectangle].create({
id: "shape1",
name: "Shape 1",
childIndex: 1,
point: [300, 300],
size: [200, 200],
style: {
fill: "#aaa",
stroke: "#777",
strokeWidth: 1,
},
}),
},
},
},

Wyświetl plik

@ -1,7 +1,7 @@
import { current } from "immer"
import { Bounds, Data, Shape, ShapeType } from "types"
import { Bounds, Data, ShapeType } from "types"
import BaseSession from "./base-session"
import shapeUtils from "utils/shape-utils"
import Shapes from "lib/shapes"
import { getBoundsFromPoints } from "utils/utils"
import * as vec from "utils/vec"
import {
@ -72,7 +72,7 @@ export default class BrushSession extends BaseSession {
.map((shape) => {
switch (shape.type) {
case ShapeType.Dot: {
const bounds = shapeUtils[shape.type].getBounds(shape)
const bounds = Shapes[shape.type].getBounds(shape)
return {
id: shape.id,
@ -82,7 +82,7 @@ export default class BrushSession extends BaseSession {
}
}
case ShapeType.Circle: {
const bounds = shapeUtils[shape.type].getBounds(shape)
const bounds = Shapes[shape.type].getBounds(shape)
return {
id: shape.id,
@ -96,7 +96,7 @@ export default class BrushSession extends BaseSession {
}
}
case ShapeType.Rectangle: {
const bounds = shapeUtils[shape.type].getBounds(shape)
const bounds = Shapes[shape.type].getBounds(shape)
return {
id: shape.id,
@ -106,7 +106,7 @@ export default class BrushSession extends BaseSession {
}
}
case ShapeType.Polyline: {
const bounds = shapeUtils[shape.type].getBounds(shape)
const bounds = Shapes[shape.type].getBounds(shape)
const points = shape.points.map((point) =>
vec.add(point, shape.point)
)

Wyświetl plik

@ -1,3 +1,5 @@
import React from "react"
export interface Data {
camera: {
point: number[]
@ -26,7 +28,7 @@ export enum ShapeType {
Ellipse = "ellipse",
Line = "line",
Ray = "ray",
Polyline = "Polyline",
Polyline = "polyline",
Rectangle = "rectangle",
// Glob = "glob",
// Spline = "spline",
@ -42,6 +44,7 @@ export interface BaseShape {
name: string
point: number[]
rotation: 0
style: Partial<React.SVGProps<SVGUseElement>>
}
export interface DotShape extends BaseShape {
@ -107,12 +110,6 @@ export interface Shapes extends Record<ShapeType, Shape> {
[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<
@ -120,7 +117,15 @@ export type ShapeSpecificProps<T extends Shape> = Pick<
Difference<keyof T, keyof BaseShape>
>
export type ShapeProps<T extends Shape> = Partial<BaseShapeStyles> &
ShapeSpecificProps<T> & { id?: Shape["id"] }
export type ShapeIndicatorProps<T extends Shape> = ShapeSpecificProps<T>
export type BaseLibShape<K extends ShapeType> = {
create(props: Partial<Shapes[K]>): Shapes[K]
getBounds(shape: Shapes[K]): Bounds
hitTest(shape: Shapes[K], test: number[]): boolean
rotate(shape: Shapes[K]): Shapes[K]
translate(shape: Shapes[K]): Shapes[K]
scale(shape: Shapes[K], scale: number): Shapes[K]
stretch(shape: Shapes[K], scaleX: number, scaleY: number): Shapes[K]
render(shape: Shapes[K]): JSX.Element
}

Wyświetl plik

@ -1,302 +0,0 @@
import {
boundsCollide,
boundsContain,
pointInBounds,
} from "state/sessions/brush-session"
import { Bounds, ShapeType, Shapes } from "types"
import { intersectCircleBounds } from "./intersections"
import * as vec from "./vec"
type BaseShapeUtils<K extends ShapeType> = {
getBounds(shape: Shapes[K]): Bounds
hitTest(shape: Shapes[K], test: number[]): boolean
rotate(shape: Shapes[K]): Shapes[K]
translate(shape: Shapes[K]): Shapes[K]
scale(shape: Shapes[K], scale: number): Shapes[K]
stretch(shape: Shapes[K], scaleX: number, scaleY: number): Shapes[K]
}
/* ----------------------- Dot ---------------------- */
const DotUtils: BaseShapeUtils<ShapeType.Dot> = {
getBounds(shape) {
const {
point: [cx, cy],
} = shape
return {
minX: cx,
maxX: cx + 4,
minY: cy,
maxY: cy + 4,
width: 4,
height: 4,
}
},
hitTest(shape, test) {
return vec.dist(shape.point, test) < 4
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
/* --------------------- Circle --------------------- */
const CircleUtils: BaseShapeUtils<ShapeType.Circle> = {
getBounds(shape) {
const {
point: [cx, cy],
radius,
} = shape
return {
minX: cx,
maxX: cx + radius * 2,
minY: cy,
maxY: cy + radius * 2,
width: radius * 2,
height: radius * 2,
}
},
hitTest(shape, test) {
return (
vec.dist(vec.addScalar(shape.point, shape.radius), test) < shape.radius
)
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
/* --------------------- Ellipse -------------------- */
const EllipseUtils: BaseShapeUtils<ShapeType.Ellipse> = {
getBounds(shape) {
return {
minX: 0,
minY: 0,
maxX: 0,
maxY: 0,
width: 0,
height: 0,
}
},
hitTest(shape) {
return true
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
/* ---------------------- Line ---------------------- */
const LineUtils: BaseShapeUtils<ShapeType.Line> = {
getBounds(shape) {
return {
minX: 0,
minY: 0,
maxX: 0,
maxY: 0,
width: 0,
height: 0,
}
},
hitTest(shape) {
return true
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
/* ----------------------- Ray ---------------------- */
const RayUtils: BaseShapeUtils<ShapeType.Ray> = {
getBounds(shape) {
return {
minX: Infinity,
minY: Infinity,
maxX: Infinity,
maxY: Infinity,
width: Infinity,
height: Infinity,
}
},
hitTest(shape) {
return true
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
/* ------------------ Line Segment ------------------ */
const PolylineUtils: BaseShapeUtils<ShapeType.Polyline> = {
getBounds(shape) {
let minX = 0
let minY = 0
let maxX = 0
let maxY = 0
for (let [x, y] of shape.points) {
minX = Math.min(x, minX)
minY = Math.min(y, minY)
maxX = Math.max(x, maxX)
maxY = Math.max(y, maxY)
}
return {
minX: minX + shape.point[0],
minY: minY + shape.point[1],
maxX: maxX + shape.point[0],
maxY: maxY + shape.point[1],
width: maxX - minX,
height: maxY - minY,
}
},
hitTest(shape) {
return true
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
/* -------------------- Rectangle ------------------- */
const RectangleUtils: BaseShapeUtils<ShapeType.Rectangle> = {
getBounds(shape) {
const {
point: [x, y],
size: [width, height],
} = shape
return {
minX: x,
maxX: x + width,
minY: y,
maxY: y + height,
width,
height,
}
},
hitTest(shape) {
return true
},
rotate(shape) {
return shape
},
translate(shape) {
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
}
const shapeUtils: { [K in ShapeType]: BaseShapeUtils<K> } = {
[ShapeType.Dot]: DotUtils,
[ShapeType.Circle]: CircleUtils,
[ShapeType.Ellipse]: EllipseUtils,
[ShapeType.Line]: LineUtils,
[ShapeType.Ray]: RayUtils,
[ShapeType.Polyline]: PolylineUtils,
[ShapeType.Rectangle]: RectangleUtils,
}
export default shapeUtils

Wyświetl plik

@ -844,12 +844,14 @@ export async function postJsonToEndpoint(
return await d.json()
}
export function getPointerEventInfo(e: React.PointerEvent | WheelEvent) {
export function getPointerEventInfo(
e: PointerEvent | React.PointerEvent | WheelEvent
) {
const { shiftKey, ctrlKey, metaKey, altKey } = e
return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
}
export function getKeyboardEventInfo(e: React.KeyboardEvent | KeyboardEvent) {
export function getKeyboardEventInfo(e: KeyboardEvent | React.KeyboardEvent) {
const { shiftKey, ctrlKey, metaKey, altKey } = e
return { key: e.key, shiftKey, ctrlKey, metaKey, altKey }
}