"use client"; import { cn } from "@/utils/ui"; import { HugeiconsIcon } from "@hugeicons/react"; import type { IconSvgElement } from "@hugeicons/react"; export const HANDLE_SIZE = 10; export const HANDLE_HIT_AREA_SIZE = 18; export const ICON_HANDLE_RADIUS = 10; export const EDGE_HANDLE_THIN_SIZE = 6; export const EDGE_HANDLE_THICK_SIZE = 14; export const LINE_HIT_AREA_SIZE = 48; export function getResizeCursor({ angleDeg }: { angleDeg: number }): string { const normalized = ((angleDeg % 180) + 180) % 180; if (normalized < 22.5 || normalized >= 157.5) return "ew-resize"; if (normalized < 67.5) return "nwse-resize"; if (normalized < 112.5) return "ns-resize"; return "nesw-resize"; } export function HandleButton({ screen, cursor, hitAreaSize, className, onPointerDown, onPointerMove, onPointerUp, children, }: { screen: { x: number; y: number }; cursor?: string; hitAreaSize: number; className?: string; onPointerDown: (event: React.PointerEvent) => void; onPointerMove: (event: React.PointerEvent) => void; onPointerUp: (event: React.PointerEvent) => void; children: React.ReactNode; }) { return ( ); } export function CornerHandle({ cursor, screen, onPointerDown, onPointerMove, onPointerUp, }: { cursor?: string; screen: { x: number; y: number }; onPointerDown: (event: React.PointerEvent) => void; onPointerMove: (event: React.PointerEvent) => void; onPointerUp: (event: React.PointerEvent) => void; }) { return (
); } export function CircleHandle({ cursor, screen, size = HANDLE_SIZE, isSelected = false, onPointerDown, onPointerMove, onPointerUp, }: { cursor?: string; screen: { x: number; y: number }; size?: number; isSelected?: boolean; onPointerDown: (event: React.PointerEvent) => void; onPointerMove: (event: React.PointerEvent) => void; onPointerUp: (event: React.PointerEvent) => void; }) { return (
); } export function EdgeHandle({ edge, screen, rotation, onPointerDown, onPointerMove, onPointerUp, }: { edge: "right" | "left" | "bottom"; screen: { x: number; y: number }; rotation: number; onPointerDown: (event: React.PointerEvent) => void; onPointerMove: (event: React.PointerEvent) => void; onPointerUp: (event: React.PointerEvent) => void; }) { const isHorizontalEdge = edge === "right" || edge === "left"; const width = isHorizontalEdge ? EDGE_HANDLE_THIN_SIZE : EDGE_HANDLE_THICK_SIZE; const height = isHorizontalEdge ? EDGE_HANDLE_THICK_SIZE : EDGE_HANDLE_THIN_SIZE; const cursor = getResizeCursor({ angleDeg: isHorizontalEdge ? rotation : rotation + 90, }); return (
); } export function IconHandle({ icon, screen, onPointerDown, onPointerMove, onPointerUp, }: { icon: IconSvgElement; screen: { x: number; y: number }; onPointerDown: (event: React.PointerEvent) => void; onPointerMove: (event: React.PointerEvent) => void; onPointerUp: (event: React.PointerEvent) => void; }) { return ( ); } export function BoundingBoxOutline({ center, outlineWidth, outlineHeight, rotation, cursor, dashed = false, onPointerDown, onPointerMove, onPointerUp, }: { center: { x: number; y: number }; outlineWidth: number; outlineHeight: number; rotation: number; cursor?: string; dashed?: boolean; onPointerDown?: (event: React.PointerEvent) => void; onPointerMove?: (event: React.PointerEvent) => void; onPointerUp?: (event: React.PointerEvent) => void; }) { return ( ); } export function ShapeOutline({ center, outlineWidth, outlineHeight, rotation, pathData, cursor, onPointerDown, onPointerMove, onPointerUp, }: { center: { x: number; y: number }; outlineWidth: number; outlineHeight: number; rotation: number; pathData: string; cursor?: string; onPointerDown?: (event: React.PointerEvent) => void; onPointerMove?: (event: React.PointerEvent) => void; onPointerUp?: (event: React.PointerEvent) => void; }) { return ( ); } export function CanvasPathOutline({ pathData, translateX = 0, translateY = 0, scaleX = 1, scaleY = 1, cursor, strokeWidth = 1, strokeOpacity = 0.75, onPointerDown, onPointerMove, onPointerUp, }: { pathData: string; translateX?: number; translateY?: number; scaleX?: number; scaleY?: number; cursor?: string; strokeWidth?: number; strokeOpacity?: number; onPointerDown?: (event: React.PointerEvent) => void; onPointerMove?: (event: React.PointerEvent) => void; onPointerUp?: (event: React.PointerEvent) => void; }) { return ( ); } export function LineOverlay({ start, end, cursor, onPointerDown, onPointerMove, onPointerUp, }: { start: { x: number; y: number }; end: { x: number; y: number }; cursor?: string; onPointerDown?: (event: React.PointerEvent) => void; onPointerMove?: (event: React.PointerEvent) => void; onPointerUp?: (event: React.PointerEvent) => void; }) { const dx = end.x - start.x; const dy = end.y - start.y; const length = Math.sqrt(dx * dx + dy * dy); const angleDeg = (Math.atan2(dy, dx) * 180) / Math.PI; const cx = (start.x + end.x) / 2; const cy = (start.y + end.y) / 2; const sharedStyle = { left: cx - length / 2, width: length, transform: `rotate(${angleDeg}deg)`, transformOrigin: "center center", }; return ( <> {onPointerDown && (
)}
); }