kopia lustrzana https://github.com/Tldraw/Tldraw
286 wiersze
6.5 KiB
TypeScript
286 wiersze
6.5 KiB
TypeScript
import state, { useSelector } from "state"
|
|
import styled from "styles"
|
|
import inputs from "state/inputs"
|
|
import { useRef } from "react"
|
|
import { TransformCorner, TransformEdge } from "types"
|
|
import { lerp } from "utils/utils"
|
|
|
|
export default function Bounds() {
|
|
const isBrushing = useSelector((s) => s.isIn("brushSelecting"))
|
|
const isSelecting = useSelector((s) => s.isIn("selecting"))
|
|
const zoom = useSelector((s) => s.data.camera.zoom)
|
|
const bounds = useSelector((s) => s.values.selectedBounds)
|
|
const rotation = useSelector((s) => {
|
|
if (s.data.selectedIds.size === 1) {
|
|
const { shapes } = s.data.document.pages[s.data.currentPageId]
|
|
const selected = Array.from(s.data.selectedIds.values())[0]
|
|
return shapes[selected].rotation
|
|
} else {
|
|
return 0
|
|
}
|
|
})
|
|
|
|
if (!bounds) return null
|
|
if (!isSelecting) return null
|
|
|
|
let { minX, minY, maxX, maxY, width, height } = bounds
|
|
|
|
const p = 4 / zoom
|
|
const cp = p * 2
|
|
|
|
return (
|
|
<g
|
|
pointerEvents={isBrushing ? "none" : "all"}
|
|
transform={`rotate(${rotation * (180 / Math.PI)},${minX + width / 2}, ${
|
|
minY + height / 2
|
|
})`}
|
|
>
|
|
<StyledBounds
|
|
x={minX}
|
|
y={minY}
|
|
width={width}
|
|
height={height}
|
|
pointerEvents="none"
|
|
/>
|
|
<EdgeHorizontal
|
|
x={minX + p}
|
|
y={minY}
|
|
width={Math.max(0, width - p * 2)}
|
|
height={p}
|
|
edge={TransformEdge.Top}
|
|
/>
|
|
<EdgeVertical
|
|
x={maxX}
|
|
y={minY + p}
|
|
width={p}
|
|
height={Math.max(0, height - p * 2)}
|
|
edge={TransformEdge.Right}
|
|
/>
|
|
<EdgeHorizontal
|
|
x={minX + p}
|
|
y={maxY}
|
|
width={Math.max(0, width - p * 2)}
|
|
height={p}
|
|
edge={TransformEdge.Bottom}
|
|
/>
|
|
<EdgeVertical
|
|
x={minX}
|
|
y={minY + p}
|
|
width={p}
|
|
height={Math.max(0, height - p * 2)}
|
|
edge={TransformEdge.Left}
|
|
/>
|
|
<Corner
|
|
x={minX}
|
|
y={minY}
|
|
width={cp}
|
|
height={cp}
|
|
corner={TransformCorner.TopLeft}
|
|
/>
|
|
<Corner
|
|
x={maxX}
|
|
y={minY}
|
|
width={cp}
|
|
height={cp}
|
|
corner={TransformCorner.TopRight}
|
|
/>
|
|
<Corner
|
|
x={maxX}
|
|
y={maxY}
|
|
width={cp}
|
|
height={cp}
|
|
corner={TransformCorner.BottomRight}
|
|
/>
|
|
<Corner
|
|
x={minX}
|
|
y={maxY}
|
|
width={cp}
|
|
height={cp}
|
|
corner={TransformCorner.BottomLeft}
|
|
/>
|
|
<RotateHandle x={minX + width / 2} y={minY - cp * 2} r={cp / 2} />
|
|
</g>
|
|
)
|
|
}
|
|
|
|
function RotateHandle({ x, y, r }: { x: number; y: number; r: number }) {
|
|
const rRotateHandle = useRef<SVGCircleElement>(null)
|
|
|
|
return (
|
|
<StyledRotateHandle
|
|
ref={rRotateHandle}
|
|
cx={x}
|
|
cy={y}
|
|
r={r}
|
|
onPointerDown={(e) => {
|
|
e.stopPropagation()
|
|
rRotateHandle.current.setPointerCapture(e.pointerId)
|
|
state.send("POINTED_ROTATE_HANDLE", inputs.pointerDown(e, "rotate"))
|
|
}}
|
|
onPointerUp={(e) => {
|
|
e.stopPropagation()
|
|
rRotateHandle.current.releasePointerCapture(e.pointerId)
|
|
rRotateHandle.current.replaceWith(rRotateHandle.current)
|
|
state.send("STOPPED_POINTING", inputs.pointerDown(e, "rotate"))
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function Corner({
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
corner,
|
|
}: {
|
|
x: number
|
|
y: number
|
|
width: number
|
|
height: number
|
|
corner: TransformCorner
|
|
}) {
|
|
const rCorner = useRef<SVGRectElement>(null)
|
|
|
|
return (
|
|
<g>
|
|
<StyledCorner
|
|
ref={rCorner}
|
|
x={x + width * -0.5}
|
|
y={y + height * -0.5}
|
|
width={width}
|
|
height={height}
|
|
corner={corner}
|
|
onPointerDown={(e) => {
|
|
e.stopPropagation()
|
|
rCorner.current.setPointerCapture(e.pointerId)
|
|
state.send("POINTED_BOUNDS_CORNER", inputs.pointerDown(e, corner))
|
|
}}
|
|
onPointerUp={(e) => {
|
|
e.stopPropagation()
|
|
rCorner.current.releasePointerCapture(e.pointerId)
|
|
rCorner.current.replaceWith(rCorner.current)
|
|
state.send("STOPPED_POINTING", inputs.pointerDown(e, corner))
|
|
}}
|
|
/>
|
|
</g>
|
|
)
|
|
}
|
|
|
|
function EdgeHorizontal({
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
edge,
|
|
}: {
|
|
x: number
|
|
y: number
|
|
width: number
|
|
height: number
|
|
edge: TransformEdge.Top | TransformEdge.Bottom
|
|
}) {
|
|
const rEdge = useRef<SVGRectElement>(null)
|
|
|
|
return (
|
|
<StyledEdge
|
|
ref={rEdge}
|
|
x={x}
|
|
y={y - height / 2}
|
|
width={width}
|
|
height={height}
|
|
onPointerDown={(e) => {
|
|
e.stopPropagation()
|
|
rEdge.current.setPointerCapture(e.pointerId)
|
|
state.send("POINTED_BOUNDS_EDGE", inputs.pointerDown(e, edge))
|
|
}}
|
|
onPointerUp={(e) => {
|
|
e.stopPropagation()
|
|
e.preventDefault()
|
|
state.send("STOPPED_POINTING", inputs.pointerUp(e))
|
|
rEdge.current.releasePointerCapture(e.pointerId)
|
|
rEdge.current.replaceWith(rEdge.current)
|
|
}}
|
|
edge={edge}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function EdgeVertical({
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
edge,
|
|
}: {
|
|
x: number
|
|
y: number
|
|
width: number
|
|
height: number
|
|
edge: TransformEdge.Right | TransformEdge.Left
|
|
}) {
|
|
const rEdge = useRef<SVGRectElement>(null)
|
|
|
|
return (
|
|
<StyledEdge
|
|
ref={rEdge}
|
|
x={x - width / 2}
|
|
y={y}
|
|
width={width}
|
|
height={height}
|
|
onPointerDown={(e) => {
|
|
e.stopPropagation()
|
|
state.send("POINTED_BOUNDS_EDGE", inputs.pointerDown(e, edge))
|
|
rEdge.current.setPointerCapture(e.pointerId)
|
|
}}
|
|
onPointerUp={(e) => {
|
|
e.stopPropagation()
|
|
state.send("STOPPED_POINTING", inputs.pointerUp(e))
|
|
rEdge.current.releasePointerCapture(e.pointerId)
|
|
rEdge.current.replaceWith(rEdge.current)
|
|
}}
|
|
edge={edge}
|
|
/>
|
|
)
|
|
}
|
|
|
|
const StyledEdge = styled("rect", {
|
|
stroke: "none",
|
|
fill: "none",
|
|
variants: {
|
|
edge: {
|
|
bottom_edge: { cursor: "ns-resize" },
|
|
right_edge: { cursor: "ew-resize" },
|
|
top_edge: { cursor: "ns-resize" },
|
|
left_edge: { cursor: "ew-resize" },
|
|
},
|
|
},
|
|
})
|
|
|
|
const StyledCorner = styled("rect", {
|
|
stroke: "$bounds",
|
|
fill: "#fff",
|
|
zStrokeWidth: 2,
|
|
variants: {
|
|
corner: {
|
|
top_left_corner: { cursor: "nwse-resize" },
|
|
top_right_corner: { cursor: "nesw-resize" },
|
|
bottom_right_corner: { cursor: "nwse-resize" },
|
|
bottom_left_corner: { cursor: "nesw-resize" },
|
|
},
|
|
},
|
|
})
|
|
|
|
const StyledRotateHandle = styled("circle", {
|
|
stroke: "$bounds",
|
|
fill: "#fff",
|
|
zStrokeWidth: 2,
|
|
cursor: "grab",
|
|
})
|
|
|
|
const StyledBounds = styled("rect", {
|
|
fill: "none",
|
|
stroke: "$bounds",
|
|
zStrokeWidth: 2,
|
|
})
|