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
130
|
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";
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 oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.data.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.data.length;
const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.data.length;
setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
if (e.altKey) this.onBreakTangent(controlIndex);
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 = (controlIndex: number) => {
const doc = this.props.inkDoc;
if (doc) {
const closed = this.props.data.lastElement().X === this.props.data[0].X && this.props.data.lastElement().Y === this.props.data[0].Y;
const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
if (!brokenIndices?.includes(controlIndex) &&
((controlIndex > 0 && controlIndex < this.props.data.length - 1) || closed)) {
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[] = [];
const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y;
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 ? (closed ? data.length - 1 : 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 ? (closed ? (i + 4) % data.length : i + 3) : i + 4 });
}
// Adding first and last (single) handle lines.
if (closed) {
handleLines.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 {
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, screenSpaceLineWidth] = 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={screenSpaceLineWidth * 2}
strokeWidth={0}
fill={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>)}
{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={Colors.MEDIUM_BLUE}
strokeWidth={screenSpaceLineWidth}
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={Colors.MEDIUM_BLUE}
strokeWidth={screenSpaceLineWidth}
display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
</svg>)}
</>
);
}
}
|