js-draw 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +4 -4
- package/dist/bundle.js +1 -1
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/src/Color4.js +2 -2
- package/dist/cjs/src/Editor.d.ts +5 -4
- package/dist/cjs/src/Editor.js +25 -7
- package/dist/cjs/src/EditorImage.js +8 -2
- package/dist/cjs/src/Viewport.js +1 -1
- package/dist/cjs/src/commands/Command.js +1 -1
- package/dist/cjs/src/commands/SerializableCommand.js +1 -1
- package/dist/cjs/src/components/AbstractComponent.d.ts +13 -1
- package/dist/cjs/src/components/AbstractComponent.js +25 -10
- package/dist/cjs/src/components/Stroke.d.ts +1 -0
- package/dist/cjs/src/components/Stroke.js +14 -1
- package/dist/cjs/src/components/TextComponent.js +1 -1
- package/dist/cjs/src/components/util/StrokeSmoother.js +12 -14
- package/dist/cjs/src/localizations/de.js +1 -1
- package/dist/cjs/src/math/LineSegment2.d.ts +2 -0
- package/dist/cjs/src/math/LineSegment2.js +4 -0
- package/dist/cjs/src/math/Mat33.js +1 -1
- package/dist/cjs/src/math/Path.d.ts +24 -3
- package/dist/cjs/src/math/Path.js +225 -4
- package/dist/cjs/src/math/Rect2.js +5 -4
- package/dist/cjs/src/math/Vec3.js +1 -1
- package/dist/cjs/src/math/polynomial/QuadraticBezier.d.ts +1 -1
- package/dist/cjs/src/math/polynomial/QuadraticBezier.js +3 -4
- package/dist/cjs/src/rendering/Display.d.ts +1 -1
- package/dist/cjs/src/rendering/Display.js +1 -2
- package/dist/cjs/src/toolbar/HTMLToolbar.d.ts +2 -0
- package/dist/cjs/src/toolbar/HTMLToolbar.js +71 -5
- package/dist/cjs/src/toolbar/widgets/BaseToolWidget.d.ts +0 -1
- package/dist/cjs/src/toolbar/widgets/BaseToolWidget.js +0 -1
- package/dist/cjs/src/toolbar/widgets/BaseWidget.d.ts +5 -0
- package/dist/cjs/src/toolbar/widgets/BaseWidget.js +17 -5
- package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.js +1 -1
- package/dist/cjs/src/toolbar/widgets/EraserToolWidget.js +1 -1
- package/dist/cjs/src/toolbar/widgets/InsertImageWidget.js +1 -1
- package/dist/cjs/src/toolbar/widgets/PenToolWidget.js +1 -1
- package/dist/cjs/src/toolbar/widgets/TextToolWidget.js +1 -1
- package/dist/cjs/src/tools/SelectionTool/Selection.js +5 -2
- package/dist/cjs/src/tools/SelectionTool/SelectionTool.js +1 -1
- package/dist/cjs/src/types.d.ts +2 -0
- package/dist/mjs/src/Color4.mjs +2 -1
- package/dist/mjs/src/Editor.d.ts +5 -4
- package/dist/mjs/src/Editor.mjs +25 -7
- package/dist/mjs/src/EditorImage.mjs +10 -2
- package/dist/mjs/src/Viewport.mjs +2 -1
- package/dist/mjs/src/commands/Command.mjs +2 -1
- package/dist/mjs/src/commands/SerializableCommand.mjs +2 -1
- package/dist/mjs/src/components/AbstractComponent.d.ts +13 -1
- package/dist/mjs/src/components/AbstractComponent.mjs +26 -10
- package/dist/mjs/src/components/Stroke.d.ts +1 -0
- package/dist/mjs/src/components/Stroke.mjs +14 -1
- package/dist/mjs/src/components/TextComponent.mjs +2 -1
- package/dist/mjs/src/components/util/StrokeSmoother.mjs +12 -14
- package/dist/mjs/src/localizations/de.mjs +1 -1
- package/dist/mjs/src/math/LineSegment2.d.ts +2 -0
- package/dist/mjs/src/math/LineSegment2.mjs +4 -0
- package/dist/mjs/src/math/Mat33.mjs +2 -1
- package/dist/mjs/src/math/Path.d.ts +24 -3
- package/dist/mjs/src/math/Path.mjs +226 -4
- package/dist/mjs/src/math/Rect2.mjs +6 -4
- package/dist/mjs/src/math/Vec3.mjs +2 -1
- package/dist/mjs/src/math/polynomial/QuadraticBezier.d.ts +1 -1
- package/dist/mjs/src/math/polynomial/QuadraticBezier.mjs +3 -4
- package/dist/mjs/src/rendering/Display.d.ts +1 -1
- package/dist/mjs/src/rendering/Display.mjs +1 -2
- package/dist/mjs/src/toolbar/HTMLToolbar.d.ts +2 -0
- package/dist/mjs/src/toolbar/HTMLToolbar.mjs +73 -6
- package/dist/mjs/src/toolbar/widgets/BaseToolWidget.d.ts +0 -1
- package/dist/mjs/src/toolbar/widgets/BaseToolWidget.mjs +0 -1
- package/dist/mjs/src/toolbar/widgets/BaseWidget.d.ts +5 -0
- package/dist/mjs/src/toolbar/widgets/BaseWidget.mjs +17 -5
- package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.mjs +2 -1
- package/dist/mjs/src/toolbar/widgets/EraserToolWidget.mjs +2 -1
- package/dist/mjs/src/toolbar/widgets/InsertImageWidget.mjs +2 -1
- package/dist/mjs/src/toolbar/widgets/PenToolWidget.mjs +2 -1
- package/dist/mjs/src/toolbar/widgets/TextToolWidget.mjs +2 -1
- package/dist/mjs/src/tools/SelectionTool/Selection.mjs +6 -2
- package/dist/mjs/src/tools/SelectionTool/SelectionTool.mjs +2 -1
- package/dist/mjs/src/types.d.ts +2 -0
- package/package.json +12 -12
- package/src/Coloris.css +52 -0
- package/src/Editor.css +12 -0
- package/src/toolbar/toolbar.css +16 -0
- package/tsconfig-typedoc.json +7 -0
- package/tsconfig.json +2 -0
- package/typedoc.json +1 -0
@@ -10,6 +10,7 @@ const LineSegment2_1 = __importDefault(require("./LineSegment2"));
|
|
10
10
|
const Mat33_1 = __importDefault(require("./Mat33"));
|
11
11
|
const Rect2_1 = __importDefault(require("./Rect2"));
|
12
12
|
const Vec2_1 = require("./Vec2");
|
13
|
+
const Vec3_1 = __importDefault(require("./Vec3"));
|
13
14
|
var PathCommandType;
|
14
15
|
(function (PathCommandType) {
|
15
16
|
PathCommandType[PathCommandType["LineTo"] = 0] = "LineTo";
|
@@ -17,6 +18,23 @@ var PathCommandType;
|
|
17
18
|
PathCommandType[PathCommandType["CubicBezierTo"] = 2] = "CubicBezierTo";
|
18
19
|
PathCommandType[PathCommandType["QuadraticBezierTo"] = 3] = "QuadraticBezierTo";
|
19
20
|
})(PathCommandType = exports.PathCommandType || (exports.PathCommandType = {}));
|
21
|
+
// Returns the bounding box of one path segment.
|
22
|
+
const getPartBBox = (part) => {
|
23
|
+
let partBBox;
|
24
|
+
if (part instanceof LineSegment2_1.default) {
|
25
|
+
partBBox = part.bbox;
|
26
|
+
}
|
27
|
+
else if (part instanceof bezier_js_1.Bezier) {
|
28
|
+
const bbox = part.bbox();
|
29
|
+
const width = bbox.x.max - bbox.x.min;
|
30
|
+
const height = bbox.y.max - bbox.y.min;
|
31
|
+
partBBox = new Rect2_1.default(bbox.x.min, bbox.y.min, width, height);
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
partBBox = new Rect2_1.default(part.x, part.y, 0, 0);
|
35
|
+
}
|
36
|
+
return partBBox;
|
37
|
+
};
|
20
38
|
class Path {
|
21
39
|
constructor(startPoint, parts) {
|
22
40
|
this.startPoint = startPoint;
|
@@ -32,6 +50,13 @@ class Path {
|
|
32
50
|
this.bbox = this.bbox.union(Path.computeBBoxForSegment(startPoint, part));
|
33
51
|
}
|
34
52
|
}
|
53
|
+
getExactBBox() {
|
54
|
+
const bboxes = [];
|
55
|
+
for (const part of this.geometry) {
|
56
|
+
bboxes.push(getPartBBox(part));
|
57
|
+
}
|
58
|
+
return Rect2_1.default.union(...bboxes);
|
59
|
+
}
|
35
60
|
// Lazy-loads and returns this path's geometry
|
36
61
|
get geometry() {
|
37
62
|
if (this.cachedGeometry) {
|
@@ -54,6 +79,7 @@ class Path {
|
|
54
79
|
startPoint = part.point;
|
55
80
|
break;
|
56
81
|
case PathCommandType.MoveTo:
|
82
|
+
geometry.push(part.point);
|
57
83
|
startPoint = part.point;
|
58
84
|
break;
|
59
85
|
}
|
@@ -109,11 +135,197 @@ class Path {
|
|
109
135
|
}
|
110
136
|
return Rect2_1.default.bboxOf(points);
|
111
137
|
}
|
112
|
-
|
113
|
-
|
138
|
+
/**
|
139
|
+
* Let `S` be a closed path a distance `strokeRadius` from this path.
|
140
|
+
*
|
141
|
+
* @returns Approximate intersections of `line` with `S` using ray marching, starting from
|
142
|
+
* both end points of `line` and each point in `additionalRaymarchStartPoints`.
|
143
|
+
*/
|
144
|
+
raymarchIntersectionWith(line, strokeRadius, additionalRaymarchStartPoints = []) {
|
145
|
+
var _a, _b;
|
146
|
+
// No intersection between bounding boxes: No possible intersection
|
147
|
+
// of the interior.
|
148
|
+
if (!line.bbox.intersects(this.bbox.grownBy(strokeRadius))) {
|
114
149
|
return [];
|
115
150
|
}
|
151
|
+
const lineLength = line.length;
|
152
|
+
const partDistFunctionRecords = [];
|
153
|
+
// Determine distance functions for all parts that the given line could possibly intersect with
|
154
|
+
for (const part of this.geometry) {
|
155
|
+
const bbox = getPartBBox(part).grownBy(strokeRadius);
|
156
|
+
if (!bbox.intersects(line.bbox)) {
|
157
|
+
continue;
|
158
|
+
}
|
159
|
+
// Signed distance function
|
160
|
+
let partDist;
|
161
|
+
if (part instanceof LineSegment2_1.default) {
|
162
|
+
partDist = (point) => part.distance(point);
|
163
|
+
}
|
164
|
+
else if (part instanceof Vec3_1.default) {
|
165
|
+
partDist = (point) => part.minus(point).magnitude();
|
166
|
+
}
|
167
|
+
else {
|
168
|
+
partDist = (point) => {
|
169
|
+
return part.project(point).d;
|
170
|
+
};
|
171
|
+
}
|
172
|
+
// Part signed distance function (negative result implies `point` is
|
173
|
+
// inside the shape).
|
174
|
+
const partSdf = (point) => partDist(point) - strokeRadius;
|
175
|
+
// If the line can't possibly intersect the part,
|
176
|
+
if (partSdf(line.p1) > lineLength && partSdf(line.p2) > lineLength) {
|
177
|
+
continue;
|
178
|
+
}
|
179
|
+
partDistFunctionRecords.push({
|
180
|
+
part,
|
181
|
+
distFn: partDist,
|
182
|
+
bbox,
|
183
|
+
});
|
184
|
+
}
|
185
|
+
// If no distance functions, there are no intersections.
|
186
|
+
if (partDistFunctionRecords.length === 0) {
|
187
|
+
return [];
|
188
|
+
}
|
189
|
+
// Returns the minimum distance to a part in this stroke, where only parts that the given
|
190
|
+
// line could intersect are considered.
|
191
|
+
const sdf = (point) => {
|
192
|
+
let minDist = Infinity;
|
193
|
+
let minDistPart = null;
|
194
|
+
const uncheckedDistFunctions = [];
|
195
|
+
// First pass: only curves for which the current point is inside
|
196
|
+
// the bounding box.
|
197
|
+
for (const distFnRecord of partDistFunctionRecords) {
|
198
|
+
const { part, distFn, bbox } = distFnRecord;
|
199
|
+
// Check later if the current point isn't in the bounding box.
|
200
|
+
if (!bbox.containsPoint(point)) {
|
201
|
+
uncheckedDistFunctions.push(distFnRecord);
|
202
|
+
continue;
|
203
|
+
}
|
204
|
+
const currentDist = distFn(point);
|
205
|
+
if (currentDist <= minDist) {
|
206
|
+
minDist = currentDist;
|
207
|
+
minDistPart = part;
|
208
|
+
}
|
209
|
+
}
|
210
|
+
// Second pass: Everything else
|
211
|
+
for (const { part, distFn, bbox } of uncheckedDistFunctions) {
|
212
|
+
// Skip if impossible for the distance to the target to be lesser than
|
213
|
+
// the current minimum.
|
214
|
+
if (!bbox.grownBy(minDist).containsPoint(point)) {
|
215
|
+
continue;
|
216
|
+
}
|
217
|
+
const currentDist = distFn(point);
|
218
|
+
if (currentDist <= minDist) {
|
219
|
+
minDist = currentDist;
|
220
|
+
minDistPart = part;
|
221
|
+
}
|
222
|
+
}
|
223
|
+
return [minDistPart, minDist - strokeRadius];
|
224
|
+
};
|
225
|
+
// Raymarch:
|
226
|
+
const maxRaymarchSteps = 7;
|
227
|
+
// Start raymarching from each of these points. This allows detection of multiple
|
228
|
+
// intersections.
|
229
|
+
const startPoints = [
|
230
|
+
line.p1, ...additionalRaymarchStartPoints, line.p2
|
231
|
+
];
|
232
|
+
// Converts a point ON THE LINE to a parameter
|
233
|
+
const pointToParameter = (point) => {
|
234
|
+
// Because line.direction is a unit vector, this computes the length
|
235
|
+
// of the projection of the vector(line.p1->point) onto line.direction.
|
236
|
+
//
|
237
|
+
// Note that this can be negative if the given point is outside of the given
|
238
|
+
// line segment.
|
239
|
+
return point.minus(line.p1).dot(line.direction);
|
240
|
+
};
|
241
|
+
// Sort start points by parameter on the line.
|
242
|
+
// This allows us to determine whether the current value of a parameter
|
243
|
+
// drops down to a value already tested.
|
244
|
+
startPoints.sort((a, b) => {
|
245
|
+
const t_a = pointToParameter(a);
|
246
|
+
const t_b = pointToParameter(b);
|
247
|
+
// Sort in increasing order
|
248
|
+
return t_a - t_b;
|
249
|
+
});
|
116
250
|
const result = [];
|
251
|
+
const stoppingThreshold = strokeRadius / 1000;
|
252
|
+
// Returns the maximum x value explored
|
253
|
+
const raymarchFrom = (startPoint,
|
254
|
+
// Direction to march in (multiplies line.direction)
|
255
|
+
directionMultiplier,
|
256
|
+
// Terminate if the current point corresponds to a parameter
|
257
|
+
// below this.
|
258
|
+
minimumLineParameter) => {
|
259
|
+
let currentPoint = startPoint;
|
260
|
+
let [lastPart, lastDist] = sdf(currentPoint);
|
261
|
+
let lastParameter = pointToParameter(currentPoint);
|
262
|
+
if (lastDist > lineLength) {
|
263
|
+
return lastParameter;
|
264
|
+
}
|
265
|
+
const direction = line.direction.times(directionMultiplier);
|
266
|
+
for (let i = 0; i < maxRaymarchSteps; i++) {
|
267
|
+
// Step in the direction of the edge of the shape.
|
268
|
+
const step = lastDist;
|
269
|
+
currentPoint = currentPoint.plus(direction.times(step));
|
270
|
+
lastParameter = pointToParameter(currentPoint);
|
271
|
+
// If we're below the minimum parameter, stop. We've already tried
|
272
|
+
// this.
|
273
|
+
if (lastParameter <= minimumLineParameter) {
|
274
|
+
return lastParameter;
|
275
|
+
}
|
276
|
+
const [currentPart, signedDist] = sdf(currentPoint);
|
277
|
+
// Ensure we're stepping in the correct direction.
|
278
|
+
// Note that because we could start with a negative distance and work towards a
|
279
|
+
// positive distance, we need absolute values here.
|
280
|
+
if (Math.abs(signedDist) > Math.abs(lastDist)) {
|
281
|
+
// If not, stop.
|
282
|
+
return null;
|
283
|
+
}
|
284
|
+
lastDist = signedDist;
|
285
|
+
lastPart = currentPart;
|
286
|
+
// Is the distance close enough that we can stop early?
|
287
|
+
if (Math.abs(lastDist) < stoppingThreshold) {
|
288
|
+
break;
|
289
|
+
}
|
290
|
+
}
|
291
|
+
// Ensure that the point we ended with is on the line.
|
292
|
+
const isOnLineSegment = lastParameter >= 0 && lastParameter <= lineLength;
|
293
|
+
if (lastPart && isOnLineSegment && Math.abs(lastDist) < stoppingThreshold) {
|
294
|
+
result.push({
|
295
|
+
point: currentPoint,
|
296
|
+
parameterValue: NaN,
|
297
|
+
curve: lastPart,
|
298
|
+
});
|
299
|
+
}
|
300
|
+
return lastParameter;
|
301
|
+
};
|
302
|
+
// The maximum value of the line's parameter explored so far (0 corresponds to
|
303
|
+
// line.p1)
|
304
|
+
let maxLineT = 0;
|
305
|
+
// Raymarch for each start point.
|
306
|
+
//
|
307
|
+
// Use a for (i from 0 to length) loop because startPoints may be added
|
308
|
+
// during iteration.
|
309
|
+
for (let i = 0; i < startPoints.length; i++) {
|
310
|
+
const startPoint = startPoints[i];
|
311
|
+
// Try raymarching in both directions.
|
312
|
+
maxLineT = Math.max(maxLineT, (_a = raymarchFrom(startPoint, 1, maxLineT)) !== null && _a !== void 0 ? _a : maxLineT);
|
313
|
+
maxLineT = Math.max(maxLineT, (_b = raymarchFrom(startPoint, -1, maxLineT)) !== null && _b !== void 0 ? _b : maxLineT);
|
314
|
+
}
|
315
|
+
return result;
|
316
|
+
}
|
317
|
+
/**
|
318
|
+
* Returns a list of intersections with this path. If `strokeRadius` is given,
|
319
|
+
* intersections are approximated with the surface `strokeRadius` away from this.
|
320
|
+
*
|
321
|
+
* If `strokeRadius > 0`, the resultant `parameterValue` has no defined value.
|
322
|
+
*/
|
323
|
+
intersection(line, strokeRadius) {
|
324
|
+
let result = [];
|
325
|
+
// Is any intersection between shapes within the bounding boxes impossible?
|
326
|
+
if (!line.bbox.intersects(this.bbox.grownBy(strokeRadius !== null && strokeRadius !== void 0 ? strokeRadius : 0))) {
|
327
|
+
return [];
|
328
|
+
}
|
117
329
|
for (const part of this.geometry) {
|
118
330
|
if (part instanceof LineSegment2_1.default) {
|
119
331
|
const intersection = part.intersection(line);
|
@@ -125,7 +337,7 @@ class Path {
|
|
125
337
|
});
|
126
338
|
}
|
127
339
|
}
|
128
|
-
else {
|
340
|
+
else if (part instanceof bezier_js_1.Bezier) {
|
129
341
|
const intersectionPoints = part.intersects(line).map(t => {
|
130
342
|
// We're using the .intersects(line) function, which is documented
|
131
343
|
// to always return numbers. However, to satisfy the type checker (and
|
@@ -148,6 +360,15 @@ class Path {
|
|
148
360
|
result.push(...intersectionPoints);
|
149
361
|
}
|
150
362
|
}
|
363
|
+
// If given a non-zero strokeWidth, attempt to raymarch.
|
364
|
+
// Even if raymarching, we need to collect starting points.
|
365
|
+
// We use the above-calculated intersections for this.
|
366
|
+
const doRaymarching = strokeRadius && strokeRadius > 1e-8;
|
367
|
+
if (doRaymarching) {
|
368
|
+
// Starting points for raymarching (in addition to the end points of the line).
|
369
|
+
const startPoints = result.map(intersection => intersection.point);
|
370
|
+
result = this.raymarchIntersectionWith(line, strokeRadius, startPoints);
|
371
|
+
}
|
151
372
|
return result;
|
152
373
|
}
|
153
374
|
static mapPathCommand(part, mapping) {
|
@@ -651,5 +872,5 @@ class Path {
|
|
651
872
|
return result;
|
652
873
|
}
|
653
874
|
}
|
654
|
-
exports.default = Path;
|
655
875
|
Path.empty = new Path(Vec2_1.Vec2.zero, []);
|
876
|
+
exports.default = Path;
|
@@ -73,9 +73,7 @@ class Rect2 {
|
|
73
73
|
}
|
74
74
|
// Returns a new rectangle containing both [this] and [other].
|
75
75
|
union(other) {
|
76
|
-
|
77
|
-
const bottomRight = this.bottomRight.zip(other.bottomRight, Math.max);
|
78
|
-
return Rect2.fromCorners(topLeft, bottomRight);
|
76
|
+
return Rect2.union(this, other);
|
79
77
|
}
|
80
78
|
// Returns a the subdivision of this into [columns] columns
|
81
79
|
// and [rows] rows. For example,
|
@@ -113,6 +111,9 @@ class Rect2 {
|
|
113
111
|
}
|
114
112
|
// Returns this grown by [margin] in both the x and y directions.
|
115
113
|
grownBy(margin) {
|
114
|
+
if (margin === 0) {
|
115
|
+
return this;
|
116
|
+
}
|
116
117
|
return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
|
117
118
|
}
|
118
119
|
getClosestPointOnBoundaryTo(target) {
|
@@ -229,6 +230,6 @@ class Rect2 {
|
|
229
230
|
return new Rect2(template.x, template.y, width, height);
|
230
231
|
}
|
231
232
|
}
|
232
|
-
exports.default = Rect2;
|
233
233
|
Rect2.empty = new Rect2(0, 0, 0, 0);
|
234
234
|
Rect2.unitSquare = new Rect2(0, 0, 1, 1);
|
235
|
+
exports.default = Rect2;
|
@@ -170,8 +170,8 @@ class Vec3 {
|
|
170
170
|
return `Vec(${this.x}, ${this.y}, ${this.z})`;
|
171
171
|
}
|
172
172
|
}
|
173
|
-
exports.default = Vec3;
|
174
173
|
Vec3.unitX = Vec3.of(1, 0, 0);
|
175
174
|
Vec3.unitY = Vec3.of(0, 1, 0);
|
176
175
|
Vec3.unitZ = Vec3.of(0, 0, 1);
|
177
176
|
Vec3.zero = Vec3.of(0, 0, 0);
|
177
|
+
exports.default = Vec3;
|
@@ -21,7 +21,7 @@ export default class QuadraticBezier {
|
|
21
21
|
*/
|
22
22
|
approximateDistance(point: Point2): number;
|
23
23
|
/**
|
24
|
-
* @returns the exact distance from `point` to this.
|
24
|
+
* @returns the (more) exact distance from `point` to this.
|
25
25
|
*/
|
26
26
|
distance(point: Point2): number;
|
27
27
|
normal(t: number): Vec2;
|
@@ -97,15 +97,14 @@ class QuadraticBezier {
|
|
97
97
|
return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
|
98
98
|
}
|
99
99
|
/**
|
100
|
-
* @returns the exact distance from `point` to this.
|
100
|
+
* @returns the (more) exact distance from `point` to this.
|
101
101
|
*/
|
102
102
|
distance(point) {
|
103
103
|
if (!this.bezierJs) {
|
104
104
|
this.bezierJs = new bezier_js_1.Bezier([this.p0.xy, this.p1.xy, this.p2.xy]);
|
105
105
|
}
|
106
|
-
|
107
|
-
|
108
|
-
return dist;
|
106
|
+
// .d: Distance
|
107
|
+
return this.bezierJs.project(point.xy).d;
|
109
108
|
}
|
110
109
|
normal(t) {
|
111
110
|
const tangent = this.derivativeAt(t);
|
@@ -53,7 +53,7 @@ export default class Display {
|
|
53
53
|
*/
|
54
54
|
rerenderAsText(): void;
|
55
55
|
/**
|
56
|
-
* Clears the drawing
|
56
|
+
* Clears the main drawing surface and otherwise prepares for a rerender.
|
57
57
|
*
|
58
58
|
* @returns the dry ink renderer.
|
59
59
|
*/
|
@@ -176,14 +176,13 @@ class Display {
|
|
176
176
|
}
|
177
177
|
}
|
178
178
|
/**
|
179
|
-
* Clears the drawing
|
179
|
+
* Clears the main drawing surface and otherwise prepares for a rerender.
|
180
180
|
*
|
181
181
|
* @returns the dry ink renderer.
|
182
182
|
*/
|
183
183
|
startRerender() {
|
184
184
|
var _a;
|
185
185
|
(_a = this.resizeSurfacesCallback) === null || _a === void 0 ? void 0 : _a.call(this);
|
186
|
-
this.wetInkRenderer.clear();
|
187
186
|
this.dryInkRenderer.clear();
|
188
187
|
return this.dryInkRenderer;
|
189
188
|
}
|
@@ -22,6 +22,8 @@ export default class HTMLToolbar {
|
|
22
22
|
private updateColoris;
|
23
23
|
/** @internal */
|
24
24
|
constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
|
25
|
+
private closeColorPickerOverlay;
|
26
|
+
private setupCloseColorPickerOverlay;
|
25
27
|
setupColorPickers(): void;
|
26
28
|
private reLayoutQueued;
|
27
29
|
private queueReLayout;
|
@@ -22,6 +22,7 @@ const ActionButtonWidget_1 = __importDefault(require("./widgets/ActionButtonWidg
|
|
22
22
|
const InsertImageWidget_1 = __importDefault(require("./widgets/InsertImageWidget"));
|
23
23
|
const DocumentPropertiesWidget_1 = __importDefault(require("./widgets/DocumentPropertiesWidget"));
|
24
24
|
const OverflowWidget_1 = __importDefault(require("./widgets/OverflowWidget"));
|
25
|
+
const Vec2_1 = require("../math/Vec2");
|
25
26
|
exports.toolbarCSSPrefix = 'toolbar-';
|
26
27
|
class HTMLToolbar {
|
27
28
|
/** @internal */
|
@@ -36,6 +37,7 @@ class HTMLToolbar {
|
|
36
37
|
// Widget to toggle overflow menu.
|
37
38
|
this.overflowWidget = null;
|
38
39
|
this.updateColoris = null;
|
40
|
+
this.closeColorPickerOverlay = null;
|
39
41
|
this.reLayoutQueued = false;
|
40
42
|
this.container = document.createElement('div');
|
41
43
|
this.container.classList.add(`${exports.toolbarCSSPrefix}root`);
|
@@ -56,6 +58,68 @@ class HTMLToolbar {
|
|
56
58
|
console.warn('ResizeObserver not supported. Toolbar will not resize.');
|
57
59
|
}
|
58
60
|
}
|
61
|
+
setupCloseColorPickerOverlay() {
|
62
|
+
if (this.closeColorPickerOverlay)
|
63
|
+
return;
|
64
|
+
this.closeColorPickerOverlay = document.createElement('div');
|
65
|
+
this.closeColorPickerOverlay.className = `${exports.toolbarCSSPrefix}closeColorPickerOverlay`;
|
66
|
+
this.editor.createHTMLOverlay(this.closeColorPickerOverlay);
|
67
|
+
// Buffer events: Send events to the editor only if the pointer has moved enough to
|
68
|
+
// suggest that the user is attempting to draw, rather than click to close the color picker.
|
69
|
+
let eventBuffer = [];
|
70
|
+
let gestureStartPos = null;
|
71
|
+
// Hide the color picker when attempting to draw on the overlay.
|
72
|
+
this.listeners.push(this.editor.handlePointerEventsFrom(this.closeColorPickerOverlay, (eventName, event) => {
|
73
|
+
var _a, _b;
|
74
|
+
// Position of the current event.
|
75
|
+
const currentPos = Vec2_1.Vec2.of(event.pageX, event.pageY);
|
76
|
+
// Whether to send the current event to the editor
|
77
|
+
let sendToEditor = true;
|
78
|
+
if (eventName === 'pointerdown') {
|
79
|
+
(0, coloris_1.close)();
|
80
|
+
// Buffer the event, but don't send it to the editor yet.
|
81
|
+
// We don't want to send single-click events, but we do want to send full strokes.
|
82
|
+
eventBuffer = [];
|
83
|
+
eventBuffer.push([eventName, event]);
|
84
|
+
gestureStartPos = currentPos;
|
85
|
+
// Capture the pointer so we receive future events even if the overlay is hidden.
|
86
|
+
(_a = this.closeColorPickerOverlay) === null || _a === void 0 ? void 0 : _a.setPointerCapture(event.pointerId);
|
87
|
+
// Don't send to the editor.
|
88
|
+
sendToEditor = false;
|
89
|
+
}
|
90
|
+
else if (eventName === 'pointermove') {
|
91
|
+
// Skip if the pointer hasn't moved enough to not be a "click".
|
92
|
+
const strokeStartThreshold = 10;
|
93
|
+
if (gestureStartPos && currentPos.minus(gestureStartPos).magnitude() < strokeStartThreshold) {
|
94
|
+
eventBuffer.push([eventName, event]);
|
95
|
+
sendToEditor = false;
|
96
|
+
}
|
97
|
+
else {
|
98
|
+
// Send all buffered events to the editor -- start the stroke.
|
99
|
+
for (const [eventName, event] of eventBuffer) {
|
100
|
+
this.editor.handleHTMLPointerEvent(eventName, event);
|
101
|
+
}
|
102
|
+
eventBuffer = [];
|
103
|
+
sendToEditor = true;
|
104
|
+
}
|
105
|
+
}
|
106
|
+
// Otherwise, if we received a pointerup/pointercancel without flushing all pointerevents from the
|
107
|
+
// buffer, the gesture wasn't recognised as a stroke. Thus, the editor isn't expecting a pointerup/
|
108
|
+
// pointercancel event.
|
109
|
+
else if ((eventName === 'pointerup' || eventName === 'pointercancel') && eventBuffer.length > 0) {
|
110
|
+
(_b = this.closeColorPickerOverlay) === null || _b === void 0 ? void 0 : _b.releasePointerCapture(event.pointerId);
|
111
|
+
eventBuffer = [];
|
112
|
+
// Don't send to the editor.
|
113
|
+
sendToEditor = false;
|
114
|
+
}
|
115
|
+
// Transfer focus to the editor to allow keyboard events to be handled.
|
116
|
+
if (eventName === 'pointerup') {
|
117
|
+
this.editor.focus();
|
118
|
+
}
|
119
|
+
// Forward all other events to the editor.
|
120
|
+
return sendToEditor;
|
121
|
+
}));
|
122
|
+
}
|
59
123
|
// @internal
|
60
124
|
setupColorPickers() {
|
61
125
|
// Much of the setup only needs to be done once.
|
@@ -63,9 +127,7 @@ class HTMLToolbar {
|
|
63
127
|
this.updateColoris();
|
64
128
|
return;
|
65
129
|
}
|
66
|
-
|
67
|
-
closePickerOverlay.className = `${exports.toolbarCSSPrefix}closeColorPickerOverlay`;
|
68
|
-
this.editor.createHTMLOverlay(closePickerOverlay);
|
130
|
+
this.setupCloseColorPickerOverlay();
|
69
131
|
const maxSwatchLen = 12;
|
70
132
|
const swatches = [
|
71
133
|
Color4_1.default.red.toHexString(),
|
@@ -110,7 +172,9 @@ class HTMLToolbar {
|
|
110
172
|
}
|
111
173
|
// Show/hide the overlay. Making the overlay visible gives users a surface to click
|
112
174
|
// on that shows/hides the color picker.
|
113
|
-
|
175
|
+
if (this.closeColorPickerOverlay) {
|
176
|
+
this.closeColorPickerOverlay.style.display = event.open ? 'block' : 'none';
|
177
|
+
}
|
114
178
|
}));
|
115
179
|
// Add newly-selected colors to the swatch.
|
116
180
|
this.listeners.push(this.editor.notifier.on(types_1.EditorEventType.ColorPickerColorSelected, event => {
|
@@ -372,12 +436,14 @@ class HTMLToolbar {
|
|
372
436
|
this.addDefaultActionButtons();
|
373
437
|
}
|
374
438
|
remove() {
|
439
|
+
var _a;
|
375
440
|
this.container.remove();
|
376
441
|
this.resizeObserver.disconnect();
|
442
|
+
(_a = this.closeColorPickerOverlay) === null || _a === void 0 ? void 0 : _a.remove();
|
377
443
|
for (const listener of this.listeners) {
|
378
444
|
listener.remove();
|
379
445
|
}
|
380
446
|
}
|
381
447
|
}
|
382
|
-
exports.default = HTMLToolbar;
|
383
448
|
HTMLToolbar.colorisStarted = false;
|
449
|
+
exports.default = HTMLToolbar;
|
@@ -3,7 +3,6 @@ import BaseTool from '../../tools/BaseTool';
|
|
3
3
|
import { ToolbarLocalization } from '../localization';
|
4
4
|
import BaseWidget from './BaseWidget';
|
5
5
|
export default abstract class BaseToolWidget extends BaseWidget {
|
6
|
-
protected editor: Editor;
|
7
6
|
protected targetTool: BaseTool;
|
8
7
|
constructor(editor: Editor, targetTool: BaseTool, id: string, localizationTable?: ToolbarLocalization);
|
9
8
|
protected handleClick(): void;
|
@@ -8,7 +8,6 @@ const BaseWidget_1 = __importDefault(require("./BaseWidget"));
|
|
8
8
|
class BaseToolWidget extends BaseWidget_1.default {
|
9
9
|
constructor(editor, targetTool, id, localizationTable) {
|
10
10
|
super(editor, id, localizationTable);
|
11
|
-
this.editor = editor;
|
12
11
|
this.targetTool = targetTool;
|
13
12
|
editor.notifier.on(types_1.EditorEventType.ToolEnabled, toolEvt => {
|
14
13
|
if (toolEvt.kind !== types_1.EditorEventType.ToolEnabled) {
|
@@ -37,6 +37,11 @@ export default abstract class BaseWidget {
|
|
37
37
|
protected get hasDropdown(): boolean;
|
38
38
|
protected addSubWidget(widget: BaseWidget): void;
|
39
39
|
private toolbarWidgetToggleListener;
|
40
|
+
/**
|
41
|
+
* Adds this to `parent`. This can only be called once for each ToolbarWidget.
|
42
|
+
* Returns the element that was just added to `parent`.
|
43
|
+
* @internal
|
44
|
+
*/
|
40
45
|
addTo(parent: HTMLElement): HTMLElement;
|
41
46
|
protected updateIcon(): void;
|
42
47
|
setDisabled(disabled: boolean): void;
|
@@ -140,14 +140,26 @@ class BaseWidget {
|
|
140
140
|
const id = widget.getUniqueIdIn(this.subWidgets);
|
141
141
|
this.subWidgets[id] = widget;
|
142
142
|
}
|
143
|
-
|
144
|
-
|
145
|
-
|
143
|
+
/**
|
144
|
+
* Adds this to `parent`. This can only be called once for each ToolbarWidget.
|
145
|
+
* Returns the element that was just added to `parent`.
|
146
|
+
* @internal
|
147
|
+
*/
|
146
148
|
addTo(parent) {
|
147
|
-
|
148
|
-
this.setupActionBtnClickListener(this.button);
|
149
|
+
// Update title and icon
|
149
150
|
this.icon = null;
|
150
151
|
this.updateIcon();
|
152
|
+
this.label.innerText = this.getTitle();
|
153
|
+
const longLabelCSSClass = 'long-label';
|
154
|
+
if (this.label.innerText.length > 7) {
|
155
|
+
this.label.classList.add(longLabelCSSClass);
|
156
|
+
}
|
157
|
+
else {
|
158
|
+
this.label.classList.remove(longLabelCSSClass);
|
159
|
+
}
|
160
|
+
// Click functionality
|
161
|
+
this.setupActionBtnClickListener(this.button);
|
162
|
+
// Clear anything already in this.container.
|
151
163
|
this.container.replaceChildren();
|
152
164
|
this.button.replaceChildren(this.icon, this.label);
|
153
165
|
this.container.appendChild(this.button);
|
@@ -153,6 +153,9 @@ class Selection {
|
|
153
153
|
this.transform = Mat33_1.default.identity;
|
154
154
|
// Make the commands undo-able
|
155
155
|
this.editor.dispatch(new Selection.ApplyTransformationCommand(this, selectedElems, fullTransform));
|
156
|
+
// Clear renderings of any in-progress transformations
|
157
|
+
const wetInkRenderer = this.editor.display.getWetInkRenderer();
|
158
|
+
wetInkRenderer.clear();
|
156
159
|
}
|
157
160
|
// Preview the effects of the current transformation on the selection
|
158
161
|
previewTransformCmds() {
|
@@ -406,7 +409,6 @@ class Selection {
|
|
406
409
|
return this.selectedElems;
|
407
410
|
}
|
408
411
|
}
|
409
|
-
exports.default = Selection;
|
410
412
|
_a = Selection;
|
411
413
|
(() => {
|
412
414
|
SerializableCommand_1.default.register('selection-tool-transform', (json, _editor) => {
|
@@ -471,7 +473,7 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand_1.defau
|
|
471
473
|
(_b = this.selection) === null || _b === void 0 ? void 0 : _b.setTransform(this.fullTransform.inverse(), false);
|
472
474
|
(_c = this.selection) === null || _c === void 0 ? void 0 : _c.updateUI();
|
473
475
|
yield editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize, true);
|
474
|
-
(_d = this.selection) === null || _d === void 0 ? void 0 : _d.setTransform(Mat33_1.default.identity);
|
476
|
+
(_d = this.selection) === null || _d === void 0 ? void 0 : _d.setTransform(Mat33_1.default.identity, false);
|
475
477
|
(_e = this.selection) === null || _e === void 0 ? void 0 : _e.recomputeRegion();
|
476
478
|
(_f = this.selection) === null || _f === void 0 ? void 0 : _f.updateUI();
|
477
479
|
});
|
@@ -486,3 +488,4 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand_1.defau
|
|
486
488
|
return localizationTable.transformedElements(this.selectedElemIds.length);
|
487
489
|
}
|
488
490
|
};
|
491
|
+
exports.default = Selection;
|
@@ -393,7 +393,6 @@ class SelectionTool extends BaseTool_1.default {
|
|
393
393
|
this.onSelectionUpdated();
|
394
394
|
}
|
395
395
|
}
|
396
|
-
exports.default = SelectionTool;
|
397
396
|
SelectionTool.handleableKeys = [
|
398
397
|
'a', 'h', 'ArrowLeft',
|
399
398
|
'd', 'l', 'ArrowRight',
|
@@ -403,3 +402,4 @@ SelectionTool.handleableKeys = [
|
|
403
402
|
'i', 'I', 'o', 'O',
|
404
403
|
'Control', 'Meta',
|
405
404
|
];
|
405
|
+
exports.default = SelectionTool;
|