Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/components/ControlsStripTools.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@
</template>

<script lang="ts">
import { computed, defineComponent, ref } from 'vue';
import { computed, defineComponent, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { onKeyDown } from '@vueuse/core';
import { onKeyDown, useMagicKeys } from '@vueuse/core';
import { Tools } from '@/src/store/tools/types';
import ControlButton from '@/src/components/ControlButton.vue';
import ItemGroup from '@/src/components/ItemGroup.vue';
Expand Down Expand Up @@ -180,6 +180,15 @@ export default defineComponent({
windowingMenu.value = false;
});

const keys = useMagicKeys();
const enableTempCrosshairs = computed(
() => keys[actionToKey.value.temporaryCrosshairs].value
);
watch(enableTempCrosshairs, (enable) => {
if (enable) toolStore.activateTemporaryCrosshairs();
else toolStore.deactivateTemporaryCrosshairs();
});

// Rename the computed property to map tool names to their keyboard shortcuts
const nameToShortcut = computed(() => {
const keyMap = actionToKey.value;
Expand Down
1 change: 1 addition & 0 deletions src/composables/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const ACTION_TO_FUNC = {
paint: setTool(Tools.Paint),
rectangle: setTool(Tools.Rectangle),
crosshairs: setTool(Tools.Crosshairs),
temporaryCrosshairs: NOOP, // behavior implemented elsewhere
crop: setTool(Tools.Crop),
polygon: setTool(Tools.Polygon),
select: setTool(Tools.Select),
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ export const ACTION_TO_KEY = {
paint: 'p',
rectangle: 'r',
crosshairs: 'c',
temporaryCrosshairs: 'shift-c',
crop: 'b',
polygon: 'g',
mergeNewPolygon: 'Shift',
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export const ACTIONS = {
crosshairs: {
readable: 'Activate Crosshairs tool',
},
temporaryCrosshairs: {
readable: 'Temporarily activate crosshairs tool',
},
crop: {
readable: 'Activate Crop tool',
},
Expand Down
37 changes: 21 additions & 16 deletions src/store/tools/crosshairs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,22 @@ export const useCrosshairsToolStore = defineStore('crosshairs', () => {

// update the slicing
watch(imagePosition, (indexPos) => {
if (active.value) {
const imageID = unref(currentImageID);
const { lpsOrientation } = unref(currentImageMetadata);

if (!imageID) {
return;
}

currentViewIDs.value.forEach((viewID) => {
const sliceConfig = viewSliceStore.getConfig(viewID, imageID);
const axis = getLPSAxisFromDir(sliceConfig!.axisDirection);
const index = lpsOrientation[axis];
const slice = Math.round(indexPos[index]);
viewSliceStore.updateConfig(viewID, imageID, { slice });
});
if (!active.value) {
return;
}
const imageID = unref(currentImageID);
if (!imageID) {
return;
}
const { lpsOrientation } = unref(currentImageMetadata);

currentViewIDs.value.forEach((viewID) => {
const sliceConfig = viewSliceStore.getConfig(viewID, imageID);
const axis = getLPSAxisFromDir(sliceConfig!.axisDirection);
const index = lpsOrientation[axis];
const slice = Math.round(indexPos[index]);
viewSliceStore.updateConfig(viewID, imageID, { slice });
});
});

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

function activateTool() {
widgetState.setPlaced(false);
active.value = true;
return true;
}

function deactivateTool() {
active.value = false;
widgetState.setDragging(false);
}

function setDragging(dragging: boolean) {
widgetState.setDragging(dragging);
}

function serialize(state: StateFile) {
Expand All @@ -125,6 +129,7 @@ export const useCrosshairsToolStore = defineStore('crosshairs', () => {
imagePosition,
activateTool,
deactivateTool,
setDragging,
serialize,
deserialize,
};
Expand Down
118 changes: 69 additions & 49 deletions src/store/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Manifest, StateFile } from '@/src/io/state-file/schema';
import { Maybe } from '@/src/types';
import type { AnnotationToolStore } from '@/src/store/tools/useAnnotationTool';
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { useCropStore } from './crop';
import { useCrosshairsToolStore } from './crosshairs';
import { usePaintToolStore } from './paint';
Expand All @@ -10,10 +11,6 @@ import { useRectangleStore } from './rectangles';
import { AnnotationToolType, IToolStore, Tools } from './types';
import { usePolygonStore } from './polygons';

interface State {
currentTool: Tools;
}

// TODO move these types out
export const AnnotationToolStoreMap: Record<
AnnotationToolType,
Expand Down Expand Up @@ -64,49 +61,72 @@ function teardownTool(tool: Tools) {
}
}

export const useToolStore = defineStore('tool', {
state: (): State => ({
currentTool: Tools.WindowLevel,
}),
actions: {
setCurrentTool(tool: Tools) {
if (!setupTool(tool)) {
return;
}
teardownTool(this.currentTool);
this.currentTool = tool;
},
serialize(state: StateFile) {
const { tools } = state.manifest;

Object.values(ToolStoreMap)
.map((useStore) => useStore?.())
.filter((store): store is IToolStore => !!store)
.forEach((store) => {
store.serialize?.(state);
});

tools.current = this.currentTool;
},
deserialize(
manifest: Manifest,
segmentGroupIDMap: Record<string, string>,
dataIDMap: Record<string, string>
) {
const { tools } = manifest;

usePaintToolStore().deserialize(manifest, segmentGroupIDMap);

Object.values(ToolStoreMap)
// paint store uses segmentGroupIDMap
.filter((useStore) => useStore !== usePaintToolStore)
.map((useStore) => useStore?.())
.filter((store): store is IToolStore => !!store)
.forEach((store) => {
store.deserialize?.(manifest, dataIDMap);
});

this.currentTool = tools.current;
},
},
export const useToolStore = defineStore('tool', () => {
const currentTool = ref(Tools.WindowLevel);
const toolBeforeTemporaryCrosshairs = ref<Tools>(currentTool.value);

function setCurrentTool(tool: Tools) {
if (currentTool.value === tool) {
return;
}
if (!setupTool(tool)) {
return;
}
teardownTool(currentTool.value);
currentTool.value = tool;
}

function activateTemporaryCrosshairs() {
toolBeforeTemporaryCrosshairs.value = currentTool.value;
setCurrentTool(Tools.Crosshairs);
useCrosshairsToolStore().setDragging(true);
}

function deactivateTemporaryCrosshairs() {
useCrosshairsToolStore().setDragging(false);
setCurrentTool(toolBeforeTemporaryCrosshairs.value);
}

function serialize(state: StateFile) {
const { tools } = state.manifest;

Object.values(ToolStoreMap)
.map((useStore) => useStore?.())
.filter((store): store is IToolStore => !!store)
.forEach((store) => {
store.serialize?.(state);
});

tools.current = currentTool.value;
}

function deserialize(
manifest: Manifest,
segmentGroupIDMap: Record<string, string>,
dataIDMap: Record<string, string>
) {
const { tools } = manifest;

usePaintToolStore().deserialize(manifest, segmentGroupIDMap);

Object.values(ToolStoreMap)
// paint store uses segmentGroupIDMap
.filter((useStore) => useStore !== usePaintToolStore)
.map((useStore) => useStore?.())
.filter((store): store is IToolStore => !!store)
.forEach((store) => {
store.deserialize?.(manifest, dataIDMap);
});

currentTool.value = tools.current;
}

return {
currentTool,
setCurrentTool,
serialize,
deserialize,
activateTemporaryCrosshairs,
deactivateTemporaryCrosshairs,
};
});
19 changes: 6 additions & 13 deletions src/vtk/CrosshairsWidget/behavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ function clampPointToBounds(bounds: Bounds, point: vec3) {

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

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

function ignoreKey(e: any) {
return e.altKey || e.controlKey || e.shiftKey;
}

// --------------------------------------------------------------------------
// Left press: Select handle to drag
// --------------------------------------------------------------------------

publicAPI.handleLeftButtonPress = (e: any) => {
if (!model.pickable || ignoreKey(e)) {
if (!model.pickable) {
return macro.VOID;
}

model.widgetState.setPlaced(true);
isDragging = true;
model.widgetState.setDragging(true);
model._interactor.requestAnimation(publicAPI);
model._apiSpecificRenderWindow.setCursor('crosshairs');
publicAPI.invokeStartInteractionEvent();
Expand All @@ -52,10 +46,9 @@ export default function widgetBehavior(publicAPI: any, model: any) {
// is actually being rendered.

if (
(!model.widgetState.getPlaced() || isDragging) &&
model.widgetState.getDragging() &&
model.pickable &&
model.manipulator &&
!ignoreKey(callData)
model.manipulator
) {
const { worldCoords: worldCoordsOfPointer } =
model.manipulator.handleEvent(callData, model._apiSpecificRenderWindow);
Expand Down Expand Up @@ -90,12 +83,12 @@ export default function widgetBehavior(publicAPI: any, model: any) {
// --------------------------------------------------------------------------

publicAPI.handleLeftButtonRelease = () => {
if (isDragging && model.pickable) {
if (model.widgetState.getDragging() && model.pickable) {
model._interactor.cancelAnimation(publicAPI);
model._apiSpecificRenderWindow.setCursor('default');
publicAPI.invokeEndInteractionEvent();
}
isDragging = false;
model.widgetState.setDragging(false);
};

// --------------------------------------------------------------------------
Expand Down
6 changes: 3 additions & 3 deletions src/vtk/CrosshairsWidget/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export interface CrosshairsHandleWidgetState extends vtkWidgetState {
}

export interface CrosshairsWidgetState extends vtkWidgetState {
setPlaced(placed: boolean): boolean;
getPlaced(): boolean;
setDragging(dragging: boolean): boolean;
getDragging(): boolean;
setIndexToWorld(indexToWorld: mat4): boolean;
getIndexToWorld(): mat4;
setWorldToIndex(worldToIndex: mat4): boolean;
Expand All @@ -28,7 +28,7 @@ export default function generateState() {
return vtkStateBuilder
.createBuilder()
.addField({
name: 'placed',
name: 'dragging',
initialValue: false,
})
.addField({
Expand Down
Loading