From f4e429af0ea7d5d49238a824b23dc8709c31a84b Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Mon, 24 May 2021 18:01:27 +0100 Subject: [PATCH] Adds zoom helpers, improves drag selection --- components/canvas/misc.tsx | 2 + components/canvas/shape.tsx | 1 - hooks/useKeyboardEvents.ts | 21 +++++++ lib/shape-utils/dot.tsx | 3 +- lib/shape-utils/line.tsx | 2 +- lib/shape-utils/ray.tsx | 2 +- state/state.ts | 107 +++++++++++++++++++++++++++++++----- styles/stitches.config.ts | 19 ++++--- utils/utils.ts | 11 ++++ 9 files changed, 143 insertions(+), 25 deletions(-) diff --git a/components/canvas/misc.tsx b/components/canvas/misc.tsx index 542562f74..7f2235f04 100644 --- a/components/canvas/misc.tsx +++ b/components/canvas/misc.tsx @@ -2,6 +2,8 @@ import styled from "styles" const DotCircle = styled("circle", { transform: "scale(var(--scale))", + strokeWidth: "2", + fill: "#000", }) export { DotCircle } diff --git a/components/canvas/shape.tsx b/components/canvas/shape.tsx index a3e8f601d..401cad289 100644 --- a/components/canvas/shape.tsx +++ b/components/canvas/shape.tsx @@ -95,7 +95,6 @@ const Indicator = styled("path", { const HoverIndicator = styled("path", { fill: "none", stroke: "transparent", - zStrokeWidth: [8, 4], pointerEvents: "all", strokeLinecap: "round", strokeLinejoin: "round", diff --git a/hooks/useKeyboardEvents.ts b/hooks/useKeyboardEvents.ts index dae903bec..def644275 100644 --- a/hooks/useKeyboardEvents.ts +++ b/hooks/useKeyboardEvents.ts @@ -10,6 +10,27 @@ export default function useKeyboardEvents() { } switch (e.key) { + case "!": { + // Shift + 1 + if (e.shiftKey) { + state.send("ZOOMED_TO_FIT") + } + break + } + case "@": { + // Shift + 2 + if (e.shiftKey) { + state.send("ZOOMED_TO_SELECTION") + } + break + } + case ")": { + // Shift + 0 + if (e.shiftKey) { + state.send("ZOOMED_TO_ACTUAL") + } + break + } case "Escape": { state.send("CANCELLED") break diff --git a/lib/shape-utils/dot.tsx b/lib/shape-utils/dot.tsx index ba0a5edb9..63790abf3 100644 --- a/lib/shape-utils/dot.tsx +++ b/lib/shape-utils/dot.tsx @@ -4,7 +4,6 @@ import { DotShape, ShapeType } from "types" import { registerShapeUtils } from "./index" import { boundsContained } from "utils/bounds" import { intersectCircleBounds } from "utils/intersections" -import styled from "styles" import { DotCircle } from "components/canvas/misc" import { translateBounds } from "utils/utils" @@ -23,7 +22,7 @@ const dot = registerShapeUtils({ rotation: 0, style: { fill: "#c6cacb", - stroke: "#000", + strokeWidth: "0", }, ...props, } diff --git a/lib/shape-utils/line.tsx b/lib/shape-utils/line.tsx index 9ec3c00ff..01702f21f 100644 --- a/lib/shape-utils/line.tsx +++ b/lib/shape-utils/line.tsx @@ -36,7 +36,7 @@ const line = registerShapeUtils({ return ( - + ) }, diff --git a/lib/shape-utils/ray.tsx b/lib/shape-utils/ray.tsx index 24e8a5b64..1637c7bb6 100644 --- a/lib/shape-utils/ray.tsx +++ b/lib/shape-utils/ray.tsx @@ -36,7 +36,7 @@ const ray = registerShapeUtils({ return ( - + ) }, diff --git a/state/state.ts b/state/state.ts index 790cbb25c..0034f4f10 100644 --- a/state/state.ts +++ b/state/state.ts @@ -1,12 +1,16 @@ import { createSelectorHook, createState } from "@state-designer/react" import { clamp, + getBoundsCenter, getChildren, getCommonBounds, getPage, + getSelectedBounds, + getSelectedShapes, getShape, getSiblings, screenToWorld, + setZoomCSS, } from "utils/utils" import * as vec from "utils/vec" import { @@ -68,6 +72,13 @@ const state = createState({ SELECTED_RECTANGLE_TOOL: { unless: "isReadOnly", to: "rectangle" }, TOGGLED_CODE_PANEL_OPEN: "toggleCodePanel", RESET_CAMERA: "resetCamera", + ZOOMED_TO_FIT: "zoomCameraToFit", + ZOOMED_TO_SELECTION: { if: "hasSelection", do: "zoomCameraToSelection" }, + ZOOMED_TO_ACTUAL: { + if: "hasSelection", + do: "zoomCameraToSelectionActual", + else: "zoomCameraToActual", + }, }, initial: "loading", states: { @@ -480,7 +491,7 @@ const state = createState({ return data.isReadOnly }, distanceImpliesDrag(data, payload: PointerInfo) { - return vec.dist2(payload.origin, payload.point) > 16 + return vec.dist2(payload.origin, payload.point) > 8 }, isPointedShapeSelected(data) { return data.selectedIds.has(data.pointedId) @@ -508,6 +519,9 @@ const state = createState({ ) { return payload.target === "rotate" }, + hasSelection(data) { + return data.selectedIds.size > 0 + }, }, actions: { /* --------------------- Shapes --------------------- */ @@ -565,7 +579,7 @@ const state = createState({ startTranslateSession(data, payload: PointerInfo) { session = new Sessions.TranslateSession( data, - screenToWorld(payload.point, data), + screenToWorld(inputs.pointer.origin, data), payload.altKey ) }, @@ -697,29 +711,96 @@ const state = createState({ document.documentElement.style.setProperty("--camera-zoom", "1") }, - centerCamera(data) { - const { shapes } = getPage(data) - getCommonBounds() - data.camera.zoom = 1 - data.camera.point = [window.innerWidth / 2, window.innerHeight / 2] + zoomCameraToSelection(data) { + const { camera } = data - document.documentElement.style.setProperty("--camera-zoom", "1") + const bounds = getSelectedBounds(data) + + const zoom = + bounds.width > bounds.height + ? (window.innerWidth - 128) / bounds.width + : (window.innerHeight - 128) / bounds.height + + const mx = window.innerWidth - bounds.width * zoom + const my = window.innerHeight - bounds.height * zoom + + camera.zoom = zoom + + camera.point = vec.add( + [-bounds.minX, -bounds.minY], + [mx / 2 / zoom, my / 2 / zoom] + ) + + setZoomCSS(camera.zoom) + }, + zoomCameraToSelectionActual(data) { + const { camera } = data + + const bounds = getSelectedBounds(data) + + const zoom = 1 + + const mx = window.innerWidth - 128 - bounds.width * zoom + const my = window.innerHeight - 128 - bounds.height * zoom + + camera.zoom = zoom + camera.point = vec.add( + [-bounds.minX, -bounds.minY], + [mx / 2 / zoom, my / 2 / zoom] + ) + + setZoomCSS(camera.zoom) + }, + zoomCameraToActual(data) { + const { camera } = data + + const center = [window.innerWidth / 2, window.innerHeight / 2] + + const p0 = screenToWorld(center, data) + camera.zoom = 1 + const p1 = screenToWorld(center, data) + camera.point = vec.add(camera.point, vec.sub(p1, p0)) + + setZoomCSS(camera.zoom) + }, + zoomCameraToFit(data) { + const { camera } = data + const { shapes } = getPage(data) + + const bounds = getCommonBounds( + ...Object.values(shapes).map((shape) => + getShapeUtils(shape).getBounds(shape) + ) + ) + + const zoom = + bounds.width > bounds.height + ? (window.innerWidth - 104) / bounds.width + : (window.innerHeight - 104) / bounds.height + + const mx = window.innerWidth - bounds.width * zoom + const my = window.innerHeight - bounds.height * zoom + + camera.zoom = zoom + camera.point = vec.add( + [-bounds.minX, -bounds.minY], + [mx / 2 / zoom, my / 2 / zoom] + ) + + setZoomCSS(camera.zoom) }, zoomCamera(data, payload: { delta: number; point: number[] }) { const { camera } = data const p0 = screenToWorld(payload.point, data) camera.zoom = clamp( camera.zoom - (payload.delta / 100) * camera.zoom, - 0.5, + 0.1, 3 ) const p1 = screenToWorld(payload.point, data) camera.point = vec.add(camera.point, vec.sub(p1, p0)) - document.documentElement.style.setProperty( - "--camera-zoom", - camera.zoom.toString() - ) + setZoomCSS(camera.zoom) }, panCamera(data, payload: { delta: number[]; point: number[] }) { const { camera } = data diff --git a/styles/stitches.config.ts b/styles/stitches.config.ts index 5415cd351..34cf15186 100644 --- a/styles/stitches.config.ts +++ b/styles/stitches.config.ts @@ -43,17 +43,22 @@ const { styled, global, css, theme, getCssString } = createCss({ utils: { 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 - ? `min(${min}, ${val} / var(--camera-zoom))` - : `calc(${val} / var(--camera-zoom))`, + strokeWidth: `calc(${value[0]} / var(--camera-zoom))`, } } + // const [val, min, max] = value + // return { + // strokeWidth: + // min !== undefined && max !== undefined + // ? `clamp(${min}, ${val} / var(--camera-zoom), ${max})` + // : min !== undefined + // ? `min(${min}, ${val} / var(--camera-zoom))` + // : `calc(${val} / var(--camera-zoom))`, + // } + // } + return { strokeWidth: `calc(${value} / var(--camera-zoom))`, } diff --git a/utils/utils.ts b/utils/utils.ts index 441465fa3..9fe6e5e95 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -1355,6 +1355,14 @@ export function getSelectedShapes(data: Data, pageId = data.currentPageId) { return ids.map((id) => page.shapes[id]) } +export function getSelectedBounds(data: Data) { + return getCommonBounds( + ...getSelectedShapes(data).map((shape) => + getShapeUtils(shape).getBounds(shape) + ) + ) +} + export function isMobile() { return _isMobile() } @@ -1474,3 +1482,6 @@ export function forceIntegerChildIndices(shapes: Shape[]) { shapes[i].childIndex = i + 1 } } +export function setZoomCSS(zoom: number) { + document.documentElement.style.setProperty("--camera-zoom", zoom.toString()) +}