107 lines
2.8 KiB
TypeScript
107 lines
2.8 KiB
TypeScript
/**
|
|
* WireInProgressRenderer — live preview while drawing a wire.
|
|
* Shows the fixed waypoints + a dynamic elbow segment to the mouse cursor.
|
|
* Includes a full-canvas crosshair at the cursor position (CircuitJS-style)
|
|
* to help with alignment and precision on touch devices.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import type { WireInProgress } from '../../types/wire';
|
|
import { generatePreviewPath, generateOrthogonalPath } from '../../utils/wireUtils';
|
|
|
|
/** Crosshair arm length in world pixels — large enough to span any visible area */
|
|
const CROSSHAIR_EXTENT = 8000;
|
|
|
|
interface Props {
|
|
wireInProgress: WireInProgress;
|
|
/** When true, show the full-canvas crosshair guides (aiming mode on touch) */
|
|
isAiming?: boolean;
|
|
}
|
|
|
|
export const WireInProgressRenderer: React.FC<Props> = ({ wireInProgress, isAiming = false }) => {
|
|
const { startEndpoint, waypoints, color, currentX, currentY } = wireInProgress;
|
|
|
|
const path = generatePreviewPath(
|
|
{ x: startEndpoint.x, y: startEndpoint.y },
|
|
waypoints,
|
|
currentX,
|
|
currentY,
|
|
);
|
|
|
|
if (!path) return null;
|
|
|
|
return (
|
|
<g className="wire-in-progress" style={{ pointerEvents: 'none' }}>
|
|
{/* ── Crosshair: full-canvas alignment guides (only during aiming) ── */}
|
|
{isAiming && (
|
|
<>
|
|
<line
|
|
x1={currentX - CROSSHAIR_EXTENT} y1={currentY}
|
|
x2={currentX + CROSSHAIR_EXTENT} y2={currentY}
|
|
stroke="rgba(255,255,255,0.25)"
|
|
strokeWidth="0.5"
|
|
strokeDasharray="8,6"
|
|
/>
|
|
<line
|
|
x1={currentX} y1={currentY - CROSSHAIR_EXTENT}
|
|
x2={currentX} y2={currentY + CROSSHAIR_EXTENT}
|
|
stroke="rgba(255,255,255,0.25)"
|
|
strokeWidth="0.5"
|
|
strokeDasharray="8,6"
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{/* Dark outline */}
|
|
<path d={path} stroke="#1a1a1a" strokeWidth="5" fill="none" />
|
|
|
|
{/* Colored wire */}
|
|
<path d={path} stroke={color} strokeWidth="2" fill="none" />
|
|
|
|
{/* Dashed overlay to indicate "in progress" */}
|
|
<path
|
|
d={path}
|
|
stroke="#ffffff"
|
|
strokeWidth="1.5"
|
|
fill="none"
|
|
strokeDasharray="6,4"
|
|
opacity="0.5"
|
|
/>
|
|
|
|
{/* Start pin marker */}
|
|
<circle
|
|
cx={startEndpoint.x}
|
|
cy={startEndpoint.y}
|
|
r="4"
|
|
fill={color}
|
|
stroke="white"
|
|
strokeWidth="1.5"
|
|
/>
|
|
|
|
{/* Waypoint markers (locked-in corners) */}
|
|
{waypoints.map((wp, i) => (
|
|
<circle
|
|
key={i}
|
|
cx={wp.x}
|
|
cy={wp.y}
|
|
r="3"
|
|
fill={color}
|
|
stroke="white"
|
|
strokeWidth="1"
|
|
/>
|
|
))}
|
|
|
|
{/* Cursor marker */}
|
|
<circle
|
|
cx={currentX}
|
|
cy={currentY}
|
|
r="4"
|
|
fill={color}
|
|
stroke="white"
|
|
strokeWidth="1.5"
|
|
opacity="0.7"
|
|
/>
|
|
</g>
|
|
);
|
|
};
|