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
@@ -4,6 +4,7 @@ import LineSegment2 from './LineSegment2.mjs';
|
|
4
4
|
import Mat33 from './Mat33.mjs';
|
5
5
|
import Rect2 from './Rect2.mjs';
|
6
6
|
import { Vec2 } from './Vec2.mjs';
|
7
|
+
import Vec3 from './Vec3.mjs';
|
7
8
|
export var PathCommandType;
|
8
9
|
(function (PathCommandType) {
|
9
10
|
PathCommandType[PathCommandType["LineTo"] = 0] = "LineTo";
|
@@ -11,7 +12,24 @@ export var PathCommandType;
|
|
11
12
|
PathCommandType[PathCommandType["CubicBezierTo"] = 2] = "CubicBezierTo";
|
12
13
|
PathCommandType[PathCommandType["QuadraticBezierTo"] = 3] = "QuadraticBezierTo";
|
13
14
|
})(PathCommandType || (PathCommandType = {}));
|
14
|
-
|
15
|
+
// Returns the bounding box of one path segment.
|
16
|
+
const getPartBBox = (part) => {
|
17
|
+
let partBBox;
|
18
|
+
if (part instanceof LineSegment2) {
|
19
|
+
partBBox = part.bbox;
|
20
|
+
}
|
21
|
+
else if (part instanceof Bezier) {
|
22
|
+
const bbox = part.bbox();
|
23
|
+
const width = bbox.x.max - bbox.x.min;
|
24
|
+
const height = bbox.y.max - bbox.y.min;
|
25
|
+
partBBox = new Rect2(bbox.x.min, bbox.y.min, width, height);
|
26
|
+
}
|
27
|
+
else {
|
28
|
+
partBBox = new Rect2(part.x, part.y, 0, 0);
|
29
|
+
}
|
30
|
+
return partBBox;
|
31
|
+
};
|
32
|
+
class Path {
|
15
33
|
constructor(startPoint, parts) {
|
16
34
|
this.startPoint = startPoint;
|
17
35
|
this.parts = parts;
|
@@ -26,6 +44,13 @@ export default class Path {
|
|
26
44
|
this.bbox = this.bbox.union(Path.computeBBoxForSegment(startPoint, part));
|
27
45
|
}
|
28
46
|
}
|
47
|
+
getExactBBox() {
|
48
|
+
const bboxes = [];
|
49
|
+
for (const part of this.geometry) {
|
50
|
+
bboxes.push(getPartBBox(part));
|
51
|
+
}
|
52
|
+
return Rect2.union(...bboxes);
|
53
|
+
}
|
29
54
|
// Lazy-loads and returns this path's geometry
|
30
55
|
get geometry() {
|
31
56
|
if (this.cachedGeometry) {
|
@@ -48,6 +73,7 @@ export default class Path {
|
|
48
73
|
startPoint = part.point;
|
49
74
|
break;
|
50
75
|
case PathCommandType.MoveTo:
|
76
|
+
geometry.push(part.point);
|
51
77
|
startPoint = part.point;
|
52
78
|
break;
|
53
79
|
}
|
@@ -103,11 +129,197 @@ export default class Path {
|
|
103
129
|
}
|
104
130
|
return Rect2.bboxOf(points);
|
105
131
|
}
|
106
|
-
|
107
|
-
|
132
|
+
/**
|
133
|
+
* Let `S` be a closed path a distance `strokeRadius` from this path.
|
134
|
+
*
|
135
|
+
* @returns Approximate intersections of `line` with `S` using ray marching, starting from
|
136
|
+
* both end points of `line` and each point in `additionalRaymarchStartPoints`.
|
137
|
+
*/
|
138
|
+
raymarchIntersectionWith(line, strokeRadius, additionalRaymarchStartPoints = []) {
|
139
|
+
var _a, _b;
|
140
|
+
// No intersection between bounding boxes: No possible intersection
|
141
|
+
// of the interior.
|
142
|
+
if (!line.bbox.intersects(this.bbox.grownBy(strokeRadius))) {
|
143
|
+
return [];
|
144
|
+
}
|
145
|
+
const lineLength = line.length;
|
146
|
+
const partDistFunctionRecords = [];
|
147
|
+
// Determine distance functions for all parts that the given line could possibly intersect with
|
148
|
+
for (const part of this.geometry) {
|
149
|
+
const bbox = getPartBBox(part).grownBy(strokeRadius);
|
150
|
+
if (!bbox.intersects(line.bbox)) {
|
151
|
+
continue;
|
152
|
+
}
|
153
|
+
// Signed distance function
|
154
|
+
let partDist;
|
155
|
+
if (part instanceof LineSegment2) {
|
156
|
+
partDist = (point) => part.distance(point);
|
157
|
+
}
|
158
|
+
else if (part instanceof Vec3) {
|
159
|
+
partDist = (point) => part.minus(point).magnitude();
|
160
|
+
}
|
161
|
+
else {
|
162
|
+
partDist = (point) => {
|
163
|
+
return part.project(point).d;
|
164
|
+
};
|
165
|
+
}
|
166
|
+
// Part signed distance function (negative result implies `point` is
|
167
|
+
// inside the shape).
|
168
|
+
const partSdf = (point) => partDist(point) - strokeRadius;
|
169
|
+
// If the line can't possibly intersect the part,
|
170
|
+
if (partSdf(line.p1) > lineLength && partSdf(line.p2) > lineLength) {
|
171
|
+
continue;
|
172
|
+
}
|
173
|
+
partDistFunctionRecords.push({
|
174
|
+
part,
|
175
|
+
distFn: partDist,
|
176
|
+
bbox,
|
177
|
+
});
|
178
|
+
}
|
179
|
+
// If no distance functions, there are no intersections.
|
180
|
+
if (partDistFunctionRecords.length === 0) {
|
108
181
|
return [];
|
109
182
|
}
|
183
|
+
// Returns the minimum distance to a part in this stroke, where only parts that the given
|
184
|
+
// line could intersect are considered.
|
185
|
+
const sdf = (point) => {
|
186
|
+
let minDist = Infinity;
|
187
|
+
let minDistPart = null;
|
188
|
+
const uncheckedDistFunctions = [];
|
189
|
+
// First pass: only curves for which the current point is inside
|
190
|
+
// the bounding box.
|
191
|
+
for (const distFnRecord of partDistFunctionRecords) {
|
192
|
+
const { part, distFn, bbox } = distFnRecord;
|
193
|
+
// Check later if the current point isn't in the bounding box.
|
194
|
+
if (!bbox.containsPoint(point)) {
|
195
|
+
uncheckedDistFunctions.push(distFnRecord);
|
196
|
+
continue;
|
197
|
+
}
|
198
|
+
const currentDist = distFn(point);
|
199
|
+
if (currentDist <= minDist) {
|
200
|
+
minDist = currentDist;
|
201
|
+
minDistPart = part;
|
202
|
+
}
|
203
|
+
}
|
204
|
+
// Second pass: Everything else
|
205
|
+
for (const { part, distFn, bbox } of uncheckedDistFunctions) {
|
206
|
+
// Skip if impossible for the distance to the target to be lesser than
|
207
|
+
// the current minimum.
|
208
|
+
if (!bbox.grownBy(minDist).containsPoint(point)) {
|
209
|
+
continue;
|
210
|
+
}
|
211
|
+
const currentDist = distFn(point);
|
212
|
+
if (currentDist <= minDist) {
|
213
|
+
minDist = currentDist;
|
214
|
+
minDistPart = part;
|
215
|
+
}
|
216
|
+
}
|
217
|
+
return [minDistPart, minDist - strokeRadius];
|
218
|
+
};
|
219
|
+
// Raymarch:
|
220
|
+
const maxRaymarchSteps = 7;
|
221
|
+
// Start raymarching from each of these points. This allows detection of multiple
|
222
|
+
// intersections.
|
223
|
+
const startPoints = [
|
224
|
+
line.p1, ...additionalRaymarchStartPoints, line.p2
|
225
|
+
];
|
226
|
+
// Converts a point ON THE LINE to a parameter
|
227
|
+
const pointToParameter = (point) => {
|
228
|
+
// Because line.direction is a unit vector, this computes the length
|
229
|
+
// of the projection of the vector(line.p1->point) onto line.direction.
|
230
|
+
//
|
231
|
+
// Note that this can be negative if the given point is outside of the given
|
232
|
+
// line segment.
|
233
|
+
return point.minus(line.p1).dot(line.direction);
|
234
|
+
};
|
235
|
+
// Sort start points by parameter on the line.
|
236
|
+
// This allows us to determine whether the current value of a parameter
|
237
|
+
// drops down to a value already tested.
|
238
|
+
startPoints.sort((a, b) => {
|
239
|
+
const t_a = pointToParameter(a);
|
240
|
+
const t_b = pointToParameter(b);
|
241
|
+
// Sort in increasing order
|
242
|
+
return t_a - t_b;
|
243
|
+
});
|
110
244
|
const result = [];
|
245
|
+
const stoppingThreshold = strokeRadius / 1000;
|
246
|
+
// Returns the maximum x value explored
|
247
|
+
const raymarchFrom = (startPoint,
|
248
|
+
// Direction to march in (multiplies line.direction)
|
249
|
+
directionMultiplier,
|
250
|
+
// Terminate if the current point corresponds to a parameter
|
251
|
+
// below this.
|
252
|
+
minimumLineParameter) => {
|
253
|
+
let currentPoint = startPoint;
|
254
|
+
let [lastPart, lastDist] = sdf(currentPoint);
|
255
|
+
let lastParameter = pointToParameter(currentPoint);
|
256
|
+
if (lastDist > lineLength) {
|
257
|
+
return lastParameter;
|
258
|
+
}
|
259
|
+
const direction = line.direction.times(directionMultiplier);
|
260
|
+
for (let i = 0; i < maxRaymarchSteps; i++) {
|
261
|
+
// Step in the direction of the edge of the shape.
|
262
|
+
const step = lastDist;
|
263
|
+
currentPoint = currentPoint.plus(direction.times(step));
|
264
|
+
lastParameter = pointToParameter(currentPoint);
|
265
|
+
// If we're below the minimum parameter, stop. We've already tried
|
266
|
+
// this.
|
267
|
+
if (lastParameter <= minimumLineParameter) {
|
268
|
+
return lastParameter;
|
269
|
+
}
|
270
|
+
const [currentPart, signedDist] = sdf(currentPoint);
|
271
|
+
// Ensure we're stepping in the correct direction.
|
272
|
+
// Note that because we could start with a negative distance and work towards a
|
273
|
+
// positive distance, we need absolute values here.
|
274
|
+
if (Math.abs(signedDist) > Math.abs(lastDist)) {
|
275
|
+
// If not, stop.
|
276
|
+
return null;
|
277
|
+
}
|
278
|
+
lastDist = signedDist;
|
279
|
+
lastPart = currentPart;
|
280
|
+
// Is the distance close enough that we can stop early?
|
281
|
+
if (Math.abs(lastDist) < stoppingThreshold) {
|
282
|
+
break;
|
283
|
+
}
|
284
|
+
}
|
285
|
+
// Ensure that the point we ended with is on the line.
|
286
|
+
const isOnLineSegment = lastParameter >= 0 && lastParameter <= lineLength;
|
287
|
+
if (lastPart && isOnLineSegment && Math.abs(lastDist) < stoppingThreshold) {
|
288
|
+
result.push({
|
289
|
+
point: currentPoint,
|
290
|
+
parameterValue: NaN,
|
291
|
+
curve: lastPart,
|
292
|
+
});
|
293
|
+
}
|
294
|
+
return lastParameter;
|
295
|
+
};
|
296
|
+
// The maximum value of the line's parameter explored so far (0 corresponds to
|
297
|
+
// line.p1)
|
298
|
+
let maxLineT = 0;
|
299
|
+
// Raymarch for each start point.
|
300
|
+
//
|
301
|
+
// Use a for (i from 0 to length) loop because startPoints may be added
|
302
|
+
// during iteration.
|
303
|
+
for (let i = 0; i < startPoints.length; i++) {
|
304
|
+
const startPoint = startPoints[i];
|
305
|
+
// Try raymarching in both directions.
|
306
|
+
maxLineT = Math.max(maxLineT, (_a = raymarchFrom(startPoint, 1, maxLineT)) !== null && _a !== void 0 ? _a : maxLineT);
|
307
|
+
maxLineT = Math.max(maxLineT, (_b = raymarchFrom(startPoint, -1, maxLineT)) !== null && _b !== void 0 ? _b : maxLineT);
|
308
|
+
}
|
309
|
+
return result;
|
310
|
+
}
|
311
|
+
/**
|
312
|
+
* Returns a list of intersections with this path. If `strokeRadius` is given,
|
313
|
+
* intersections are approximated with the surface `strokeRadius` away from this.
|
314
|
+
*
|
315
|
+
* If `strokeRadius > 0`, the resultant `parameterValue` has no defined value.
|
316
|
+
*/
|
317
|
+
intersection(line, strokeRadius) {
|
318
|
+
let result = [];
|
319
|
+
// Is any intersection between shapes within the bounding boxes impossible?
|
320
|
+
if (!line.bbox.intersects(this.bbox.grownBy(strokeRadius !== null && strokeRadius !== void 0 ? strokeRadius : 0))) {
|
321
|
+
return [];
|
322
|
+
}
|
111
323
|
for (const part of this.geometry) {
|
112
324
|
if (part instanceof LineSegment2) {
|
113
325
|
const intersection = part.intersection(line);
|
@@ -119,7 +331,7 @@ export default class Path {
|
|
119
331
|
});
|
120
332
|
}
|
121
333
|
}
|
122
|
-
else {
|
334
|
+
else if (part instanceof Bezier) {
|
123
335
|
const intersectionPoints = part.intersects(line).map(t => {
|
124
336
|
// We're using the .intersects(line) function, which is documented
|
125
337
|
// to always return numbers. However, to satisfy the type checker (and
|
@@ -142,6 +354,15 @@ export default class Path {
|
|
142
354
|
result.push(...intersectionPoints);
|
143
355
|
}
|
144
356
|
}
|
357
|
+
// If given a non-zero strokeWidth, attempt to raymarch.
|
358
|
+
// Even if raymarching, we need to collect starting points.
|
359
|
+
// We use the above-calculated intersections for this.
|
360
|
+
const doRaymarching = strokeRadius && strokeRadius > 1e-8;
|
361
|
+
if (doRaymarching) {
|
362
|
+
// Starting points for raymarching (in addition to the end points of the line).
|
363
|
+
const startPoints = result.map(intersection => intersection.point);
|
364
|
+
result = this.raymarchIntersectionWith(line, strokeRadius, startPoints);
|
365
|
+
}
|
145
366
|
return result;
|
146
367
|
}
|
147
368
|
static mapPathCommand(part, mapping) {
|
@@ -646,3 +867,4 @@ export default class Path {
|
|
646
867
|
}
|
647
868
|
}
|
648
869
|
Path.empty = new Path(Vec2.zero, []);
|
870
|
+
export default Path;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import LineSegment2 from './LineSegment2.mjs';
|
2
2
|
import { Vec2 } from './Vec2.mjs';
|
3
3
|
// invariant: w ≥ 0, h ≥ 0.
|
4
|
-
|
4
|
+
class Rect2 {
|
5
5
|
constructor(x, y, w, h) {
|
6
6
|
this.x = x;
|
7
7
|
this.y = y;
|
@@ -68,9 +68,7 @@ export default class Rect2 {
|
|
68
68
|
}
|
69
69
|
// Returns a new rectangle containing both [this] and [other].
|
70
70
|
union(other) {
|
71
|
-
|
72
|
-
const bottomRight = this.bottomRight.zip(other.bottomRight, Math.max);
|
73
|
-
return Rect2.fromCorners(topLeft, bottomRight);
|
71
|
+
return Rect2.union(this, other);
|
74
72
|
}
|
75
73
|
// Returns a the subdivision of this into [columns] columns
|
76
74
|
// and [rows] rows. For example,
|
@@ -108,6 +106,9 @@ export default class Rect2 {
|
|
108
106
|
}
|
109
107
|
// Returns this grown by [margin] in both the x and y directions.
|
110
108
|
grownBy(margin) {
|
109
|
+
if (margin === 0) {
|
110
|
+
return this;
|
111
|
+
}
|
111
112
|
return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
|
112
113
|
}
|
113
114
|
getClosestPointOnBoundaryTo(target) {
|
@@ -226,3 +227,4 @@ export default class Rect2 {
|
|
226
227
|
}
|
227
228
|
Rect2.empty = new Rect2(0, 0, 0, 0);
|
228
229
|
Rect2.unitSquare = new Rect2(0, 0, 1, 1);
|
230
|
+
export default Rect2;
|
@@ -3,7 +3,7 @@
|
|
3
3
|
*
|
4
4
|
* A `Vec3` is immutable.
|
5
5
|
*/
|
6
|
-
|
6
|
+
class Vec3 {
|
7
7
|
constructor(x, y, z) {
|
8
8
|
this.x = x;
|
9
9
|
this.y = y;
|
@@ -172,3 +172,4 @@ Vec3.unitX = Vec3.of(1, 0, 0);
|
|
172
172
|
Vec3.unitY = Vec3.of(0, 1, 0);
|
173
173
|
Vec3.unitZ = Vec3.of(0, 0, 1);
|
174
174
|
Vec3.zero = Vec3.of(0, 0, 0);
|
175
|
+
export 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;
|
@@ -92,15 +92,14 @@ export default class QuadraticBezier {
|
|
92
92
|
return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
|
93
93
|
}
|
94
94
|
/**
|
95
|
-
* @returns the exact distance from `point` to this.
|
95
|
+
* @returns the (more) exact distance from `point` to this.
|
96
96
|
*/
|
97
97
|
distance(point) {
|
98
98
|
if (!this.bezierJs) {
|
99
99
|
this.bezierJs = new Bezier([this.p0.xy, this.p1.xy, this.p2.xy]);
|
100
100
|
}
|
101
|
-
|
102
|
-
|
103
|
-
return dist;
|
101
|
+
// .d: Distance
|
102
|
+
return this.bezierJs.project(point.xy).d;
|
104
103
|
}
|
105
104
|
normal(t) {
|
106
105
|
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
|
*/
|
@@ -170,14 +170,13 @@ export default class Display {
|
|
170
170
|
}
|
171
171
|
}
|
172
172
|
/**
|
173
|
-
* Clears the drawing
|
173
|
+
* Clears the main drawing surface and otherwise prepares for a rerender.
|
174
174
|
*
|
175
175
|
* @returns the dry ink renderer.
|
176
176
|
*/
|
177
177
|
startRerender() {
|
178
178
|
var _a;
|
179
179
|
(_a = this.resizeSurfacesCallback) === null || _a === void 0 ? void 0 : _a.call(this);
|
180
|
-
this.wetInkRenderer.clear();
|
181
180
|
this.dryInkRenderer.clear();
|
182
181
|
return this.dryInkRenderer;
|
183
182
|
}
|
@@ -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;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { EditorEventType } from '../types.mjs';
|
2
|
-
import { coloris, init as colorisInit } from '@melloware/coloris';
|
2
|
+
import { coloris, close as closeColoris, init as colorisInit } from '@melloware/coloris';
|
3
3
|
import Color4 from '../Color4.mjs';
|
4
4
|
import { defaultToolbarLocalization } from './localization.mjs';
|
5
5
|
import SelectionTool from '../tools/SelectionTool/SelectionTool.mjs';
|
@@ -16,8 +16,9 @@ import ActionButtonWidget from './widgets/ActionButtonWidget.mjs';
|
|
16
16
|
import InsertImageWidget from './widgets/InsertImageWidget.mjs';
|
17
17
|
import DocumentPropertiesWidget from './widgets/DocumentPropertiesWidget.mjs';
|
18
18
|
import OverflowWidget from './widgets/OverflowWidget.mjs';
|
19
|
+
import { Vec2 } from '../math/Vec2.mjs';
|
19
20
|
export const toolbarCSSPrefix = 'toolbar-';
|
20
|
-
|
21
|
+
class HTMLToolbar {
|
21
22
|
/** @internal */
|
22
23
|
constructor(editor, parent, localizationTable = defaultToolbarLocalization) {
|
23
24
|
this.editor = editor;
|
@@ -30,6 +31,7 @@ export default class HTMLToolbar {
|
|
30
31
|
// Widget to toggle overflow menu.
|
31
32
|
this.overflowWidget = null;
|
32
33
|
this.updateColoris = null;
|
34
|
+
this.closeColorPickerOverlay = null;
|
33
35
|
this.reLayoutQueued = false;
|
34
36
|
this.container = document.createElement('div');
|
35
37
|
this.container.classList.add(`${toolbarCSSPrefix}root`);
|
@@ -50,6 +52,68 @@ export default class HTMLToolbar {
|
|
50
52
|
console.warn('ResizeObserver not supported. Toolbar will not resize.');
|
51
53
|
}
|
52
54
|
}
|
55
|
+
setupCloseColorPickerOverlay() {
|
56
|
+
if (this.closeColorPickerOverlay)
|
57
|
+
return;
|
58
|
+
this.closeColorPickerOverlay = document.createElement('div');
|
59
|
+
this.closeColorPickerOverlay.className = `${toolbarCSSPrefix}closeColorPickerOverlay`;
|
60
|
+
this.editor.createHTMLOverlay(this.closeColorPickerOverlay);
|
61
|
+
// Buffer events: Send events to the editor only if the pointer has moved enough to
|
62
|
+
// suggest that the user is attempting to draw, rather than click to close the color picker.
|
63
|
+
let eventBuffer = [];
|
64
|
+
let gestureStartPos = null;
|
65
|
+
// Hide the color picker when attempting to draw on the overlay.
|
66
|
+
this.listeners.push(this.editor.handlePointerEventsFrom(this.closeColorPickerOverlay, (eventName, event) => {
|
67
|
+
var _a, _b;
|
68
|
+
// Position of the current event.
|
69
|
+
const currentPos = Vec2.of(event.pageX, event.pageY);
|
70
|
+
// Whether to send the current event to the editor
|
71
|
+
let sendToEditor = true;
|
72
|
+
if (eventName === 'pointerdown') {
|
73
|
+
closeColoris();
|
74
|
+
// Buffer the event, but don't send it to the editor yet.
|
75
|
+
// We don't want to send single-click events, but we do want to send full strokes.
|
76
|
+
eventBuffer = [];
|
77
|
+
eventBuffer.push([eventName, event]);
|
78
|
+
gestureStartPos = currentPos;
|
79
|
+
// Capture the pointer so we receive future events even if the overlay is hidden.
|
80
|
+
(_a = this.closeColorPickerOverlay) === null || _a === void 0 ? void 0 : _a.setPointerCapture(event.pointerId);
|
81
|
+
// Don't send to the editor.
|
82
|
+
sendToEditor = false;
|
83
|
+
}
|
84
|
+
else if (eventName === 'pointermove') {
|
85
|
+
// Skip if the pointer hasn't moved enough to not be a "click".
|
86
|
+
const strokeStartThreshold = 10;
|
87
|
+
if (gestureStartPos && currentPos.minus(gestureStartPos).magnitude() < strokeStartThreshold) {
|
88
|
+
eventBuffer.push([eventName, event]);
|
89
|
+
sendToEditor = false;
|
90
|
+
}
|
91
|
+
else {
|
92
|
+
// Send all buffered events to the editor -- start the stroke.
|
93
|
+
for (const [eventName, event] of eventBuffer) {
|
94
|
+
this.editor.handleHTMLPointerEvent(eventName, event);
|
95
|
+
}
|
96
|
+
eventBuffer = [];
|
97
|
+
sendToEditor = true;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
// Otherwise, if we received a pointerup/pointercancel without flushing all pointerevents from the
|
101
|
+
// buffer, the gesture wasn't recognised as a stroke. Thus, the editor isn't expecting a pointerup/
|
102
|
+
// pointercancel event.
|
103
|
+
else if ((eventName === 'pointerup' || eventName === 'pointercancel') && eventBuffer.length > 0) {
|
104
|
+
(_b = this.closeColorPickerOverlay) === null || _b === void 0 ? void 0 : _b.releasePointerCapture(event.pointerId);
|
105
|
+
eventBuffer = [];
|
106
|
+
// Don't send to the editor.
|
107
|
+
sendToEditor = false;
|
108
|
+
}
|
109
|
+
// Transfer focus to the editor to allow keyboard events to be handled.
|
110
|
+
if (eventName === 'pointerup') {
|
111
|
+
this.editor.focus();
|
112
|
+
}
|
113
|
+
// Forward all other events to the editor.
|
114
|
+
return sendToEditor;
|
115
|
+
}));
|
116
|
+
}
|
53
117
|
// @internal
|
54
118
|
setupColorPickers() {
|
55
119
|
// Much of the setup only needs to be done once.
|
@@ -57,9 +121,7 @@ export default class HTMLToolbar {
|
|
57
121
|
this.updateColoris();
|
58
122
|
return;
|
59
123
|
}
|
60
|
-
|
61
|
-
closePickerOverlay.className = `${toolbarCSSPrefix}closeColorPickerOverlay`;
|
62
|
-
this.editor.createHTMLOverlay(closePickerOverlay);
|
124
|
+
this.setupCloseColorPickerOverlay();
|
63
125
|
const maxSwatchLen = 12;
|
64
126
|
const swatches = [
|
65
127
|
Color4.red.toHexString(),
|
@@ -104,7 +166,9 @@ export default class HTMLToolbar {
|
|
104
166
|
}
|
105
167
|
// Show/hide the overlay. Making the overlay visible gives users a surface to click
|
106
168
|
// on that shows/hides the color picker.
|
107
|
-
|
169
|
+
if (this.closeColorPickerOverlay) {
|
170
|
+
this.closeColorPickerOverlay.style.display = event.open ? 'block' : 'none';
|
171
|
+
}
|
108
172
|
}));
|
109
173
|
// Add newly-selected colors to the swatch.
|
110
174
|
this.listeners.push(this.editor.notifier.on(EditorEventType.ColorPickerColorSelected, event => {
|
@@ -366,11 +430,14 @@ export default class HTMLToolbar {
|
|
366
430
|
this.addDefaultActionButtons();
|
367
431
|
}
|
368
432
|
remove() {
|
433
|
+
var _a;
|
369
434
|
this.container.remove();
|
370
435
|
this.resizeObserver.disconnect();
|
436
|
+
(_a = this.closeColorPickerOverlay) === null || _a === void 0 ? void 0 : _a.remove();
|
371
437
|
for (const listener of this.listeners) {
|
372
438
|
listener.remove();
|
373
439
|
}
|
374
440
|
}
|
375
441
|
}
|
376
442
|
HTMLToolbar.colorisStarted = false;
|
443
|
+
export 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;
|
@@ -3,7 +3,6 @@ import BaseWidget from './BaseWidget.mjs';
|
|
3
3
|
export default class BaseToolWidget extends BaseWidget {
|
4
4
|
constructor(editor, targetTool, id, localizationTable) {
|
5
5
|
super(editor, id, localizationTable);
|
6
|
-
this.editor = editor;
|
7
6
|
this.targetTool = targetTool;
|
8
7
|
editor.notifier.on(EditorEventType.ToolEnabled, toolEvt => {
|
9
8
|
if (toolEvt.kind !== 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;
|
@@ -135,14 +135,26 @@ export default class BaseWidget {
|
|
135
135
|
const id = widget.getUniqueIdIn(this.subWidgets);
|
136
136
|
this.subWidgets[id] = widget;
|
137
137
|
}
|
138
|
-
|
139
|
-
|
140
|
-
|
138
|
+
/**
|
139
|
+
* Adds this to `parent`. This can only be called once for each ToolbarWidget.
|
140
|
+
* Returns the element that was just added to `parent`.
|
141
|
+
* @internal
|
142
|
+
*/
|
141
143
|
addTo(parent) {
|
142
|
-
|
143
|
-
this.setupActionBtnClickListener(this.button);
|
144
|
+
// Update title and icon
|
144
145
|
this.icon = null;
|
145
146
|
this.updateIcon();
|
147
|
+
this.label.innerText = this.getTitle();
|
148
|
+
const longLabelCSSClass = 'long-label';
|
149
|
+
if (this.label.innerText.length > 7) {
|
150
|
+
this.label.classList.add(longLabelCSSClass);
|
151
|
+
}
|
152
|
+
else {
|
153
|
+
this.label.classList.remove(longLabelCSSClass);
|
154
|
+
}
|
155
|
+
// Click functionality
|
156
|
+
this.setupActionBtnClickListener(this.button);
|
157
|
+
// Clear anything already in this.container.
|
146
158
|
this.container.replaceChildren();
|
147
159
|
this.button.replaceChildren(this.icon, this.label);
|
148
160
|
this.container.appendChild(this.button);
|
@@ -7,7 +7,7 @@ import { EditorEventType } from '../../types.mjs';
|
|
7
7
|
import { toolbarCSSPrefix } from '../HTMLToolbar.mjs';
|
8
8
|
import makeColorInput from '../makeColorInput.mjs';
|
9
9
|
import BaseWidget from './BaseWidget.mjs';
|
10
|
-
|
10
|
+
class DocumentPropertiesWidget extends BaseWidget {
|
11
11
|
constructor(editor, localizationTable) {
|
12
12
|
super(editor, 'document-properties-widget', localizationTable);
|
13
13
|
this.updateDropdownContent = () => { };
|
@@ -170,3 +170,4 @@ export default class DocumentPropertiesWidget extends BaseWidget {
|
|
170
170
|
}
|
171
171
|
}
|
172
172
|
DocumentPropertiesWidget.idCounter = 0;
|
173
|
+
export default DocumentPropertiesWidget;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { EditorEventType } from '../../types.mjs';
|
2
2
|
import { toolbarCSSPrefix } from '../HTMLToolbar.mjs';
|
3
3
|
import BaseToolWidget from './BaseToolWidget.mjs';
|
4
|
-
|
4
|
+
class EraserToolWidget extends BaseToolWidget {
|
5
5
|
constructor(editor, tool, localizationTable) {
|
6
6
|
super(editor, tool, 'eraser-tool-widget', localizationTable);
|
7
7
|
this.tool = tool;
|
@@ -55,3 +55,4 @@ export default class EraserToolWidget extends BaseToolWidget {
|
|
55
55
|
}
|
56
56
|
}
|
57
57
|
EraserToolWidget.nextThicknessInputId = 0;
|
58
|
+
export default EraserToolWidget;
|
@@ -15,7 +15,7 @@ import SelectionTool from '../../tools/SelectionTool/SelectionTool.mjs';
|
|
15
15
|
import Mat33 from '../../math/Mat33.mjs';
|
16
16
|
import fileToBase64 from '../../util/fileToBase64.mjs';
|
17
17
|
import ActionButtonWidget from './ActionButtonWidget.mjs';
|
18
|
-
|
18
|
+
class InsertImageWidget extends ActionButtonWidget {
|
19
19
|
constructor(editor, localization) {
|
20
20
|
localization !== null && localization !== void 0 ? localization : (localization = editor.localization);
|
21
21
|
super(editor, 'insert-image-widget', () => editor.icons.makeInsertImageIcon(), localization.image, () => this.onClicked());
|
@@ -168,3 +168,4 @@ export default class InsertImageWidget extends ActionButtonWidget {
|
|
168
168
|
}
|
169
169
|
}
|
170
170
|
InsertImageWidget.nextInputId = 0;
|
171
|
+
export default InsertImageWidget;
|
@@ -8,7 +8,7 @@ import { toolbarCSSPrefix } from '../HTMLToolbar.mjs';
|
|
8
8
|
import makeColorInput from '../makeColorInput.mjs';
|
9
9
|
import BaseToolWidget from './BaseToolWidget.mjs';
|
10
10
|
import Color4 from '../../Color4.mjs';
|
11
|
-
|
11
|
+
class PenToolWidget extends BaseToolWidget {
|
12
12
|
constructor(editor, tool, localization) {
|
13
13
|
super(editor, tool, 'pen', localization);
|
14
14
|
this.tool = tool;
|
@@ -218,3 +218,4 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
218
218
|
}
|
219
219
|
}
|
220
220
|
PenToolWidget.idCounter = 0;
|
221
|
+
export default PenToolWidget;
|
@@ -3,7 +3,7 @@ import { EditorEventType } from '../../types.mjs';
|
|
3
3
|
import { toolbarCSSPrefix } from '../HTMLToolbar.mjs';
|
4
4
|
import makeColorInput from '../makeColorInput.mjs';
|
5
5
|
import BaseToolWidget from './BaseToolWidget.mjs';
|
6
|
-
|
6
|
+
class TextToolWidget extends BaseToolWidget {
|
7
7
|
constructor(editor, tool, localization) {
|
8
8
|
super(editor, tool, 'text-tool-widget', localization);
|
9
9
|
this.tool = tool;
|
@@ -107,3 +107,4 @@ export default class TextToolWidget extends BaseToolWidget {
|
|
107
107
|
}
|
108
108
|
}
|
109
109
|
TextToolWidget.idCounter = 0;
|
110
|
+
export default TextToolWidget;
|