Skip to content

Commit 1d78dab

Browse files
authored
Merge pull request #743 from PaulHax/crosshairs-toggle
feat(crosshairs): add hold key for temp crosshairs tool
2 parents c58c990 + da5eba0 commit 1d78dab

8 files changed

Lines changed: 115 additions & 83 deletions

File tree

src/components/ControlsStripTools.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@
126126
</template>
127127

128128
<script lang="ts">
129-
import { computed, defineComponent, ref } from 'vue';
129+
import { computed, defineComponent, ref, watch } from 'vue';
130130
import { storeToRefs } from 'pinia';
131-
import { onKeyDown } from '@vueuse/core';
131+
import { onKeyDown, useMagicKeys } from '@vueuse/core';
132132
import { Tools } from '@/src/store/tools/types';
133133
import ControlButton from '@/src/components/ControlButton.vue';
134134
import ItemGroup from '@/src/components/ItemGroup.vue';
@@ -180,6 +180,15 @@ export default defineComponent({
180180
windowingMenu.value = false;
181181
});
182182
183+
const keys = useMagicKeys();
184+
const enableTempCrosshairs = computed(
185+
() => keys[actionToKey.value.temporaryCrosshairs].value
186+
);
187+
watch(enableTempCrosshairs, (enable) => {
188+
if (enable) toolStore.activateTemporaryCrosshairs();
189+
else toolStore.deactivateTemporaryCrosshairs();
190+
});
191+
183192
// Rename the computed property to map tool names to their keyboard shortcuts
184193
const nameToShortcut = computed(() => {
185194
const keyMap = actionToKey.value;

src/composables/actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export const ACTION_TO_FUNC = {
6969
paint: setTool(Tools.Paint),
7070
rectangle: setTool(Tools.Rectangle),
7171
crosshairs: setTool(Tools.Crosshairs),
72+
temporaryCrosshairs: NOOP, // behavior implemented elsewhere
7273
crop: setTool(Tools.Crop),
7374
polygon: setTool(Tools.Polygon),
7475
select: setTool(Tools.Select),

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ export const ACTION_TO_KEY = {
270270
paint: 'p',
271271
rectangle: 'r',
272272
crosshairs: 'c',
273+
temporaryCrosshairs: 'shift-c',
273274
crop: 'b',
274275
polygon: 'g',
275276
mergeNewPolygon: 'Shift',

src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export const ACTIONS = {
4444
crosshairs: {
4545
readable: 'Activate Crosshairs tool',
4646
},
47+
temporaryCrosshairs: {
48+
readable: 'Temporarily activate crosshairs tool',
49+
},
4750
crop: {
4851
readable: 'Activate Crop tool',
4952
},

src/store/tools/crosshairs.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,22 @@ export const useCrosshairsToolStore = defineStore('crosshairs', () => {
5757

5858
// update the slicing
5959
watch(imagePosition, (indexPos) => {
60-
if (active.value) {
61-
const imageID = unref(currentImageID);
62-
const { lpsOrientation } = unref(currentImageMetadata);
63-
64-
if (!imageID) {
65-
return;
66-
}
67-
68-
currentViewIDs.value.forEach((viewID) => {
69-
const sliceConfig = viewSliceStore.getConfig(viewID, imageID);
70-
const axis = getLPSAxisFromDir(sliceConfig!.axisDirection);
71-
const index = lpsOrientation[axis];
72-
const slice = Math.round(indexPos[index]);
73-
viewSliceStore.updateConfig(viewID, imageID, { slice });
74-
});
60+
if (!active.value) {
61+
return;
7562
}
63+
const imageID = unref(currentImageID);
64+
if (!imageID) {
65+
return;
66+
}
67+
const { lpsOrientation } = unref(currentImageMetadata);
68+
69+
currentViewIDs.value.forEach((viewID) => {
70+
const sliceConfig = viewSliceStore.getConfig(viewID, imageID);
71+
const axis = getLPSAxisFromDir(sliceConfig!.axisDirection);
72+
const index = lpsOrientation[axis];
73+
const slice = Math.round(indexPos[index]);
74+
viewSliceStore.updateConfig(viewID, imageID, { slice });
75+
});
7676
});
7777

7878
// update widget state based on current image
@@ -99,13 +99,17 @@ export const useCrosshairsToolStore = defineStore('crosshairs', () => {
9999
});
100100

101101
function activateTool() {
102-
widgetState.setPlaced(false);
103102
active.value = true;
104103
return true;
105104
}
106105

107106
function deactivateTool() {
108107
active.value = false;
108+
widgetState.setDragging(false);
109+
}
110+
111+
function setDragging(dragging: boolean) {
112+
widgetState.setDragging(dragging);
109113
}
110114

111115
function serialize(state: StateFile) {
@@ -125,6 +129,7 @@ export const useCrosshairsToolStore = defineStore('crosshairs', () => {
125129
imagePosition,
126130
activateTool,
127131
deactivateTool,
132+
setDragging,
128133
serialize,
129134
deserialize,
130135
};

src/store/tools/index.ts

Lines changed: 69 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Manifest, StateFile } from '@/src/io/state-file/schema';
22
import { Maybe } from '@/src/types';
33
import type { AnnotationToolStore } from '@/src/store/tools/useAnnotationTool';
44
import { defineStore } from 'pinia';
5+
import { ref } from 'vue';
56
import { useCropStore } from './crop';
67
import { useCrosshairsToolStore } from './crosshairs';
78
import { usePaintToolStore } from './paint';
@@ -10,10 +11,6 @@ import { useRectangleStore } from './rectangles';
1011
import { AnnotationToolType, IToolStore, Tools } from './types';
1112
import { usePolygonStore } from './polygons';
1213

13-
interface State {
14-
currentTool: Tools;
15-
}
16-
1714
// TODO move these types out
1815
export const AnnotationToolStoreMap: Record<
1916
AnnotationToolType,
@@ -64,49 +61,72 @@ function teardownTool(tool: Tools) {
6461
}
6562
}
6663

67-
export const useToolStore = defineStore('tool', {
68-
state: (): State => ({
69-
currentTool: Tools.WindowLevel,
70-
}),
71-
actions: {
72-
setCurrentTool(tool: Tools) {
73-
if (!setupTool(tool)) {
74-
return;
75-
}
76-
teardownTool(this.currentTool);
77-
this.currentTool = tool;
78-
},
79-
serialize(state: StateFile) {
80-
const { tools } = state.manifest;
81-
82-
Object.values(ToolStoreMap)
83-
.map((useStore) => useStore?.())
84-
.filter((store): store is IToolStore => !!store)
85-
.forEach((store) => {
86-
store.serialize?.(state);
87-
});
88-
89-
tools.current = this.currentTool;
90-
},
91-
deserialize(
92-
manifest: Manifest,
93-
segmentGroupIDMap: Record<string, string>,
94-
dataIDMap: Record<string, string>
95-
) {
96-
const { tools } = manifest;
97-
98-
usePaintToolStore().deserialize(manifest, segmentGroupIDMap);
99-
100-
Object.values(ToolStoreMap)
101-
// paint store uses segmentGroupIDMap
102-
.filter((useStore) => useStore !== usePaintToolStore)
103-
.map((useStore) => useStore?.())
104-
.filter((store): store is IToolStore => !!store)
105-
.forEach((store) => {
106-
store.deserialize?.(manifest, dataIDMap);
107-
});
108-
109-
this.currentTool = tools.current;
110-
},
111-
},
64+
export const useToolStore = defineStore('tool', () => {
65+
const currentTool = ref(Tools.WindowLevel);
66+
const toolBeforeTemporaryCrosshairs = ref<Tools>(currentTool.value);
67+
68+
function setCurrentTool(tool: Tools) {
69+
if (currentTool.value === tool) {
70+
return;
71+
}
72+
if (!setupTool(tool)) {
73+
return;
74+
}
75+
teardownTool(currentTool.value);
76+
currentTool.value = tool;
77+
}
78+
79+
function activateTemporaryCrosshairs() {
80+
toolBeforeTemporaryCrosshairs.value = currentTool.value;
81+
setCurrentTool(Tools.Crosshairs);
82+
useCrosshairsToolStore().setDragging(true);
83+
}
84+
85+
function deactivateTemporaryCrosshairs() {
86+
useCrosshairsToolStore().setDragging(false);
87+
setCurrentTool(toolBeforeTemporaryCrosshairs.value);
88+
}
89+
90+
function serialize(state: StateFile) {
91+
const { tools } = state.manifest;
92+
93+
Object.values(ToolStoreMap)
94+
.map((useStore) => useStore?.())
95+
.filter((store): store is IToolStore => !!store)
96+
.forEach((store) => {
97+
store.serialize?.(state);
98+
});
99+
100+
tools.current = currentTool.value;
101+
}
102+
103+
function deserialize(
104+
manifest: Manifest,
105+
segmentGroupIDMap: Record<string, string>,
106+
dataIDMap: Record<string, string>
107+
) {
108+
const { tools } = manifest;
109+
110+
usePaintToolStore().deserialize(manifest, segmentGroupIDMap);
111+
112+
Object.values(ToolStoreMap)
113+
// paint store uses segmentGroupIDMap
114+
.filter((useStore) => useStore !== usePaintToolStore)
115+
.map((useStore) => useStore?.())
116+
.filter((store): store is IToolStore => !!store)
117+
.forEach((store) => {
118+
store.deserialize?.(manifest, dataIDMap);
119+
});
120+
121+
currentTool.value = tools.current;
122+
}
123+
124+
return {
125+
currentTool,
126+
setCurrentTool,
127+
serialize,
128+
deserialize,
129+
activateTemporaryCrosshairs,
130+
deactivateTemporaryCrosshairs,
131+
};
112132
});

src/vtk/CrosshairsWidget/behavior.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ function clampPointToBounds(bounds: Bounds, point: vec3) {
1010

1111
export default function widgetBehavior(publicAPI: any, model: any) {
1212
model.classHierarchy.push('vtkCrosshairsWidgetProp');
13-
let isDragging = false;
1413

1514
// support setting per-view widget manipulators
1615
macro.setGet(publicAPI, model, ['manipulator']);
@@ -19,21 +18,16 @@ export default function widgetBehavior(publicAPI: any, model: any) {
1918
// Interactor events
2019
// --------------------------------------------------------------------------
2120

22-
function ignoreKey(e: any) {
23-
return e.altKey || e.controlKey || e.shiftKey;
24-
}
25-
2621
// --------------------------------------------------------------------------
2722
// Left press: Select handle to drag
2823
// --------------------------------------------------------------------------
2924

3025
publicAPI.handleLeftButtonPress = (e: any) => {
31-
if (!model.pickable || ignoreKey(e)) {
26+
if (!model.pickable) {
3227
return macro.VOID;
3328
}
3429

35-
model.widgetState.setPlaced(true);
36-
isDragging = true;
30+
model.widgetState.setDragging(true);
3731
model._interactor.requestAnimation(publicAPI);
3832
model._apiSpecificRenderWindow.setCursor('crosshairs');
3933
publicAPI.invokeStartInteractionEvent();
@@ -52,10 +46,9 @@ export default function widgetBehavior(publicAPI: any, model: any) {
5246
// is actually being rendered.
5347

5448
if (
55-
(!model.widgetState.getPlaced() || isDragging) &&
49+
model.widgetState.getDragging() &&
5650
model.pickable &&
57-
model.manipulator &&
58-
!ignoreKey(callData)
51+
model.manipulator
5952
) {
6053
const { worldCoords: worldCoordsOfPointer } =
6154
model.manipulator.handleEvent(callData, model._apiSpecificRenderWindow);
@@ -90,12 +83,12 @@ export default function widgetBehavior(publicAPI: any, model: any) {
9083
// --------------------------------------------------------------------------
9184

9285
publicAPI.handleLeftButtonRelease = () => {
93-
if (isDragging && model.pickable) {
86+
if (model.widgetState.getDragging() && model.pickable) {
9487
model._interactor.cancelAnimation(publicAPI);
9588
model._apiSpecificRenderWindow.setCursor('default');
9689
publicAPI.invokeEndInteractionEvent();
9790
}
98-
isDragging = false;
91+
model.widgetState.setDragging(false);
9992
};
10093

10194
// --------------------------------------------------------------------------

src/vtk/CrosshairsWidget/state.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export interface CrosshairsHandleWidgetState extends vtkWidgetState {
1515
}
1616

1717
export interface CrosshairsWidgetState extends vtkWidgetState {
18-
setPlaced(placed: boolean): boolean;
19-
getPlaced(): boolean;
18+
setDragging(dragging: boolean): boolean;
19+
getDragging(): boolean;
2020
setIndexToWorld(indexToWorld: mat4): boolean;
2121
getIndexToWorld(): mat4;
2222
setWorldToIndex(worldToIndex: mat4): boolean;
@@ -28,7 +28,7 @@ export default function generateState() {
2828
return vtkStateBuilder
2929
.createBuilder()
3030
.addField({
31-
name: 'placed',
31+
name: 'dragging',
3232
initialValue: false,
3333
})
3434
.addField({

0 commit comments

Comments
 (0)