copper3d 3.0.2 → 3.1.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.
Files changed (104) hide show
  1. package/README.md +183 -1028
  2. package/dist/Utils/segmentation/CanvasState.d.ts +31 -0
  3. package/dist/Utils/segmentation/CanvasState.js +167 -0
  4. package/dist/Utils/segmentation/CanvasState.js.map +1 -0
  5. package/dist/Utils/segmentation/DragOperator.d.ts +1 -1
  6. package/dist/Utils/segmentation/DragOperator.js +2 -4
  7. package/dist/Utils/segmentation/DragOperator.js.map +1 -1
  8. package/dist/Utils/segmentation/DrawToolCore.d.ts +49 -27
  9. package/dist/Utils/segmentation/DrawToolCore.js +275 -344
  10. package/dist/Utils/segmentation/DrawToolCore.js.map +1 -1
  11. package/dist/Utils/segmentation/NrrdTools.d.ts +89 -467
  12. package/dist/Utils/segmentation/NrrdTools.js +479 -1335
  13. package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
  14. package/dist/Utils/segmentation/RenderingUtils.d.ts +73 -0
  15. package/dist/Utils/segmentation/RenderingUtils.js +180 -0
  16. package/dist/Utils/segmentation/RenderingUtils.js.map +1 -0
  17. package/dist/Utils/segmentation/core/MaskVolume.d.ts +1 -1
  18. package/dist/Utils/segmentation/core/MaskVolume.js +1 -1
  19. package/dist/Utils/segmentation/core/index.d.ts +1 -1
  20. package/dist/Utils/segmentation/core/index.js.map +1 -1
  21. package/dist/Utils/segmentation/core/types.d.ts +384 -0
  22. package/dist/Utils/segmentation/core/types.js +1 -0
  23. package/dist/Utils/segmentation/core/types.js.map +1 -1
  24. package/dist/Utils/segmentation/coreTools/GuiState.d.ts +1 -1
  25. package/dist/Utils/segmentation/coreTools/NrrdState.d.ts +1 -1
  26. package/dist/Utils/segmentation/coreTools/divControlTools.d.ts +1 -1
  27. package/dist/Utils/segmentation/coreTools/gui.d.ts +1 -1
  28. package/dist/Utils/segmentation/tools/BaseTool.d.ts +1 -1
  29. package/dist/Utils/segmentation/tools/ContrastTool.d.ts +3 -6
  30. package/dist/Utils/segmentation/tools/ContrastTool.js.map +1 -1
  31. package/dist/Utils/segmentation/tools/CrosshairTool.d.ts +11 -1
  32. package/dist/Utils/segmentation/tools/CrosshairTool.js +25 -0
  33. package/dist/Utils/segmentation/tools/CrosshairTool.js.map +1 -1
  34. package/dist/Utils/segmentation/tools/DataLoader.d.ts +33 -0
  35. package/dist/Utils/segmentation/tools/DataLoader.js +159 -0
  36. package/dist/Utils/segmentation/tools/DataLoader.js.map +1 -0
  37. package/dist/Utils/segmentation/tools/DragSliceTool.d.ts +3 -12
  38. package/dist/Utils/segmentation/tools/DragSliceTool.js.map +1 -1
  39. package/dist/Utils/segmentation/tools/DrawingTool.d.ts +18 -26
  40. package/dist/Utils/segmentation/tools/DrawingTool.js +48 -0
  41. package/dist/Utils/segmentation/tools/DrawingTool.js.map +1 -1
  42. package/dist/Utils/segmentation/tools/ImageStoreHelper.d.ts +2 -5
  43. package/dist/Utils/segmentation/tools/ImageStoreHelper.js.map +1 -1
  44. package/dist/Utils/segmentation/tools/LayerChannelManager.d.ts +52 -0
  45. package/dist/Utils/segmentation/tools/LayerChannelManager.js +175 -0
  46. package/dist/Utils/segmentation/tools/LayerChannelManager.js.map +1 -0
  47. package/dist/Utils/segmentation/tools/PanTool.d.ts +2 -8
  48. package/dist/Utils/segmentation/tools/PanTool.js.map +1 -1
  49. package/dist/Utils/segmentation/tools/SliceRenderPipeline.d.ts +80 -0
  50. package/dist/Utils/segmentation/tools/SliceRenderPipeline.js +355 -0
  51. package/dist/Utils/segmentation/tools/SliceRenderPipeline.js.map +1 -0
  52. package/dist/Utils/segmentation/tools/SphereTool.d.ts +25 -19
  53. package/dist/Utils/segmentation/tools/SphereTool.js +61 -7
  54. package/dist/Utils/segmentation/tools/SphereTool.js.map +1 -1
  55. package/dist/Utils/segmentation/tools/ToolHost.d.ts +74 -0
  56. package/dist/Utils/segmentation/tools/ToolHost.js +10 -0
  57. package/dist/Utils/segmentation/tools/ToolHost.js.map +1 -0
  58. package/dist/Utils/segmentation/tools/ZoomTool.d.ts +2 -6
  59. package/dist/Utils/segmentation/tools/ZoomTool.js.map +1 -1
  60. package/dist/Utils/segmentation/tools/index.d.ts +5 -7
  61. package/dist/Utils/segmentation/tools/index.js +6 -0
  62. package/dist/Utils/segmentation/tools/index.js.map +1 -1
  63. package/dist/Utils/workers/reformatSaveDataWorker.d.ts +1 -1
  64. package/dist/bundle.esm.js +2109 -2357
  65. package/dist/bundle.umd.js +2109 -2357
  66. package/dist/index.d.ts +3 -3
  67. package/dist/index.js +1 -1
  68. package/dist/types/Utils/segmentation/CanvasState.d.ts +31 -0
  69. package/dist/types/Utils/segmentation/DragOperator.d.ts +1 -1
  70. package/dist/types/Utils/segmentation/DrawToolCore.d.ts +49 -27
  71. package/dist/types/Utils/segmentation/NrrdTools.d.ts +89 -467
  72. package/dist/types/Utils/segmentation/RenderingUtils.d.ts +73 -0
  73. package/dist/types/Utils/segmentation/core/MaskVolume.d.ts +1 -1
  74. package/dist/types/Utils/segmentation/core/index.d.ts +1 -1
  75. package/dist/types/Utils/segmentation/core/types.d.ts +384 -0
  76. package/dist/types/Utils/segmentation/coreTools/GuiState.d.ts +1 -1
  77. package/dist/types/Utils/segmentation/coreTools/NrrdState.d.ts +1 -1
  78. package/dist/types/Utils/segmentation/coreTools/divControlTools.d.ts +1 -1
  79. package/dist/types/Utils/segmentation/coreTools/gui.d.ts +1 -1
  80. package/dist/types/Utils/segmentation/tools/BaseTool.d.ts +1 -1
  81. package/dist/types/Utils/segmentation/tools/ContrastTool.d.ts +3 -6
  82. package/dist/types/Utils/segmentation/tools/CrosshairTool.d.ts +11 -1
  83. package/dist/types/Utils/segmentation/tools/DataLoader.d.ts +33 -0
  84. package/dist/types/Utils/segmentation/tools/DragSliceTool.d.ts +3 -12
  85. package/dist/types/Utils/segmentation/tools/DrawingTool.d.ts +18 -26
  86. package/dist/types/Utils/segmentation/tools/ImageStoreHelper.d.ts +2 -5
  87. package/dist/types/Utils/segmentation/tools/LayerChannelManager.d.ts +52 -0
  88. package/dist/types/Utils/segmentation/tools/PanTool.d.ts +2 -8
  89. package/dist/types/Utils/segmentation/tools/SliceRenderPipeline.d.ts +80 -0
  90. package/dist/types/Utils/segmentation/tools/SphereTool.d.ts +25 -19
  91. package/dist/types/Utils/segmentation/tools/ToolHost.d.ts +74 -0
  92. package/dist/types/Utils/segmentation/tools/ZoomTool.d.ts +2 -6
  93. package/dist/types/Utils/segmentation/tools/index.d.ts +5 -7
  94. package/dist/types/Utils/workers/reformatSaveDataWorker.d.ts +1 -1
  95. package/dist/types/index.d.ts +3 -3
  96. package/package.json +1 -1
  97. package/dist/Utils/segmentation/CommToolsData.d.ts +0 -192
  98. package/dist/Utils/segmentation/CommToolsData.js +0 -474
  99. package/dist/Utils/segmentation/CommToolsData.js.map +0 -1
  100. package/dist/Utils/segmentation/coreTools/coreType.d.ts +0 -389
  101. package/dist/Utils/segmentation/coreTools/coreType.js +0 -3
  102. package/dist/Utils/segmentation/coreTools/coreType.js.map +0 -1
  103. package/dist/types/Utils/segmentation/CommToolsData.d.ts +0 -192
  104. package/dist/types/Utils/segmentation/coreTools/coreType.d.ts +0 -389
@@ -1,4 +1,4 @@
1
- import { CommToolsData } from "./CommToolsData";
1
+ import { RenderingUtils } from "./RenderingUtils";
2
2
  import { switchPencilIcon } from "../utils";
3
3
  import { EventRouter } from "./eventRouter";
4
4
  import { SphereTool } from "./tools/SphereTool";
@@ -10,10 +10,17 @@ import { PanTool } from "./tools/PanTool";
10
10
  import { DrawingTool } from "./tools/DrawingTool";
11
11
  import { ImageStoreHelper } from "./tools/ImageStoreHelper";
12
12
  import { UndoManager } from "./core";
13
- export class DrawToolCore extends CommToolsData {
14
- constructor(container, options) {
15
- const mainAreaContainer = document.createElement("div");
16
- super(container, mainAreaContainer, options);
13
+ /**
14
+ * DrawToolCore — Tool orchestration and event routing.
15
+ *
16
+ * Previously extended CommToolsData; now uses composition:
17
+ * - `state: CanvasState` — pure state container
18
+ * - `renderer: RenderingUtils` — rendering / slice-buffer helpers
19
+ *
20
+ * Created by NrrdTools which passes in a shared CanvasState.
21
+ */
22
+ export class DrawToolCore {
23
+ constructor(container, state) {
17
24
  this.drawingPrameters = {
18
25
  handleOnDrawingMouseDown: (ev) => { },
19
26
  handleOnDrawingMouseMove: (ev) => { },
@@ -39,20 +46,12 @@ export class DrawToolCore extends CommToolsData {
39
46
  this.eraserUrls = [];
40
47
  this.pencilUrls = [];
41
48
  this.undoManager = new UndoManager();
42
- // Centralized event router
43
- this.eventRouter = null;
44
49
  /** Slice index recorded when paintOnCanvas() starts, guards stale-click */
45
50
  this.paintSliceIndex = 0;
51
+ /** Wheel event dispatch mode — replaces manual wheel add/remove (Phase 2) */
52
+ this.activeWheelMode = 'zoom';
46
53
  // need to return to parent
47
54
  this.start = () => { };
48
- /*************************************May consider to move outside *******************************************/
49
- this.drawLine = (x1, y1, x2, y2) => {
50
- this.protectedData.ctxes.drawingCtx.beginPath();
51
- this.protectedData.ctxes.drawingCtx.moveTo(x1, y1);
52
- this.protectedData.ctxes.drawingCtx.lineTo(x2, y2);
53
- this.protectedData.ctxes.drawingCtx.strokeStyle = this.gui_states.drawing.color;
54
- this.protectedData.ctxes.drawingCtx.stroke();
55
- };
56
55
  this.configContrastDragMode = () => {
57
56
  this.contrastTool.configContrastDragMode();
58
57
  };
@@ -66,17 +65,19 @@ export class DrawToolCore extends CommToolsData {
66
65
  this.contrastTool.repraintCurrentContrastSlice();
67
66
  };
68
67
  this.container = container;
69
- this.mainAreaContainer = mainAreaContainer;
68
+ this.mainAreaContainer = state.protectedData.mainAreaContainer;
69
+ this.state = state;
70
+ this.renderer = new RenderingUtils(state);
70
71
  this.initTools();
71
72
  this.initDrawToolCore();
72
73
  }
73
74
  initTools() {
74
75
  const toolCtx = {
75
- nrrd_states: this.nrrd_states,
76
- gui_states: this.gui_states,
77
- protectedData: this.protectedData,
78
- cursorPage: this.cursorPage,
79
- callbacks: this.annotationCallbacks,
76
+ nrrd_states: this.state.nrrd_states,
77
+ gui_states: this.state.gui_states,
78
+ protectedData: this.state.protectedData,
79
+ cursorPage: this.state.cursorPage,
80
+ callbacks: this.state.annotationCallbacks,
80
81
  };
81
82
  this.imageStoreHelper = new ImageStoreHelper(toolCtx, {
82
83
  setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
@@ -85,6 +86,8 @@ export class DrawToolCore extends CommToolsData {
85
86
  this.sphereTool = new SphereTool(toolCtx, {
86
87
  setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
87
88
  drawImageOnEmptyImage: (canvas) => this.drawImageOnEmptyImage(canvas),
89
+ enableCrosshair: () => this.enableCrosshair(),
90
+ setUpSphereOrigins: (x, y, s) => this.setUpSphereOrigins(x, y, s),
88
91
  });
89
92
  this.crosshairTool = new CrosshairTool(toolCtx);
90
93
  this.contrastTool = new ContrastTool(toolCtx, this.container, this.contrastEventPrameters, {
@@ -102,10 +105,10 @@ export class DrawToolCore extends CommToolsData {
102
105
  });
103
106
  this.drawingTool = new DrawingTool(toolCtx, {
104
107
  setCurrentLayer: () => this.setCurrentLayer(),
105
- compositeAllLayers: () => this.compositeAllLayers(),
108
+ compositeAllLayers: () => this.renderer.compositeAllLayers(),
106
109
  syncLayerSliceData: (index, layer) => this.syncLayerSliceData(index, layer),
107
- filterDrawedImage: (axis, index) => this.filterDrawedImage(axis, index),
108
- getVolumeForLayer: (layer) => this.getVolumeForLayer(layer),
110
+ filterDrawedImage: (axis, index) => this.renderer.filterDrawedImage(axis, index),
111
+ getVolumeForLayer: (layer) => this.renderer.getVolumeForLayer(layer),
109
112
  pushUndoDelta: (delta) => this.undoManager.push(delta),
110
113
  getEraserUrls: () => this.eraserUrls,
111
114
  });
@@ -114,7 +117,7 @@ export class DrawToolCore extends CommToolsData {
114
117
  // Initialize EventRouter for centralized event handling
115
118
  this.eventRouter = new EventRouter({
116
119
  container: this.container,
117
- canvas: this.protectedData.canvases.drawingCanvas,
120
+ canvas: this.state.protectedData.canvases.drawingCanvas,
118
121
  onModeChange: (prevMode, newMode) => {
119
122
  // Use string comparison to avoid TypeScript narrowing issues
120
123
  const prev = prevMode;
@@ -124,56 +127,53 @@ export class DrawToolCore extends CommToolsData {
124
127
  }
125
128
  else if (prev === 'contrast') {
126
129
  this.removeContrastDragMode();
127
- this.gui_states.viewConfig.readyToUpdate = true;
130
+ this.state.gui_states.viewConfig.readyToUpdate = true;
128
131
  }
129
132
  if (next === 'crosshair') {
130
- this.protectedData.isDrawing = false;
133
+ this.state.protectedData.isDrawing = false;
131
134
  }
132
135
  }
133
136
  });
134
137
  // Inject eventRouter into ToolContext so tools can query mode/state
135
138
  const toolCtx = this.sphereTool['ctx'];
136
139
  toolCtx.eventRouter = this.eventRouter;
137
- // Configure keyboard settings from class field
138
- this.eventRouter.setKeyboardSettings(this._keyboardSettings);
140
+ // Configure keyboard settings from state
141
+ this.eventRouter.setKeyboardSettings(this.state.keyboardSettings);
139
142
  // Track undo flag for Ctrl+Z handling
140
143
  let undoFlag = false;
141
144
  // Register keyboard handlers with EventRouter
142
145
  this.eventRouter.setKeydownHandler((ev) => {
143
- var _a, _b, _c, _d;
144
- if (this._configKeyBoard)
146
+ if (this.state.configKeyBoard)
145
147
  return;
146
148
  // Handle undo (Ctrl+Z)
147
- if ((ev.ctrlKey || ev.metaKey) && ev.key === this._keyboardSettings.undo) {
149
+ if ((ev.ctrlKey || ev.metaKey) && ev.key === this.state.keyboardSettings.undo) {
148
150
  undoFlag = true;
149
151
  this.undoLastPainting();
150
152
  }
151
153
  // Handle redo (Ctrl+<redo key> or Ctrl+Shift+<undo key>)
152
- const redoKey = this._keyboardSettings.redo;
153
- const undoKeyUpper = this._keyboardSettings.undo.toUpperCase();
154
+ const redoKey = this.state.keyboardSettings.redo;
155
+ const undoKeyUpper = this.state.keyboardSettings.undo.toUpperCase();
154
156
  if ((ev.ctrlKey || ev.metaKey) && (ev.key === redoKey || (ev.shiftKey && ev.key === undoKeyUpper))) {
155
157
  this.redoLastPainting();
156
158
  }
157
159
  // Handle crosshair toggle (allowed in drawing tools AND sphere mode)
158
- if (ev.key === this._keyboardSettings.crosshair) {
159
- (_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.toggleCrosshair();
160
+ if (ev.key === this.state.keyboardSettings.crosshair) {
161
+ this.eventRouter.toggleCrosshair();
160
162
  }
161
163
  // Handle draw mode (Shift key) - EventRouter already tracks this
162
- // EventRouter's handleKeyDown will enforce mutual exclusion
163
- if (ev.key === this._keyboardSettings.draw && !this.gui_states.mode.sphere) {
164
- if ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCtrlHeld()) {
164
+ if (ev.key === this.state.keyboardSettings.draw && !this.state.gui_states.mode.sphere) {
165
+ if (this.eventRouter.isCtrlHeld()) {
165
166
  return; // Ctrl takes priority
166
167
  }
167
- // EventRouter will set mode to 'draw' via internal handler
168
168
  }
169
169
  // Handle sphere mode toggle
170
- if (ev.key === this._keyboardSettings.sphere) {
170
+ if (ev.key === this.state.keyboardSettings.sphere) {
171
171
  // Block during draw mode or contrast mode
172
- if (((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.isShiftHeld()) || ((_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.isCtrlHeld())) {
172
+ if (this.eventRouter.isShiftHeld() || this.eventRouter.isCtrlHeld()) {
173
173
  return;
174
174
  }
175
- this.gui_states.mode.sphere = !this.gui_states.mode.sphere;
176
- if (this.gui_states.mode.sphere) {
175
+ this.state.gui_states.mode.sphere = !this.state.gui_states.mode.sphere;
176
+ if (this.state.gui_states.mode.sphere) {
177
177
  this.enterSphereMode();
178
178
  }
179
179
  else {
@@ -182,33 +182,57 @@ export class DrawToolCore extends CommToolsData {
182
182
  }
183
183
  });
184
184
  this.eventRouter.setKeyupHandler((ev) => {
185
- var _a, _b, _c, _d, _e, _f;
186
- if (this._configKeyBoard)
185
+ if (this.state.configKeyBoard)
187
186
  return;
188
187
  // Handle Ctrl key release (contrast mode toggle)
189
- if (this._keyboardSettings.contrast.includes(ev.key)) {
188
+ if (this.state.keyboardSettings.contrast.includes(ev.key)) {
190
189
  if (undoFlag) {
191
- this.gui_states.viewConfig.readyToUpdate = true;
190
+ this.state.gui_states.viewConfig.readyToUpdate = true;
192
191
  undoFlag = false;
193
192
  return;
194
193
  }
195
194
  // Skip mode toggle when contrast shortcut is disabled
196
- if (!((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isContrastEnabled()))
195
+ if (!this.eventRouter.isContrastEnabled())
197
196
  return;
198
197
  // Block contrast toggle during crosshair, draw, or sphere (mutual exclusion)
199
- if (((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled()) || ((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.getMode()) === 'draw')
198
+ if (this.eventRouter.isCrosshairEnabled() || this.eventRouter.getMode() === 'draw')
200
199
  return;
201
- if (this.gui_states.mode.sphere)
200
+ if (this.state.gui_states.mode.sphere)
202
201
  return;
203
202
  // Toggle contrast mode manually since it's on keyup
204
- if (((_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.getMode()) !== 'contrast') {
205
- (_e = this.eventRouter) === null || _e === void 0 ? void 0 : _e.setMode('contrast');
203
+ if (this.eventRouter.getMode() !== 'contrast') {
204
+ this.eventRouter.setMode('contrast');
206
205
  }
207
206
  else {
208
- (_f = this.eventRouter) === null || _f === void 0 ? void 0 : _f.setMode('idle');
207
+ this.eventRouter.setMode('idle');
209
208
  }
210
209
  }
211
210
  });
211
+ // Register pointer handlers with EventRouter
212
+ this.eventRouter.setPointerMoveHandler((e) => {
213
+ if (this.drawingTool.isActive || this.panTool.isActive) {
214
+ this.drawingPrameters.handleOnDrawingMouseMove(e);
215
+ }
216
+ });
217
+ this.eventRouter.setPointerUpHandler((e) => {
218
+ if (this.drawingTool.isActive || this.drawingTool.painting
219
+ || this.panTool.isActive
220
+ || (this.state.gui_states.mode.sphere && this.eventRouter.getMode() !== 'crosshair')) {
221
+ this.drawingPrameters.handleOnDrawingMouseUp(e);
222
+ }
223
+ });
224
+ this.eventRouter.setPointerLeaveHandler((e) => {
225
+ this.handlePointerLeave();
226
+ });
227
+ // Register wheel handler with EventRouter
228
+ this.eventRouter.setWheelHandler((e) => {
229
+ if (this.activeWheelMode === 'zoom') {
230
+ this.drawingPrameters.handleMouseZoomSliceWheel(e);
231
+ }
232
+ else if (this.activeWheelMode === 'sphere') {
233
+ this.drawingPrameters.handleSphereWheel(e);
234
+ }
235
+ });
212
236
  // Bind all event listeners
213
237
  this.eventRouter.bindAll();
214
238
  }
@@ -217,245 +241,157 @@ export class DrawToolCore extends CommToolsData {
217
241
  }
218
242
  setPencilIconUrls(urls) {
219
243
  this.pencilUrls = urls;
220
- this.gui_states.viewConfig.defaultPaintCursor = switchPencilIcon("dot", this.pencilUrls);
221
- this.protectedData.canvases.drawingCanvas.style.cursor =
222
- this.gui_states.viewConfig.defaultPaintCursor;
244
+ this.state.gui_states.viewConfig.defaultPaintCursor = switchPencilIcon("dot", this.pencilUrls);
245
+ this.state.protectedData.canvases.drawingCanvas.style.cursor =
246
+ this.state.gui_states.viewConfig.defaultPaintCursor;
223
247
  }
224
248
  setCurrentLayer() {
225
- const layer = this.gui_states.layerChannel.layer;
226
- let target = this.protectedData.layerTargets.get(layer);
249
+ const layer = this.state.gui_states.layerChannel.layer;
250
+ let target = this.state.protectedData.layerTargets.get(layer);
227
251
  if (!target) {
228
- // Fallback to first layer
229
- const firstId = this.nrrd_states.image.layers[0];
230
- target = this.protectedData.layerTargets.get(firstId);
252
+ const firstId = this.state.nrrd_states.image.layers[0];
253
+ target = this.state.protectedData.layerTargets.get(firstId);
231
254
  }
232
255
  return { ctx: target.ctx, canvas: target.canvas };
233
256
  }
234
257
  draw(opts) {
235
258
  if (!!opts) {
236
- // Phase 2: Store callbacks in annotationCallbacks instead of nrrd_states
237
259
  if (opts.getMaskData) {
238
- this.annotationCallbacks.onMaskChanged = opts.getMaskData;
260
+ this.state.annotationCallbacks.onMaskChanged = opts.getMaskData;
239
261
  }
240
262
  if (opts.onClearLayerVolume) {
241
- this.annotationCallbacks.onLayerVolumeCleared = opts.onClearLayerVolume;
263
+ this.state.annotationCallbacks.onLayerVolumeCleared = opts.onClearLayerVolume;
242
264
  }
243
265
  if (opts.getSphereData) {
244
- this.annotationCallbacks.onSphereChanged = opts.getSphereData;
266
+ this.state.annotationCallbacks.onSphereChanged = opts.getSphereData;
245
267
  }
246
268
  if (opts.getCalculateSpherePositionsData) {
247
- this.annotationCallbacks.onCalculatorPositionsChanged = opts.getCalculateSpherePositionsData;
269
+ this.state.annotationCallbacks.onCalculatorPositionsChanged = opts.getCalculateSpherePositionsData;
248
270
  }
249
271
  }
250
272
  this.paintOnCanvas();
251
273
  }
252
274
  zoomActionAfterDrawSphere() {
253
- this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
254
- }
255
- clearSpherePrintStoreImages() {
256
- this.sphereTool.clearSpherePrintStoreImages();
275
+ this.activeWheelMode = 'zoom';
257
276
  }
258
277
  paintOnCanvas() {
259
278
  var _a, _b, _c;
260
279
  // Initialize tools for this paint cycle
261
280
  this.drawingTool.reset(this.useEraser());
262
281
  this.panTool.reset();
263
- this.paintSliceIndex = this.protectedData.mainPreSlices.index;
282
+ this.paintSliceIndex = this.state.protectedData.mainPreSlices.index;
264
283
  this.updateOriginAndChangedWH();
265
284
  this.initAllCanvas();
266
- (_a = this.protectedData.ctxes.displayCtx) === null || _a === void 0 ? void 0 : _a.save();
285
+ (_a = this.state.protectedData.ctxes.displayCtx) === null || _a === void 0 ? void 0 : _a.save();
267
286
  this.flipDisplayImageByAxis();
268
- (_b = this.protectedData.ctxes.displayCtx) === null || _b === void 0 ? void 0 : _b.drawImage(this.protectedData.canvases.originCanvas, 0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
269
- (_c = this.protectedData.ctxes.displayCtx) === null || _c === void 0 ? void 0 : _c.restore();
270
- // let a global variable to store the wheel move event
271
- // Remove existing listener before creating a new one to prevent leaks
272
- this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
273
- if (this._keyboardSettings.mouseWheel === "Scroll:Zoom") {
287
+ (_b = this.state.protectedData.ctxes.displayCtx) === null || _b === void 0 ? void 0 : _b.drawImage(this.state.protectedData.canvases.originCanvas, 0, 0, this.state.nrrd_states.view.changedWidth, this.state.nrrd_states.view.changedHeight);
288
+ (_c = this.state.protectedData.ctxes.displayCtx) === null || _c === void 0 ? void 0 : _c.restore();
289
+ // Configure wheel handler functions
290
+ if (this.state.keyboardSettings.mouseWheel === "Scroll:Zoom") {
274
291
  this.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseZoomWheel();
275
292
  }
276
293
  else {
277
294
  this.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseSliceWheel();
278
295
  }
279
- // Keep wheel as direct addEventListener due to dynamic add/remove patterns in handleOnDrawingMouseUp
280
- // EventRouter routing would conflict with the dynamic wheel switching (zoom vs sphere wheel)
281
- this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel, {
282
- passive: false,
283
- });
284
296
  // sphere Wheel
285
297
  this.drawingPrameters.handleSphereWheel = this.configMouseSphereWheel();
286
- // pan move — now handled by PanTool (listeners managed internally)
287
- // brush circle move
288
- this.drawingPrameters.handleOnDrawingBrushCricleMove = (e) => {
289
- e.preventDefault();
290
- this.nrrd_states.interaction.mouseOverX = e.offsetX;
291
- this.nrrd_states.interaction.mouseOverY = e.offsetY;
292
- if (this.nrrd_states.interaction.mouseOverX === undefined) {
293
- this.nrrd_states.interaction.mouseOverX = e.clientX;
294
- this.nrrd_states.interaction.mouseOverY = e.clientY;
295
- }
296
- if (e.type === "mouseout") {
297
- this.nrrd_states.interaction.mouseOver = false;
298
- this.protectedData.canvases.drawingCanvas.removeEventListener("mousemove", this.drawingPrameters.handleOnDrawingBrushCricleMove);
299
- }
300
- else if (e.type === "mouseover") {
301
- this.nrrd_states.interaction.mouseOver = true;
302
- this.protectedData.canvases.drawingCanvas.addEventListener("mousemove", this.drawingPrameters.handleOnDrawingBrushCricleMove);
303
- }
304
- };
298
+ // brush circle move — delegated to DrawingTool
299
+ this.drawingPrameters.handleOnDrawingBrushCricleMove =
300
+ this.drawingTool.createBrushTrackingHandler();
305
301
  // drawing move — delegated to DrawingTool
306
302
  this.drawingPrameters.handleOnDrawingMouseMove = (e) => {
307
303
  this.drawingTool.onPointerMove(e);
308
304
  };
309
305
  this.drawingPrameters.handleOnDrawingMouseDown = (e) => {
310
- var _a, _b, _c;
311
306
  if (this.drawingTool.isActive || this.panTool.isActive) {
312
- this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
313
- this.protectedData.ctxes.drawingLayerMasterCtx.closePath();
307
+ this.state.protectedData.ctxes.drawingLayerMasterCtx.closePath();
314
308
  return;
315
309
  }
316
- if (this.paintSliceIndex !== this.protectedData.mainPreSlices.index) {
317
- this.paintSliceIndex = this.protectedData.mainPreSlices.index;
310
+ if (this.paintSliceIndex !== this.state.protectedData.mainPreSlices.index) {
311
+ this.paintSliceIndex = this.state.protectedData.mainPreSlices.index;
312
+ }
313
+ // Suppress wheel only when starting a draw operation
314
+ if (this.eventRouter.getMode() === 'draw') {
315
+ this.activeWheelMode = 'none';
318
316
  }
319
- // remove it when mouse click down
320
- this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
321
317
  if (e.button === 0) {
322
- if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode()) === 'draw') {
318
+ if (this.eventRouter.getMode() === 'draw') {
323
319
  this.drawingTool.onPointerDown(e);
324
- this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
325
- this.protectedData.canvases.drawingCanvas.addEventListener("pointermove", this.drawingPrameters.handleOnDrawingMouseMove);
326
320
  }
327
- else if ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled()) {
328
- this.nrrd_states.interaction.cursorPageX =
329
- e.offsetX / this.nrrd_states.view.sizeFactor;
330
- this.nrrd_states.interaction.cursorPageY =
331
- e.offsetY / this.nrrd_states.view.sizeFactor;
321
+ else if (this.eventRouter.isCrosshairEnabled()) {
322
+ this.state.nrrd_states.interaction.cursorPageX =
323
+ e.offsetX / this.state.nrrd_states.view.sizeFactor;
324
+ this.state.nrrd_states.interaction.cursorPageY =
325
+ e.offsetY / this.state.nrrd_states.view.sizeFactor;
332
326
  this.enableCrosshair();
333
- this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
334
327
  }
335
- else if (this.gui_states.mode.sphere && !((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.isCrosshairEnabled())) {
328
+ else if (this.state.gui_states.mode.sphere && !this.eventRouter.isCrosshairEnabled()) {
336
329
  this.handleSphereClick(e);
337
330
  }
338
331
  }
339
332
  else if (e.button === 2) {
340
333
  this.panTool.onPointerDown(e);
341
- this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
342
334
  }
343
335
  else {
344
336
  return;
345
337
  }
346
338
  };
347
- // Route pointerdown through EventRouter for centralized event management
348
- // The handler is still the existing logic, just registered through EventRouter
349
- if (this.eventRouter) {
350
- this.eventRouter.setPointerDownHandler((e) => {
351
- this.drawingPrameters.handleOnDrawingMouseDown(e);
352
- });
353
- }
354
- else {
355
- // Fallback for legacy mode without EventRouter
356
- this.protectedData.canvases.drawingCanvas.addEventListener("pointerdown", this.drawingPrameters.handleOnDrawingMouseDown, true);
357
- }
339
+ // Route pointerdown through EventRouter
340
+ this.eventRouter.setPointerDownHandler((e) => {
341
+ this.drawingPrameters.handleOnDrawingMouseDown(e);
342
+ });
358
343
  this.drawingPrameters.handleOnDrawingMouseUp = (e) => {
359
- var _a, _b, _c;
360
344
  if (e.button === 0) {
361
- if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode()) === 'draw' || this.drawingTool.painting) {
345
+ if (this.eventRouter.getMode() === 'draw' || this.drawingTool.painting) {
362
346
  this.drawingTool.onPointerUp(e);
363
- this.protectedData.canvases.drawingCanvas.removeEventListener("pointermove", this.drawingPrameters.handleOnDrawingMouseMove);
364
- // add wheel after pointer up
365
- this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel, {
366
- passive: false,
367
- });
347
+ this.activeWheelMode = 'zoom';
368
348
  }
369
- else if (this.gui_states.mode.sphere &&
370
- !((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled())) {
371
- // Write all placed calculator spheres to volume
372
- this.sphereTool.writeAllCalculatorSpheresToVolume();
373
- // Render current slice from volume to sphere canvas
374
- this.sphereTool.refreshSphereCanvas();
375
- this.annotationCallbacks.onSphereChanged(this.nrrd_states.sphere.sphereOrigin.z, this.nrrd_states.sphere.sphereRadius / this.nrrd_states.view.sizeFactor);
376
- this.annotationCallbacks.onCalculatorPositionsChanged(this.nrrd_states.sphere.tumourSphereOrigin, this.nrrd_states.sphere.skinSphereOrigin, this.nrrd_states.sphere.ribSphereOrigin, this.nrrd_states.sphere.nippleSphereOrigin, this.protectedData.axis);
377
- this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleSphereWheel, true);
378
- this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
379
- this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
349
+ else if (this.state.gui_states.mode.sphere &&
350
+ !this.eventRouter.isCrosshairEnabled()) {
351
+ this.sphereTool.onSpherePointerUp();
352
+ this.activeWheelMode = 'zoom';
380
353
  this.zoomActionAfterDrawSphere();
381
354
  }
382
- else if (this.gui_states.mode.sphere &&
383
- ((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.isCrosshairEnabled())) {
384
- this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
385
- this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
355
+ else if (this.state.gui_states.mode.sphere &&
356
+ this.eventRouter.isCrosshairEnabled()) {
357
+ this.activeWheelMode = 'zoom';
386
358
  }
387
359
  }
388
360
  else if (e.button === 2) {
389
- this.panTool.onPointerUp(e, this.gui_states.viewConfig.defaultPaintCursor);
361
+ this.panTool.onPointerUp(e, this.state.gui_states.viewConfig.defaultPaintCursor);
390
362
  }
391
363
  else {
392
364
  return;
393
365
  }
394
- if (!this.gui_states.mode.pencil) {
366
+ if (!this.state.gui_states.mode.pencil) {
395
367
  this.setIsDrawFalse(100);
396
368
  }
397
369
  };
398
- this.protectedData.canvases.drawingCanvas.addEventListener("pointerleave", () => {
399
- const wasDrawing = this.drawingTool.onPointerLeave();
400
- if (wasDrawing) {
401
- this.protectedData.canvases.drawingCanvas.removeEventListener("pointermove", this.drawingPrameters.handleOnDrawingMouseMove);
402
- this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleSphereWheel, true);
403
- }
404
- this.panTool.onPointerLeave();
405
- this.setIsDrawFalse(100);
406
- if (this.gui_states.mode.pencil) {
407
- this.setIsDrawFalse(1000);
408
- }
409
- });
410
370
  this.start = () => {
411
- var _a, _b;
412
- if (this.gui_states.viewConfig.readyToUpdate) {
413
- this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
414
- this.protectedData.ctxes.drawingCtx.globalAlpha =
415
- this.gui_states.drawing.globalAlpha;
416
- if (this.protectedData.isDrawing) {
417
- this.protectedData.ctxes.drawingLayerMasterCtx.lineCap = "round";
418
- this.protectedData.ctxes.drawingLayerMasterCtx.globalAlpha = 1;
419
- for (const [, target] of this.protectedData.layerTargets) {
371
+ if (this.state.gui_states.viewConfig.readyToUpdate) {
372
+ this.state.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.state.nrrd_states.view.changedWidth, this.state.nrrd_states.view.changedHeight);
373
+ this.state.protectedData.ctxes.drawingCtx.globalAlpha =
374
+ this.state.gui_states.drawing.globalAlpha;
375
+ if (this.state.protectedData.isDrawing) {
376
+ this.state.protectedData.ctxes.drawingLayerMasterCtx.lineCap = "round";
377
+ this.state.protectedData.ctxes.drawingLayerMasterCtx.globalAlpha = 1;
378
+ for (const [, target] of this.state.protectedData.layerTargets) {
420
379
  target.ctx.lineCap = "round";
421
380
  target.ctx.globalAlpha = 1;
422
381
  }
423
382
  }
424
383
  else {
425
- // Use EventRouter mode for mutually exclusive crosshair vs draw rendering
426
- const currentMode = (_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode();
384
+ const currentMode = this.eventRouter.getMode();
427
385
  if (currentMode === 'draw') {
428
- // Draw mode: show brush circle preview
429
- if (!this.gui_states.mode.pencil &&
430
- !this.gui_states.mode.eraser &&
431
- this.nrrd_states.interaction.mouseOver) {
432
- this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
433
- this.protectedData.ctxes.drawingCtx.fillStyle =
434
- this.gui_states.drawing.brushColor;
435
- this.protectedData.ctxes.drawingCtx.beginPath();
436
- this.protectedData.ctxes.drawingCtx.arc(this.nrrd_states.interaction.mouseOverX, this.nrrd_states.interaction.mouseOverY, this.gui_states.drawing.brushAndEraserSize / 2 + 1, 0, Math.PI * 2);
437
- this.protectedData.ctxes.drawingCtx.strokeStyle =
438
- this.gui_states.drawing.brushColor;
439
- this.protectedData.ctxes.drawingCtx.stroke();
440
- }
386
+ this.drawingTool.renderBrushPreview(this.state.protectedData.ctxes.drawingCtx, this.state.nrrd_states.view.changedWidth, this.state.nrrd_states.view.changedHeight);
441
387
  }
442
- else if (currentMode === 'crosshair' || ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled())) {
443
- // Crosshair mode: show red cross lines (mutually exclusive with draw)
444
- this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
445
- const ex = this.nrrd_states.interaction.cursorPageX * this.nrrd_states.view.sizeFactor;
446
- const ey = this.nrrd_states.interaction.cursorPageY * this.nrrd_states.view.sizeFactor;
447
- this.drawLine(ex, 0, ex, this.nrrd_states.view.changedWidth);
448
- this.drawLine(0, ey, this.nrrd_states.view.changedHeight, ey);
388
+ else if (currentMode === 'crosshair' || this.eventRouter.isCrosshairEnabled()) {
389
+ this.crosshairTool.renderCrosshair(this.state.protectedData.ctxes.drawingCtx, this.state.nrrd_states.view.changedWidth, this.state.nrrd_states.view.changedHeight);
449
390
  }
450
391
  }
451
- // globalAlpha was set to gui_states.drawing.globalAlpha at the top of start().
452
- // Master stores full-alpha pixels; transparency applied here only.
453
- this.protectedData.ctxes.drawingCtx.drawImage(this.protectedData.canvases.drawingCanvasLayerMaster, 0, 0);
454
- // Draw sphere overlay from cached sphere canvas (separate from master).
455
- // During preview: drawSphere/drawCalculatorSphere update the sphere canvas.
456
- // After placement: refreshSphereCanvas renders from sphereMaskVolume.
457
- if (this.gui_states.mode.sphere) {
458
- this.protectedData.ctxes.drawingCtx.drawImage(this.protectedData.canvases.drawingSphereCanvas, 0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
392
+ this.state.protectedData.ctxes.drawingCtx.drawImage(this.state.protectedData.canvases.drawingCanvasLayerMaster, 0, 0);
393
+ if (this.state.gui_states.mode.sphere) {
394
+ this.state.protectedData.ctxes.drawingCtx.drawImage(this.state.protectedData.canvases.drawingSphereCanvas, 0, 0, this.state.nrrd_states.view.changedWidth, this.state.nrrd_states.view.changedHeight);
459
395
  }
460
396
  }
461
397
  else {
@@ -463,87 +399,67 @@ export class DrawToolCore extends CommToolsData {
463
399
  }
464
400
  };
465
401
  }
402
+ /**
403
+ * Extracted from paintOnCanvas() pointerleave handler.
404
+ */
405
+ handlePointerLeave() {
406
+ const wasDrawing = this.drawingTool.onPointerLeave();
407
+ if (wasDrawing) {
408
+ this.activeWheelMode = 'zoom';
409
+ }
410
+ this.panTool.onPointerLeave();
411
+ this.setIsDrawFalse(100);
412
+ if (this.state.gui_states.mode.pencil) {
413
+ this.setIsDrawFalse(1000);
414
+ }
415
+ }
466
416
  /** Extracted from paintOnCanvas() — handles sphere placement on left-click */
467
417
  handleSphereClick(e) {
468
- this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
469
- let mouseX = e.offsetX / this.nrrd_states.view.sizeFactor;
470
- let mouseY = e.offsetY / this.nrrd_states.view.sizeFactor;
471
- // record mouseX,Y, and enable crosshair function
472
- this.nrrd_states.sphere.sphereOrigin[this.protectedData.axis] = [
473
- mouseX,
474
- mouseY,
475
- this.nrrd_states.view.currentSliceIndex,
476
- ];
477
- this.setUpSphereOrigins(mouseX, mouseY, this.nrrd_states.view.currentSliceIndex);
478
- // Store origin for the active sphere type
479
- const calPos = this.gui_states.mode.activeSphereType;
480
- switch (calPos) {
481
- case "tumour":
482
- this.nrrd_states.sphere.tumourSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphere.sphereOrigin));
483
- break;
484
- case "skin":
485
- this.nrrd_states.sphere.skinSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphere.sphereOrigin));
486
- break;
487
- case "nipple":
488
- this.nrrd_states.sphere.nippleSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphere.sphereOrigin));
489
- break;
490
- case "ribcage":
491
- this.nrrd_states.sphere.ribSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphere.sphereOrigin));
492
- break;
493
- }
494
- this.nrrd_states.interaction.cursorPageX = mouseX;
495
- this.nrrd_states.interaction.cursorPageY = mouseY;
496
- this.enableCrosshair();
497
- // draw circle setup width/height for sphere canvas
498
- this.drawCalculatorSphere(this.nrrd_states.sphere.sphereRadius);
499
- this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleSphereWheel, true);
500
- this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
418
+ this.activeWheelMode = 'sphere';
419
+ this.sphereTool.onSphereClick(e);
501
420
  }
502
421
  initAllCanvas() {
503
422
  /**
504
423
  * display canvas
505
424
  */
506
- this.protectedData.canvases.displayCanvas.style.position = "absolute";
507
- this.protectedData.canvases.displayCanvas.style.zIndex = "9";
508
- this.protectedData.canvases.displayCanvas.width =
509
- this.nrrd_states.view.changedWidth;
510
- this.protectedData.canvases.displayCanvas.height =
511
- this.nrrd_states.view.changedHeight;
425
+ this.state.protectedData.canvases.displayCanvas.style.position = "absolute";
426
+ this.state.protectedData.canvases.displayCanvas.style.zIndex = "9";
427
+ this.state.protectedData.canvases.displayCanvas.width =
428
+ this.state.nrrd_states.view.changedWidth;
429
+ this.state.protectedData.canvases.displayCanvas.height =
430
+ this.state.nrrd_states.view.changedHeight;
512
431
  /**
513
432
  * drawing canvas
514
433
  */
515
- this.protectedData.canvases.drawingCanvas.style.zIndex = "10";
516
- this.protectedData.canvases.drawingCanvas.style.position = "absolute";
517
- this.protectedData.canvases.drawingCanvas.width =
518
- this.nrrd_states.view.changedWidth;
519
- this.protectedData.canvases.drawingCanvas.height =
520
- this.nrrd_states.view.changedHeight;
521
- this.protectedData.canvases.drawingCanvas.style.cursor =
522
- this.gui_states.viewConfig.defaultPaintCursor;
523
- this.protectedData.canvases.drawingCanvas.oncontextmenu = () => false;
434
+ this.state.protectedData.canvases.drawingCanvas.style.zIndex = "10";
435
+ this.state.protectedData.canvases.drawingCanvas.style.position = "absolute";
436
+ this.state.protectedData.canvases.drawingCanvas.width =
437
+ this.state.nrrd_states.view.changedWidth;
438
+ this.state.protectedData.canvases.drawingCanvas.height =
439
+ this.state.nrrd_states.view.changedHeight;
440
+ this.state.protectedData.canvases.drawingCanvas.style.cursor =
441
+ this.state.gui_states.viewConfig.defaultPaintCursor;
442
+ this.state.protectedData.canvases.drawingCanvas.oncontextmenu = () => false;
524
443
  /**
525
- * layer1
526
- * it should be hide, so we don't need to add it to mainAreaContainer
444
+ * layer canvases
527
445
  */
528
- this.protectedData.canvases.drawingCanvasLayerMaster.width =
529
- this.nrrd_states.view.changedWidth;
530
- this.protectedData.canvases.drawingCanvasLayerMaster.height =
531
- this.nrrd_states.view.changedHeight;
532
- for (const [, target] of this.protectedData.layerTargets) {
533
- target.canvas.width = this.nrrd_states.view.changedWidth;
534
- target.canvas.height = this.nrrd_states.view.changedHeight;
446
+ this.state.protectedData.canvases.drawingCanvasLayerMaster.width =
447
+ this.state.nrrd_states.view.changedWidth;
448
+ this.state.protectedData.canvases.drawingCanvasLayerMaster.height =
449
+ this.state.nrrd_states.view.changedHeight;
450
+ for (const [, target] of this.state.protectedData.layerTargets) {
451
+ target.canvas.width = this.state.nrrd_states.view.changedWidth;
452
+ target.canvas.height = this.state.nrrd_states.view.changedHeight;
535
453
  }
536
454
  /**
537
455
  * display and drawing canvas container
538
456
  */
539
- // this.mainAreaContainer.style.width = this.nrrd_states.view.changedWidth + "px";
540
- // this.mainAreaContainer.style.height = this.nrrd_states.view.changedHeight + "px";
541
457
  this.mainAreaContainer.style.width =
542
- this.nrrd_states.image.originWidth * 8 + "px";
458
+ this.state.nrrd_states.image.originWidth * 8 + "px";
543
459
  this.mainAreaContainer.style.height =
544
- this.nrrd_states.image.originHeight * 8 + "px";
545
- this.mainAreaContainer.appendChild(this.protectedData.canvases.displayCanvas);
546
- this.mainAreaContainer.appendChild(this.protectedData.canvases.drawingCanvas);
460
+ this.state.nrrd_states.image.originHeight * 8 + "px";
461
+ this.mainAreaContainer.appendChild(this.state.protectedData.canvases.displayCanvas);
462
+ this.mainAreaContainer.appendChild(this.state.protectedData.canvases.drawingCanvas);
547
463
  }
548
464
  useEraser() {
549
465
  return this.eraserTool.createClearArc();
@@ -563,13 +479,9 @@ export class DrawToolCore extends CommToolsData {
563
479
  this.crosshairTool.enableCrosshair();
564
480
  }
565
481
  drawImageOnEmptyImage(canvas) {
566
- const ctx = this.protectedData.ctxes.emptyCtx;
567
- const w = this.protectedData.canvases.emptyCanvas.width;
568
- const h = this.protectedData.canvases.emptyCanvas.height;
569
- // No flip needed: the layer canvas screen coordinates already match the
570
- // Three.js source coordinate system used by MaskVolume. Applying a flip
571
- // here would invert cross-axis slice indices (e.g. coronal slice 220
572
- // becoming 228 when total=448).
482
+ const ctx = this.state.protectedData.ctxes.emptyCtx;
483
+ const w = this.state.protectedData.canvases.emptyCanvas.width;
484
+ const h = this.state.protectedData.canvases.emptyCanvas.height;
573
485
  ctx.imageSmoothingEnabled = false;
574
486
  ctx.drawImage(canvas, 0, 0, w, h);
575
487
  }
@@ -577,9 +489,6 @@ export class DrawToolCore extends CommToolsData {
577
489
  drawCalculatorSphereOnEachViews(axis) {
578
490
  this.sphereTool.drawCalculatorSphereOnEachViews(axis);
579
491
  }
580
- drawSphereOnEachViews(decay, axis) {
581
- this.sphereTool.drawSphereOnEachViews(decay, axis);
582
- }
583
492
  configMouseSphereWheel() {
584
493
  return this.sphereTool.configMouseSphereWheel();
585
494
  }
@@ -591,10 +500,9 @@ export class DrawToolCore extends CommToolsData {
591
500
  }
592
501
  /**
593
502
  * Refresh sphere canvas from sphereMaskVolume for the current slice/axis.
594
- * Called after view changes (slice switch, zoom, contrast, axis switch).
595
503
  */
596
504
  refreshSphereOverlay() {
597
- if (this.gui_states.mode.sphere) {
505
+ if (this.state.gui_states.mode.sphere) {
598
506
  this.sphereTool.refreshSphereCanvas();
599
507
  }
600
508
  }
@@ -610,42 +518,30 @@ export class DrawToolCore extends CommToolsData {
610
518
  }
611
519
  /****************************layer div controls****************************************************/
612
520
  getRestLayer() {
613
- const layers = this.nrrd_states.image.layers;
521
+ const layers = this.state.nrrd_states.image.layers;
614
522
  const restLayer = layers.filter((item) => {
615
- return item !== this.gui_states.layerChannel.layer;
523
+ return item !== this.state.gui_states.layerChannel.layer;
616
524
  });
617
525
  return restLayer;
618
526
  }
619
527
  /**************************** Undo/Redo functions (Phase 6 — Delta-based) ****************************/
620
528
  /**
621
529
  * Clear the mask on the current slice canvas for the active layer ONLY.
622
- *
623
- * This method only clears the active layer's MaskVolume slice data for the
624
- * currently viewed slice index. Other slices in the same layer remain intact.
625
- * Other background layers are also left untouched.
626
- * After clearing, all layer canvases are re-rendered from the `MaskVolume`
627
- * to keep the visuals in sync.
628
- *
629
- * The operation is recorded to the UndoManager to allow for user rollback.
630
530
  */
631
531
  clearActiveSlice() {
632
- this.protectedData.isDrawing = true;
633
- this.protectedData.canvases.originCanvas.width =
634
- this.protectedData.canvases.originCanvas.width;
635
- this.protectedData.mainPreSlices.repaint.call(this.protectedData.mainPreSlices);
532
+ this.state.protectedData.isDrawing = true;
533
+ this.state.protectedData.canvases.originCanvas.width =
534
+ this.state.protectedData.canvases.originCanvas.width;
535
+ this.state.protectedData.mainPreSlices.repaint.call(this.state.protectedData.mainPreSlices);
636
536
  // Clear only the active layer's MaskVolume slice and record undo delta
637
537
  try {
638
- const axis = this.protectedData.axis;
639
- const idx = this.nrrd_states.view.currentSliceIndex;
640
- const activeLayer = this.gui_states.layerChannel.layer;
641
- const vol = this.getVolumeForLayer(activeLayer);
642
- // Capture old slice for undo before clearing
538
+ const axis = this.state.protectedData.axis;
539
+ const idx = this.state.nrrd_states.view.currentSliceIndex;
540
+ const activeLayer = this.state.gui_states.layerChannel.layer;
541
+ const vol = this.renderer.getVolumeForLayer(activeLayer);
643
542
  const oldSlice = vol.getSliceUint8(idx, axis).data.slice();
644
- // Clear only the active layer's MaskVolume slice
645
543
  vol.clearSlice(idx, axis);
646
- // New (all-zero) slice for undo newSlice
647
544
  const { data: newSlice, width, height } = vol.getSliceUint8(idx, axis);
648
- // Push clearActiveSlice delta to UndoManager (supports undo)
649
545
  this.undoManager.push({
650
546
  layerId: activeLayer,
651
547
  axis,
@@ -653,106 +549,96 @@ export class DrawToolCore extends CommToolsData {
653
549
  oldSlice,
654
550
  newSlice: newSlice.slice(),
655
551
  });
656
- // Notify external that slice was cleared
657
- if (!this.nrrd_states.flags.loadingMaskData && !this.gui_states.mode.sphere) {
658
- const activeChannel = this.gui_states.layerChannel.activeChannel || 1;
659
- this.annotationCallbacks.onMaskChanged(newSlice, activeLayer, activeChannel, idx, axis, width, height, true // clearFlag = true
660
- );
552
+ if (!this.state.nrrd_states.flags.loadingMaskData && !this.state.gui_states.mode.sphere) {
553
+ const activeChannel = this.state.gui_states.layerChannel.activeChannel || 1;
554
+ this.state.annotationCallbacks.onMaskChanged(newSlice, activeLayer, activeChannel, idx, axis, width, height, true);
661
555
  }
662
556
  }
663
557
  catch (_a) {
664
558
  // Volume not ready (1×1×1 placeholder)
665
559
  }
666
- // Re-render ALL layers from MaskVolume to canvas (rebuilds visuals from source of truth)
667
560
  this.resetLayerCanvas();
668
- const buffer = this.getOrCreateSliceBuffer(this.protectedData.axis);
561
+ const buffer = this.renderer.getOrCreateSliceBuffer(this.state.protectedData.axis);
669
562
  if (buffer) {
670
- const w = this.nrrd_states.view.changedWidth;
671
- const h = this.nrrd_states.view.changedHeight;
672
- for (const layerId of this.nrrd_states.image.layers) {
673
- const target = this.protectedData.layerTargets.get(layerId);
563
+ const w = this.state.nrrd_states.view.changedWidth;
564
+ const h = this.state.nrrd_states.view.changedHeight;
565
+ for (const layerId of this.state.nrrd_states.image.layers) {
566
+ const target = this.state.protectedData.layerTargets.get(layerId);
674
567
  if (!target)
675
568
  continue;
676
569
  target.ctx.clearRect(0, 0, w, h);
677
- this.renderSliceToCanvas(layerId, this.protectedData.axis, this.nrrd_states.view.currentSliceIndex, buffer, target.ctx, w, h);
570
+ this.renderer.renderSliceToCanvas(layerId, this.state.protectedData.axis, this.state.nrrd_states.view.currentSliceIndex, buffer, target.ctx, w, h);
678
571
  }
679
572
  }
680
- this.compositeAllLayers();
573
+ this.renderer.compositeAllLayers();
681
574
  this.setIsDrawFalse(1000);
682
575
  }
683
576
  /**
684
577
  * Undo the last drawing operation on the active layer.
685
- * Restores the MaskVolume slice to its pre-draw state, re-renders
686
- * the canvas, and notifies the backend.
687
578
  */
688
579
  undoLastPainting() {
689
580
  const delta = this.undoManager.undo();
690
581
  if (!delta)
691
582
  return;
692
583
  try {
693
- const vol = this.getVolumeForLayer(delta.layerId);
584
+ const vol = this.renderer.getVolumeForLayer(delta.layerId);
694
585
  vol.setSliceUint8(delta.sliceIndex, delta.oldSlice, delta.axis);
695
586
  }
696
587
  catch (_a) {
697
- return; // Volume not ready
588
+ return;
698
589
  }
699
- this.protectedData.isDrawing = true;
700
- if (delta.axis === this.protectedData.axis && delta.sliceIndex === this.nrrd_states.view.currentSliceIndex) {
590
+ this.state.protectedData.isDrawing = true;
591
+ if (delta.axis === this.state.protectedData.axis && delta.sliceIndex === this.state.nrrd_states.view.currentSliceIndex) {
701
592
  this.applyUndoRedoToCanvas(delta.layerId);
702
593
  }
703
- if (!this.nrrd_states.flags.loadingMaskData) {
704
- const { data: sliceData, width, height } = this.getVolumeForLayer(delta.layerId)
594
+ if (!this.state.nrrd_states.flags.loadingMaskData) {
595
+ const { data: sliceData, width, height } = this.renderer.getVolumeForLayer(delta.layerId)
705
596
  .getSliceUint8(delta.sliceIndex, delta.axis);
706
- this.annotationCallbacks.onMaskChanged(sliceData, delta.layerId, this.gui_states.layerChannel.activeChannel || 1, delta.sliceIndex, delta.axis, width, height, false);
597
+ this.state.annotationCallbacks.onMaskChanged(sliceData, delta.layerId, this.state.gui_states.layerChannel.activeChannel || 1, delta.sliceIndex, delta.axis, width, height, false);
707
598
  }
708
599
  this.setIsDrawFalse(1000);
709
600
  }
710
601
  /**
711
602
  * Redo the last undone operation on the active layer.
712
- * Reapplies the MaskVolume slice to its post-draw state, re-renders
713
- * the canvas, and notifies the backend.
714
603
  */
715
604
  redoLastPainting() {
716
605
  const delta = this.undoManager.redo();
717
606
  if (!delta)
718
607
  return;
719
608
  try {
720
- const vol = this.getVolumeForLayer(delta.layerId);
609
+ const vol = this.renderer.getVolumeForLayer(delta.layerId);
721
610
  vol.setSliceUint8(delta.sliceIndex, delta.newSlice, delta.axis);
722
611
  }
723
612
  catch (_a) {
724
- return; // Volume not ready
613
+ return;
725
614
  }
726
- this.protectedData.isDrawing = true;
727
- if (delta.axis === this.protectedData.axis && delta.sliceIndex === this.nrrd_states.view.currentSliceIndex) {
615
+ this.state.protectedData.isDrawing = true;
616
+ if (delta.axis === this.state.protectedData.axis && delta.sliceIndex === this.state.nrrd_states.view.currentSliceIndex) {
728
617
  this.applyUndoRedoToCanvas(delta.layerId);
729
618
  }
730
- if (!this.nrrd_states.flags.loadingMaskData) {
731
- const { data: sliceData, width, height } = this.getVolumeForLayer(delta.layerId)
619
+ if (!this.state.nrrd_states.flags.loadingMaskData) {
620
+ const { data: sliceData, width, height } = this.renderer.getVolumeForLayer(delta.layerId)
732
621
  .getSliceUint8(delta.sliceIndex, delta.axis);
733
- this.annotationCallbacks.onMaskChanged(sliceData, delta.layerId, this.gui_states.layerChannel.activeChannel || 1, delta.sliceIndex, delta.axis, width, height, false);
622
+ this.state.annotationCallbacks.onMaskChanged(sliceData, delta.layerId, this.state.gui_states.layerChannel.activeChannel || 1, delta.sliceIndex, delta.axis, width, height, false);
734
623
  }
735
624
  this.setIsDrawFalse(1000);
736
625
  }
737
626
  /**
738
627
  * Re-render a layer canvas from MaskVolume and composite to master.
739
- * Called after writing oldSlice/newSlice back to the volume during undo/redo.
740
628
  */
741
629
  applyUndoRedoToCanvas(layerId) {
742
- let target = this.protectedData.layerTargets.get(layerId);
630
+ let target = this.state.protectedData.layerTargets.get(layerId);
743
631
  if (!target) {
744
- const firstId = this.nrrd_states.image.layers[0];
745
- target = this.protectedData.layerTargets.get(firstId);
632
+ const firstId = this.state.nrrd_states.image.layers[0];
633
+ target = this.state.protectedData.layerTargets.get(firstId);
746
634
  }
747
635
  const { ctx, canvas } = target;
748
- // Clear and re-render the affected layer canvas from MaskVolume
749
636
  canvas.width = canvas.width;
750
- const buffer = this.getOrCreateSliceBuffer(this.protectedData.axis);
637
+ const buffer = this.renderer.getOrCreateSliceBuffer(this.state.protectedData.axis);
751
638
  if (buffer) {
752
- this.renderSliceToCanvas(layerId, this.protectedData.axis, this.nrrd_states.view.currentSliceIndex, buffer, ctx, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
639
+ this.renderer.renderSliceToCanvas(layerId, this.state.protectedData.axis, this.state.nrrd_states.view.currentSliceIndex, buffer, ctx, this.state.nrrd_states.view.changedWidth, this.state.nrrd_states.view.changedHeight);
753
640
  }
754
- // Re-composite all layers to master
755
- this.compositeAllLayers();
641
+ this.renderer.compositeAllLayers();
756
642
  }
757
643
  /****************************Store images (delegated to ImageStoreHelper)****************************************************/
758
644
  syncLayerSliceData(index, layer) {
@@ -762,5 +648,50 @@ export class DrawToolCore extends CommToolsData {
762
648
  setupConrastEvents(callback) {
763
649
  this.contrastTool.setupConrastEvents(callback);
764
650
  }
651
+ // ── Pseudo-abstract methods (overridden by NrrdTools via callback injection) ──
652
+ /** Override in NrrdTools */
653
+ setIsDrawFalse(target) {
654
+ throw new Error("setIsDrawFalse must be provided by NrrdTools");
655
+ }
656
+ /** Override in NrrdTools */
657
+ setSyncsliceNum() {
658
+ throw new Error("setSyncsliceNum must be provided by NrrdTools");
659
+ }
660
+ /** Override in NrrdTools */
661
+ resetPaintAreaUIPosition(l, t) {
662
+ throw new Error("resetPaintAreaUIPosition must be provided by NrrdTools");
663
+ }
664
+ /** Override in NrrdTools */
665
+ resizePaintArea(factor) {
666
+ throw new Error("resizePaintArea must be provided by NrrdTools");
667
+ }
668
+ /** Override in NrrdTools */
669
+ setEmptyCanvasSize(axis) {
670
+ throw new Error("setEmptyCanvasSize must be provided by NrrdTools");
671
+ }
672
+ /** Override in NrrdTools */
673
+ flipDisplayImageByAxis() {
674
+ throw new Error("flipDisplayImageByAxis must be provided by NrrdTools");
675
+ }
676
+ /** Override in NrrdTools */
677
+ updateOriginAndChangedWH() {
678
+ throw new Error("updateOriginAndChangedWH must be provided by NrrdTools");
679
+ }
680
+ /** Override in NrrdTools */
681
+ resetLayerCanvas() {
682
+ throw new Error("resetLayerCanvas must be provided by NrrdTools");
683
+ }
684
+ /** Override in NrrdTools */
685
+ redrawDisplayCanvas() {
686
+ throw new Error("redrawDisplayCanvas must be provided by NrrdTools");
687
+ }
688
+ /** Override in NrrdTools */
689
+ enterSphereMode() {
690
+ throw new Error("enterSphereMode must be provided by NrrdTools");
691
+ }
692
+ /** Override in NrrdTools */
693
+ exitSphereMode() {
694
+ throw new Error("exitSphereMode must be provided by NrrdTools");
695
+ }
765
696
  }
766
697
  //# sourceMappingURL=DrawToolCore.js.map