diff --git a/components/canvas/bounds/bounding-box.tsx b/components/canvas/bounds/bounding-box.tsx index 21fd40dd8..99a17c5bd 100644 --- a/components/canvas/bounds/bounding-box.tsx +++ b/components/canvas/bounds/bounding-box.tsx @@ -20,7 +20,7 @@ export default function Bounds() { if (!bounds) return null if (!isSelecting) return null - const size = (isMobile().any ? 16 : 8) / zoom // Touch target size + const size = (isMobile().any ? 12 : 8) / zoom // Touch target size return ( { + return Object.values(getPage(data).shapes) + .sort((a, b) => a.childIndex - b.childIndex) + .map((shape) => shape.id) + }, deepCompareArrays) + + return ( + + {currentPageShapeIds.map((id) => ( + + ))} + + ) +} + +export function Def({ id }: { id: string }) { + const shape = useSelector(({ data }) => getPage(data).shapes[id]) + + return getShapeUtils(shape).render(shape) +} diff --git a/components/canvas/shape.tsx b/components/canvas/shape.tsx index 401cad289..148efe186 100644 --- a/components/canvas/shape.tsx +++ b/components/canvas/shape.tsx @@ -6,14 +6,14 @@ import { getShapeUtils } from "lib/shape-utils" import { getPage } from "utils/utils" function Shape({ id }: { id: string }) { - const rGroup = useRef(null) - const isHovered = useSelector((state) => state.data.hoveredId === id) const isSelected = useSelector((state) => state.values.selectedIds.has(id)) const shape = useSelector(({ data }) => getPage(data).shapes[id]) + const rGroup = useRef(null) + const handlePointerDown = useCallback( (e: React.PointerEvent) => { e.stopPropagation() @@ -72,9 +72,9 @@ function Shape({ id }: { id: string }) { onPointerMove={handlePointerMove} > {getShapeUtils(shape).render(shape)} - - - + + + ) } diff --git a/hooks/useZoomEvents.ts b/hooks/useZoomEvents.ts index 6b90f9038..f93871f51 100644 --- a/hooks/useZoomEvents.ts +++ b/hooks/useZoomEvents.ts @@ -2,6 +2,7 @@ import React, { useEffect, useRef } from "react" import state from "state" import inputs from "state/inputs" import * as vec from "utils/vec" +import { usePinch } from "react-use-gesture" /** * Capture zoom gestures (pinches, wheels and pans) and send to the state. @@ -65,5 +66,36 @@ export default function useZoomEvents( } }, [ref]) - return {} + const rPinchDa = useRef(undefined) + const rPinchAngle = useRef(undefined) + const rPinchPoint = useRef(undefined) + + const bind = usePinch(({ pinching, da, origin }) => { + if (!pinching) { + state.send("STOPPED_PINCHING") + rPinchDa.current = undefined + rPinchPoint.current = undefined + return + } + + if (rPinchPoint.current === undefined) { + state.send("STARTED_PINCHING") + rPinchDa.current = da + rPinchPoint.current = origin + } + + const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da) + + state.send("PINCHED", { + delta: vec.sub(rPinchPoint.current, origin), + point: origin, + distanceDelta, + angleDelta, + }) + + rPinchDa.current = da + rPinchPoint.current = origin + }) + + return { ...bind() } } diff --git a/package.json b/package.json index ffd557431..c5c31558d 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react": "17.0.2", "react-dom": "17.0.2", "react-feather": "^2.0.9", + "react-use-gesture": "^9.1.3", "uuid": "^8.3.2" }, "devDependencies": { diff --git a/state/state.ts b/state/state.ts index 35e7064da..9c54e85d8 100644 --- a/state/state.ts +++ b/state/state.ts @@ -130,6 +130,7 @@ const state = createState({ STRETCHED: "stretchSelection", DISTRIBUTED: "distributeSelection", MOVED: "moveSelection", + STARTED_PINCHING: { to: "pinching" }, }, initial: "notPointing", states: { @@ -248,6 +249,12 @@ const state = createState({ }, }, }, + pinching: { + on: { + STOPPED_PINCHING: { to: "selecting" }, + PINCHED: { do: "pinchCamera" }, + }, + }, draw: { initial: "creating", states: { @@ -829,12 +836,31 @@ const state = createState({ setZoomCSS(camera.zoom) }, - panCamera(data, payload: { delta: number[]; point: number[] }) { + panCamera(data, payload: { delta: number[] }) { const { camera } = data - data.camera.point = vec.sub( - camera.point, - vec.div(payload.delta, camera.zoom) - ) + camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom)) + }, + pinchCamera( + data, + payload: { + delta: number[] + distanceDelta: number + angleDelta: number + point: number[] + } + ) { + const { camera } = data + + camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom)) + + const next = camera.zoom - (payload.distanceDelta / 300) * camera.zoom + + const p0 = screenToWorld(payload.point, data) + camera.zoom = clamp(next, 0.1, 3) + const p1 = screenToWorld(payload.point, data) + camera.point = vec.add(camera.point, vec.sub(p1, p0)) + + setZoomCSS(camera.zoom) }, deleteSelectedIds(data) { commands.deleteSelected(data) diff --git a/styles/stitches.config.ts b/styles/stitches.config.ts index d7f495e20..f8db4a406 100644 --- a/styles/stitches.config.ts +++ b/styles/stitches.config.ts @@ -45,7 +45,7 @@ const { styled, global, css, theme, getCssString } = createCss({ zStrokeWidth: () => (value: number | number[]) => { if (Array.isArray(value)) { return { - strokeWidth: `calc(${value[0]} / var(--camera-zoom))`, + strokeWidth: `calc(${value[0]}px / var(--camera-zoom))`, } } @@ -61,7 +61,7 @@ const { styled, global, css, theme, getCssString } = createCss({ // } return { - strokeWidth: `calc(${value} / var(--camera-zoom))`, + strokeWidth: `calc(${value}px / var(--camera-zoom))`, } }, }, diff --git a/yarn.lock b/yarn.lock index d26890712..82b9cfc62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6697,6 +6697,11 @@ react-style-singleton@^2.1.0: invariant "^2.2.4" tslib "^1.0.0" +react-use-gesture@^9.1.3: + version "9.1.3" + resolved "https://registry.yarnpkg.com/react-use-gesture/-/react-use-gesture-9.1.3.tgz#92bd143e4f58e69bd424514a5bfccba2a1d62ec0" + integrity sha512-CdqA2SmS/fj3kkS2W8ZU8wjTbVBAIwDWaRprX7OKaj7HlGwBasGEFggmk5qNklknqk9zK/h8D355bEJFTpqEMg== + react@17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"