@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
package/canvas/ui/cursor.js
CHANGED
|
@@ -1,438 +1,463 @@
|
|
|
1
|
-
export class Cursor {
|
|
2
|
-
/**
|
|
3
|
-
* @param {OMDCanvas} canvas - Canvas instance
|
|
4
|
-
*/
|
|
5
|
-
constructor(canvas) {
|
|
6
|
-
this.canvas = canvas;
|
|
7
|
-
this.isVisible = true;
|
|
8
|
-
this.currentShape = 'pencil';
|
|
9
|
-
this.size = 20;
|
|
10
|
-
this.color = '#007bff';
|
|
11
|
-
|
|
12
|
-
// Create cursor element
|
|
13
|
-
this._createElement();
|
|
14
|
-
|
|
15
|
-
// Add to UI layer
|
|
16
|
-
this.canvas.uiLayer.appendChild(this.element);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Create the cursor SVG element
|
|
21
|
-
* @private
|
|
22
|
-
*/
|
|
23
|
-
_createElement() {
|
|
24
|
-
this.element = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
25
|
-
this.element.setAttribute('class', 'omd-cursor');
|
|
26
|
-
this.element.style.pointerEvents = 'none';
|
|
27
|
-
this.element.style.opacity = '0.8';
|
|
28
|
-
|
|
29
|
-
// Create different cursor shapes
|
|
30
|
-
this._createShapes();
|
|
31
|
-
|
|
32
|
-
// Initially hidden
|
|
33
|
-
this.hide();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Create different cursor shape elements
|
|
38
|
-
* @private
|
|
39
|
-
*/
|
|
40
|
-
_createShapes() {
|
|
41
|
-
this.shapes = {};
|
|
42
|
-
|
|
43
|
-
// Default cursor (crosshair)
|
|
44
|
-
this.shapes.default = this._createCrosshair();
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
this.shapes.
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
this.shapes.
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
this.shapes.
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
hLine.
|
|
77
|
-
hLine.setAttribute('
|
|
78
|
-
hLine.setAttribute('
|
|
79
|
-
hLine.setAttribute('
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
vLine.
|
|
86
|
-
vLine.setAttribute('
|
|
87
|
-
vLine.setAttribute('
|
|
88
|
-
vLine.setAttribute('
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this.brushCircle.
|
|
109
|
-
this.brushCircle.setAttribute('
|
|
110
|
-
this.brushCircle.setAttribute('
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
circle.
|
|
130
|
-
circle.setAttribute('
|
|
131
|
-
circle.setAttribute('
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
modeIndicator.
|
|
138
|
-
modeIndicator.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
line1.
|
|
145
|
-
line1.setAttribute('
|
|
146
|
-
line1.setAttribute('
|
|
147
|
-
line1.setAttribute('
|
|
148
|
-
line1.setAttribute('
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
line2.
|
|
154
|
-
line2.setAttribute('
|
|
155
|
-
line2.setAttribute('
|
|
156
|
-
line2.setAttribute('
|
|
157
|
-
line2.setAttribute('
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
group.appendChild(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
path.
|
|
180
|
-
path.setAttribute('
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Set cursor
|
|
214
|
-
* @param {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Set cursor
|
|
239
|
-
* @param {number}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
this.
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
*
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
*
|
|
332
|
-
* @param {number}
|
|
333
|
-
*/
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
*
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
this.
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Update
|
|
380
|
-
* @
|
|
381
|
-
*/
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
1
|
+
export class Cursor {
|
|
2
|
+
/**
|
|
3
|
+
* @param {OMDCanvas} canvas - Canvas instance
|
|
4
|
+
*/
|
|
5
|
+
constructor(canvas) {
|
|
6
|
+
this.canvas = canvas;
|
|
7
|
+
this.isVisible = true;
|
|
8
|
+
this.currentShape = 'pencil';
|
|
9
|
+
this.size = 20;
|
|
10
|
+
this.color = '#007bff';
|
|
11
|
+
|
|
12
|
+
// Create cursor element
|
|
13
|
+
this._createElement();
|
|
14
|
+
|
|
15
|
+
// Add to UI layer
|
|
16
|
+
this.canvas.uiLayer.appendChild(this.element);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create the cursor SVG element
|
|
21
|
+
* @private
|
|
22
|
+
*/
|
|
23
|
+
_createElement() {
|
|
24
|
+
this.element = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
25
|
+
this.element.setAttribute('class', 'omd-cursor');
|
|
26
|
+
this.element.style.pointerEvents = 'none';
|
|
27
|
+
this.element.style.opacity = '0.8';
|
|
28
|
+
|
|
29
|
+
// Create different cursor shapes
|
|
30
|
+
this._createShapes();
|
|
31
|
+
|
|
32
|
+
// Initially hidden
|
|
33
|
+
this.hide();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create different cursor shape elements
|
|
38
|
+
* @private
|
|
39
|
+
*/
|
|
40
|
+
_createShapes() {
|
|
41
|
+
this.shapes = {};
|
|
42
|
+
|
|
43
|
+
// Default cursor (crosshair)
|
|
44
|
+
this.shapes.default = this._createCrosshair();
|
|
45
|
+
|
|
46
|
+
// Pointer cursor (arrow)
|
|
47
|
+
this.shapes.pointer = this._createPointerCursor();
|
|
48
|
+
|
|
49
|
+
// Pencil cursor
|
|
50
|
+
this.shapes.pencil = this._createPencilCursor();
|
|
51
|
+
|
|
52
|
+
// Eraser cursor
|
|
53
|
+
this.shapes.eraser = this._createEraserCursor();
|
|
54
|
+
|
|
55
|
+
// Select cursor
|
|
56
|
+
this.shapes.select = this._createSelectCursor();
|
|
57
|
+
|
|
58
|
+
// Add all shapes to cursor element
|
|
59
|
+
Object.values(this.shapes).forEach(shape => {
|
|
60
|
+
this.element.appendChild(shape);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Show default shape initially
|
|
64
|
+
this.setShape('default');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create crosshair cursor
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
_createCrosshair() {
|
|
72
|
+
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
73
|
+
group.setAttribute('data-shape', 'default');
|
|
74
|
+
|
|
75
|
+
// Horizontal line
|
|
76
|
+
const hLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
77
|
+
hLine.setAttribute('x1', '-10');
|
|
78
|
+
hLine.setAttribute('y1', '0');
|
|
79
|
+
hLine.setAttribute('x2', '10');
|
|
80
|
+
hLine.setAttribute('y2', '0');
|
|
81
|
+
hLine.setAttribute('stroke', this.color);
|
|
82
|
+
hLine.setAttribute('stroke-width', '1');
|
|
83
|
+
|
|
84
|
+
// Vertical line
|
|
85
|
+
const vLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
86
|
+
vLine.setAttribute('x1', '0');
|
|
87
|
+
vLine.setAttribute('y1', '-10');
|
|
88
|
+
vLine.setAttribute('x2', '0');
|
|
89
|
+
vLine.setAttribute('y2', '10');
|
|
90
|
+
vLine.setAttribute('stroke', this.color);
|
|
91
|
+
vLine.setAttribute('stroke-width', '1');
|
|
92
|
+
|
|
93
|
+
group.appendChild(hLine);
|
|
94
|
+
group.appendChild(vLine);
|
|
95
|
+
|
|
96
|
+
return group;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create pencil cursor
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
_createPencilCursor() {
|
|
104
|
+
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
105
|
+
group.setAttribute('data-shape', 'pencil');
|
|
106
|
+
|
|
107
|
+
// Solid dot cursor
|
|
108
|
+
this.brushCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
109
|
+
this.brushCircle.setAttribute('cx', '0');
|
|
110
|
+
this.brushCircle.setAttribute('cy', '0');
|
|
111
|
+
this.brushCircle.setAttribute('r', this.size / 2);
|
|
112
|
+
this.brushCircle.setAttribute('fill', this.color);
|
|
113
|
+
this.brushCircle.setAttribute('stroke', 'none');
|
|
114
|
+
|
|
115
|
+
group.appendChild(this.brushCircle);
|
|
116
|
+
|
|
117
|
+
return group;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create eraser cursor
|
|
122
|
+
* @private
|
|
123
|
+
*/
|
|
124
|
+
_createEraserCursor() {
|
|
125
|
+
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
126
|
+
group.setAttribute('data-shape', 'eraser');
|
|
127
|
+
|
|
128
|
+
// Circle eraser indicator
|
|
129
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
130
|
+
circle.setAttribute('r', this.size / 2);
|
|
131
|
+
circle.setAttribute('fill', 'none');
|
|
132
|
+
circle.setAttribute('stroke', '#dc3545');
|
|
133
|
+
circle.setAttribute('stroke-width', '1.5');
|
|
134
|
+
circle.setAttribute('class', 'eraser-circle');
|
|
135
|
+
|
|
136
|
+
// Mode indicator (inner fill for radius mode)
|
|
137
|
+
const modeIndicator = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
138
|
+
modeIndicator.setAttribute('r', this.size / 3);
|
|
139
|
+
modeIndicator.setAttribute('fill', 'rgba(220, 53, 69, 0.15)');
|
|
140
|
+
modeIndicator.setAttribute('class', 'eraser-mode-indicator');
|
|
141
|
+
modeIndicator.style.display = 'none';
|
|
142
|
+
|
|
143
|
+
// X mark inside (for stroke mode)
|
|
144
|
+
const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
145
|
+
line1.setAttribute('x1', -this.size / 5);
|
|
146
|
+
line1.setAttribute('y1', -this.size / 5);
|
|
147
|
+
line1.setAttribute('x2', this.size / 5);
|
|
148
|
+
line1.setAttribute('y2', this.size / 5);
|
|
149
|
+
line1.setAttribute('stroke', '#dc3545');
|
|
150
|
+
line1.setAttribute('stroke-width', '1.5');
|
|
151
|
+
line1.setAttribute('class', 'eraser-x1');
|
|
152
|
+
|
|
153
|
+
const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
154
|
+
line2.setAttribute('x1', this.size / 5);
|
|
155
|
+
line2.setAttribute('y1', -this.size / 5);
|
|
156
|
+
line2.setAttribute('x2', -this.size / 5);
|
|
157
|
+
line2.setAttribute('y2', this.size / 5);
|
|
158
|
+
line2.setAttribute('stroke', '#dc3545');
|
|
159
|
+
line2.setAttribute('stroke-width', '1.5');
|
|
160
|
+
line2.setAttribute('class', 'eraser-x2');
|
|
161
|
+
|
|
162
|
+
group.appendChild(circle);
|
|
163
|
+
group.appendChild(modeIndicator);
|
|
164
|
+
group.appendChild(line1);
|
|
165
|
+
group.appendChild(line2);
|
|
166
|
+
|
|
167
|
+
return group;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Create select cursor
|
|
172
|
+
* @private
|
|
173
|
+
*/
|
|
174
|
+
_createSelectCursor() {
|
|
175
|
+
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
176
|
+
group.setAttribute('data-shape', 'select');
|
|
177
|
+
|
|
178
|
+
// Better arrow pointer - classic selection cursor
|
|
179
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
180
|
+
path.setAttribute('d', 'M 2,2 L 2,14 L 6,10 L 10,14 L 14,10 L 6,6 Z');
|
|
181
|
+
path.setAttribute('fill', '#333333');
|
|
182
|
+
path.setAttribute('stroke', 'white');
|
|
183
|
+
path.setAttribute('stroke-width', '1');
|
|
184
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
185
|
+
|
|
186
|
+
group.appendChild(path);
|
|
187
|
+
|
|
188
|
+
return group;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Create pointer cursor (standard arrow)
|
|
193
|
+
* @private
|
|
194
|
+
*/
|
|
195
|
+
_createPointerCursor() {
|
|
196
|
+
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
197
|
+
group.setAttribute('data-shape', 'pointer');
|
|
198
|
+
|
|
199
|
+
// Standard arrow pointer cursor
|
|
200
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
201
|
+
path.setAttribute('d', 'M 0,0 L 0,16 L 5,11 L 8,16 L 10,15 L 7,10 L 12,10 Z');
|
|
202
|
+
path.setAttribute('fill', '#000000');
|
|
203
|
+
path.setAttribute('stroke', 'white');
|
|
204
|
+
path.setAttribute('stroke-width', '1');
|
|
205
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
206
|
+
|
|
207
|
+
group.appendChild(path);
|
|
208
|
+
|
|
209
|
+
return group;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Set cursor shape
|
|
214
|
+
* @param {string} shape - Shape name ('default', 'pencil', 'eraser', 'select')
|
|
215
|
+
*/
|
|
216
|
+
setShape(shape) {
|
|
217
|
+
this.currentShape = shape;
|
|
218
|
+
|
|
219
|
+
// Hide all shapes
|
|
220
|
+
Object.values(this.shapes).forEach(shapeElement => {
|
|
221
|
+
shapeElement.style.display = 'none';
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Show current shape
|
|
225
|
+
if (this.shapes[shape]) {
|
|
226
|
+
this.shapes[shape].style.display = 'block';
|
|
227
|
+
} else {
|
|
228
|
+
this.shapes.default.style.display = 'block';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Update brush size for pencil
|
|
232
|
+
if (shape === 'pencil' && this.brushCircle) {
|
|
233
|
+
this._updateBrushSize();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Set cursor position
|
|
239
|
+
* @param {number} x - X coordinate
|
|
240
|
+
* @param {number} y - Y coordinate
|
|
241
|
+
*/
|
|
242
|
+
setPosition(x, y) {
|
|
243
|
+
this.element.setAttribute('transform', `translate(${x}, ${y})`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Show cursor
|
|
248
|
+
*/
|
|
249
|
+
show() {
|
|
250
|
+
this.isVisible = true;
|
|
251
|
+
this.element.style.display = 'block';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Hide cursor
|
|
256
|
+
*/
|
|
257
|
+
hide() {
|
|
258
|
+
this.isVisible = false;
|
|
259
|
+
this.element.style.display = 'none';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Set cursor size (for tools that support it)
|
|
264
|
+
* @param {number} size - Cursor size
|
|
265
|
+
*/
|
|
266
|
+
setSize(size) {
|
|
267
|
+
this.size = size;
|
|
268
|
+
this._updateBrushSize();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Update brush size for pencil cursor
|
|
273
|
+
* @private
|
|
274
|
+
*/
|
|
275
|
+
_updateBrushSize() {
|
|
276
|
+
if (this.brushCircle) {
|
|
277
|
+
this.brushCircle.setAttribute('r', this.size / 2);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Update eraser size if applicable
|
|
281
|
+
const eraserShape = this.shapes.eraser;
|
|
282
|
+
if (eraserShape) {
|
|
283
|
+
const circle = eraserShape.querySelector('.eraser-circle');
|
|
284
|
+
const modeIndicator = eraserShape.querySelector('.eraser-mode-indicator');
|
|
285
|
+
const x1 = eraserShape.querySelector('.eraser-x1');
|
|
286
|
+
const x2 = eraserShape.querySelector('.eraser-x2');
|
|
287
|
+
|
|
288
|
+
if (circle) {
|
|
289
|
+
circle.setAttribute('r', this.size / 2);
|
|
290
|
+
}
|
|
291
|
+
if (modeIndicator) {
|
|
292
|
+
modeIndicator.setAttribute('r', this.size / 3);
|
|
293
|
+
}
|
|
294
|
+
if (x1) {
|
|
295
|
+
x1.setAttribute('x1', -this.size / 5);
|
|
296
|
+
x1.setAttribute('y1', -this.size / 5);
|
|
297
|
+
x1.setAttribute('x2', this.size / 5);
|
|
298
|
+
x1.setAttribute('y2', this.size / 5);
|
|
299
|
+
}
|
|
300
|
+
if (x2) {
|
|
301
|
+
x2.setAttribute('x1', this.size / 5);
|
|
302
|
+
x2.setAttribute('y1', -this.size / 5);
|
|
303
|
+
x2.setAttribute('x2', -this.size / 5);
|
|
304
|
+
x2.setAttribute('y2', this.size / 5);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Set cursor color
|
|
311
|
+
* @param {string} color - CSS color value
|
|
312
|
+
*/
|
|
313
|
+
setColor(color) {
|
|
314
|
+
this.color = color;
|
|
315
|
+
|
|
316
|
+
// Update all shape colors
|
|
317
|
+
this.element.querySelectorAll('[stroke]').forEach(element => {
|
|
318
|
+
if (element.getAttribute('stroke') === this.color) {
|
|
319
|
+
element.setAttribute('stroke', color);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
this.element.querySelectorAll('[fill]').forEach(element => {
|
|
324
|
+
if (element.getAttribute('fill') === this.color) {
|
|
325
|
+
element.setAttribute('fill', color);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Set cursor opacity
|
|
332
|
+
* @param {number} opacity - Opacity value (0-1)
|
|
333
|
+
*/
|
|
334
|
+
setOpacity(opacity) {
|
|
335
|
+
this.element.style.opacity = opacity;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Enable pressure feedback (for pressure-sensitive devices)
|
|
340
|
+
* @param {number} pressure - Pressure value (0-1)
|
|
341
|
+
*/
|
|
342
|
+
setPressureFeedback(pressure) {
|
|
343
|
+
if (this.currentShape === 'pencil' && this.brushCircle) {
|
|
344
|
+
// Scale brush circle based on pressure
|
|
345
|
+
const scale = 0.5 + (pressure * 0.5); // Scale from 50% to 100%
|
|
346
|
+
this.brushCircle.setAttribute('transform', `scale(${scale})`);
|
|
347
|
+
|
|
348
|
+
// Adjust opacity based on pressure
|
|
349
|
+
const opacity = 0.3 + (pressure * 0.5); // Opacity from 30% to 80%
|
|
350
|
+
this.brushCircle.style.opacity = opacity;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Add temporary visual feedback
|
|
356
|
+
* @param {string} type - Feedback type ('success', 'error', 'info')
|
|
357
|
+
* @param {number} [duration=500] - Duration in milliseconds
|
|
358
|
+
*/
|
|
359
|
+
showFeedback(type, duration = 500) {
|
|
360
|
+
const colors = {
|
|
361
|
+
success: '#28a745',
|
|
362
|
+
error: '#dc3545',
|
|
363
|
+
info: '#17a2b8'
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const originalColor = this.color;
|
|
367
|
+
this.setColor(colors[type] || colors.info);
|
|
368
|
+
|
|
369
|
+
// Pulse animation
|
|
370
|
+
this.element.style.animation = 'pulse 0.3s ease-in-out';
|
|
371
|
+
|
|
372
|
+
setTimeout(() => {
|
|
373
|
+
this.setColor(originalColor);
|
|
374
|
+
this.element.style.animation = '';
|
|
375
|
+
}, duration);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Update cursor based on tool configuration
|
|
380
|
+
* @param {Object} toolConfig - Tool configuration
|
|
381
|
+
*/
|
|
382
|
+
updateFromToolConfig(toolConfig) {
|
|
383
|
+
if (toolConfig.strokeWidth) {
|
|
384
|
+
// Scale the cursor size for better visibility - multiply by 4 for pencil to make it clearly visible
|
|
385
|
+
const scaledSize = this.currentShape === 'pencil' ? toolConfig.strokeWidth * 4 : toolConfig.strokeWidth;
|
|
386
|
+
this.setSize(scaledSize);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (toolConfig.strokeColor) {
|
|
390
|
+
this.setColor(toolConfig.strokeColor);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (toolConfig.size) {
|
|
394
|
+
this.setSize(toolConfig.size);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Update eraser mode indicator
|
|
398
|
+
if (toolConfig.mode !== undefined && this.currentShape === 'eraser') {
|
|
399
|
+
this._updateEraserMode(toolConfig.mode);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Update eraser mode visual indicator
|
|
405
|
+
* @private
|
|
406
|
+
*/
|
|
407
|
+
_updateEraserMode(mode) {
|
|
408
|
+
const eraserShape = this.shapes.eraser;
|
|
409
|
+
if (!eraserShape) return;
|
|
410
|
+
|
|
411
|
+
const circle = eraserShape.querySelector('.eraser-circle');
|
|
412
|
+
const modeIndicator = eraserShape.querySelector('.eraser-mode-indicator');
|
|
413
|
+
const x1 = eraserShape.querySelector('.eraser-x1');
|
|
414
|
+
const x2 = eraserShape.querySelector('.eraser-x2');
|
|
415
|
+
|
|
416
|
+
if (mode === 'radius') {
|
|
417
|
+
// Radius mode: show filled circle, hide X, use orange color
|
|
418
|
+
if (circle) {
|
|
419
|
+
circle.setAttribute('stroke', '#ff9f43');
|
|
420
|
+
circle.setAttribute('stroke-dasharray', '3,3');
|
|
421
|
+
}
|
|
422
|
+
if (modeIndicator) {
|
|
423
|
+
modeIndicator.style.display = 'block';
|
|
424
|
+
modeIndicator.setAttribute('fill', 'rgba(255, 159, 67, 0.3)');
|
|
425
|
+
}
|
|
426
|
+
if (x1) x1.style.display = 'none';
|
|
427
|
+
if (x2) x2.style.display = 'none';
|
|
428
|
+
} else {
|
|
429
|
+
// Stroke mode: show X, hide fill, use red color
|
|
430
|
+
if (circle) {
|
|
431
|
+
circle.setAttribute('stroke', '#dc3545');
|
|
432
|
+
circle.setAttribute('stroke-dasharray', 'none');
|
|
433
|
+
}
|
|
434
|
+
if (modeIndicator) {
|
|
435
|
+
modeIndicator.style.display = 'none';
|
|
436
|
+
}
|
|
437
|
+
if (x1) x1.style.display = 'block';
|
|
438
|
+
if (x2) x2.style.display = 'block';
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Get current cursor state
|
|
444
|
+
* @returns {Object} Cursor state
|
|
445
|
+
*/
|
|
446
|
+
getState() {
|
|
447
|
+
return {
|
|
448
|
+
isVisible: this.isVisible,
|
|
449
|
+
shape: this.currentShape,
|
|
450
|
+
size: this.size,
|
|
451
|
+
color: this.color
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Clean up cursor resources
|
|
457
|
+
*/
|
|
458
|
+
destroy() {
|
|
459
|
+
if (this.element.parentNode) {
|
|
460
|
+
this.element.parentNode.removeChild(this.element);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
438
463
|
}
|