import React = require("react"); import { action } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; import { HandleLine, HandlePoint, InkData } from "../../fields/InkField"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; import { Cast } from "../../fields/Types"; import { emptyFunction, setupMoveUpEvents } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { InkingStroke } from "./InkingStroke"; export interface InkHandlesProps { inkDoc: Doc; screenCtrlPoints: InkData; screenSpaceLineWidth: number; ScreenToLocalTransform: () => Transform; } @observer export class InkTangentHandles extends React.Component { /** * Handles the movement of a selected handle point when the user clicks and drags. * @param handleNum The index of the currently selected handle point. */ onHandleDown = (e: React.PointerEvent, handleIndex: number): void => { if (InkStrokeProperties.Instance) { var controlUndo: UndoManager.Batch | undefined; const screenScale = this.props.ScreenToLocalTransform().Scale; const order = handleIndex % 4; const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length; const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length; setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent"); if (e.altKey) this.onBreakTangent(controlIndex); InkStrokeProperties.Instance?.moveTangentHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); return false; }, () => { controlUndo?.end(); UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); }, emptyFunction ); } } /** * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and * its matching (opposite) handle to a list of broken handle indices. * @param handleNum The index of the currently selected handle point. */ @action onBreakTangent = (controlIndex: number) => { const closed = InkingStroke.IsClosed(this.props.screenCtrlPoints); const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); if (!brokenIndices?.includes(controlIndex) && ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { if (brokenIndices) brokenIndices.push(controlIndex); else this.props.inkDoc.brokenInkIndices = new List([controlIndex]); } } render() { const formatInstance = InkStrokeProperties.Instance; if (!formatInstance) return (null); // Accessing the current ink's data and extracting all handle points and handle lines. const data = this.props.screenCtrlPoints; const tangentHandles: HandlePoint[] = []; const tangentLines: HandleLine[] = []; const closed = InkingStroke.IsClosed(data); if (data.length >= 4) { for (let i = 0; i <= data.length - 4; i += 4) { tangentHandles.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); tangentHandles.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); } // Adding first and last (single) handle lines. if (closed) { tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); } else { tangentLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 }); tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); } for (let i = 2; i < data.length - 4; i += 4) { tangentLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); } } const screenSpaceLineWidth = this.props.screenSpaceLineWidth; return ( <> {tangentHandles.map((pts, i) => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> )} {tangentLines.map((pts, i) => { const tangentLine = (x1: number, y1: number, x2: number, y2: number) => ; return {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)} {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)} ; })} ); } }