@teachinglab/omd 0.6.0 → 0.6.2
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/README.md +257 -251
- package/README.old.md +137 -137
- package/canvas/core/canvasConfig.js +202 -202
- package/canvas/drawing/segment.js +167 -167
- package/canvas/drawing/stroke.js +385 -385
- package/canvas/events/eventManager.js +444 -444
- package/canvas/events/pointerEventHandler.js +262 -262
- package/canvas/index.js +48 -48
- package/canvas/tools/PointerTool.js +71 -71
- package/canvas/tools/tool.js +222 -222
- package/canvas/utils/boundingBox.js +377 -377
- package/canvas/utils/mathUtils.js +258 -258
- package/docs/api/configuration-options.md +198 -198
- package/docs/api/eventManager.md +82 -82
- package/docs/api/focusFrameManager.md +144 -144
- package/docs/api/index.md +105 -105
- package/docs/api/main.md +62 -62
- package/docs/api/omdBinaryExpressionNode.md +86 -86
- package/docs/api/omdCanvas.md +83 -83
- package/docs/api/omdConfigManager.md +112 -112
- package/docs/api/omdConstantNode.md +52 -52
- package/docs/api/omdDisplay.md +87 -87
- package/docs/api/omdEquationNode.md +174 -174
- package/docs/api/omdEquationSequenceNode.md +258 -258
- package/docs/api/omdEquationStack.md +192 -192
- package/docs/api/omdFunctionNode.md +82 -82
- package/docs/api/omdGroupNode.md +78 -78
- package/docs/api/omdHelpers.md +87 -87
- package/docs/api/omdLeafNode.md +85 -85
- package/docs/api/omdNode.md +201 -201
- package/docs/api/omdOperationDisplayNode.md +117 -117
- package/docs/api/omdOperatorNode.md +91 -91
- package/docs/api/omdParenthesisNode.md +133 -133
- package/docs/api/omdPopup.md +191 -191
- package/docs/api/omdPowerNode.md +131 -131
- package/docs/api/omdRationalNode.md +144 -144
- package/docs/api/omdSequenceNode.md +128 -128
- package/docs/api/omdSimplification.md +78 -78
- package/docs/api/omdSqrtNode.md +144 -144
- package/docs/api/omdStepVisualizer.md +146 -146
- package/docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/docs/api/omdStepVisualizerLayout.md +70 -70
- package/docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/docs/api/omdToolbar.md +130 -130
- package/docs/api/omdTranscriptionService.md +95 -95
- package/docs/api/omdTreeDiff.md +169 -169
- package/docs/api/omdUnaryExpressionNode.md +137 -137
- package/docs/api/omdUtilities.md +82 -82
- package/docs/api/omdVariableNode.md +123 -123
- package/docs/api/selectTool.md +74 -74
- package/docs/api/simplificationEngine.md +97 -97
- package/docs/api/simplificationRules.md +76 -76
- package/docs/api/simplificationUtils.md +64 -64
- package/docs/api/transcribe.md +43 -43
- package/docs/api-reference.md +85 -85
- package/docs/index.html +453 -453
- package/docs/index.md +38 -38
- package/docs/omd-objects.md +258 -258
- package/index.js +79 -79
- package/jsvg/index.js +3 -0
- package/jsvg/jsvg.js +898 -898
- package/jsvg/jsvgComponents.js +357 -358
- package/npm-docs/DOCUMENTATION_SUMMARY.md +220 -220
- package/npm-docs/README.md +251 -251
- package/npm-docs/api/api-reference.md +85 -85
- package/npm-docs/api/configuration-options.md +198 -198
- package/npm-docs/api/eventManager.md +82 -82
- package/npm-docs/api/expression-nodes.md +561 -561
- package/npm-docs/api/focusFrameManager.md +144 -144
- package/npm-docs/api/index.md +105 -105
- package/npm-docs/api/main.md +62 -62
- package/npm-docs/api/omdBinaryExpressionNode.md +86 -86
- package/npm-docs/api/omdCanvas.md +83 -83
- package/npm-docs/api/omdConfigManager.md +112 -112
- package/npm-docs/api/omdConstantNode.md +52 -52
- package/npm-docs/api/omdDisplay.md +87 -87
- package/npm-docs/api/omdEquationNode.md +174 -174
- package/npm-docs/api/omdEquationSequenceNode.md +258 -258
- package/npm-docs/api/omdEquationStack.md +192 -192
- package/npm-docs/api/omdFunctionNode.md +82 -82
- package/npm-docs/api/omdGroupNode.md +78 -78
- package/npm-docs/api/omdHelpers.md +87 -87
- package/npm-docs/api/omdLeafNode.md +85 -85
- package/npm-docs/api/omdNode.md +201 -201
- package/npm-docs/api/omdOperationDisplayNode.md +117 -117
- package/npm-docs/api/omdOperatorNode.md +91 -91
- package/npm-docs/api/omdParenthesisNode.md +133 -133
- package/npm-docs/api/omdPopup.md +191 -191
- package/npm-docs/api/omdPowerNode.md +131 -131
- package/npm-docs/api/omdRationalNode.md +144 -144
- package/npm-docs/api/omdSequenceNode.md +128 -128
- package/npm-docs/api/omdSimplification.md +78 -78
- package/npm-docs/api/omdSqrtNode.md +144 -144
- package/npm-docs/api/omdStepVisualizer.md +146 -146
- package/npm-docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/npm-docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/npm-docs/api/omdStepVisualizerLayout.md +70 -70
- package/npm-docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/npm-docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/npm-docs/api/omdToolbar.md +130 -130
- package/npm-docs/api/omdTranscriptionService.md +95 -95
- package/npm-docs/api/omdTreeDiff.md +169 -169
- package/npm-docs/api/omdUnaryExpressionNode.md +137 -137
- package/npm-docs/api/omdUtilities.md +82 -82
- package/npm-docs/api/omdVariableNode.md +123 -123
- package/npm-docs/api/selectTool.md +74 -74
- package/npm-docs/api/simplificationEngine.md +97 -97
- package/npm-docs/api/simplificationRules.md +76 -76
- package/npm-docs/api/simplificationUtils.md +64 -64
- package/npm-docs/api/transcribe.md +43 -43
- package/npm-docs/guides/equations.md +854 -854
- package/npm-docs/guides/factory-functions.md +354 -354
- package/npm-docs/guides/getting-started.md +318 -318
- package/npm-docs/guides/quick-examples.md +525 -525
- package/npm-docs/guides/visualizations.md +682 -682
- package/npm-docs/index.html +12 -0
- package/npm-docs/json-schemas.md +826 -826
- package/omd/config/omdConfigManager.js +279 -267
- package/omd/core/index.js +158 -158
- package/omd/core/omdEquationStack.js +546 -546
- package/omd/core/omdUtilities.js +113 -113
- package/omd/display/omdDisplay.js +969 -962
- package/omd/display/omdToolbar.js +501 -501
- package/omd/nodes/omdBinaryExpressionNode.js +459 -459
- package/omd/nodes/omdConstantNode.js +141 -141
- package/omd/nodes/omdEquationNode.js +1327 -1327
- package/omd/nodes/omdFunctionNode.js +351 -351
- package/omd/nodes/omdGroupNode.js +67 -67
- package/omd/nodes/omdLeafNode.js +76 -76
- package/omd/nodes/omdNode.js +556 -556
- package/omd/nodes/omdOperationDisplayNode.js +321 -321
- package/omd/nodes/omdOperatorNode.js +108 -108
- package/omd/nodes/omdParenthesisNode.js +292 -292
- package/omd/nodes/omdPowerNode.js +235 -235
- package/omd/nodes/omdRationalNode.js +295 -295
- package/omd/nodes/omdSqrtNode.js +307 -307
- package/omd/nodes/omdUnaryExpressionNode.js +227 -227
- package/omd/nodes/omdVariableNode.js +122 -122
- package/omd/simplification/omdSimplification.js +140 -140
- package/omd/simplification/omdSimplificationEngine.js +887 -887
- package/omd/simplification/package.json +5 -5
- package/omd/simplification/rules/binaryRules.js +1037 -1037
- package/omd/simplification/rules/functionRules.js +111 -111
- package/omd/simplification/rules/index.js +48 -48
- package/omd/simplification/rules/parenthesisRules.js +19 -19
- package/omd/simplification/rules/powerRules.js +143 -143
- package/omd/simplification/rules/rationalRules.js +725 -725
- package/omd/simplification/rules/sqrtRules.js +48 -48
- package/omd/simplification/rules/unaryRules.js +37 -37
- package/omd/simplification/simplificationRules.js +31 -31
- package/omd/simplification/simplificationUtils.js +1055 -1055
- package/omd/step-visualizer/omdStepVisualizer.js +947 -947
- package/omd/step-visualizer/omdStepVisualizerHighlighting.js +246 -246
- package/omd/step-visualizer/omdStepVisualizerLayout.js +892 -892
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +200 -200
- package/omd/utils/aiNextEquationStep.js +106 -106
- package/omd/utils/omdNodeOverlay.js +638 -638
- package/omd/utils/omdPopup.js +1203 -1203
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +684 -684
- package/omd/utils/omdStepVisualizerNodeUtils.js +267 -267
- package/omd/utils/omdTranscriptionService.js +123 -123
- package/omd/utils/omdTreeDiff.js +733 -733
- package/package.json +59 -56
- package/readme.html +184 -120
- package/src/index.js +74 -74
- package/src/json-schemas.md +576 -576
- package/src/omd-json-samples.js +147 -147
- package/src/omdApp.js +391 -391
- package/src/omdAppCanvas.js +335 -335
- package/src/omdBalanceHanger.js +199 -199
- package/src/omdColor.js +13 -13
- package/src/omdCoordinatePlane.js +541 -541
- package/src/omdExpression.js +115 -115
- package/src/omdFactory.js +150 -150
- package/src/omdFunction.js +114 -114
- package/src/omdMetaExpression.js +290 -290
- package/src/omdNaturalExpression.js +563 -563
- package/src/omdNode.js +383 -383
- package/src/omdNumber.js +52 -52
- package/src/omdNumberLine.js +114 -112
- package/src/omdNumberTile.js +118 -118
- package/src/omdOperator.js +72 -72
- package/src/omdPowerExpression.js +91 -91
- package/src/omdProblem.js +259 -259
- package/src/omdRatioChart.js +251 -251
- package/src/omdRationalExpression.js +114 -114
- package/src/omdSampleData.js +215 -215
- package/src/omdShapes.js +512 -512
- package/src/omdSpinner.js +151 -151
- package/src/omdString.js +49 -49
- package/src/omdTable.js +498 -498
- package/src/omdTapeDiagram.js +244 -244
- package/src/omdTerm.js +91 -91
- package/src/omdTileEquation.js +349 -349
- package/src/omdUtils.js +84 -84
- package/src/omdVariable.js +51 -51
|
@@ -1,445 +1,445 @@
|
|
|
1
|
-
import { pointerEventHandler } from './pointerEventHandler.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Event manager for canvas interactions
|
|
5
|
-
* Handles pointer, keyboard, and wheel events
|
|
6
|
-
*/
|
|
7
|
-
export class EventManager {
|
|
8
|
-
/**
|
|
9
|
-
* @param {OMDCanvas} canvas - Canvas instance
|
|
10
|
-
*/
|
|
11
|
-
constructor(canvas) {
|
|
12
|
-
this.canvas = canvas;
|
|
13
|
-
this.isInitialized = false;
|
|
14
|
-
|
|
15
|
-
// Event handlers
|
|
16
|
-
this.pointerEventHandler = new pointerEventHandler(canvas);
|
|
17
|
-
|
|
18
|
-
// Bound event listeners
|
|
19
|
-
this._onPointerDown = this._onPointerDown.bind(this);
|
|
20
|
-
this._onPointerMove = this._onPointerMove.bind(this);
|
|
21
|
-
this._onPointerUp = this._onPointerUp.bind(this);
|
|
22
|
-
this._onPointerCancel = this._onPointerCancel.bind(this);
|
|
23
|
-
this._onPointerEnter = this._onPointerEnter.bind(this);
|
|
24
|
-
this._onPointerLeave = this._onPointerLeave.bind(this);
|
|
25
|
-
this._onKeyDown = this._onKeyDown.bind(this);
|
|
26
|
-
this._onKeyUp = this._onKeyUp.bind(this);
|
|
27
|
-
this._onWheel = this._onWheel.bind(this);
|
|
28
|
-
this._onContextMenu = this._onContextMenu.bind(this);
|
|
29
|
-
|
|
30
|
-
// State tracking
|
|
31
|
-
this.activePointers = new Map();
|
|
32
|
-
this.isDrawing = false;
|
|
33
|
-
this.lastEventTime = 0;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Initialize event listeners
|
|
38
|
-
*/
|
|
39
|
-
initialize() {
|
|
40
|
-
if (this.isInitialized) return;
|
|
41
|
-
|
|
42
|
-
const svg = this.canvas.svg;
|
|
43
|
-
|
|
44
|
-
// Pointer events (unified mouse/touch/pen handling)
|
|
45
|
-
svg.addEventListener('pointerdown', this._onPointerDown);
|
|
46
|
-
svg.addEventListener('pointermove', this._onPointerMove);
|
|
47
|
-
svg.addEventListener('pointerup', this._onPointerUp);
|
|
48
|
-
svg.addEventListener('pointercancel', this._onPointerCancel);
|
|
49
|
-
svg.addEventListener('pointerenter', this._onPointerEnter);
|
|
50
|
-
svg.addEventListener('pointerleave', this._onPointerLeave);
|
|
51
|
-
|
|
52
|
-
// Keyboard events (on document for global shortcuts)
|
|
53
|
-
if (this.canvas.config.enableKeyboardShortcuts) {
|
|
54
|
-
document.addEventListener('keydown', this._onKeyDown);
|
|
55
|
-
document.addEventListener('keyup', this._onKeyUp);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Mouse wheel events
|
|
59
|
-
svg.addEventListener('wheel', this._onWheel);
|
|
60
|
-
|
|
61
|
-
// Prevent context menu
|
|
62
|
-
svg.addEventListener('contextmenu', this._onContextMenu);
|
|
63
|
-
|
|
64
|
-
// Set touch-action for better touch handling
|
|
65
|
-
svg.style.touchAction = 'none';
|
|
66
|
-
|
|
67
|
-
this.isInitialized = true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Handle pointer down events
|
|
72
|
-
* @private
|
|
73
|
-
*/
|
|
74
|
-
_onPointerDown(event) {
|
|
75
|
-
event.preventDefault();
|
|
76
|
-
// Track active pointer
|
|
77
|
-
this.activePointers.set(event.pointerId, event);
|
|
78
|
-
// Set pointer capture so canvas continues to receive events
|
|
79
|
-
event.target.setPointerCapture(event.pointerId);
|
|
80
|
-
// Convert to canvas coordinates
|
|
81
|
-
const canvasCoords = this.canvas.clientToSVG(event.clientX, event.clientY);
|
|
82
|
-
// Create normalized event
|
|
83
|
-
const normalizedEvent = this._normalizePointerEvent(event, canvasCoords);
|
|
84
|
-
// Handle multi-touch
|
|
85
|
-
if (this.canvas.config.enableMultiTouch && this.activePointers.size > 1) {
|
|
86
|
-
this.pointerEventHandler.handleMultiTouchStart(this.activePointers);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
// Delegate to pointer event handler
|
|
90
|
-
this.pointerEventHandler.handlePointerDown(event, normalizedEvent);
|
|
91
|
-
// Delegate to active tool
|
|
92
|
-
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
93
|
-
if (activeTool) {
|
|
94
|
-
// Let the tool decide if it's drawing (e.g., PointerTool won't set isDrawing)
|
|
95
|
-
activeTool.onPointerDown(normalizedEvent);
|
|
96
|
-
// If tool hasn't explicitly set isDrawing, set it for backwards compatibility
|
|
97
|
-
// (tools like PencilTool and EraserTool expect isDrawing to be true)
|
|
98
|
-
if (this.isDrawing === false && activeTool.constructor.name !== 'PointerTool') {
|
|
99
|
-
this.isDrawing = true;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// Emit canvas event
|
|
103
|
-
this.canvas.emit('pointerDown', normalizedEvent);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Handle pointer move events
|
|
108
|
-
* @private
|
|
109
|
-
*/
|
|
110
|
-
_onPointerMove(event) {
|
|
111
|
-
// Throttle events for performance
|
|
112
|
-
const now = Date.now();
|
|
113
|
-
if (now - this.lastEventTime < 16) return; // ~60fps
|
|
114
|
-
this.lastEventTime = now;
|
|
115
|
-
|
|
116
|
-
// Update active pointer
|
|
117
|
-
if (this.activePointers.has(event.pointerId)) {
|
|
118
|
-
this.activePointers.set(event.pointerId, event);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Convert to canvas coordinates
|
|
122
|
-
const canvasCoords = this.canvas.clientToSVG(event.clientX, event.clientY);
|
|
123
|
-
|
|
124
|
-
// Create normalized event
|
|
125
|
-
const normalizedEvent = this._normalizePointerEvent(event, canvasCoords);
|
|
126
|
-
|
|
127
|
-
// Handle multi-touch
|
|
128
|
-
if (this.canvas.config.enableMultiTouch && this.activePointers.size > 1) {
|
|
129
|
-
this.pointerEventHandler.handleMultiTouchMove(this.activePointers);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Update cursor position
|
|
134
|
-
if (this.canvas.cursor) {
|
|
135
|
-
this.canvas.cursor.setPosition(canvasCoords.x, canvasCoords.y);
|
|
136
|
-
|
|
137
|
-
// Update pressure feedback if available
|
|
138
|
-
if (event.pressure !== undefined) {
|
|
139
|
-
this.canvas.cursor.setPressureFeedback(event.pressure);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Delegate to pointer event handler
|
|
144
|
-
this.pointerEventHandler.handlePointerMove(event, normalizedEvent);
|
|
145
|
-
|
|
146
|
-
// Delegate to active tool if drawing
|
|
147
|
-
if (this.isDrawing) {
|
|
148
|
-
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
149
|
-
if (activeTool) {
|
|
150
|
-
activeTool.onPointerMove(normalizedEvent);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Emit canvas event
|
|
155
|
-
this.canvas.emit('pointerMove', normalizedEvent);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Handle pointer up events
|
|
160
|
-
* @private
|
|
161
|
-
*/
|
|
162
|
-
_onPointerUp(event) {
|
|
163
|
-
// Remove from active pointers
|
|
164
|
-
this.activePointers.delete(event.pointerId);
|
|
165
|
-
// Release pointer capture
|
|
166
|
-
event.target.releasePointerCapture(event.pointerId);
|
|
167
|
-
// Convert to canvas coordinates
|
|
168
|
-
const canvasCoords = this.canvas.clientToSVG(event.clientX, event.clientY);
|
|
169
|
-
// Create normalized event
|
|
170
|
-
const normalizedEvent = this._normalizePointerEvent(event, canvasCoords);
|
|
171
|
-
// Handle multi-touch
|
|
172
|
-
if (this.canvas.config.enableMultiTouch && this.activePointers.size >= 1) {
|
|
173
|
-
this.pointerEventHandler.handleMultiTouchEnd(this.activePointers);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
// Clear drawing state
|
|
177
|
-
this.isDrawing = false;
|
|
178
|
-
// Delegate to pointer event handler
|
|
179
|
-
this.pointerEventHandler.handlePointerUp(event, normalizedEvent);
|
|
180
|
-
// Delegate to active tool
|
|
181
|
-
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
182
|
-
if (activeTool) {
|
|
183
|
-
activeTool.onPointerUp(normalizedEvent);
|
|
184
|
-
}
|
|
185
|
-
// Emit canvas event
|
|
186
|
-
this.canvas.emit('pointerUp', normalizedEvent);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Handle pointer cancel events
|
|
191
|
-
* @private
|
|
192
|
-
*/
|
|
193
|
-
_onPointerCancel(event) {
|
|
194
|
-
// Remove from active pointers
|
|
195
|
-
this.activePointers.delete(event.pointerId);
|
|
196
|
-
// Release pointer capture
|
|
197
|
-
event.target.releasePointerCapture(event.pointerId);
|
|
198
|
-
// Clear drawing state
|
|
199
|
-
this.isDrawing = false;
|
|
200
|
-
// Cancel active tool
|
|
201
|
-
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
202
|
-
if (activeTool) {
|
|
203
|
-
activeTool.onCancel();
|
|
204
|
-
}
|
|
205
|
-
// Emit canvas event
|
|
206
|
-
this.canvas.emit('pointerCancel', { pointerId: event.pointerId });
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Handle pointer enter events
|
|
211
|
-
* @private
|
|
212
|
-
*/
|
|
213
|
-
_onPointerEnter(event) {
|
|
214
|
-
// Show custom cursor when entering canvas
|
|
215
|
-
if (this.canvas.cursor) {
|
|
216
|
-
this.canvas.cursor.show();
|
|
217
|
-
// Update cursor from current tool config
|
|
218
|
-
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
219
|
-
if (activeTool && activeTool.config) {
|
|
220
|
-
this.canvas.cursor.updateFromToolConfig(activeTool.config);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// If mouse is down (event.buttons !== 0), start drawing
|
|
224
|
-
if (event.buttons !== 0) {
|
|
225
|
-
// Convert to canvas coordinates
|
|
226
|
-
const canvasCoords = this.canvas.clientToSVG(event.clientX, event.clientY);
|
|
227
|
-
const normalizedEvent = this._normalizePointerEvent(event, canvasCoords);
|
|
228
|
-
this.isDrawing = true;
|
|
229
|
-
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
230
|
-
if (activeTool) {
|
|
231
|
-
activeTool.onPointerDown(normalizedEvent);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
// Emit canvas event
|
|
235
|
-
this.canvas.emit('pointerEnter', { event });
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Handle pointer leave events
|
|
240
|
-
* @private
|
|
241
|
-
*/
|
|
242
|
-
_onPointerLeave(event) {
|
|
243
|
-
// Hide custom cursor when leaving canvas
|
|
244
|
-
if (this.canvas.cursor) {
|
|
245
|
-
this.canvas.cursor.hide();
|
|
246
|
-
}
|
|
247
|
-
// Do NOT cancel drawing on pointerleave; drawing continues outside canvas
|
|
248
|
-
// But if no buttons are pressed, we can safely clear the drawing state
|
|
249
|
-
if (event.buttons === 0) {
|
|
250
|
-
this.isDrawing = false;
|
|
251
|
-
}
|
|
252
|
-
// Clear active pointers only if no buttons pressed
|
|
253
|
-
if (event.buttons === 0) {
|
|
254
|
-
this.activePointers.clear();
|
|
255
|
-
}
|
|
256
|
-
// Emit canvas event
|
|
257
|
-
this.canvas.emit('pointerLeave', { event });
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Handle keyboard down events
|
|
262
|
-
* @private
|
|
263
|
-
*/
|
|
264
|
-
_onKeyDown(event) {
|
|
265
|
-
// Ignore if typing in input fields
|
|
266
|
-
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const key = event.key.toLowerCase();
|
|
271
|
-
|
|
272
|
-
// Global shortcuts
|
|
273
|
-
switch (key) {
|
|
274
|
-
case 'p':
|
|
275
|
-
if (this.canvas.config.enabledTools.includes('pencil')) {
|
|
276
|
-
this.canvas.toolManager.setActiveTool('pencil');
|
|
277
|
-
event.preventDefault();
|
|
278
|
-
}
|
|
279
|
-
break;
|
|
280
|
-
case 'e':
|
|
281
|
-
if (this.canvas.config.enabledTools.includes('eraser')) {
|
|
282
|
-
this.canvas.toolManager.setActiveTool('eraser');
|
|
283
|
-
event.preventDefault();
|
|
284
|
-
}
|
|
285
|
-
break;
|
|
286
|
-
case 's':
|
|
287
|
-
if (this.canvas.config.enabledTools.includes('select')) {
|
|
288
|
-
this.canvas.toolManager.setActiveTool('select');
|
|
289
|
-
event.preventDefault();
|
|
290
|
-
}
|
|
291
|
-
break;
|
|
292
|
-
case 'escape':
|
|
293
|
-
// Cancel current operation
|
|
294
|
-
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
295
|
-
if (activeTool) {
|
|
296
|
-
activeTool.onCancel();
|
|
297
|
-
}
|
|
298
|
-
event.preventDefault();
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Delegate to active tool
|
|
303
|
-
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
304
|
-
if (activeTool && activeTool.onKeyboardShortcut) {
|
|
305
|
-
const handled = activeTool.onKeyboardShortcut(key, event);
|
|
306
|
-
if (handled) {
|
|
307
|
-
event.preventDefault();
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Emit canvas event
|
|
312
|
-
this.canvas.emit('keyDown', { key, event });
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Handle keyboard up events
|
|
317
|
-
* @private
|
|
318
|
-
*/
|
|
319
|
-
_onKeyUp(event) {
|
|
320
|
-
// Ignore if typing in input fields
|
|
321
|
-
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const key = event.key.toLowerCase();
|
|
326
|
-
|
|
327
|
-
// Emit canvas event
|
|
328
|
-
this.canvas.emit('keyUp', { key, event });
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Handle wheel events (zoom/pan)
|
|
333
|
-
* @private
|
|
334
|
-
*/
|
|
335
|
-
_onWheel(event) {
|
|
336
|
-
// Prevent default scrolling
|
|
337
|
-
event.preventDefault();
|
|
338
|
-
|
|
339
|
-
// Convert to canvas coordinates
|
|
340
|
-
const canvasCoords = this.canvas.clientToSVG(event.clientX, event.clientY);
|
|
341
|
-
|
|
342
|
-
// Emit canvas event
|
|
343
|
-
this.canvas.emit('wheel', {
|
|
344
|
-
deltaX: event.deltaX,
|
|
345
|
-
deltaY: event.deltaY,
|
|
346
|
-
deltaZ: event.deltaZ,
|
|
347
|
-
x: canvasCoords.x,
|
|
348
|
-
y: canvasCoords.y,
|
|
349
|
-
ctrlKey: event.ctrlKey,
|
|
350
|
-
metaKey: event.metaKey
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Prevent context menu
|
|
356
|
-
* @private
|
|
357
|
-
*/
|
|
358
|
-
_onContextMenu(event) {
|
|
359
|
-
event.preventDefault();
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Normalize pointer event data
|
|
364
|
-
* @private
|
|
365
|
-
*/
|
|
366
|
-
_normalizePointerEvent(event, canvasCoords) {
|
|
367
|
-
return {
|
|
368
|
-
pointerId: event.pointerId,
|
|
369
|
-
pointerType: event.pointerType,
|
|
370
|
-
isPrimary: event.isPrimary,
|
|
371
|
-
x: canvasCoords.x,
|
|
372
|
-
y: canvasCoords.y,
|
|
373
|
-
clientX: event.clientX,
|
|
374
|
-
clientY: event.clientY,
|
|
375
|
-
pressure: this._normalizePressure(event.pressure),
|
|
376
|
-
tiltX: event.tiltX || 0,
|
|
377
|
-
tiltY: event.tiltY || 0,
|
|
378
|
-
twist: event.twist || 0,
|
|
379
|
-
width: event.width || 1,
|
|
380
|
-
height: event.height || 1,
|
|
381
|
-
tangentialPressure: event.tangentialPressure || 0,
|
|
382
|
-
buttons: event.buttons,
|
|
383
|
-
shiftKey: event.shiftKey,
|
|
384
|
-
ctrlKey: event.ctrlKey,
|
|
385
|
-
altKey: event.altKey,
|
|
386
|
-
metaKey: event.metaKey,
|
|
387
|
-
timestamp: event.timeStamp || Date.now()
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Normalize pressure values
|
|
393
|
-
* @private
|
|
394
|
-
*/
|
|
395
|
-
_normalizePressure(pressure) {
|
|
396
|
-
if (pressure === undefined || pressure === null) {
|
|
397
|
-
return 0.5; // Default pressure for devices without pressure sensitivity
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Clamp pressure between 0 and 1
|
|
401
|
-
return Math.max(0, Math.min(1, pressure));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Get current pointer information
|
|
406
|
-
* @returns {Object} Pointer information
|
|
407
|
-
*/
|
|
408
|
-
getPointerInfo() {
|
|
409
|
-
return {
|
|
410
|
-
activePointers: this.activePointers.size,
|
|
411
|
-
isDrawing: this.isDrawing,
|
|
412
|
-
multiTouch: this.activePointers.size > 1
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Destroy event manager
|
|
418
|
-
*/
|
|
419
|
-
destroy() {
|
|
420
|
-
if (!this.isInitialized) return;
|
|
421
|
-
|
|
422
|
-
const svg = this.canvas.svg;
|
|
423
|
-
|
|
424
|
-
// Remove pointer events
|
|
425
|
-
svg.removeEventListener('pointerdown', this._onPointerDown);
|
|
426
|
-
svg.removeEventListener('pointermove', this._onPointerMove);
|
|
427
|
-
svg.removeEventListener('pointerup', this._onPointerUp);
|
|
428
|
-
svg.removeEventListener('pointercancel', this._onPointerCancel);
|
|
429
|
-
svg.removeEventListener('pointerenter', this._onPointerEnter);
|
|
430
|
-
svg.removeEventListener('pointerleave', this._onPointerLeave);
|
|
431
|
-
|
|
432
|
-
// Remove keyboard events
|
|
433
|
-
document.removeEventListener('keydown', this._onKeyDown);
|
|
434
|
-
document.removeEventListener('keyup', this._onKeyUp);
|
|
435
|
-
|
|
436
|
-
// Remove other events
|
|
437
|
-
svg.removeEventListener('wheel', this._onWheel);
|
|
438
|
-
svg.removeEventListener('contextmenu', this._onContextMenu);
|
|
439
|
-
|
|
440
|
-
// Clear state
|
|
441
|
-
this.activePointers.clear();
|
|
442
|
-
this.isDrawing = false;
|
|
443
|
-
this.isInitialized = false;
|
|
444
|
-
}
|
|
1
|
+
import { pointerEventHandler } from './pointerEventHandler.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Event manager for canvas interactions
|
|
5
|
+
* Handles pointer, keyboard, and wheel events
|
|
6
|
+
*/
|
|
7
|
+
export class EventManager {
|
|
8
|
+
/**
|
|
9
|
+
* @param {OMDCanvas} canvas - Canvas instance
|
|
10
|
+
*/
|
|
11
|
+
constructor(canvas) {
|
|
12
|
+
this.canvas = canvas;
|
|
13
|
+
this.isInitialized = false;
|
|
14
|
+
|
|
15
|
+
// Event handlers
|
|
16
|
+
this.pointerEventHandler = new pointerEventHandler(canvas);
|
|
17
|
+
|
|
18
|
+
// Bound event listeners
|
|
19
|
+
this._onPointerDown = this._onPointerDown.bind(this);
|
|
20
|
+
this._onPointerMove = this._onPointerMove.bind(this);
|
|
21
|
+
this._onPointerUp = this._onPointerUp.bind(this);
|
|
22
|
+
this._onPointerCancel = this._onPointerCancel.bind(this);
|
|
23
|
+
this._onPointerEnter = this._onPointerEnter.bind(this);
|
|
24
|
+
this._onPointerLeave = this._onPointerLeave.bind(this);
|
|
25
|
+
this._onKeyDown = this._onKeyDown.bind(this);
|
|
26
|
+
this._onKeyUp = this._onKeyUp.bind(this);
|
|
27
|
+
this._onWheel = this._onWheel.bind(this);
|
|
28
|
+
this._onContextMenu = this._onContextMenu.bind(this);
|
|
29
|
+
|
|
30
|
+
// State tracking
|
|
31
|
+
this.activePointers = new Map();
|
|
32
|
+
this.isDrawing = false;
|
|
33
|
+
this.lastEventTime = 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Initialize event listeners
|
|
38
|
+
*/
|
|
39
|
+
initialize() {
|
|
40
|
+
if (this.isInitialized) return;
|
|
41
|
+
|
|
42
|
+
const svg = this.canvas.svg;
|
|
43
|
+
|
|
44
|
+
// Pointer events (unified mouse/touch/pen handling)
|
|
45
|
+
svg.addEventListener('pointerdown', this._onPointerDown);
|
|
46
|
+
svg.addEventListener('pointermove', this._onPointerMove);
|
|
47
|
+
svg.addEventListener('pointerup', this._onPointerUp);
|
|
48
|
+
svg.addEventListener('pointercancel', this._onPointerCancel);
|
|
49
|
+
svg.addEventListener('pointerenter', this._onPointerEnter);
|
|
50
|
+
svg.addEventListener('pointerleave', this._onPointerLeave);
|
|
51
|
+
|
|
52
|
+
// Keyboard events (on document for global shortcuts)
|
|
53
|
+
if (this.canvas.config.enableKeyboardShortcuts) {
|
|
54
|
+
document.addEventListener('keydown', this._onKeyDown);
|
|
55
|
+
document.addEventListener('keyup', this._onKeyUp);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Mouse wheel events
|
|
59
|
+
svg.addEventListener('wheel', this._onWheel);
|
|
60
|
+
|
|
61
|
+
// Prevent context menu
|
|
62
|
+
svg.addEventListener('contextmenu', this._onContextMenu);
|
|
63
|
+
|
|
64
|
+
// Set touch-action for better touch handling
|
|
65
|
+
svg.style.touchAction = 'none';
|
|
66
|
+
|
|
67
|
+
this.isInitialized = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle pointer down events
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
_onPointerDown(event) {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
// Track active pointer
|
|
77
|
+
this.activePointers.set(event.pointerId, event);
|
|
78
|
+
// Set pointer capture so canvas continues to receive events
|
|
79
|
+
event.target.setPointerCapture(event.pointerId);
|
|
80
|
+
// Convert to canvas coordinates
|
|
81
|
+
const canvasCoords = this.canvas.clientToSVG(event.clientX, event.clientY);
|
|
82
|
+
// Create normalized event
|
|
83
|
+
const normalizedEvent = this._normalizePointerEvent(event, canvasCoords);
|
|
84
|
+
// Handle multi-touch
|
|
85
|
+
if (this.canvas.config.enableMultiTouch && this.activePointers.size > 1) {
|
|
86
|
+
this.pointerEventHandler.handleMultiTouchStart(this.activePointers);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Delegate to pointer event handler
|
|
90
|
+
this.pointerEventHandler.handlePointerDown(event, normalizedEvent);
|
|
91
|
+
// Delegate to active tool
|
|
92
|
+
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
93
|
+
if (activeTool) {
|
|
94
|
+
// Let the tool decide if it's drawing (e.g., PointerTool won't set isDrawing)
|
|
95
|
+
activeTool.onPointerDown(normalizedEvent);
|
|
96
|
+
// If tool hasn't explicitly set isDrawing, set it for backwards compatibility
|
|
97
|
+
// (tools like PencilTool and EraserTool expect isDrawing to be true)
|
|
98
|
+
if (this.isDrawing === false && activeTool.constructor.name !== 'PointerTool') {
|
|
99
|
+
this.isDrawing = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Emit canvas event
|
|
103
|
+
this.canvas.emit('pointerDown', normalizedEvent);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Handle pointer move events
|
|
108
|
+
* @private
|
|
109
|
+
*/
|
|
110
|
+
_onPointerMove(event) {
|
|
111
|
+
// Throttle events for performance
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
if (now - this.lastEventTime < 16) return; // ~60fps
|
|
114
|
+
this.lastEventTime = now;
|
|
115
|
+
|
|
116
|
+
// Update active pointer
|
|
117
|
+
if (this.activePointers.has(event.pointerId)) {
|
|
118
|
+
this.activePointers.set(event.pointerId, event);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Convert to canvas coordinates
|
|
122
|
+
const canvasCoords = this.canvas.clientToSVG(event.clientX, event.clientY);
|
|
123
|
+
|
|
124
|
+
// Create normalized event
|
|
125
|
+
const normalizedEvent = this._normalizePointerEvent(event, canvasCoords);
|
|
126
|
+
|
|
127
|
+
// Handle multi-touch
|
|
128
|
+
if (this.canvas.config.enableMultiTouch && this.activePointers.size > 1) {
|
|
129
|
+
this.pointerEventHandler.handleMultiTouchMove(this.activePointers);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Update cursor position
|
|
134
|
+
if (this.canvas.cursor) {
|
|
135
|
+
this.canvas.cursor.setPosition(canvasCoords.x, canvasCoords.y);
|
|
136
|
+
|
|
137
|
+
// Update pressure feedback if available
|
|
138
|
+
if (event.pressure !== undefined) {
|
|
139
|
+
this.canvas.cursor.setPressureFeedback(event.pressure);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Delegate to pointer event handler
|
|
144
|
+
this.pointerEventHandler.handlePointerMove(event, normalizedEvent);
|
|
145
|
+
|
|
146
|
+
// Delegate to active tool if drawing
|
|
147
|
+
if (this.isDrawing) {
|
|
148
|
+
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
149
|
+
if (activeTool) {
|
|
150
|
+
activeTool.onPointerMove(normalizedEvent);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Emit canvas event
|
|
155
|
+
this.canvas.emit('pointerMove', normalizedEvent);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Handle pointer up events
|
|
160
|
+
* @private
|
|
161
|
+
*/
|
|
162
|
+
_onPointerUp(event) {
|
|
163
|
+
// Remove from active pointers
|
|
164
|
+
this.activePointers.delete(event.pointerId);
|
|
165
|
+
// Release pointer capture
|
|
166
|
+
event.target.releasePointerCapture(event.pointerId);
|
|
167
|
+
// Convert to canvas coordinates
|
|
168
|
+
const canvasCoords = this.canvas.clientToSVG(event.clientX, event.clientY);
|
|
169
|
+
// Create normalized event
|
|
170
|
+
const normalizedEvent = this._normalizePointerEvent(event, canvasCoords);
|
|
171
|
+
// Handle multi-touch
|
|
172
|
+
if (this.canvas.config.enableMultiTouch && this.activePointers.size >= 1) {
|
|
173
|
+
this.pointerEventHandler.handleMultiTouchEnd(this.activePointers);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// Clear drawing state
|
|
177
|
+
this.isDrawing = false;
|
|
178
|
+
// Delegate to pointer event handler
|
|
179
|
+
this.pointerEventHandler.handlePointerUp(event, normalizedEvent);
|
|
180
|
+
// Delegate to active tool
|
|
181
|
+
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
182
|
+
if (activeTool) {
|
|
183
|
+
activeTool.onPointerUp(normalizedEvent);
|
|
184
|
+
}
|
|
185
|
+
// Emit canvas event
|
|
186
|
+
this.canvas.emit('pointerUp', normalizedEvent);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Handle pointer cancel events
|
|
191
|
+
* @private
|
|
192
|
+
*/
|
|
193
|
+
_onPointerCancel(event) {
|
|
194
|
+
// Remove from active pointers
|
|
195
|
+
this.activePointers.delete(event.pointerId);
|
|
196
|
+
// Release pointer capture
|
|
197
|
+
event.target.releasePointerCapture(event.pointerId);
|
|
198
|
+
// Clear drawing state
|
|
199
|
+
this.isDrawing = false;
|
|
200
|
+
// Cancel active tool
|
|
201
|
+
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
202
|
+
if (activeTool) {
|
|
203
|
+
activeTool.onCancel();
|
|
204
|
+
}
|
|
205
|
+
// Emit canvas event
|
|
206
|
+
this.canvas.emit('pointerCancel', { pointerId: event.pointerId });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Handle pointer enter events
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
213
|
+
_onPointerEnter(event) {
|
|
214
|
+
// Show custom cursor when entering canvas
|
|
215
|
+
if (this.canvas.cursor) {
|
|
216
|
+
this.canvas.cursor.show();
|
|
217
|
+
// Update cursor from current tool config
|
|
218
|
+
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
219
|
+
if (activeTool && activeTool.config) {
|
|
220
|
+
this.canvas.cursor.updateFromToolConfig(activeTool.config);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// If mouse is down (event.buttons !== 0), start drawing
|
|
224
|
+
if (event.buttons !== 0) {
|
|
225
|
+
// Convert to canvas coordinates
|
|
226
|
+
const canvasCoords = this.canvas.clientToSVG(event.clientX, event.clientY);
|
|
227
|
+
const normalizedEvent = this._normalizePointerEvent(event, canvasCoords);
|
|
228
|
+
this.isDrawing = true;
|
|
229
|
+
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
230
|
+
if (activeTool) {
|
|
231
|
+
activeTool.onPointerDown(normalizedEvent);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Emit canvas event
|
|
235
|
+
this.canvas.emit('pointerEnter', { event });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Handle pointer leave events
|
|
240
|
+
* @private
|
|
241
|
+
*/
|
|
242
|
+
_onPointerLeave(event) {
|
|
243
|
+
// Hide custom cursor when leaving canvas
|
|
244
|
+
if (this.canvas.cursor) {
|
|
245
|
+
this.canvas.cursor.hide();
|
|
246
|
+
}
|
|
247
|
+
// Do NOT cancel drawing on pointerleave; drawing continues outside canvas
|
|
248
|
+
// But if no buttons are pressed, we can safely clear the drawing state
|
|
249
|
+
if (event.buttons === 0) {
|
|
250
|
+
this.isDrawing = false;
|
|
251
|
+
}
|
|
252
|
+
// Clear active pointers only if no buttons pressed
|
|
253
|
+
if (event.buttons === 0) {
|
|
254
|
+
this.activePointers.clear();
|
|
255
|
+
}
|
|
256
|
+
// Emit canvas event
|
|
257
|
+
this.canvas.emit('pointerLeave', { event });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Handle keyboard down events
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
264
|
+
_onKeyDown(event) {
|
|
265
|
+
// Ignore if typing in input fields
|
|
266
|
+
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const key = event.key.toLowerCase();
|
|
271
|
+
|
|
272
|
+
// Global shortcuts
|
|
273
|
+
switch (key) {
|
|
274
|
+
case 'p':
|
|
275
|
+
if (this.canvas.config.enabledTools.includes('pencil')) {
|
|
276
|
+
this.canvas.toolManager.setActiveTool('pencil');
|
|
277
|
+
event.preventDefault();
|
|
278
|
+
}
|
|
279
|
+
break;
|
|
280
|
+
case 'e':
|
|
281
|
+
if (this.canvas.config.enabledTools.includes('eraser')) {
|
|
282
|
+
this.canvas.toolManager.setActiveTool('eraser');
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
}
|
|
285
|
+
break;
|
|
286
|
+
case 's':
|
|
287
|
+
if (this.canvas.config.enabledTools.includes('select')) {
|
|
288
|
+
this.canvas.toolManager.setActiveTool('select');
|
|
289
|
+
event.preventDefault();
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
case 'escape':
|
|
293
|
+
// Cancel current operation
|
|
294
|
+
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
295
|
+
if (activeTool) {
|
|
296
|
+
activeTool.onCancel();
|
|
297
|
+
}
|
|
298
|
+
event.preventDefault();
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Delegate to active tool
|
|
303
|
+
const activeTool = this.canvas.toolManager.getActiveTool();
|
|
304
|
+
if (activeTool && activeTool.onKeyboardShortcut) {
|
|
305
|
+
const handled = activeTool.onKeyboardShortcut(key, event);
|
|
306
|
+
if (handled) {
|
|
307
|
+
event.preventDefault();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Emit canvas event
|
|
312
|
+
this.canvas.emit('keyDown', { key, event });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Handle keyboard up events
|
|
317
|
+
* @private
|
|
318
|
+
*/
|
|
319
|
+
_onKeyUp(event) {
|
|
320
|
+
// Ignore if typing in input fields
|
|
321
|
+
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const key = event.key.toLowerCase();
|
|
326
|
+
|
|
327
|
+
// Emit canvas event
|
|
328
|
+
this.canvas.emit('keyUp', { key, event });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Handle wheel events (zoom/pan)
|
|
333
|
+
* @private
|
|
334
|
+
*/
|
|
335
|
+
_onWheel(event) {
|
|
336
|
+
// Prevent default scrolling
|
|
337
|
+
event.preventDefault();
|
|
338
|
+
|
|
339
|
+
// Convert to canvas coordinates
|
|
340
|
+
const canvasCoords = this.canvas.clientToSVG(event.clientX, event.clientY);
|
|
341
|
+
|
|
342
|
+
// Emit canvas event
|
|
343
|
+
this.canvas.emit('wheel', {
|
|
344
|
+
deltaX: event.deltaX,
|
|
345
|
+
deltaY: event.deltaY,
|
|
346
|
+
deltaZ: event.deltaZ,
|
|
347
|
+
x: canvasCoords.x,
|
|
348
|
+
y: canvasCoords.y,
|
|
349
|
+
ctrlKey: event.ctrlKey,
|
|
350
|
+
metaKey: event.metaKey
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Prevent context menu
|
|
356
|
+
* @private
|
|
357
|
+
*/
|
|
358
|
+
_onContextMenu(event) {
|
|
359
|
+
event.preventDefault();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Normalize pointer event data
|
|
364
|
+
* @private
|
|
365
|
+
*/
|
|
366
|
+
_normalizePointerEvent(event, canvasCoords) {
|
|
367
|
+
return {
|
|
368
|
+
pointerId: event.pointerId,
|
|
369
|
+
pointerType: event.pointerType,
|
|
370
|
+
isPrimary: event.isPrimary,
|
|
371
|
+
x: canvasCoords.x,
|
|
372
|
+
y: canvasCoords.y,
|
|
373
|
+
clientX: event.clientX,
|
|
374
|
+
clientY: event.clientY,
|
|
375
|
+
pressure: this._normalizePressure(event.pressure),
|
|
376
|
+
tiltX: event.tiltX || 0,
|
|
377
|
+
tiltY: event.tiltY || 0,
|
|
378
|
+
twist: event.twist || 0,
|
|
379
|
+
width: event.width || 1,
|
|
380
|
+
height: event.height || 1,
|
|
381
|
+
tangentialPressure: event.tangentialPressure || 0,
|
|
382
|
+
buttons: event.buttons,
|
|
383
|
+
shiftKey: event.shiftKey,
|
|
384
|
+
ctrlKey: event.ctrlKey,
|
|
385
|
+
altKey: event.altKey,
|
|
386
|
+
metaKey: event.metaKey,
|
|
387
|
+
timestamp: event.timeStamp || Date.now()
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Normalize pressure values
|
|
393
|
+
* @private
|
|
394
|
+
*/
|
|
395
|
+
_normalizePressure(pressure) {
|
|
396
|
+
if (pressure === undefined || pressure === null) {
|
|
397
|
+
return 0.5; // Default pressure for devices without pressure sensitivity
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Clamp pressure between 0 and 1
|
|
401
|
+
return Math.max(0, Math.min(1, pressure));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Get current pointer information
|
|
406
|
+
* @returns {Object} Pointer information
|
|
407
|
+
*/
|
|
408
|
+
getPointerInfo() {
|
|
409
|
+
return {
|
|
410
|
+
activePointers: this.activePointers.size,
|
|
411
|
+
isDrawing: this.isDrawing,
|
|
412
|
+
multiTouch: this.activePointers.size > 1
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Destroy event manager
|
|
418
|
+
*/
|
|
419
|
+
destroy() {
|
|
420
|
+
if (!this.isInitialized) return;
|
|
421
|
+
|
|
422
|
+
const svg = this.canvas.svg;
|
|
423
|
+
|
|
424
|
+
// Remove pointer events
|
|
425
|
+
svg.removeEventListener('pointerdown', this._onPointerDown);
|
|
426
|
+
svg.removeEventListener('pointermove', this._onPointerMove);
|
|
427
|
+
svg.removeEventListener('pointerup', this._onPointerUp);
|
|
428
|
+
svg.removeEventListener('pointercancel', this._onPointerCancel);
|
|
429
|
+
svg.removeEventListener('pointerenter', this._onPointerEnter);
|
|
430
|
+
svg.removeEventListener('pointerleave', this._onPointerLeave);
|
|
431
|
+
|
|
432
|
+
// Remove keyboard events
|
|
433
|
+
document.removeEventListener('keydown', this._onKeyDown);
|
|
434
|
+
document.removeEventListener('keyup', this._onKeyUp);
|
|
435
|
+
|
|
436
|
+
// Remove other events
|
|
437
|
+
svg.removeEventListener('wheel', this._onWheel);
|
|
438
|
+
svg.removeEventListener('contextmenu', this._onContextMenu);
|
|
439
|
+
|
|
440
|
+
// Clear state
|
|
441
|
+
this.activePointers.clear();
|
|
442
|
+
this.isDrawing = false;
|
|
443
|
+
this.isInitialized = false;
|
|
444
|
+
}
|
|
445
445
|
}
|