Skip to content

Commit

Permalink
ENH: Add cancel action for interaction handles
Browse files Browse the repository at this point in the history
This commit adds a cancel action when translating/rotating/scaling using interaction handles.
Users can press "Escape" or right-click to cancel the current action and restore the Transform or Markups node back to its original state.

Re #7570
  • Loading branch information
Sunderlandkyl authored and lassoan committed Feb 29, 2024
1 parent ea92a92 commit 401a4b2
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Libs/MRML/Core/vtkMRMLTransformNode.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ else
}

// copy the center of transformation
node->SetCenterOfTransformation(this->GetCenterOfTransformation());
this->SetCenterOfTransformation(node->GetCenterOfTransformation());

this->Modified();
this->TransformModified();
Expand Down
79 changes: 67 additions & 12 deletions Libs/MRML/DisplayableManager/vtkMRMLInteractionWidget.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ vtkMRMLInteractionWidget::vtkMRMLInteractionWidget()
{
this->LastEventPosition[0] = 0.0;
this->LastEventPosition[1] = 0.0;
this->StartEventOffsetPosition[0] = 0.0;
this->StartEventOffsetPosition[1] = 0.0;

// Update active component
this->SetEventTranslation(WidgetStateIdle, vtkCommand::MouseMoveEvent, vtkEvent::AnyModifier, WidgetEventMouseMove);
Expand Down Expand Up @@ -79,6 +77,19 @@ vtkMRMLInteractionWidget::vtkMRMLInteractionWidget()
this->SetEventTranslation(interactionHandleState, vtkCommand::MouseMoveEvent, vtkEvent::NoModifier, WidgetEventMouseMove);
this->SetEventTranslation(interactionHandleState, vtkCommand::Move3DEvent, vtkEvent::NoModifier, WidgetEventMouseMove);
}

int interactionStates[4] =
{
WidgetStateTranslate,
WidgetStateRotate,
WidgetStateScale,
WidgetStateUniformScale
};
for (int interactionState : interactionStates)
{
this->SetEventTranslation(interactionState, vtkCommand::RightButtonPressEvent, vtkEvent::NoModifier, WidgetEventCancel);
this->SetKeyboardEventTranslation(interactionState, vtkEvent::NoModifier, 0, 0, "Escape", WidgetEventCancel);
}
}

//----------------------------------------------------------------------
Expand Down Expand Up @@ -432,6 +443,9 @@ bool vtkMRMLInteractionWidget::ProcessInteractionEvent(vtkMRMLInteractionEventDa
case WidgetEventJumpCursor:
processedEvent = ProcessJumpCursor(eventData);
break;
case WidgetEventCancel:
processedEvent = ProcessCancelEvent(eventData);
break;
default:
break;
}
Expand Down Expand Up @@ -468,6 +482,8 @@ void vtkMRMLInteractionWidget::StartWidgetInteraction(vtkMRMLInteractionEventDat
}
rep->InteractingOn();

this->SaveInitialState();

double startEventPos[2]
{
static_cast<double>(eventData->GetDisplayPosition()[0]),
Expand All @@ -477,9 +493,6 @@ void vtkMRMLInteractionWidget::StartWidgetInteraction(vtkMRMLInteractionEventDat
// save the cursor position
this->LastEventPosition[0] = startEventPos[0];
this->LastEventPosition[1] = startEventPos[1];

this->StartEventOffsetPosition[0] = 0;
this->StartEventOffsetPosition[1] = 0;
}

//----------------------------------------------------------------------
Expand All @@ -496,7 +509,7 @@ void vtkMRMLInteractionWidget::EndWidgetInteraction()
//----------------------------------------------------------------------
void vtkMRMLInteractionWidget::TranslateWidget(double eventPos[2])
{
double lastEventPos_World[3] = { 0.0, 0.0, 0.0 };
double previousEventPos_World[3] = { 0.0, 0.0, 0.0 };
double eventPos_World[3] = { 0.0, 0.0, 0.0 };

vtkMRMLInteractionWidgetRepresentation* rep = vtkMRMLInteractionWidgetRepresentation::SafeDownCast(this->WidgetRep);
Expand All @@ -511,7 +524,7 @@ void vtkMRMLInteractionWidget::TranslateWidget(double eventPos[2])
double eventPos_Slice[3] = { 0.0, 0.0, 0.0 };
eventPos_Slice[0] = this->LastEventPosition[0];
eventPos_Slice[1] = this->LastEventPosition[1];
rep->GetSliceToWorldCoordinates(eventPos_Slice, lastEventPos_World);
rep->GetSliceToWorldCoordinates(eventPos_Slice, previousEventPos_World);

eventPos_Slice[0] = eventPos[0];
eventPos_Slice[1] = eventPos[1];
Expand All @@ -526,21 +539,21 @@ void vtkMRMLInteractionWidget::TranslateWidget(double eventPos[2])
0.0, 0.0, 1.0
};
if (!rep->GetPointPlacer()->ComputeWorldPosition(this->Renderer,
this->LastEventPosition, lastEventPos_World, orientation_World))
this->LastEventPosition, previousEventPos_World, orientation_World))
{
return;
}
if (!rep->GetPointPlacer()->ComputeWorldPosition(this->Renderer,
eventPos, lastEventPos_World, eventPos_World, orientation_World))
eventPos, previousEventPos_World, eventPos_World, orientation_World))
{
return;
}
}

double translationVector_World[3] = { 0.0, 0.0, 0.0 };
translationVector_World[0] = eventPos_World[0] - lastEventPos_World[0];
translationVector_World[1] = eventPos_World[1] - lastEventPos_World[1];
translationVector_World[2] = eventPos_World[2] - lastEventPos_World[2];
translationVector_World[0] = eventPos_World[0] - previousEventPos_World[0];
translationVector_World[1] = eventPos_World[1] - previousEventPos_World[1];
translationVector_World[2] = eventPos_World[2] - previousEventPos_World[2];

int index = this->GetActiveComponentIndex();
double translationAxis_World[3] = { 0.0, 0.0, 0.0 };
Expand Down Expand Up @@ -1009,3 +1022,45 @@ void vtkMRMLInteractionWidget::SetActiveComponentIndex(int index)
vtkSmartPointer<vtkMRMLInteractionWidgetRepresentation> rep = vtkMRMLInteractionWidgetRepresentation::SafeDownCast(this->WidgetRep);
rep->SetActiveComponentIndex(index);
}

//-------------------------------------------------------------------------
bool vtkMRMLInteractionWidget::ProcessCancelEvent(vtkMRMLInteractionEventData* eventData)
{
this->RestoreInitialState();
return this->ProcessEndMouseDrag(eventData);
}

//-------------------------------------------------------------------------
void vtkMRMLInteractionWidget::SaveInitialState()
{
vtkMRMLNode* node = this->GetMRMLNode();
if (!node)
{
vtkErrorMacro("SaveInitialState: Invalid transform node");
return;
}

this->OriginalStateNode = vtkSmartPointer<vtkMRMLNode>::Take(node->CreateNodeInstance());
this->OriginalStateNode->CopyContent(node);
}

//-------------------------------------------------------------------------
void vtkMRMLInteractionWidget::RestoreInitialState()
{
if (!this->OriginalStateNode)
{
vtkErrorMacro("RestoreInitialState: Original state node is not set");
return;
}

vtkMRMLNode* node = this->GetMRMLNode();
if (!node)
{
vtkErrorMacro("RestoreInitialState: Invalid transform node");
return;
}

node->CopyContent(this->OriginalStateNode);
this->OriginalStateNode = nullptr;
this->WidgetRep->NeedToRenderOn();
}
20 changes: 14 additions & 6 deletions Libs/MRML/DisplayableManager/vtkMRMLInteractionWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@
#ifndef vtkMRMLInteractionWidget_h
#define vtkMRMLInteractionWidget_h

// MRMLDisplayableManager includes
#include "vtkMRMLDisplayableManagerExport.h"

#include "vtkMRMLAbstractWidget.h"

// MRML includes
#include <vtkMRMLNode.h>

class vtkMRMLAbstractViewNode;
class vtkMRMLApplicationLogic;
class vtkMRMLDisplayNode;
Expand Down Expand Up @@ -68,16 +71,18 @@ class VTK_MRML_DISPLAYABLEMANAGER_EXPORT vtkMRMLInteractionWidget
WidgetStateOnTranslationHandle = WidgetStateInteraction_First, // hovering over a translation interaction handle
WidgetStateOnRotationHandle, // hovering over a rotation interaction handle
WidgetStateOnScaleHandle, // hovering over a scale interaction handle
WidgetStateUniformScale, // uniform scaling
WidgetStateInteraction_Last
};

/// Widget events
enum
{
WidgetEventReserved = WidgetEventUser, // this events is only to prevent other widgets from processing an event
WidgetStateUniformScale,
WidgetEventInteraction_First = WidgetEventUser,
WidgetEventReserved = WidgetEventInteraction_First, // this events is only to prevent other widgets from processing an event
WidgetEventUniformScaleStart,
WidgetEventUniformScaleEnd,
WidgetEventCancel,
WidgetEventInteraction_Last
};

Expand Down Expand Up @@ -129,6 +134,11 @@ class VTK_MRML_DISPLAYABLEMANAGER_EXPORT vtkMRMLInteractionWidget
virtual bool ProcessWidgetUniformScaleStart(vtkMRMLInteractionEventData* eventData);
virtual bool ProcessEndMouseDrag(vtkMRMLInteractionEventData* eventData);
virtual bool ProcessJumpCursor(vtkMRMLInteractionEventData* eventData);
virtual bool ProcessCancelEvent(vtkMRMLInteractionEventData* eventData);

virtual vtkMRMLNode* GetMRMLNode() = 0;
virtual void SaveInitialState();
virtual void RestoreInitialState();

// Jump to the handle position for the given type and index. Returns true if successful.
virtual bool JumpToHandlePosition(int type, int index);
Expand All @@ -143,10 +153,8 @@ class VTK_MRML_DISPLAYABLEMANAGER_EXPORT vtkMRMLInteractionWidget

/// Variables for translate/rotate/scale
double LastEventPosition[2];
double StartEventOffsetPosition[2];

bool ConvertDisplayPositionToWorld(const int displayPos[2],
double worldPos[3], double worldOrientationMatrix[9], double* refWorldPos = nullptr);
vtkSmartPointer<vtkMRMLNode> OriginalStateNode{ nullptr };

private:
vtkMRMLInteractionWidget(const vtkMRMLInteractionWidget&) = delete;
Expand Down
8 changes: 6 additions & 2 deletions Modules/Loadable/Markups/MRML/vtkMRMLMarkupsPlaneNode.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,11 @@ void vtkMRMLMarkupsPlaneNode::CopyContent(vtkMRMLNode* anode, bool deepCopy/*=tr
MRMLNodeModifyBlocker blocker(this);
vtkMRMLCopyBeginMacro(anode);
vtkMRMLCopyEnumMacro(PlaneType);
vtkMRMLCopyIntMacro(MaximumNumberOfControlPoints);
vtkMRMLCopyIntMacro(RequiredNumberOfControlPoints);
vtkMRMLCopyEnumMacro(SizeMode);
vtkMRMLCopyVectorMacro(Size, double, 2);
vtkMRMLCopyVectorMacro(Normal, double, 3);
vtkMRMLCopyVectorMacro(Center, double, 3);
vtkMRMLCopyVectorMacro(PlaneBounds, double, 4);
vtkMRMLCopyFloatMacro(AutoSizeScalingFactor);
vtkMRMLCopyEndMacro();

Expand All @@ -126,10 +125,15 @@ void vtkMRMLMarkupsPlaneNode::PrintSelf(ostream& os, vtkIndent indent)
{
Superclass::PrintSelf(os,indent);
vtkMRMLPrintBeginMacro(os, indent);
vtkMRMLPrintEnumMacro(PlaneType);
vtkMRMLPrintEnumMacro(SizeMode);
vtkMRMLPrintVectorMacro(Size, double, 2);
vtkMRMLPrintVectorMacro(Normal, double, 3);
vtkMRMLPrintVectorMacro(Center, double, 3);
vtkMRMLPrintVectorMacro(PlaneBounds, double, 4);
vtkMRMLPrintFloatMacro(AutoSizeScalingFactor);
vtkMRMLPrintMatrix4x4Macro(ObjectToBaseMatrix);
vtkMRMLPrintMatrix4x4Macro(BaseToNodeMatrix);
vtkMRMLPrintEndMacro();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -938,3 +938,9 @@ void vtkSlicerMarkupsInteractionWidget::FlipROIHandles(bool flipLRHandle, bool f

displayNode->SetActiveComponent(displayNode->GetActiveComponentType(), index);
}

//-------------------------------------------------------------------------
vtkMRMLNode* vtkSlicerMarkupsInteractionWidget::GetMRMLNode()
{
return this->GetMarkupsNode();
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class VTK_SLICER_MARKUPS_MODULE_VTKWIDGETS_EXPORT vtkSlicerMarkupsInteractionWid

bool ProcessWidgetMenu(vtkMRMLInteractionEventData* eventData) override;

vtkMRMLNode* GetMRMLNode() override;

protected:
vtkSlicerMarkupsInteractionWidget();
~vtkSlicerMarkupsInteractionWidget() override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ vtkMRMLTransformHandleWidget::vtkMRMLTransformHandleWidget()
// Handle interactions
this->SetEventTranslationClickAndDrag(WidgetStateOnTranslationHandle, vtkCommand::LeftButtonPressEvent, vtkEvent::AltModifier,
WidgetStateTranslateTransformCenter, WidgetEventTranslateTransformCenterStart, WidgetEventTranslateTransformCenterEnd);

this->SetEventTranslation(WidgetStateTranslateTransformCenter, vtkCommand::RightButtonPressEvent, vtkEvent::NoModifier, WidgetEventCancel);
this->SetKeyboardEventTranslation(WidgetStateTranslateTransformCenter, vtkEvent::NoModifier, 0, 0, "Escape", WidgetEventCancel);
}

//----------------------------------------------------------------------
Expand Down Expand Up @@ -414,3 +417,9 @@ bool vtkMRMLTransformHandleWidget::ProcessJumpCursor(vtkMRMLInteractionEventData

return Superclass::ProcessJumpCursor(eventData);
}

//-------------------------------------------------------------------------
vtkMRMLNode* vtkMRMLTransformHandleWidget::GetMRMLNode()
{
return this->GetTransformNode();
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
==============================================================================*/


///
/// \class vtkMRMLTransformHandleWidget
/// \brief Process interaction events to update state of interaction widgets
Expand All @@ -33,16 +32,17 @@

#include "vtkMRMLInteractionWidget.h"

class vtkIdList;
class vtkMatrix4x4;
class vtkMRMLAbstractViewNode;
class vtkMRMLApplicationLogic;
class vtkMRMLDisplayableNode;
class vtkMRMLInteractionEventData;
class vtkMRMLInteractionNode;
class vtkIdList;
class vtkPolyData;
class vtkTransform;
class vtkMRMLTransformDisplayNode;
class vtkMRMLTransformNode;
class vtkPolyData;
class vtkTransform;

class VTK_SLICER_TRANSFORMS_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLTransformHandleWidget
: public vtkMRMLInteractionWidget
Expand Down Expand Up @@ -92,6 +92,8 @@ class VTK_SLICER_TRANSFORMS_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLTransfor

void ApplyTransform(vtkTransform* transform) override;

vtkMRMLNode* GetMRMLNode() override;

private:
vtkMRMLTransformHandleWidget(const vtkMRMLTransformHandleWidget&) = delete;
void operator=(const vtkMRMLTransformHandleWidget&) = delete;
Expand Down

0 comments on commit 401a4b2

Please sign in to comment.