diff options
Diffstat (limited to 'src/client/views/InkTangentHandles.tsx')
| -rw-r--r-- | src/client/views/InkTangentHandles.tsx | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx new file mode 100644 index 000000000..df5bebf31 --- /dev/null +++ b/src/client/views/InkTangentHandles.tsx @@ -0,0 +1,129 @@ +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<InkHandlesProps> { + /** + * 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<number>([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) => + <svg height="10" width="10" key={`hdl${i}`}> + <circle + cx={pts.X} + cy={pts.Y} + r={screenSpaceLineWidth * 2} + fill={Colors.MEDIUM_BLUE} + strokeWidth={1} + stroke={Colors.MEDIUM_BLUE} + onPointerDown={e => this.onHandleDown(e, pts.I)} + pointerEvents="all" + cursor="default" + display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> + </svg>)} + {tangentLines.map((pts, i) => { + const tangentLine = (x1: number, y1: number, x2: number, y2: number) => + <line + x1={x1} + y1={y1} + x2={x2} + y2={y2} + stroke={Colors.MEDIUM_BLUE} + strokeDasharray={"1 1"} + strokeWidth={1} + display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />; + return <svg height="100" width="100" key={`line${i}`}> + {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)} + {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)} + </svg>; + })} + </> + ); + } +}
\ No newline at end of file |
