Fixes hover width, cursors on pointer inputs
This commit is contained in:
parent
9bca2dd646
commit
0ec723e0d6
8 changed files with 165 additions and 146 deletions
|
@ -20,8 +20,9 @@ export default function BoundsBg() {
|
|||
height={height}
|
||||
onPointerDown={(e) => {
|
||||
if (e.buttons !== 1) return
|
||||
e.stopPropagation()
|
||||
rBounds.current.setPointerCapture(e.pointerId)
|
||||
state.send("POINTED_BOUNDS", inputs.pointerDown(e))
|
||||
state.send("POINTED_BOUNDS", inputs.pointerDown(e, "bounds"))
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -2,11 +2,12 @@ import state, { useSelector } from "state"
|
|||
import { motion } from "framer-motion"
|
||||
import styled from "styles"
|
||||
import inputs from "state/inputs"
|
||||
import { useRef } from "react"
|
||||
|
||||
export default function Bounds() {
|
||||
const zoom = useSelector((state) => state.data.camera.zoom)
|
||||
const bounds = useSelector((state) => state.values.selectedBounds)
|
||||
const isBrushing = useSelector((state) => state.isIn("brushSelecting"))
|
||||
const zoom = useSelector((state) => state.data.camera.zoom)
|
||||
|
||||
if (!bounds) return null
|
||||
|
||||
|
@ -29,97 +30,61 @@ export default function Bounds() {
|
|||
<Corner
|
||||
x={minX}
|
||||
y={minY}
|
||||
corner={0}
|
||||
width={cp}
|
||||
height={cp}
|
||||
cursor="nwse-resize"
|
||||
corner="top_left_corner"
|
||||
/>
|
||||
<Corner
|
||||
x={maxX}
|
||||
y={minY}
|
||||
corner={1}
|
||||
width={cp}
|
||||
height={cp}
|
||||
cursor="nesw-resize"
|
||||
corner="top_right_corner"
|
||||
/>
|
||||
<Corner
|
||||
x={maxX}
|
||||
y={maxY}
|
||||
corner={2}
|
||||
width={cp}
|
||||
height={cp}
|
||||
cursor="nwse-resize"
|
||||
corner="bottom_right_corner"
|
||||
/>
|
||||
<Corner
|
||||
x={minX}
|
||||
y={maxY}
|
||||
corner={3}
|
||||
width={cp}
|
||||
height={cp}
|
||||
cursor="nesw-resize"
|
||||
corner="bottom_left_corner"
|
||||
/>
|
||||
<EdgeHorizontal
|
||||
x={minX + p}
|
||||
y={minY}
|
||||
width={Math.max(0, width - p * 2)}
|
||||
height={p}
|
||||
edge="top_edge"
|
||||
/>
|
||||
<EdgeVertical
|
||||
x={maxX}
|
||||
y={minY + p}
|
||||
width={p}
|
||||
height={Math.max(0, height - p * 2)}
|
||||
edge="right_edge"
|
||||
/>
|
||||
<EdgeHorizontal
|
||||
x={minX + p}
|
||||
y={maxY}
|
||||
width={Math.max(0, width - p * 2)}
|
||||
height={p}
|
||||
edge="bottom_edge"
|
||||
/>
|
||||
<EdgeVertical
|
||||
x={minX}
|
||||
y={minY + p}
|
||||
width={p}
|
||||
height={Math.max(0, height - p * 2)}
|
||||
edge="left_edge"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<EdgeHorizontal
|
||||
x={minX + p}
|
||||
y={minY}
|
||||
width={Math.max(0, width - p * 2)}
|
||||
height={p}
|
||||
onSelect={(e) => {
|
||||
e.stopPropagation()
|
||||
if (e.buttons !== 1) return
|
||||
state.send("POINTED_BOUNDS_EDGE", {
|
||||
edge: 0,
|
||||
...inputs.pointerDown(e),
|
||||
})
|
||||
document.body.style.cursor = "ns-resize"
|
||||
}}
|
||||
/>
|
||||
<EdgeVertical
|
||||
x={maxX}
|
||||
y={minY + p}
|
||||
width={p}
|
||||
height={Math.max(0, height - p * 2)}
|
||||
onSelect={(e) => {
|
||||
e.stopPropagation()
|
||||
if (e.buttons !== 1) return
|
||||
state.send("POINTED_BOUNDS_EDGE", {
|
||||
edge: 1,
|
||||
...inputs.pointerDown(e),
|
||||
})
|
||||
document.body.style.cursor = "ew-resize"
|
||||
}}
|
||||
/>
|
||||
<EdgeHorizontal
|
||||
x={minX + p}
|
||||
y={maxY}
|
||||
width={Math.max(0, width - p * 2)}
|
||||
height={p}
|
||||
onSelect={(e) => {
|
||||
e.stopPropagation()
|
||||
if (e.buttons !== 1) return
|
||||
state.send("POINTED_BOUNDS_EDGE", {
|
||||
edge: 2,
|
||||
...inputs.pointerDown(e),
|
||||
})
|
||||
document.body.style.cursor = "ns-resize"
|
||||
}}
|
||||
/>
|
||||
<EdgeVertical
|
||||
x={minX}
|
||||
y={minY + p}
|
||||
width={p}
|
||||
height={Math.max(0, height - p * 2)}
|
||||
onSelect={(e) => {
|
||||
e.stopPropagation()
|
||||
if (e.buttons !== 1) return
|
||||
state.send("POINTED_BOUNDS_EDGE", {
|
||||
edge: 3,
|
||||
...inputs.pointerDown(e),
|
||||
})
|
||||
document.body.style.cursor = "ew-resize"
|
||||
}}
|
||||
/>
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
@ -129,59 +94,62 @@ function Corner({
|
|||
y,
|
||||
width,
|
||||
height,
|
||||
cursor,
|
||||
onHover,
|
||||
corner,
|
||||
}: {
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
cursor: string
|
||||
corner: number
|
||||
onHover?: () => void
|
||||
corner:
|
||||
| "top_left_corner"
|
||||
| "top_right_corner"
|
||||
| "bottom_right_corner"
|
||||
| "bottom_left_corner"
|
||||
}) {
|
||||
const isTop = corner === 0 || corner === 1
|
||||
const isLeft = corner === 0 || corner === 3
|
||||
const rRotateCorner = useRef<SVGRectElement>(null)
|
||||
const rCorner = useRef<SVGRectElement>(null)
|
||||
|
||||
const isTop = corner.includes("top")
|
||||
const isLeft = corner.includes("bottom")
|
||||
|
||||
return (
|
||||
<g>
|
||||
<motion.rect
|
||||
x={x + width * (isLeft ? -1.25 : -0.5)} // + width * 2 * transformOffset[0]}
|
||||
y={y + width * (isTop ? -1.25 : -0.5)} // + height * 2 * transformOffset[1]}
|
||||
<StyledRotateCorner
|
||||
ref={rRotateCorner}
|
||||
x={x + width * (isLeft ? -1.25 : -0.5)}
|
||||
y={y + width * (isTop ? -1.25 : -0.5)}
|
||||
width={width * 1.75}
|
||||
height={height * 1.75}
|
||||
onPanEnd={restoreCursor}
|
||||
onTap={restoreCursor}
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation()
|
||||
if (e.buttons !== 1) return
|
||||
state.send("POINTED_ROTATE_CORNER", {
|
||||
corner,
|
||||
...inputs.pointerDown(e),
|
||||
})
|
||||
document.body.style.cursor = "grabbing"
|
||||
rRotateCorner.current.setPointerCapture(e.pointerId)
|
||||
state.send("POINTED_ROTATE_CORNER", inputs.pointerDown(e, corner))
|
||||
}}
|
||||
onPointerUp={(e) => {
|
||||
e.stopPropagation()
|
||||
rRotateCorner.current.releasePointerCapture(e.pointerId)
|
||||
rRotateCorner.current.replaceWith(rRotateCorner.current)
|
||||
state.send("STOPPED_POINTING", inputs.pointerDown(e, corner))
|
||||
}}
|
||||
style={{ cursor: "grab" }}
|
||||
fill="transparent"
|
||||
/>
|
||||
<StyledCorner
|
||||
ref={rCorner}
|
||||
x={x + width * -0.5}
|
||||
y={y + height * -0.5}
|
||||
width={width}
|
||||
height={height}
|
||||
onPointerEnter={onHover}
|
||||
corner={corner}
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation()
|
||||
if (e.buttons !== 1) return
|
||||
state.send("POINTED_BOUNDS_CORNER", {
|
||||
corner,
|
||||
...inputs.pointerDown(e),
|
||||
})
|
||||
document.body.style.cursor = "nesw-resize"
|
||||
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))
|
||||
}}
|
||||
onPanEnd={restoreCursor}
|
||||
onTap={restoreCursor}
|
||||
style={{ cursor }}
|
||||
/>
|
||||
</g>
|
||||
)
|
||||
|
@ -192,28 +160,36 @@ function EdgeHorizontal({
|
|||
y,
|
||||
width,
|
||||
height,
|
||||
onHover,
|
||||
onSelect,
|
||||
edge,
|
||||
}: {
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
onHover?: () => void
|
||||
onSelect?: (e: React.PointerEvent) => void
|
||||
edge: "top_edge" | "bottom_edge"
|
||||
}) {
|
||||
const rEdge = useRef<SVGRectElement>(null)
|
||||
|
||||
return (
|
||||
<StyledEdge
|
||||
ref={rEdge}
|
||||
x={x}
|
||||
y={y - height / 2}
|
||||
width={width}
|
||||
height={height}
|
||||
onPointerEnter={onHover}
|
||||
onPointerDown={onSelect}
|
||||
onPanEnd={restoreCursor}
|
||||
onTap={restoreCursor}
|
||||
style={{ cursor: "ns-resize" }}
|
||||
direction="horizontal"
|
||||
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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -223,27 +199,35 @@ function EdgeVertical({
|
|||
y,
|
||||
width,
|
||||
height,
|
||||
onHover,
|
||||
onSelect,
|
||||
edge,
|
||||
}: {
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
onHover?: () => void
|
||||
onSelect?: (e: React.PointerEvent) => void
|
||||
edge: "right_edge" | "left_edge"
|
||||
}) {
|
||||
const rEdge = useRef<SVGRectElement>(null)
|
||||
|
||||
return (
|
||||
<StyledEdge
|
||||
ref={rEdge}
|
||||
x={x - width / 2}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height}
|
||||
onPointerEnter={onHover}
|
||||
onPointerDown={onSelect}
|
||||
onPanEnd={restoreCursor}
|
||||
onTap={restoreCursor}
|
||||
direction="vertical"
|
||||
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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -253,21 +237,31 @@ function restoreCursor(e: PointerEvent) {
|
|||
document.body.style.cursor = "default"
|
||||
}
|
||||
|
||||
const StyledEdge = styled(motion.rect, {
|
||||
const StyledEdge = styled("rect", {
|
||||
stroke: "none",
|
||||
fill: "none",
|
||||
variant: {
|
||||
direction: {
|
||||
horizontal: { cursor: "ns-resize" },
|
||||
vertical: { cursor: "ew-resize" },
|
||||
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(motion.rect, {
|
||||
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 StyledBounds = styled("rect", {
|
||||
|
@ -275,3 +269,8 @@ const StyledBounds = styled("rect", {
|
|||
stroke: "$bounds",
|
||||
zStrokeWidth: 2,
|
||||
})
|
||||
|
||||
const StyledRotateCorner = styled("rect", {
|
||||
cursor: "grab",
|
||||
fill: "transparent",
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ export default function Canvas() {
|
|||
|
||||
const handlePointerDown = useCallback((e: React.PointerEvent) => {
|
||||
rCanvas.current.setPointerCapture(e.pointerId)
|
||||
state.send("POINTED_CANVAS", inputs.pointerDown(e))
|
||||
state.send("POINTED_CANVAS", inputs.pointerDown(e, "canvas"))
|
||||
}, [])
|
||||
|
||||
const handlePointerMove = useCallback((e: React.PointerEvent) => {
|
||||
|
|
|
@ -7,10 +7,10 @@ import styled from "styles"
|
|||
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]
|
||||
})
|
||||
const shape = useSelector(
|
||||
({ data: { currentPageId, document } }) =>
|
||||
document.pages[currentPageId].shapes[id]
|
||||
)
|
||||
|
||||
const isSelected = useSelector((state) => state.values.selectedIds.has(id))
|
||||
|
||||
|
@ -18,7 +18,7 @@ function Shape({ id }: { id: string }) {
|
|||
(e: React.PointerEvent) => {
|
||||
e.stopPropagation()
|
||||
rGroup.current.setPointerCapture(e.pointerId)
|
||||
state.send("POINTED_SHAPE", { id, ...inputs.pointerDown(e) })
|
||||
state.send("POINTED_SHAPE", inputs.pointerDown(e, id))
|
||||
},
|
||||
[id]
|
||||
)
|
||||
|
@ -27,7 +27,7 @@ function Shape({ id }: { id: string }) {
|
|||
(e: React.PointerEvent) => {
|
||||
e.stopPropagation()
|
||||
rGroup.current.releasePointerCapture(e.pointerId)
|
||||
state.send("STOPPED_POINTING", { id, ...inputs.pointerUp(e) })
|
||||
state.send("STOPPED_POINTING", inputs.pointerUp(e))
|
||||
},
|
||||
[id]
|
||||
)
|
||||
|
@ -74,7 +74,7 @@ const Indicator = styled("path", {
|
|||
const HoverIndicator = styled("path", {
|
||||
fill: "none",
|
||||
stroke: "transparent",
|
||||
zStrokeWidth: 8,
|
||||
zStrokeWidth: [8, 4],
|
||||
pointerEvents: "all",
|
||||
strokeLinecap: "round",
|
||||
strokeLinejoin: "round",
|
||||
|
|
|
@ -4,10 +4,11 @@ import { isDarwin } from "utils/utils"
|
|||
class Inputs {
|
||||
points: Record<string, PointerInfo> = {}
|
||||
|
||||
pointerDown(e: PointerEvent | React.PointerEvent) {
|
||||
pointerDown(e: PointerEvent | React.PointerEvent, target: string) {
|
||||
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
||||
|
||||
const info = {
|
||||
target,
|
||||
pointerId: e.pointerId,
|
||||
origin: [e.clientX, e.clientY],
|
||||
point: [e.clientX, e.clientY],
|
||||
|
@ -28,8 +29,8 @@ class Inputs {
|
|||
const prev = this.points[e.pointerId]
|
||||
|
||||
const info = {
|
||||
...prev,
|
||||
pointerId: e.pointerId,
|
||||
origin: prev?.origin || [e.clientX, e.clientY],
|
||||
point: [e.clientX, e.clientY],
|
||||
shiftKey,
|
||||
ctrlKey,
|
||||
|
@ -50,7 +51,7 @@ class Inputs {
|
|||
const prev = this.points[e.pointerId]
|
||||
|
||||
const info = {
|
||||
pointerId: e.pointerId,
|
||||
...prev,
|
||||
origin: prev?.origin || [e.clientX, e.clientY],
|
||||
point: [e.clientX, e.clientY],
|
||||
shiftKey,
|
||||
|
|
|
@ -72,7 +72,7 @@ const state = createState({
|
|||
on: {
|
||||
STOPPED_POINTING: [
|
||||
{
|
||||
unless: "isPressingShiftKey",
|
||||
unless: ["isPointingBounds", "isPressingShiftKey"],
|
||||
do: ["clearSelectedIds", "pushPointedIdToSelectedIds"],
|
||||
},
|
||||
{ to: "notPointing" },
|
||||
|
@ -109,6 +109,9 @@ const state = createState({
|
|||
},
|
||||
},
|
||||
conditions: {
|
||||
isPointingBounds(data, payload: PointerInfo) {
|
||||
return payload.target === "bounds"
|
||||
},
|
||||
isReadOnly(data) {
|
||||
return data.isReadOnly
|
||||
},
|
||||
|
@ -148,29 +151,29 @@ const state = createState({
|
|||
},
|
||||
|
||||
// Brushing
|
||||
startBrushSession(data, payload: { point: number[] }) {
|
||||
startBrushSession(data, payload: PointerInfo) {
|
||||
session = new Sessions.BrushSession(
|
||||
data,
|
||||
screenToWorld(payload.point, data)
|
||||
)
|
||||
},
|
||||
updateBrushSession(data, payload: { point: number[] }) {
|
||||
updateBrushSession(data, payload: PointerInfo) {
|
||||
session.update(data, screenToWorld(payload.point, data))
|
||||
},
|
||||
// Dragging / Translating
|
||||
startTranslateSession(data, payload: { point: number[] }) {
|
||||
startTranslateSession(data, payload: PointerInfo) {
|
||||
session = new Sessions.TranslateSession(
|
||||
data,
|
||||
screenToWorld(payload.point, data)
|
||||
)
|
||||
},
|
||||
updateTranslateSession(data, payload: { point: number[] }) {
|
||||
updateTranslateSession(data, payload: PointerInfo) {
|
||||
session.update(data, screenToWorld(payload.point, data))
|
||||
},
|
||||
|
||||
// Selection
|
||||
setPointedId(data, payload: { id: string }) {
|
||||
data.pointedId = payload.id
|
||||
setPointedId(data, payload: PointerInfo) {
|
||||
data.pointedId = payload.target
|
||||
},
|
||||
clearPointedId(data) {
|
||||
data.pointedId = undefined
|
||||
|
|
|
@ -36,9 +36,23 @@ const { styled, global, css, theme, getCssString } = createCss({
|
|||
transitions: {},
|
||||
},
|
||||
utils: {
|
||||
zStrokeWidth: () => (value: number) => ({
|
||||
strokeWidth: `calc(${value}px / var(--camera-zoom))`,
|
||||
}),
|
||||
zStrokeWidth: () => (value: number | number[]) => {
|
||||
if (Array.isArray(value)) {
|
||||
const [val, min, max] = value
|
||||
return {
|
||||
strokeWidth:
|
||||
min !== undefined && max !== undefined
|
||||
? `clamp(${min}, ${val} / var(--camera-zoom), ${max})`
|
||||
: min !== undefined
|
||||
? `max(${min}, ${val} / var(--camera-zoom))`
|
||||
: `calc(${val} / var(--camera-zoom))`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
strokeWidth: `calc(${value} / var(--camera-zoom))`,
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
|
1
types.ts
1
types.ts
|
@ -132,6 +132,7 @@ export type BaseLibShape<K extends ShapeType> = {
|
|||
}
|
||||
|
||||
export interface PointerInfo {
|
||||
target: string
|
||||
pointerId: number
|
||||
origin: number[]
|
||||
point: number[]
|
||||
|
|
Loading…
Reference in a new issue