diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
| commit | 3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch) | |
| tree | 57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/lib/masks/handle-positions.ts | |
| parent | 729d10592f4e802f91c555c309ff3ba4b119f843 (diff) | |
| download | opencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz | |
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/lib/masks/handle-positions.ts')
| -rw-r--r-- | apps/web/src/lib/masks/handle-positions.ts | 497 |
1 files changed, 0 insertions, 497 deletions
diff --git a/apps/web/src/lib/masks/handle-positions.ts b/apps/web/src/lib/masks/handle-positions.ts deleted file mode 100644 index d32b8424..00000000 --- a/apps/web/src/lib/masks/handle-positions.ts +++ /dev/null @@ -1,497 +0,0 @@ -import { FEATHER_HANDLE_SCALE } from "@/lib/masks/feather"; -import type { ElementBounds } from "@/lib/preview/element-bounds"; -import type { - MaskFeatures, - MaskHandlePosition, - MaskLineOverlay, - MaskOverlay, - MaskRectOverlay, - RectangleMaskParams, - MaskShapeOverlay, -} from "@/lib/masks/types"; - -const LINE_HANDLE_OFFSET_SCREEN_PX = 20; -const BOX_HANDLE_OFFSET_SCREEN_PX = 20; -const LINE_EXTENT_MULTIPLIER = 50; - -const CURSOR = { - rotate: "crosshair", - resizeDiagonal: "nwse-resize", - resizeHorizontal: "ew-resize", - resizeVertical: "ns-resize", -} as const; - -/** - * The renderer defines the split line as: - * - normal direction: (cos(rotation), sin(rotation)) - * - line direction (parallel to cut): (-sin(rotation), cos(rotation)) - * - reference point: (centerX * width, centerY * height) from element centre - * - * So rotation=0 → normal points right → line runs vertically. - */ -export function getLineMaskLinePoints({ - centerX, - centerY, - rotation, - bounds, -}: { - centerX: number; - centerY: number; - rotation: number; - bounds: ElementBounds; -}): { start: { x: number; y: number }; end: { x: number; y: number } } { - const angleRad = (rotation * Math.PI) / 180; - const normalX = Math.cos(angleRad); - const normalY = Math.sin(angleRad); - const lineDirX = -normalY; - const lineDirY = normalX; - - const cx = bounds.cx + centerX * bounds.width; - const cy = bounds.cy + centerY * bounds.height; - - const extent = Math.max(bounds.width, bounds.height) * LINE_EXTENT_MULTIPLIER; - - return { - start: { - x: cx - lineDirX * extent, - y: cy - lineDirY * extent, - }, - end: { - x: cx + lineDirX * extent, - y: cy + lineDirY * extent, - }, - }; -} - -export function getLineMaskOverlay({ - centerX, - centerY, - rotation, - bounds, - handleId = "position", - cursor = "move", -}: { - centerX: number; - centerY: number; - rotation: number; - bounds: ElementBounds; - handleId?: string; - cursor?: string; -}): MaskLineOverlay { - const { start, end } = getLineMaskLinePoints({ - centerX, - centerY, - rotation, - bounds, - }); - - return { - id: "line", - type: "line", - start, - end, - handleId, - cursor, - }; -} - -export function getLineMaskHandlePositions({ - centerX, - centerY, - rotation, - feather, - bounds, - displayScale, -}: { - centerX: number; - centerY: number; - rotation: number; - feather: number; - bounds: ElementBounds; - displayScale: number; -}): MaskHandlePosition[] { - const angleRad = (rotation * Math.PI) / 180; - const normalX = Math.cos(angleRad); - const normalY = Math.sin(angleRad); - - const cx = bounds.cx + centerX * bounds.width; - const cy = bounds.cy + centerY * bounds.height; - - const iconOffsetCanvas = LINE_HANDLE_OFFSET_SCREEN_PX / displayScale; - const featherOffset = iconOffsetCanvas + feather * FEATHER_HANDLE_SCALE; - - return [ - { - id: "rotation", - x: cx + normalX * iconOffsetCanvas, - y: cy + normalY * iconOffsetCanvas, - cursor: CURSOR.rotate, - kind: "icon", - icon: "rotate", - }, - { - id: "feather", - x: cx - normalX * featherOffset, - y: cy - normalY * featherOffset, - cursor: CURSOR.resizeHorizontal, - kind: "icon", - icon: "feather", - }, - ]; -} - -function rotatePoint({ - localX, - localY, - cx, - cy, - angleRad, -}: { - localX: number; - localY: number; - cx: number; - cy: number; - angleRad: number; -}): { x: number; y: number } { - const cos = Math.cos(angleRad); - const sin = Math.sin(angleRad); - return { - x: cx + localX * cos - localY * sin, - y: cy + localX * sin + localY * cos, - }; -} - -export function getBoxMaskHandlePositions({ - centerX, - centerY, - width, - height, - rotation, - feather, - sizeMode, - showScaleHandle = true, - bounds, - displayScale, -}: { - centerX: number; - centerY: number; - width: number; - height: number; - rotation: number; - feather: number; - sizeMode: MaskFeatures["sizeMode"]; - showScaleHandle?: boolean; - bounds: ElementBounds; - displayScale: number; -}): MaskHandlePosition[] { - const cx = bounds.cx + centerX * bounds.width; - const cy = bounds.cy + centerY * bounds.height; - const angleRad = (rotation * Math.PI) / 180; - const halfWidth = (width * bounds.width) / 2; - const halfHeight = (height * bounds.height) / 2; - - const handles: MaskHandlePosition[] = []; - const handleOffsetCanvas = BOX_HANDLE_OFFSET_SCREEN_PX / displayScale; - - const rotHandle = rotatePoint({ - localX: 0, - localY: -halfHeight - handleOffsetCanvas, - cx, - cy, - angleRad, - }); - handles.push({ - id: "rotation", - x: rotHandle.x, - y: rotHandle.y, - cursor: CURSOR.rotate, - kind: "icon", - icon: "rotate", - }); - - const featherHandle = rotatePoint({ - localX: 0, - localY: halfHeight + handleOffsetCanvas + feather * FEATHER_HANDLE_SCALE, - cx, - cy, - angleRad, - }); - handles.push({ - id: "feather", - x: featherHandle.x, - y: featherHandle.y, - cursor: CURSOR.resizeVertical, - kind: "icon", - icon: "feather", - }); - - if (sizeMode === "width-height") { - const corners = [ - { localX: -halfWidth, localY: -halfHeight, id: "top-left" }, - { localX: halfWidth, localY: -halfHeight, id: "top-right" }, - { localX: halfWidth, localY: halfHeight, id: "bottom-right" }, - { localX: -halfWidth, localY: halfHeight, id: "bottom-left" }, - ]; - for (const { localX, localY, id } of corners) { - const point = rotatePoint({ localX, localY, cx, cy, angleRad }); - handles.push({ - id, - x: point.x, - y: point.y, - cursor: CURSOR.resizeDiagonal, - kind: "corner", - }); - } - const right = rotatePoint({ - localX: halfWidth, - localY: 0, - cx, - cy, - angleRad, - }); - const left = rotatePoint({ - localX: -halfWidth, - localY: 0, - cx, - cy, - angleRad, - }); - const bottom = rotatePoint({ - localX: 0, - localY: halfHeight, - cx, - cy, - angleRad, - }); - handles.push({ - id: "left", - x: left.x, - y: left.y, - cursor: CURSOR.resizeHorizontal, - kind: "edge", - edgeAxis: "horizontal", - rotation, - }); - handles.push({ - id: "right", - x: right.x, - y: right.y, - cursor: CURSOR.resizeHorizontal, - kind: "edge", - edgeAxis: "horizontal", - rotation, - }); - handles.push({ - id: "bottom", - x: bottom.x, - y: bottom.y, - cursor: CURSOR.resizeVertical, - kind: "edge", - edgeAxis: "vertical", - rotation, - }); - } else if (sizeMode === "height-only") { - const top = rotatePoint({ - localX: 0, - localY: -halfHeight, - cx, - cy, - angleRad, - }); - const bottom = rotatePoint({ - localX: 0, - localY: halfHeight, - cx, - cy, - angleRad, - }); - handles.push({ - id: "top", - x: top.x, - y: top.y, - cursor: CURSOR.resizeVertical, - kind: "edge", - edgeAxis: "vertical", - rotation, - }); - handles.push({ - id: "bottom", - x: bottom.x, - y: bottom.y, - cursor: CURSOR.resizeVertical, - kind: "edge", - edgeAxis: "vertical", - rotation, - }); - } else if (sizeMode === "width-only") { - const left = rotatePoint({ - localX: -halfWidth, - localY: 0, - cx, - cy, - angleRad, - }); - const right = rotatePoint({ - localX: halfWidth, - localY: 0, - cx, - cy, - angleRad, - }); - handles.push({ - id: "left", - x: left.x, - y: left.y, - cursor: CURSOR.resizeHorizontal, - kind: "edge", - edgeAxis: "horizontal", - rotation, - }); - handles.push({ - id: "right", - x: right.x, - y: right.y, - cursor: CURSOR.resizeHorizontal, - kind: "edge", - edgeAxis: "horizontal", - rotation, - }); - } else if (sizeMode === "uniform" && showScaleHandle) { - const point = rotatePoint({ - localX: halfWidth, - localY: halfHeight, - cx, - cy, - angleRad, - }); - handles.push({ - id: "scale", - x: point.x, - y: point.y, - cursor: CURSOR.resizeDiagonal, - kind: "corner", - }); - } - - return handles; -} - -export function getBoxMaskRectOverlay({ - centerX, - centerY, - width, - height, - rotation, - bounds, - handleId = "position", - cursor = "move", - dashed = false, -}: { - centerX: number; - centerY: number; - width: number; - height: number; - rotation: number; - bounds: ElementBounds; - handleId?: string; - cursor?: string; - dashed?: boolean; -}): MaskRectOverlay { - return { - id: "bounding-box", - type: "rect", - center: { - x: bounds.cx + centerX * bounds.width, - y: bounds.cy + centerY * bounds.height, - }, - width: width * bounds.width, - height: height * bounds.height, - rotation, - handleId, - cursor, - dashed, - }; -} - -export function getBoxMaskShapeOverlay({ - centerX, - centerY, - width, - height, - rotation, - bounds, - pathData, - handleId = "position", - cursor = "move", -}: { - centerX: number; - centerY: number; - width: number; - height: number; - rotation: number; - bounds: ElementBounds; - pathData: string; - handleId?: string; - cursor?: string; -}): MaskShapeOverlay { - return { - id: "shape-outline", - type: "shape", - center: { - x: bounds.cx + centerX * bounds.width, - y: bounds.cy + centerY * bounds.height, - }, - width: width * bounds.width, - height: height * bounds.height, - rotation, - pathData, - handleId, - cursor, - }; -} - -export function getBoxMaskOverlays({ - params, - bounds, - pathData, - showBoundingBox = true, -}: { - params: Pick< - RectangleMaskParams, - "centerX" | "centerY" | "width" | "height" | "rotation" - >; - bounds: ElementBounds; - pathData?: string; - showBoundingBox?: boolean; -}): MaskOverlay[] { - const overlays: MaskOverlay[] = []; - if (showBoundingBox) { - overlays.push( - getBoxMaskRectOverlay({ - centerX: params.centerX, - centerY: params.centerY, - width: params.width, - height: params.height, - rotation: params.rotation, - bounds, - dashed: Boolean(pathData), - }), - ); - } - - if (pathData) { - overlays.push( - getBoxMaskShapeOverlay({ - centerX: params.centerX, - centerY: params.centerY, - width: params.width, - height: params.height, - rotation: params.rotation, - bounds, - pathData, - }), - ); - } - - return overlays; -} |
