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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
import * as React from '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 } from '../../Utils';
import { setupMoveUpEvents } from '../../ClientUtils';
import { UndoManager } from '../util/UndoManager';
import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
export interface InkHandlesProps {
inkDoc: Doc;
inkView: InkingStroke;
screenCtrlPoints: InkData;
screenSpaceLineWidth: number;
}
@observer
export class InkTangentHandles extends React.Component<InkHandlesProps> {
get docView() {
return this.props.inkView.DocumentView?.();
}
/**
* 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 => {
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,
action((moveEv: PointerEvent, down: number[], delta: number[]) => {
if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('DocDecs move tangent');
if (moveEv.altKey) this.onBreakTangent(controlIndex);
const inkMoveEnd = this.props.inkView.ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = this.props.inkView.ptFromScreen({ X: 0, Y: 0 });
this.docView && InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
return false;
}),
action(() => {
this.props.inkView.controlUndo?.end();
this.props.inkView.controlUndo = undefined;
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() {
// 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;
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.BLACK}
onPointerDown={e => this.onHandleDown(e, pts.I)}
pointerEvents="all"
cursor="default"
display={pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._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 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._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>
);
})}
</>
);
}
}
|