aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/InkTangentHandles.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/InkTangentHandles.tsx')
-rw-r--r--src/client/views/InkTangentHandles.tsx129
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