From 8db3bead13441a5882d8689dfc6a9f09c82375d9 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 25 Mar 2026 16:47:22 +0100 Subject: feat: masks, properties refactor, shaders, storage migrations, and more --- .../editor/panels/preview/transform-handles.tsx | 273 +++++---------------- 1 file changed, 65 insertions(+), 208 deletions(-) (limited to 'apps/web/src/components/editor/panels/preview/transform-handles.tsx') diff --git a/apps/web/src/components/editor/panels/preview/transform-handles.tsx b/apps/web/src/components/editor/panels/preview/transform-handles.tsx index 2fcf8096..88332625 100644 --- a/apps/web/src/components/editor/panels/preview/transform-handles.tsx +++ b/apps/web/src/components/editor/panels/preview/transform-handles.tsx @@ -1,112 +1,55 @@ "use client"; +import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; import { useTransformHandles } from "@/hooks/use-transform-handles"; -import { useEditor } from "@/hooks/use-editor"; import { isVisualElement } from "@/lib/timeline/element-utils"; -import { SnapGuides } from "./snap-guides"; -import { canvasToOverlay, getDisplayScale } from "@/lib/preview/preview-coords"; -import type { ElementBounds } from "@/lib/preview/element-bounds"; -import { cn } from "@/utils/ui"; -import { HugeiconsIcon } from "@hugeicons/react"; +import { + getCornerPosition, + getEdgeHandlePosition, + getRotationHandlePosition, + type Corner, + type Edge, +} from "@/lib/preview/element-bounds"; +import type { OnSnapLinesChange } from "@/hooks/use-preview-interaction"; +import { + BoundingBoxOutline, + CornerHandle, + EdgeHandle, + IconHandle, + getResizeCursor, +} from "./handle-primitives"; import { Rotate01Icon } from "@hugeicons/core-free-icons"; -const HANDLE_SIZE = 10; -const ROTATION_HANDLE_OFFSET = 24; -const ROTATION_HANDLE_RADIUS = 10; -const CORNER_HIT_AREA_SIZE = 18; - -type Corner = "top-left" | "top-right" | "bottom-left" | "bottom-right"; const CORNERS: Corner[] = [ "top-left", "top-right", "bottom-left", "bottom-right", ]; - -export function getCornerPosition({ - bounds, - corner, -}: { - bounds: ElementBounds; - corner: Corner; -}): { x: number; y: number } { - const halfW = bounds.width / 2; - const halfH = bounds.height / 2; - const angleRad = (bounds.rotation * Math.PI) / 180; - const cos = Math.cos(angleRad); - const sin = Math.sin(angleRad); - - const localX = - corner === "top-left" || corner === "bottom-left" ? -halfW : halfW; - const localY = - corner === "top-left" || corner === "top-right" ? -halfH : halfH; - - return { - x: bounds.cx + (localX * cos - localY * sin), - y: bounds.cy + (localX * sin + localY * cos), - }; -} - -export function getRotationHandlePosition({ bounds }: { bounds: ElementBounds }): { - x: number; - y: number; -} { - const angleRad = (bounds.rotation * Math.PI) / 180; - const cos = Math.cos(angleRad); - const sin = Math.sin(angleRad); - const localY = -bounds.height / 2 - ROTATION_HANDLE_OFFSET; - return { - x: bounds.cx - localY * sin, - y: bounds.cy + localY * cos, - }; -} - -export function getOverlayContext({ - canvasRef, - containerRef, -}: { - canvasRef: React.RefObject; - containerRef: React.RefObject; -}): { - canvasRect: DOMRect; - containerRect: DOMRect; -} | null { - const canvasRect = canvasRef.current?.getBoundingClientRect(); - const containerRect = containerRef.current?.getBoundingClientRect(); - if (!canvasRect || !containerRect) return null; - return { canvasRect, containerRect }; -} +const EDGES: Edge[] = ["right", "left", "bottom"]; export function TransformHandles({ - canvasRef, - containerRef, + onSnapLinesChange, }: { - canvasRef: React.RefObject; - containerRef: React.RefObject; + onSnapLinesChange?: OnSnapLinesChange; }) { + const viewport = usePreviewViewport(); const { selectedWithBounds, hasVisualSelection, - snapLines, handleCornerPointerDown, + handleEdgePointerDown, handleRotationPointerDown, handlePointerMove, handlePointerUp, - } = useTransformHandles({ canvasRef }); - - const editor = useEditor(); - const canvasSize = editor.project.getActive().settings.canvasSize; + } = useTransformHandles({ onSnapLinesChange }); if (!hasVisualSelection || !selectedWithBounds) return null; - const overlayContext = getOverlayContext({ canvasRef, containerRef }); - if (!overlayContext) return null; - const { bounds, element } = selectedWithBounds; if (!isVisualElement(element)) return null; - const { canvasRect, containerRect } = overlayContext; - const displayScale = getDisplayScale({ canvasRect, canvasSize }); + const displayScale = viewport.getDisplayScale(); const toOverlay = ({ canvasX, @@ -115,31 +58,30 @@ export function TransformHandles({ canvasX: number; canvasY: number; }) => - canvasToOverlay({ + viewport.canvasToOverlay({ canvasX, canvasY, - canvasRect, - containerRect, - canvasSize, }); const center = toOverlay({ canvasX: bounds.cx, canvasY: bounds.cy }); - const outlineWidth = bounds.width * displayScale.x; - const outlineHeight = bounds.height * displayScale.y; + const outlineWidth = Math.abs(bounds.width) * displayScale.x; + const outlineHeight = Math.abs(bounds.height) * displayScale.y; - const rotationHandle = getRotationHandlePosition({ bounds }); + const rotationHandleCanvas = getRotationHandlePosition({ bounds }); const rotationHandleScreen = toOverlay({ - canvasX: rotationHandle.x, - canvasY: rotationHandle.y, + canvasX: rotationHandleCanvas.x, + canvasY: rotationHandleCanvas.y, }); + const onPointerMove = (event: React.PointerEvent) => + handlePointerMove({ event }); + const onPointerUp = () => handlePointerUp(); + return ( -
- +
handleCornerPointerDown({ event, corner }) } - onPointerMove={(event) => handlePointerMove({ event })} - onPointerUp={(event) => handlePointerUp({ event })} + onPointerMove={onPointerMove} + onPointerUp={onPointerUp} + /> + ); + })} + {EDGES.map((edge) => { + const edgePosition = getEdgeHandlePosition({ bounds, edge }); + const screen = toOverlay({ + canvasX: edgePosition.x, + canvasY: edgePosition.y, + }); + return ( + handleEdgePointerDown({ event, edge })} + onPointerMove={onPointerMove} + onPointerUp={onPointerUp} /> ); })} - handleRotationPointerDown({ event })} - onPointerMove={(event) => handlePointerMove({ event })} - onPointerUp={(event) => handlePointerUp({ event })} + onPointerMove={onPointerMove} + onPointerUp={onPointerUp} />
); } - -function BoundingBoxOutline({ - center, - outlineWidth, - outlineHeight, - rotation, -}: { - center: { x: number; y: number }; - outlineWidth: number; - outlineHeight: number; - rotation: number; -}) { - return ( -
- ); -} - -function CornerHandle({ - corner, - screen, - onPointerDown, - onPointerMove, - onPointerUp, -}: { - corner: Corner; - screen: { x: number; y: number }; - onPointerDown: (event: React.PointerEvent) => void; - onPointerMove: (event: React.PointerEvent) => void; - onPointerUp: (event: React.PointerEvent) => void; -}) { - const isNesw = corner === "top-right" || corner === "bottom-left"; - - return ( - - ); -} - -function RotationHandle({ - screen, - onPointerDown, - onPointerMove, - onPointerUp, -}: { - screen: { x: number; y: number }; - onPointerDown: (event: React.PointerEvent) => void; - onPointerMove: (event: React.PointerEvent) => void; - onPointerUp: (event: React.PointerEvent) => void; -}) { - return ( - - ); -} -- cgit v1.3.1