@teachinglab/omd 0.3.7 → 0.3.9
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/canvas/core/canvasConfig.js +3 -3
- package/canvas/core/omdCanvas.js +479 -479
- package/canvas/events/eventManager.js +14 -4
- package/canvas/features/focusFrameManager.js +284 -286
- package/canvas/features/resizeHandleManager.js +482 -0
- package/canvas/index.js +2 -1
- package/canvas/tools/EraserTool.js +321 -322
- package/canvas/tools/PencilTool.js +321 -324
- package/canvas/tools/PointerTool.js +71 -0
- package/canvas/tools/SelectTool.js +902 -457
- package/canvas/tools/toolManager.js +389 -393
- package/canvas/ui/cursor.js +462 -437
- package/canvas/ui/toolbar.js +291 -290
- package/docs/omd-objects.md +258 -0
- package/jsvg/jsvg.js +898 -0
- package/jsvg/jsvgComponents.js +359 -0
- package/omd/nodes/omdEquationSequenceNode.js +1280 -1246
- package/package.json +1 -1
- package/src/json-schemas.md +546 -78
- package/src/omd.js +212 -109
- package/src/omdEquation.js +188 -156
- package/src/omdExpression.js +7 -0
- package/src/omdFunction.js +5 -3
- package/src/omdProblem.js +216 -11
- package/src/omdString.js +12 -1
- package/src/omdUtils.js +84 -0
- package/src/omdVariable.js +1 -0
|
@@ -1,322 +1,321 @@
|
|
|
1
|
-
import { Tool } from './tool.js';
|
|
2
|
-
import { Stroke } from '../drawing/stroke.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Eraser tool for removing strokes
|
|
6
|
-
*/
|
|
7
|
-
export class EraserTool extends Tool {
|
|
8
|
-
constructor(canvas, options = {}) {
|
|
9
|
-
super(canvas, {
|
|
10
|
-
size: 12,
|
|
11
|
-
hardness: 0.8,
|
|
12
|
-
mode: 'radius', // 'stroke' or 'radius'
|
|
13
|
-
...options
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
this.displayName = 'Eraser';
|
|
17
|
-
this.description = 'Erase strokes (M to toggle mode)';
|
|
18
|
-
this.icon = 'eraser';
|
|
19
|
-
this.shortcut = 'E';
|
|
20
|
-
this.category = 'editing';
|
|
21
|
-
|
|
22
|
-
// Eraser state
|
|
23
|
-
this.isErasing = false;
|
|
24
|
-
this.erasedPoints = new Set(); // Track erased points for radius mode
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Start erasing
|
|
29
|
-
*/
|
|
30
|
-
onPointerDown(event) {
|
|
31
|
-
if (!this.canUse()) return;
|
|
32
|
-
|
|
33
|
-
this.isErasing = true;
|
|
34
|
-
this._eraseAtPoint(event.x, event.y);
|
|
35
|
-
|
|
36
|
-
this.canvas.emit('eraseStarted', {
|
|
37
|
-
tool: this.name,
|
|
38
|
-
point: { x: event.x, y: event.y }
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Continue erasing
|
|
44
|
-
*/
|
|
45
|
-
onPointerMove(event) {
|
|
46
|
-
if (!this.isErasing) return;
|
|
47
|
-
|
|
48
|
-
this._eraseAtPoint(event.x, event.y);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Stop erasing
|
|
53
|
-
*/
|
|
54
|
-
onPointerUp(event) {
|
|
55
|
-
if (!this.isErasing) return;
|
|
56
|
-
|
|
57
|
-
this.isErasing = false;
|
|
58
|
-
|
|
59
|
-
this.canvas.emit('eraseCompleted', {
|
|
60
|
-
tool: this.name
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Cancel erasing
|
|
66
|
-
*/
|
|
67
|
-
onCancel() {
|
|
68
|
-
this.isErasing = false;
|
|
69
|
-
super.onCancel();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Erase strokes at point
|
|
74
|
-
* @private
|
|
75
|
-
*/
|
|
76
|
-
_eraseAtPoint(x, y) {
|
|
77
|
-
if (this.config.mode === 'stroke') {
|
|
78
|
-
this._eraseWholeStrokes(x, y);
|
|
79
|
-
} else {
|
|
80
|
-
this._eraseInRadius(x, y);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Erase whole strokes (original behavior)
|
|
86
|
-
* @private
|
|
87
|
-
*/
|
|
88
|
-
_eraseWholeStrokes(x, y) {
|
|
89
|
-
const tolerance = this.config.size || 20;
|
|
90
|
-
const strokesToRemove = [];
|
|
91
|
-
|
|
92
|
-
// Check each stroke to see if it's near the eraser point
|
|
93
|
-
for (const [id, stroke] of this.canvas.strokes) {
|
|
94
|
-
if (stroke.isNearPoint(x, y, tolerance)) {
|
|
95
|
-
strokesToRemove.push(id);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Remove the strokes
|
|
100
|
-
strokesToRemove.forEach(id => {
|
|
101
|
-
this.canvas.removeStroke(id);
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Erase within radius (traditional eraser behavior)
|
|
107
|
-
* @private
|
|
108
|
-
*/
|
|
109
|
-
_eraseInRadius(x, y) {
|
|
110
|
-
const radius = this.config.size || 20;
|
|
111
|
-
const radiusSquared = radius * radius;
|
|
112
|
-
const strokesToModify = [];
|
|
113
|
-
|
|
114
|
-
// Find strokes that intersect with the eraser circle
|
|
115
|
-
for (const [id, stroke] of this.canvas.strokes) {
|
|
116
|
-
const boundingBox = stroke.getBoundingBox();
|
|
117
|
-
|
|
118
|
-
// Quick bounding box check first
|
|
119
|
-
if (this._circleIntersectsRect(x, y, radius, boundingBox)) {
|
|
120
|
-
// Check individual points
|
|
121
|
-
const pointsToRemove = [];
|
|
122
|
-
|
|
123
|
-
for (let i = 0; i < stroke.points.length; i++) {
|
|
124
|
-
const point = stroke.points[i];
|
|
125
|
-
const dx = point.x - x;
|
|
126
|
-
const dy = point.y - y;
|
|
127
|
-
const distanceSquared = dx * dx + dy * dy;
|
|
128
|
-
|
|
129
|
-
if (distanceSquared <= radiusSquared) {
|
|
130
|
-
pointsToRemove.push(i);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (pointsToRemove.length > 0) {
|
|
135
|
-
strokesToModify.push({ id, stroke, pointsToRemove });
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Modify or remove strokes
|
|
141
|
-
strokesToModify.forEach(({ id, stroke, pointsToRemove }) => {
|
|
142
|
-
if (pointsToRemove.length >= stroke.points.length * 0.8) {
|
|
143
|
-
// If most points are erased, remove the whole stroke
|
|
144
|
-
this.canvas.removeStroke(id);
|
|
145
|
-
} else {
|
|
146
|
-
// Remove points and split stroke if necessary
|
|
147
|
-
this._splitStrokeAtErasedPoints(stroke, pointsToRemove);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Check if circle intersects with rectangle
|
|
154
|
-
* @private
|
|
155
|
-
*/
|
|
156
|
-
_circleIntersectsRect(cx, cy, radius, rect) {
|
|
157
|
-
const closestX = Math.max(rect.left, Math.min(cx, rect.right));
|
|
158
|
-
const closestY = Math.max(rect.top, Math.min(cy, rect.bottom));
|
|
159
|
-
|
|
160
|
-
const dx = cx - closestX;
|
|
161
|
-
const dy = cy - closestY;
|
|
162
|
-
|
|
163
|
-
return (dx * dx + dy * dy) <= (radius * radius);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Split stroke at erased points or remove segments
|
|
168
|
-
* @private
|
|
169
|
-
*/
|
|
170
|
-
_splitStrokeAtErasedPoints(stroke, pointsToRemove) {
|
|
171
|
-
if (pointsToRemove.length === 0) return;
|
|
172
|
-
|
|
173
|
-
// Sort indices in ascending order for processing
|
|
174
|
-
pointsToRemove.sort((a, b) => a - b);
|
|
175
|
-
|
|
176
|
-
// Find continuous segments to keep
|
|
177
|
-
const segments = [];
|
|
178
|
-
let startIndex = 0;
|
|
179
|
-
|
|
180
|
-
for (let i = 0; i < pointsToRemove.length; i++) {
|
|
181
|
-
const removeIndex = pointsToRemove[i];
|
|
182
|
-
|
|
183
|
-
// If there's a gap before this point, create a segment
|
|
184
|
-
if (removeIndex > startIndex) {
|
|
185
|
-
const segmentPoints = stroke.points.slice(startIndex, removeIndex);
|
|
186
|
-
if (segmentPoints.length >= 2) {
|
|
187
|
-
segments.push(segmentPoints);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
startIndex = removeIndex + 1;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Add final segment if there are remaining points
|
|
195
|
-
if (startIndex < stroke.points.length) {
|
|
196
|
-
const finalSegment = stroke.points.slice(startIndex);
|
|
197
|
-
if (finalSegment.length >= 2) {
|
|
198
|
-
segments.push(finalSegment);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Remove original stroke
|
|
203
|
-
this.canvas.removeStroke(stroke.id);
|
|
204
|
-
|
|
205
|
-
// Create new strokes for each segment
|
|
206
|
-
segments.forEach((segmentPoints, index) => {
|
|
207
|
-
const newStroke = new Stroke({
|
|
208
|
-
strokeWidth: stroke.strokeWidth,
|
|
209
|
-
strokeColor: stroke.strokeColor,
|
|
210
|
-
strokeOpacity: stroke.strokeOpacity,
|
|
211
|
-
tool: stroke.tool
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Add all points to the new stroke
|
|
215
|
-
segmentPoints.forEach(point => {
|
|
216
|
-
newStroke.addPoint(point);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
newStroke.finish();
|
|
220
|
-
this.canvas.addStroke(newStroke);
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Get eraser cursor
|
|
226
|
-
*/
|
|
227
|
-
getCursor() {
|
|
228
|
-
return 'eraser';
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Handle keyboard shortcuts
|
|
233
|
-
*/
|
|
234
|
-
onKeyboardShortcut(key, event) {
|
|
235
|
-
switch (key) {
|
|
236
|
-
case '[':
|
|
237
|
-
// Decrease eraser size
|
|
238
|
-
this.updateConfig({
|
|
239
|
-
size: Math.max(5, this.config.size - 5)
|
|
240
|
-
});
|
|
241
|
-
return true;
|
|
242
|
-
case ']':
|
|
243
|
-
// Increase eraser size
|
|
244
|
-
this.updateConfig({
|
|
245
|
-
size: Math.min(100, this.config.size + 5)
|
|
246
|
-
});
|
|
247
|
-
return true;
|
|
248
|
-
case 'm':
|
|
249
|
-
// Toggle eraser mode
|
|
250
|
-
this.toggleMode();
|
|
251
|
-
return true;
|
|
252
|
-
default:
|
|
253
|
-
return super.onKeyboardShortcut(key, event);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Toggle between stroke and radius erasing modes
|
|
259
|
-
*/
|
|
260
|
-
toggleMode() {
|
|
261
|
-
const newMode = this.config.mode === 'stroke' ? 'radius' : 'stroke';
|
|
262
|
-
this.updateConfig({ mode: newMode });
|
|
263
|
-
|
|
264
|
-
// Update cursor appearance
|
|
265
|
-
if (this.canvas.cursor) {
|
|
266
|
-
this.canvas.cursor.updateFromToolConfig(this.config);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Emit mode change event
|
|
270
|
-
this.canvas.emit('eraserModeChanged', {
|
|
271
|
-
mode: newMode,
|
|
272
|
-
description: this._getModeDescription(newMode)
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
*
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
*
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
this.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
1
|
+
import { Tool } from './tool.js';
|
|
2
|
+
import { Stroke } from '../drawing/stroke.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Eraser tool for removing strokes
|
|
6
|
+
*/
|
|
7
|
+
export class EraserTool extends Tool {
|
|
8
|
+
constructor(canvas, options = {}) {
|
|
9
|
+
super(canvas, {
|
|
10
|
+
size: 12,
|
|
11
|
+
hardness: 0.8,
|
|
12
|
+
mode: 'radius', // 'stroke' or 'radius'
|
|
13
|
+
...options
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
this.displayName = 'Eraser';
|
|
17
|
+
this.description = 'Erase strokes (M to toggle mode)';
|
|
18
|
+
this.icon = 'eraser';
|
|
19
|
+
this.shortcut = 'E';
|
|
20
|
+
this.category = 'editing';
|
|
21
|
+
|
|
22
|
+
// Eraser state
|
|
23
|
+
this.isErasing = false;
|
|
24
|
+
this.erasedPoints = new Set(); // Track erased points for radius mode
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Start erasing
|
|
29
|
+
*/
|
|
30
|
+
onPointerDown(event) {
|
|
31
|
+
if (!this.canUse()) return;
|
|
32
|
+
|
|
33
|
+
this.isErasing = true;
|
|
34
|
+
this._eraseAtPoint(event.x, event.y);
|
|
35
|
+
|
|
36
|
+
this.canvas.emit('eraseStarted', {
|
|
37
|
+
tool: this.name,
|
|
38
|
+
point: { x: event.x, y: event.y }
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Continue erasing
|
|
44
|
+
*/
|
|
45
|
+
onPointerMove(event) {
|
|
46
|
+
if (!this.isErasing) return;
|
|
47
|
+
|
|
48
|
+
this._eraseAtPoint(event.x, event.y);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Stop erasing
|
|
53
|
+
*/
|
|
54
|
+
onPointerUp(event) {
|
|
55
|
+
if (!this.isErasing) return;
|
|
56
|
+
|
|
57
|
+
this.isErasing = false;
|
|
58
|
+
|
|
59
|
+
this.canvas.emit('eraseCompleted', {
|
|
60
|
+
tool: this.name
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Cancel erasing
|
|
66
|
+
*/
|
|
67
|
+
onCancel() {
|
|
68
|
+
this.isErasing = false;
|
|
69
|
+
super.onCancel();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Erase strokes at point
|
|
74
|
+
* @private
|
|
75
|
+
*/
|
|
76
|
+
_eraseAtPoint(x, y) {
|
|
77
|
+
if (this.config.mode === 'stroke') {
|
|
78
|
+
this._eraseWholeStrokes(x, y);
|
|
79
|
+
} else {
|
|
80
|
+
this._eraseInRadius(x, y);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Erase whole strokes (original behavior)
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
_eraseWholeStrokes(x, y) {
|
|
89
|
+
const tolerance = this.config.size || 20;
|
|
90
|
+
const strokesToRemove = [];
|
|
91
|
+
|
|
92
|
+
// Check each stroke to see if it's near the eraser point
|
|
93
|
+
for (const [id, stroke] of this.canvas.strokes) {
|
|
94
|
+
if (stroke.isNearPoint(x, y, tolerance)) {
|
|
95
|
+
strokesToRemove.push(id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Remove the strokes
|
|
100
|
+
strokesToRemove.forEach(id => {
|
|
101
|
+
this.canvas.removeStroke(id);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Erase within radius (traditional eraser behavior)
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
_eraseInRadius(x, y) {
|
|
110
|
+
const radius = this.config.size || 20;
|
|
111
|
+
const radiusSquared = radius * radius;
|
|
112
|
+
const strokesToModify = [];
|
|
113
|
+
|
|
114
|
+
// Find strokes that intersect with the eraser circle
|
|
115
|
+
for (const [id, stroke] of this.canvas.strokes) {
|
|
116
|
+
const boundingBox = stroke.getBoundingBox();
|
|
117
|
+
|
|
118
|
+
// Quick bounding box check first
|
|
119
|
+
if (this._circleIntersectsRect(x, y, radius, boundingBox)) {
|
|
120
|
+
// Check individual points
|
|
121
|
+
const pointsToRemove = [];
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < stroke.points.length; i++) {
|
|
124
|
+
const point = stroke.points[i];
|
|
125
|
+
const dx = point.x - x;
|
|
126
|
+
const dy = point.y - y;
|
|
127
|
+
const distanceSquared = dx * dx + dy * dy;
|
|
128
|
+
|
|
129
|
+
if (distanceSquared <= radiusSquared) {
|
|
130
|
+
pointsToRemove.push(i);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (pointsToRemove.length > 0) {
|
|
135
|
+
strokesToModify.push({ id, stroke, pointsToRemove });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Modify or remove strokes
|
|
141
|
+
strokesToModify.forEach(({ id, stroke, pointsToRemove }) => {
|
|
142
|
+
if (pointsToRemove.length >= stroke.points.length * 0.8) {
|
|
143
|
+
// If most points are erased, remove the whole stroke
|
|
144
|
+
this.canvas.removeStroke(id);
|
|
145
|
+
} else {
|
|
146
|
+
// Remove points and split stroke if necessary
|
|
147
|
+
this._splitStrokeAtErasedPoints(stroke, pointsToRemove);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if circle intersects with rectangle
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
_circleIntersectsRect(cx, cy, radius, rect) {
|
|
157
|
+
const closestX = Math.max(rect.left, Math.min(cx, rect.right));
|
|
158
|
+
const closestY = Math.max(rect.top, Math.min(cy, rect.bottom));
|
|
159
|
+
|
|
160
|
+
const dx = cx - closestX;
|
|
161
|
+
const dy = cy - closestY;
|
|
162
|
+
|
|
163
|
+
return (dx * dx + dy * dy) <= (radius * radius);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Split stroke at erased points or remove segments
|
|
168
|
+
* @private
|
|
169
|
+
*/
|
|
170
|
+
_splitStrokeAtErasedPoints(stroke, pointsToRemove) {
|
|
171
|
+
if (pointsToRemove.length === 0) return;
|
|
172
|
+
|
|
173
|
+
// Sort indices in ascending order for processing
|
|
174
|
+
pointsToRemove.sort((a, b) => a - b);
|
|
175
|
+
|
|
176
|
+
// Find continuous segments to keep
|
|
177
|
+
const segments = [];
|
|
178
|
+
let startIndex = 0;
|
|
179
|
+
|
|
180
|
+
for (let i = 0; i < pointsToRemove.length; i++) {
|
|
181
|
+
const removeIndex = pointsToRemove[i];
|
|
182
|
+
|
|
183
|
+
// If there's a gap before this point, create a segment
|
|
184
|
+
if (removeIndex > startIndex) {
|
|
185
|
+
const segmentPoints = stroke.points.slice(startIndex, removeIndex);
|
|
186
|
+
if (segmentPoints.length >= 2) {
|
|
187
|
+
segments.push(segmentPoints);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
startIndex = removeIndex + 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Add final segment if there are remaining points
|
|
195
|
+
if (startIndex < stroke.points.length) {
|
|
196
|
+
const finalSegment = stroke.points.slice(startIndex);
|
|
197
|
+
if (finalSegment.length >= 2) {
|
|
198
|
+
segments.push(finalSegment);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Remove original stroke
|
|
203
|
+
this.canvas.removeStroke(stroke.id);
|
|
204
|
+
|
|
205
|
+
// Create new strokes for each segment
|
|
206
|
+
segments.forEach((segmentPoints, index) => {
|
|
207
|
+
const newStroke = new Stroke({
|
|
208
|
+
strokeWidth: stroke.strokeWidth,
|
|
209
|
+
strokeColor: stroke.strokeColor,
|
|
210
|
+
strokeOpacity: stroke.strokeOpacity,
|
|
211
|
+
tool: stroke.tool
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Add all points to the new stroke
|
|
215
|
+
segmentPoints.forEach(point => {
|
|
216
|
+
newStroke.addPoint(point);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
newStroke.finish();
|
|
220
|
+
this.canvas.addStroke(newStroke);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get eraser cursor
|
|
226
|
+
*/
|
|
227
|
+
getCursor() {
|
|
228
|
+
return 'eraser';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Handle keyboard shortcuts
|
|
233
|
+
*/
|
|
234
|
+
onKeyboardShortcut(key, event) {
|
|
235
|
+
switch (key) {
|
|
236
|
+
case '[':
|
|
237
|
+
// Decrease eraser size
|
|
238
|
+
this.updateConfig({
|
|
239
|
+
size: Math.max(5, this.config.size - 5)
|
|
240
|
+
});
|
|
241
|
+
return true;
|
|
242
|
+
case ']':
|
|
243
|
+
// Increase eraser size
|
|
244
|
+
this.updateConfig({
|
|
245
|
+
size: Math.min(100, this.config.size + 5)
|
|
246
|
+
});
|
|
247
|
+
return true;
|
|
248
|
+
case 'm':
|
|
249
|
+
// Toggle eraser mode
|
|
250
|
+
this.toggleMode();
|
|
251
|
+
return true;
|
|
252
|
+
default:
|
|
253
|
+
return super.onKeyboardShortcut(key, event);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Toggle between stroke and radius erasing modes
|
|
259
|
+
*/
|
|
260
|
+
toggleMode() {
|
|
261
|
+
const newMode = this.config.mode === 'stroke' ? 'radius' : 'stroke';
|
|
262
|
+
this.updateConfig({ mode: newMode });
|
|
263
|
+
|
|
264
|
+
// Update cursor appearance
|
|
265
|
+
if (this.canvas.cursor) {
|
|
266
|
+
this.canvas.cursor.updateFromToolConfig(this.config);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Emit mode change event
|
|
270
|
+
this.canvas.emit('eraserModeChanged', {
|
|
271
|
+
mode: newMode,
|
|
272
|
+
description: this._getModeDescription(newMode)
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get mode description
|
|
279
|
+
* @private
|
|
280
|
+
*/
|
|
281
|
+
_getModeDescription(mode) {
|
|
282
|
+
return mode === 'stroke' ? 'Whole Stroke Erasing' : 'Radius Erasing';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Update configuration
|
|
287
|
+
*/
|
|
288
|
+
onConfigUpdate() {
|
|
289
|
+
// Update cursor size if available
|
|
290
|
+
if (this.canvas.cursor) {
|
|
291
|
+
this.canvas.cursor.setSize(this.config.size);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get help text
|
|
297
|
+
*/
|
|
298
|
+
getHelpText() {
|
|
299
|
+
return `${super.getHelpText()}\nShortcuts: [ ] to adjust size, M to toggle mode\nCurrent mode: ${this._getModeDescription(this.config.mode)}`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get current eraser mode
|
|
304
|
+
*/
|
|
305
|
+
getMode() {
|
|
306
|
+
return this.config.mode;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Set eraser mode
|
|
311
|
+
* @param {string} mode - 'stroke' or 'radius'
|
|
312
|
+
*/
|
|
313
|
+
setMode(mode) {
|
|
314
|
+
if (mode === 'stroke' || mode === 'radius') {
|
|
315
|
+
this.updateConfig({ mode });
|
|
316
|
+
if (this.canvas.cursor) {
|
|
317
|
+
this.canvas.cursor.updateFromToolConfig(this.config);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|