Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotation Tool Hover #404

Merged
merged 15 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from 12 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
63 changes: 63 additions & 0 deletions src/components/tools/AnnotationInfo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script setup lang="ts" generic="ToolID extends string">
/* global ToolID:readonly */
import { computed, ref } from 'vue';
import { useElementSize } from '@vueuse/core';
import { AnnotationToolStore } from '@/src/store/tools/useAnnotationTool';
import { OverlayInfo } from '@/src/composables/annotationTool';

// These seem to work ¯\_(ツ)_/¯
const TOOLTIP_PADDING_X = 30;
const TOOLTIP_PADDING_Y = 20;

const props = defineProps<{
info: OverlayInfo<ToolID>;
toolStore: AnnotationToolStore<ToolID>;
}>();

const visible = computed(() => {
return props.info.visible;
});

const label = computed(() => {
if (!props.info.visible) return '';
return props.toolStore.toolByID[props.info.toolID].labelName;
});

const tooltip = ref();
const content = computed(() => {
return tooltip.value?.contentEl;
});

const { width, height } = useElementSize(content);
const offset = computed(() => {
return {
// Tooltip location is above cursor and centered
// Don't know how to get ref to parent v-tooltip element, so adding fudge padding.
x: (width.value + TOOLTIP_PADDING_X) / 2,
y: height.value + TOOLTIP_PADDING_Y,
};
});
</script>

<template>
<v-tooltip
ref="tooltip"
v-if="info.visible"
v-model="visible"
:style="{
left: `${info.displayXY[0] - offset.x}px`,
top: `${info.displayXY[1] - offset.y}px`,
zIndex: 500, // stay under context menu
}"
class="better-contrast"
>
{{ label }}
</v-tooltip>
</template>

<style scoped>
.better-contrast :deep(.v-overlay__content) {
opacity: 1 !important;
background: rgba(255, 255, 255, 0.9) !important;
}
</style>
76 changes: 76 additions & 0 deletions src/components/tools/BoundingRectangle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<script setup lang="ts">
import { computed, ref, watch, toRefs } from 'vue';
import { ANNOTATION_TOOL_HANDLE_RADIUS } from '@/src/constants';
import { useViewStore } from '@/src/store/views';
import { worldToSVG } from '@/src/utils/vtk-helpers';
import { nonNullable } from '@/src/utils/index';
import vtkLPSView2DProxy from '@/src/vtk/LPSView2DProxy';
import vtkBoundingBox from '@kitware/vtk.js/Common/DataModel/BoundingBox';
import { Bounds, Vector3 } from '@kitware/vtk.js/types';
import { onVTKEvent } from '@/src/composables/onVTKEvent';

const props = defineProps<{
points: Array<Vector3>;
viewId: string;
}>();

const viewStore = useViewStore();
const viewProxy = computed(
() => viewStore.getViewProxy<vtkLPSView2DProxy>(props.viewId)!
);

const visible = computed(() => {
return props.points.length > 0;
});

const rectangle = ref({
x: 0,
y: 0,
width: 0,
height: 0,
});

const updateRectangle = () => {
const viewRenderer = viewProxy.value.getRenderer();

const screenBounds = [...vtkBoundingBox.INIT_BOUNDS] as Bounds;
props.points
.map((point) => {
const point2D = worldToSVG(point, viewRenderer);
return point2D;
})
.filter(nonNullable)
.forEach(([x, y]) => {
vtkBoundingBox.addPoint(screenBounds, x, y, 0);
});
const [x, y] = vtkBoundingBox.getMinPoint(screenBounds);
const [maxX, maxY] = vtkBoundingBox.getMaxPoint(screenBounds);
// Plus 2 to account for the stroke width
const handleRadius = (ANNOTATION_TOOL_HANDLE_RADIUS + 2) / devicePixelRatio;
const handleDiameter = 2 * handleRadius;
rectangle.value = {
x: x - handleRadius,
y: y - handleRadius,
width: maxX - x + handleDiameter,
height: maxY - y + handleDiameter,
};
};

const { points } = toRefs(props);
watch(points, updateRectangle, { immediate: true, deep: true });

onVTKEvent(viewProxy, 'onModified', updateRectangle);
</script>

<template>
<rect
v-if="visible"
:x="rectangle.x"
:y="rectangle.y"
:width="rectangle.width"
:height="rectangle.height"
stroke-width="2"
fill="transparent"
stroke="lightgray"
/>
</template>
8 changes: 4 additions & 4 deletions src/components/tools/polygon/PolygonSVG2D.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<g>
<!-- radius is related to the vtkRectangleWidget scale, specified in state -->
<!-- radius should match constants.ANNOTATION_TOOL_HANDLE_RADIUS and should be related to vtkHandleWidget scale. -->
<circle
v-for="({ point: [x, y], radius }, index) in handlePoints"
:key="index"
Expand All @@ -23,7 +23,7 @@
<script lang="ts">
import { useResizeObserver } from '@/src/composables/useResizeObserver';
import { onVTKEvent } from '@/src/composables/onVTKEvent';
import { ToolContainer } from '@/src/constants';
import { ANNOTATION_TOOL_HANDLE_RADIUS, ToolContainer } from '@/src/constants';
import { useViewStore } from '@/src/store/views';
import { worldToSVG } from '@/src/utils/vtk-helpers';
import vtkLPSView2DProxy from '@/src/vtk/LPSView2DProxy';
Expand All @@ -38,8 +38,8 @@ import {
inject,
} from 'vue';

const POINT_RADIUS = 10;
const FINISHABLE_POINT_RADIUS = 16;
const POINT_RADIUS = ANNOTATION_TOOL_HANDLE_RADIUS;
const FINISHABLE_POINT_RADIUS = POINT_RADIUS + 6;

export default defineComponent({
props: {
Expand Down
19 changes: 19 additions & 0 deletions src/components/tools/polygon/PolygonTool.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<div class="overlay-no-events">
<svg class="overlay-no-events">
<bounding-rectangle :points="points" :view-id="viewId" />
<polygon-widget-2D
v-for="tool in tools"
:key="tool.id"
Expand All @@ -12,9 +13,11 @@
:widget-manager="widgetManager"
@contextmenu="openContextMenu(tool.id, $event)"
@placed="onToolPlaced"
@widgetHover="onHover(tool.id, $event)"
/>
</svg>
<annotation-context-menu ref="contextMenu" :tool-store="activeToolStore" />
<annotation-info :info="overlayInfo" :tool-store="activeToolStore" />
</div>
</template>

Expand Down Expand Up @@ -43,8 +46,11 @@ import { PolygonID } from '@/src/types/polygon';
import {
useContextMenu,
useCurrentTools,
useHover,
} from '@/src/composables/annotationTool';
import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue';
import AnnotationInfo from '@/src/components/tools/AnnotationInfo.vue';
import BoundingRectangle from '@/src/components/tools/BoundingRectangle.vue';
import PolygonWidget2D from './PolygonWidget2D.vue';

type ToolID = PolygonID;
Expand Down Expand Up @@ -74,6 +80,8 @@ export default defineComponent({
components: {
PolygonWidget2D,
AnnotationContextMenu,
AnnotationInfo,
BoundingRectangle,
},
setup(props) {
const { viewDirection, currentSlice } = toRefs(props);
Expand Down Expand Up @@ -181,13 +189,24 @@ export default defineComponent({

const currentTools = useCurrentTools(activeToolStore, viewAxis);

const { onHover, overlayInfo } = useHover(currentTools, currentSlice);

const points = computed(() => {
if (!overlayInfo.value.visible) return [];
const tool = activeToolStore.toolByID[overlayInfo.value.toolID];
return tool.points;
});

return {
tools: currentTools,
placingToolID,
onToolPlaced,
contextMenu,
openContextMenu,
activeToolStore,
onHover,
overlayInfo,
points,
};
},
});
Expand Down
9 changes: 7 additions & 2 deletions src/components/tools/polygon/PolygonWidget2D.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { useCurrentImage } from '@/src/composables/useCurrentImage';
import { updatePlaneManipulatorFor2DView } from '@/src/utils/manipulators';
import { LPSAxisDir } from '@/src/types/lps';
import { onVTKEvent } from '@/src/composables/onVTKEvent';
import { useRightClickContextMenu } from '@/src/composables/annotationTool';
import {
useHoverEvent,
useRightClickContextMenu,
} from '@/src/composables/annotationTool';
import { usePolygonStore as useStore } from '@/src/store/tools/polygons';
import { PolygonID as ToolID } from '@/src/types/polygon';
import vtkWidgetFactory, {
Expand All @@ -27,7 +30,7 @@ import SVG2DComponent from './PolygonSVG2D.vue';

export default defineComponent({
name: 'PolygonWidget2D',
emits: ['placed', 'contextmenu'],
emits: ['placed', 'contextmenu', 'widgetHover'],
props: {
toolId: {
type: String,
Expand Down Expand Up @@ -103,6 +106,8 @@ export default defineComponent({
emit('placed');
});

useHoverEvent(emit, widget);

// --- right click handling --- //

useRightClickContextMenu(emit, widget);
Expand Down
2 changes: 1 addition & 1 deletion src/components/tools/rectangle/RectangleSVG2D.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
stroke-width="1"
:fill="fillColor"
/>
<!-- radius is related to the vtkRectangleWidget scale, specified in state -->
<!-- radius should match constants.ANNOTATION_TOOL_HANDLE_RADIUS and should be related to vtkHandleWidget scale. -->
<circle
v-if="first"
:cx="first.x"
Expand Down
19 changes: 19 additions & 0 deletions src/components/tools/rectangle/RectangleTool.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<div class="overlay-no-events">
<svg class="overlay-no-events">
<bounding-rectangle :points="points" :view-id="viewId" />
<rectangle-widget-2D
v-for="tool in tools"
:key="tool.id"
Expand All @@ -12,8 +13,10 @@
:widget-manager="widgetManager"
@contextmenu="openContextMenu(tool.id, $event)"
@placed="onToolPlaced"
@widgetHover="onHover(tool.id, $event)"
/>
</svg>
<annotation-info :info="overlayInfo" :tool-store="activeToolStore" />
<annotation-context-menu ref="contextMenu" :tool-store="activeToolStore" />
</div>
</template>
Expand Down Expand Up @@ -43,8 +46,11 @@ import { RectangleID } from '@/src/types/rectangle';
import {
useCurrentTools,
useContextMenu,
useHover,
} from '@/src/composables/annotationTool';
import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue';
import AnnotationInfo from '@/src/components/tools/AnnotationInfo.vue';
import BoundingRectangle from '@/src/components/tools/BoundingRectangle.vue';
import RectangleWidget2D from './RectangleWidget2D.vue';

type ToolID = RectangleID;
Expand Down Expand Up @@ -74,6 +80,8 @@ export default defineComponent({
components: {
RectangleWidget2D,
AnnotationContextMenu,
AnnotationInfo,
BoundingRectangle,
},
setup(props) {
const { viewDirection, currentSlice } = toRefs(props);
Expand Down Expand Up @@ -182,13 +190,24 @@ export default defineComponent({

const currentTools = useCurrentTools(activeToolStore, viewAxis);

const { onHover, overlayInfo } = useHover(currentTools, currentSlice);

const points = computed(() => {
if (!overlayInfo.value.visible) return [];
const tool = activeToolStore.toolByID[overlayInfo.value.toolID];
return [tool.firstPoint, tool.secondPoint];
});

return {
tools: currentTools,
placingToolID,
onToolPlaced,
contextMenu,
openContextMenu,
activeToolStore,
onHover,
overlayInfo,
points,
};
},
});
Expand Down
9 changes: 7 additions & 2 deletions src/components/tools/rectangle/RectangleWidget2D.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import RectangleSVG2D from '@/src/components/tools/rectangle/RectangleSVG2D.vue'
import { vtkRulerWidgetPointState } from '@/src/vtk/RulerWidget';
import { watchOnce } from '@vueuse/core';
import { RectangleID } from '@/src/types/rectangle';
import { useRightClickContextMenu } from '@/src/composables/annotationTool';
import {
useRightClickContextMenu,
useHoverEvent,
} from '@/src/composables/annotationTool';

const useStore = useRectangleStore;
const vtkWidgetFactory = vtkRectangleWidget;
Expand All @@ -37,7 +40,7 @@ const SVG2DComponent = RectangleSVG2D;

export default defineComponent({
name: 'RectangleWidget2D',
emits: ['placed', 'contextmenu'],
emits: ['placed', 'contextmenu', 'widgetHover'],
props: {
toolId: {
type: String,
Expand Down Expand Up @@ -125,6 +128,8 @@ export default defineComponent({
emit('placed');
});

useHoverEvent(emit, widget);

// --- right click handling --- //

useRightClickContextMenu(emit, widget);
Expand Down
2 changes: 1 addition & 1 deletion src/components/tools/ruler/RulerSVG2D.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
:stroke="color"
stroke-width="1"
/>
<!-- radius is related to the vtkRulerWidget scale, specified in state -->
<!-- radius should match constants.ANNOTATION_TOOL_HANDLE_RADIUS and should be related to vtkHandleWidget scale. -->
<circle
v-if="first"
:cx="first.x"
Expand Down
Loading