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
123
124
125
126
127
128
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>;
})}
</>
);
}
}
|