|
|
@@ -93,6 +93,10 @@ export interface VHSExportedData {
|
|
|
export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
static toolName = 'VHSMeasurementTool';
|
|
|
|
|
|
+ // 高亮颜色定义,方便修改
|
|
|
+ static readonly HOVER_COLOR = 'rgb(255, 255, 0)'; // 悬停时线段颜色:黄色
|
|
|
+ static readonly SELECTED_COLOR = 'rgb(0, 120, 255)'; // 选中时线段颜色:蓝色
|
|
|
+
|
|
|
editData: {
|
|
|
annotation: Types.Annotation;
|
|
|
viewportIdsToRender: string[];
|
|
|
@@ -101,6 +105,8 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
hasMoved?: boolean;
|
|
|
textBoxBeingMoved?: boolean;
|
|
|
textBoxOffset?: CoreTypes.Point2;
|
|
|
+ toolBeingMoved?: boolean;
|
|
|
+ initialCanvasPosition?: CoreTypes.Point2;
|
|
|
} | null = null;
|
|
|
|
|
|
isDrawing: boolean = false;
|
|
|
@@ -603,6 +609,10 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
'CORNERSTONE_TOOLS_MOUSE_UP',
|
|
|
this._mouseUpModifyCallback as EventListener
|
|
|
);
|
|
|
+ element.addEventListener(
|
|
|
+ 'CORNERSTONE_TOOLS_MOUSE_MOVE',
|
|
|
+ this._mouseMoveModifyCallback as EventListener
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -621,6 +631,10 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
'CORNERSTONE_TOOLS_MOUSE_UP',
|
|
|
this._mouseUpModifyCallback as EventListener
|
|
|
);
|
|
|
+ element.removeEventListener(
|
|
|
+ 'CORNERSTONE_TOOLS_MOUSE_MOVE',
|
|
|
+ this._mouseMoveModifyCallback as EventListener
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -649,8 +663,15 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
const targetId = this.getTargetId(viewport);
|
|
|
const cachedStats = targetId ? customAnn.data.cachedStats?.[targetId] : undefined;
|
|
|
|
|
|
- if (cachedStats && customAnn.data.textBox) {
|
|
|
- const textBoxPosition = customAnn.data.textBox;
|
|
|
+ if (cachedStats) {
|
|
|
+ // 计算文本框位置(与渲染逻辑一致)
|
|
|
+ const points = customAnn.data.handles.points;
|
|
|
+ const canvasPoints = points.map((p) => viewport.worldToCanvas(p));
|
|
|
+ const defaultTextBoxPosition: CoreTypes.Point2 = [
|
|
|
+ canvasPoints[3][0] + 20,
|
|
|
+ canvasPoints[3][1]
|
|
|
+ ];
|
|
|
+ const textBoxPosition = customAnn.data.textBox || defaultTextBoxPosition;
|
|
|
|
|
|
if (this._isPointInTextBox(canvasCoords, textBoxPosition)) {
|
|
|
const viewportIdsToRender =
|
|
|
@@ -671,12 +692,14 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
textBoxBeingMoved: true,
|
|
|
textBoxOffset: textBoxOffset,
|
|
|
};
|
|
|
+ console.log(`文本框开始移动--`);
|
|
|
|
|
|
customAnn.highlighted = true;
|
|
|
utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
|
|
|
|
|
|
- evt.preventDefault();
|
|
|
- evt.stopPropagation();
|
|
|
+ // 不阻止事件,让拖拽事件正常开始
|
|
|
+ // evt.preventDefault();
|
|
|
+ // evt.stopPropagation();
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
@@ -717,6 +740,42 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 如果没有点击手柄,检查是否点击在线段上(用于整个工具移动)
|
|
|
+ for (const ann of annotations) {
|
|
|
+ const customAnn = ann as VHSMeasurementAnnotation;
|
|
|
+
|
|
|
+ // 检查是否靠近线段(但不是手柄)
|
|
|
+ if (this.isPointNearTool(element, customAnn, canvasCoords, 6)) {
|
|
|
+ // 额外检查是否真的靠近线段而不是手柄
|
|
|
+ const handle = this.getHandleNearImagePoint(element, customAnn, canvasCoords, 6);
|
|
|
+ if (!handle) {
|
|
|
+ const viewportIdsToRender =
|
|
|
+ utilities.viewportFilters.getViewportIdsWithToolToRender(
|
|
|
+ element,
|
|
|
+ this.getToolName()
|
|
|
+ );
|
|
|
+
|
|
|
+ this.editData = {
|
|
|
+ annotation: customAnn,
|
|
|
+ viewportIdsToRender,
|
|
|
+ hasMoved: false,
|
|
|
+ toolBeingMoved: true,
|
|
|
+ initialCanvasPosition: canvasCoords,
|
|
|
+ };
|
|
|
+
|
|
|
+ customAnn.highlighted = true;
|
|
|
+
|
|
|
+ utilities.triggerAnnotationRenderForViewportIds(
|
|
|
+ viewportIdsToRender
|
|
|
+ );
|
|
|
+
|
|
|
+ evt.preventDefault();
|
|
|
+ evt.stopPropagation();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -736,10 +795,55 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- const { annotation: ann, viewportIdsToRender, textBoxBeingMoved } = this.editData;
|
|
|
+ const { annotation: ann, viewportIdsToRender, textBoxBeingMoved, toolBeingMoved, initialCanvasPosition } = this.editData;
|
|
|
const customAnn = ann as VHSMeasurementAnnotation;
|
|
|
const { data } = customAnn;
|
|
|
|
|
|
+ // 如果正在拖拽整个工具
|
|
|
+ if (toolBeingMoved && initialCanvasPosition) {
|
|
|
+ // 计算位移
|
|
|
+ const deltaX = canvasCoords[0] - initialCanvasPosition[0];
|
|
|
+ const deltaY = canvasCoords[1] - initialCanvasPosition[1];
|
|
|
+
|
|
|
+ // 将所有点转换为canvas坐标,应用位移,然后转换回世界坐标
|
|
|
+ const points = data.handles.points;
|
|
|
+ for (let i = 0; i < points.length; i++) {
|
|
|
+ const canvasPoint = enabledElement.viewport.worldToCanvas(points[i]);
|
|
|
+ const newCanvasPoint: CoreTypes.Point2 = [
|
|
|
+ canvasPoint[0] + deltaX,
|
|
|
+ canvasPoint[1] + deltaY
|
|
|
+ ];
|
|
|
+ points[i] = enabledElement.viewport.canvasToWorld(newCanvasPoint);
|
|
|
+ }
|
|
|
+ // 初始位置经过拖拽后,也需要更新,否则会造成移动距离的累加效果
|
|
|
+ if (this.editData.initialCanvasPosition) {
|
|
|
+ this.editData.initialCanvasPosition[0] = this.editData.initialCanvasPosition[0] + deltaX;
|
|
|
+ this.editData.initialCanvasPosition[1] = this.editData.initialCanvasPosition[1] + deltaY;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 更新文本框位置(如果存在)
|
|
|
+ if (data.textBox) {
|
|
|
+ data.textBox = [
|
|
|
+ data.textBox[0] + deltaX,
|
|
|
+ data.textBox[1] + deltaY
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新计算统计数据
|
|
|
+ this._updateCachedStats(customAnn, enabledElement);
|
|
|
+
|
|
|
+ this.editData.hasMoved = true;
|
|
|
+
|
|
|
+ utilities.triggerAnnotationRenderForViewportIds(
|
|
|
+ viewportIdsToRender
|
|
|
+ );
|
|
|
+
|
|
|
+ evt.preventDefault();
|
|
|
+ evt.stopPropagation();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
// 如果正在拖拽文本框
|
|
|
if (textBoxBeingMoved && this.editData.textBoxOffset) {
|
|
|
const newTextBoxPosition: CoreTypes.Point2 = [
|
|
|
@@ -781,6 +885,105 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ /**
|
|
|
+ * 鼠标移动回调 - 用于修改模式,处理悬停检测
|
|
|
+ */
|
|
|
+ _mouseMoveModifyCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
|
+ const eventDetail = evt.detail;
|
|
|
+ const { element, currentPoints } = eventDetail;
|
|
|
+ const canvasCoords = currentPoints.canvas;
|
|
|
+
|
|
|
+ const enabledElement = getEnabledElement(element);
|
|
|
+ if (!enabledElement) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const { viewport } = enabledElement;
|
|
|
+
|
|
|
+ const annotations = annotation.state.getAnnotations(this.getToolName(), element);
|
|
|
+
|
|
|
+ if (!annotations || annotations.length === 0) {
|
|
|
+ // 没有注解时,重置光标
|
|
|
+ element.style.cursor = 'default';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let isHovering = false;
|
|
|
+
|
|
|
+ // 检查是否悬停在文本框上
|
|
|
+ for (const ann of annotations) {
|
|
|
+ const customAnn = ann as VHSMeasurementAnnotation;
|
|
|
+ const targetId = this.getTargetId(viewport);
|
|
|
+ const cachedStats = targetId ? customAnn.data.cachedStats?.[targetId] : undefined;
|
|
|
+
|
|
|
+ if (cachedStats) {
|
|
|
+ // 计算文本框位置(与渲染逻辑一致)
|
|
|
+ const points = customAnn.data.handles.points;
|
|
|
+ const canvasPoints = points.map((p) => viewport.worldToCanvas(p));
|
|
|
+ const defaultTextBoxPosition: CoreTypes.Point2 = [
|
|
|
+ canvasPoints[3][0] + 20,
|
|
|
+ canvasPoints[3][1]
|
|
|
+ ];
|
|
|
+ const textBoxPosition = customAnn.data.textBox || defaultTextBoxPosition;
|
|
|
+
|
|
|
+ if (this._isPointInTextBox(canvasCoords, textBoxPosition)) {
|
|
|
+ // 悬停在文本框上,光标为手型
|
|
|
+ element.style.cursor = 'pointer';
|
|
|
+ customAnn.highlighted = true;
|
|
|
+ isHovering = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有悬停在文本框上,检查是否悬停在手柄或线段上
|
|
|
+ if (!isHovering) {
|
|
|
+ for (const ann of annotations) {
|
|
|
+ const customAnn = ann as VHSMeasurementAnnotation;
|
|
|
+
|
|
|
+ // 检查是否靠近手柄
|
|
|
+ const handle = this.getHandleNearImagePoint(element, customAnn, canvasCoords, 6);
|
|
|
+ if (handle) {
|
|
|
+ // 悬停在手柄上,光标为移动光标
|
|
|
+ element.style.cursor = 'move';
|
|
|
+ customAnn.highlighted = true;
|
|
|
+ isHovering = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否靠近线段
|
|
|
+ if (this.isPointNearTool(element, customAnn, canvasCoords, 6)) {
|
|
|
+ // 悬停在线段上,光标为移动光标
|
|
|
+ element.style.cursor = 'move';
|
|
|
+ customAnn.highlighted = true;
|
|
|
+ isHovering = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有悬停在任何地方,重置高亮和光标
|
|
|
+ if (!isHovering) {
|
|
|
+ for (const ann of annotations) {
|
|
|
+ const customAnn = ann as VHSMeasurementAnnotation;
|
|
|
+ customAnn.highlighted = false;
|
|
|
+ }
|
|
|
+ element.style.cursor = 'default';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 触发渲染以更新高亮状态
|
|
|
+ const viewportIdsToRender =
|
|
|
+ utilities.viewportFilters.getViewportIdsWithToolToRender(
|
|
|
+ element,
|
|
|
+ this.getToolName()
|
|
|
+ );
|
|
|
+
|
|
|
+ utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
|
|
|
+
|
|
|
+ // 移除事件阻止,让拖拽事件正常工作
|
|
|
+ // evt.preventDefault();
|
|
|
+ // evt.stopPropagation();
|
|
|
+ };
|
|
|
+
|
|
|
/**
|
|
|
* 鼠标释放回调 - 用于修改模式
|
|
|
*/
|
|
|
@@ -1060,10 +1263,13 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
// 转换所有点为 canvas 坐标
|
|
|
const canvasPoints = points.map((p) => viewport.worldToCanvas(p));
|
|
|
|
|
|
- // 确定线段颜色(选中蓝色,未选中白色)
|
|
|
- const lineColor = annotation.highlighted
|
|
|
- ? 'rgb(0, 120, 255)'
|
|
|
- : 'rgb(255, 255, 255)';
|
|
|
+ // 确定线段颜色和状态
|
|
|
+ const isSelected = this.editData && this.editData.annotation === annotation;
|
|
|
+ const lineColor = isSelected
|
|
|
+ ? VHSMeasurementTool.SELECTED_COLOR
|
|
|
+ : annotation.highlighted
|
|
|
+ ? VHSMeasurementTool.HOVER_COLOR
|
|
|
+ : 'rgb(255, 255, 255)';
|
|
|
|
|
|
// 绘制胸椎长度线(点1-2)
|
|
|
if (points.length >= 2 && annotationUID) {
|
|
|
@@ -1129,7 +1335,7 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
canvasPoints,
|
|
|
{
|
|
|
color: 'rgb(255, 255, 255)',
|
|
|
- handleRadius: 6,
|
|
|
+ handleRadius: isSelected ? 12 : 6,
|
|
|
}
|
|
|
);
|
|
|
}
|