aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/InkHandles.tsx
blob: 28b6dd8209706949acf90a305468fd804229145e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import React = require("react");
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import { InkStrokeProperties } from "./InkStrokeProperties";
import { setupMoveUpEvents, emptyFunction } from "../../Utils";
import { UndoManager } from "../util/UndoManager";
import { InkData, HandlePoint, HandleLine } from "../../fields/InkField";
import { Transform } from "../util/Transform";
import { Doc } from "../../fields/Doc";
import { listSpec } from "../../fields/Schema";
import { List } from "../../fields/List";
import { Cast } from "../../fields/Types";

export interface InkHandlesProps {
    inkDoc: Doc;
    data: InkData;
    format: number[];
    ScreenToLocalTransform: () => Transform;
}

@observer
export class InkHandles 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) {
            InkStrokeProperties.Instance.moveControl(0, 0, 1);
            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
            const screenScale = this.props.ScreenToLocalTransform().Scale;
            const order = handleIndex % 4;
            const oppositeHandleIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
            const controlIndex = order === 1 ? handleIndex - 1 : handleIndex + 2;
            document.addEventListener("keydown", (e: KeyboardEvent) => this.onBreakTangent(e, controlIndex), true);
            setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
                InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
                return false;
            }, () => controlUndo?.end(), 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 = (e: KeyboardEvent, controlIndex: number) => {
        const doc: Doc = this.props.inkDoc;
        if (["Alt"].includes(e.key)) {
            if (doc) {
                const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
                if (brokenIndices && !brokenIndices.includes(controlIndex)) {
                    brokenIndices.push(controlIndex);
                }
                doc.brokenInkIndices = brokenIndices;
            }
        }
    }

    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.data;
        const handlePoints: HandlePoint[] = [];
        const handleLines: HandleLine[] = [];
        if (data.length >= 4) {
            for (let i = 0; i <= data.length - 4; i += 4) {
                handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 });
                handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
            }
            // Adding first and last (single) handle lines.
            handleLines.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 });
            handleLines.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) {
                handleLines.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 [left, top, scaleX, scaleY, strokeWidth, dotsize] = this.props.format;

        return (
            <>
                {handlePoints.map((pts, i) =>
                    <svg height="10" width="10" key={`hdl${i}`}>
                        <circle
                            cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                            cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
                            r={strokeWidth / 2}
                            strokeWidth={0}
                            fill="#1F85DE"
                            onPointerDown={(e) => this.onHandleDown(e, pts.I)}
                            pointerEvents="all"
                            cursor="default"
                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
                    </svg>)}
                {handleLines.map((pts, i) =>
                    <svg height="100" width="100" key={`line${i}`}>
                        <line
                            x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                            y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
                            x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                            y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
                            stroke="#1F85DE"
                            strokeWidth={dotsize / 8}
                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
                        <line
                            x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                            y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
                            x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                            y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
                            stroke="#1F85DE"
                            strokeWidth={dotsize / 8}
                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
                    </svg>)}
            </>
        );
    }
}