// Copyright Epic Games, Inc. All Rights Reserved. #include "MouseDeltaTracker.h" #include "EngineDefines.h" #include "SceneView.h" #include "EditorViewportClient.h" #include "Settings/LevelEditorViewportSettings.h" #include "Editor.h" #include "EditorDragTools.h" #include "SnappingUtils.h" #include "UnrealWidget.h" #include "EditorDragTools/EditorDragToolBehaviorTarget.h" #include "Misc/AxisDisplayInfo.h" #define LOCTEXT_NAMESPACE "MouseDeltaTracker" /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // FMouseDeltaTracker // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// FMouseDeltaTracker::FMouseDeltaTracker() : Start( FVector::ZeroVector ) , StartSnapped( FVector::ZeroVector ) , StartScreen( FVector::ZeroVector ) , End( FVector::ZeroVector ) , EndSnapped( FVector::ZeroVector ) , EndScreen( FVector::ZeroVector ) , RawDelta( FVector::ZeroVector ) , ReductionAmount( FVector::ZeroVector ) , DragTool( NULL ) , bHasAttemptedDragTool(false) , bUsedDragModifier(false) , bIsDeletingDragTool(false) { } FMouseDeltaTracker::~FMouseDeltaTracker() { } /** * Sets the current axis of the widget for the specified viewport. * * @param InViewportClient The viewport whose widget axis is to be set. */ void FMouseDeltaTracker::DetermineCurrentAxis(FEditorViewportClient* InViewportClient) { const bool AltDown = InViewportClient->IsAltPressed(); const bool ShiftDown = InViewportClient->IsShiftPressed(); const bool ControlDown = InViewportClient->IsCtrlPressed(); const bool LeftMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::LeftMouseButton); const bool RightMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::RightMouseButton); const bool MiddleMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::MiddleMouseButton); const bool bIsRotateObjectMode = InViewportClient->IsOrtho() && ControlDown && RightMouseButtonDown; // Ctrl + LEFT/RIGHT mouse button acts the same as dragging the most appropriate widget handle. if( (!InViewportClient->ShouldOrbitCamera() && bIsRotateObjectMode ) || ((!bIsRotateObjectMode && ControlDown && !AltDown ) && (LeftMouseButtonDown || RightMouseButtonDown)) ) { // Only try to pick an axis if we're not dragging by widget handle. if ( InViewportClient->GetCurrentWidgetAxis() == EAxisList::None ) { switch( InViewportClient->GetWidgetMode() ) { case UE::Widget::WM_Scale: // Non-uniform scale when shift is down, uniform when it is up if (ShiftDown) { switch( InViewportClient->ViewportType ) { case LVT_Perspective: if( LeftMouseButtonDown && !RightMouseButtonDown ) { InViewportClient->SetCurrentWidgetAxis( EAxisList::X ); } else if( !LeftMouseButtonDown && RightMouseButtonDown ) { InViewportClient->SetCurrentWidgetAxis( EAxisList::Y ); } else if( LeftMouseButtonDown && RightMouseButtonDown ) { InViewportClient->SetCurrentWidgetAxis( EAxisList::Z ); } break; case LVT_OrthoXY: case LVT_OrthoNegativeXY: InViewportClient->SetCurrentWidgetAxis( EAxisList::XY ); break; case LVT_OrthoXZ: case LVT_OrthoNegativeXZ: InViewportClient->SetCurrentWidgetAxis( EAxisList::XZ ); break; case LVT_OrthoYZ: case LVT_OrthoNegativeYZ: InViewportClient->SetCurrentWidgetAxis( EAxisList::YZ ); break; default: break; } } else { InViewportClient->SetCurrentWidgetAxis( EAxisList::XYZ ); } break; case UE::Widget::WM_Translate: case UE::Widget::WM_TranslateRotateZ: case UE::Widget::WM_2D: switch( InViewportClient->ViewportType ) { case LVT_Perspective: if( LeftMouseButtonDown && !RightMouseButtonDown ) { InViewportClient->SetCurrentWidgetAxis( EAxisList::X ); } else if( !LeftMouseButtonDown && RightMouseButtonDown ) { InViewportClient->SetCurrentWidgetAxis( EAxisList::Y ); } else if( LeftMouseButtonDown && RightMouseButtonDown ) { InViewportClient->SetCurrentWidgetAxis( EAxisList::Z ); } break; case LVT_OrthoXY: case LVT_OrthoNegativeXY: InViewportClient->SetCurrentWidgetAxis(EAxisList::XY); break; case LVT_OrthoXZ: case LVT_OrthoNegativeXZ: InViewportClient->SetCurrentWidgetAxis(EAxisList::XZ); break; case LVT_OrthoYZ: case LVT_OrthoNegativeYZ: InViewportClient->SetCurrentWidgetAxis(EAxisList::YZ); break; default: break; } break; case UE::Widget::WM_Rotate: switch( InViewportClient->ViewportType ) { case LVT_Perspective: if (LeftMouseButtonDown && !RightMouseButtonDown) { InViewportClient->SetCurrentWidgetAxis(EAxisList::X); } else if (!LeftMouseButtonDown && RightMouseButtonDown) { InViewportClient->SetCurrentWidgetAxis(EAxisList::Y); } else if (LeftMouseButtonDown && RightMouseButtonDown) { InViewportClient->SetCurrentWidgetAxis(EAxisList::Z); } break; case LVT_OrthoXY: case LVT_OrthoNegativeXY: InViewportClient->SetCurrentWidgetAxis(EAxisList::Z); break; case LVT_OrthoXZ: case LVT_OrthoNegativeXZ: InViewportClient->SetCurrentWidgetAxis(EAxisList::Y); break; case LVT_OrthoYZ: case LVT_OrthoNegativeYZ: InViewportClient->SetCurrentWidgetAxis(EAxisList::X); break; default: break; } break; default: break; } //if we now have a widget axis we must have used a modifier to get it if( InViewportClient->GetCurrentWidgetAxis() != EAxisList::None ) { bUsedDragModifier = true; } } } } /** * Begin tracking at the specified location for the specified viewport. */ void FMouseDeltaTracker::StartTracking(FEditorViewportClient* InViewportClient, const int32 InX, const int32 InY, const FInputEventState& InInputState, bool bNudge, bool bResetDragToolState) { DetermineCurrentAxis(InViewportClient); // Initialize widget axis (in case it hasn't been set by the hovered hit proxy) if (InViewportClient->Widget && InViewportClient->GetCurrentWidgetAxis() == EAxisList::None) { check(InViewportClient->Viewport); HHitProxy* HitProxy = InViewportClient->Viewport->GetHitProxy(InX, InY); if (HitProxy && HitProxy->IsA(HWidgetAxis::StaticGetType())) { EAxisList::Type ProxyAxis = ((HWidgetAxis*)HitProxy)->Axis; InViewportClient->SetCurrentWidgetAxis(ProxyAxis); } } const bool AltDown = InViewportClient->IsAltPressed(); const bool ShiftDown = InViewportClient->IsShiftPressed(); const bool ControlDown = InViewportClient->IsCtrlPressed(); const bool LeftMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::LeftMouseButton); const bool RightMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::RightMouseButton); const bool MiddleMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::MiddleMouseButton); bool bIsDragging = ((ControlDown || ShiftDown) && (LeftMouseButtonDown || RightMouseButtonDown || MiddleMouseButtonDown)) || (InViewportClient->GetCurrentWidgetAxis() != EAxisList::None) || bNudge; // Update bWidgetAxisControlledByDrag since we now know that we have begun dragging an object with the mouse. if ( bIsDragging ) { InViewportClient->bWidgetAxisControlledByDrag = true; } InViewportClient->TrackingStarted( InInputState, bIsDragging, bNudge ); if (InViewportClient->Widget) { InViewportClient->Widget->SetDragStartPosition(FVector2D(InX, InY)); InViewportClient->Widget->SetDragging(bIsDragging); if (InViewportClient->GetWidgetMode() == UE::Widget::WM_Rotate) { InViewportClient->Invalidate(); } } // Clear bool that tracks whether AddDelta has been called bHasReceivedAddDelta = false; if( bResetDragToolState ) { bHasAttemptedDragTool = false; } ensure( !DragTool.IsValid() ); StartSnapped = Start = StartScreen = FVector( InX, InY, 0 ); RawDelta = FVector::ZeroVector; TrackingWidgetMode = InViewportClient->GetWidgetMode(); // No drag tool is active, so handle snapping. switch( TrackingWidgetMode ) { case UE::Widget::WM_Translate: FSnappingUtils::SnapPointToGrid( StartSnapped, FVector(GEditor->GetGridSize(),GEditor->GetGridSize(),GEditor->GetGridSize()) ); break; case UE::Widget::WM_Scale: FSnappingUtils::SnapScale( StartSnapped, FVector(GEditor->GetGridSize(),GEditor->GetGridSize(),GEditor->GetGridSize()) ); break; case UE::Widget::WM_Rotate: { FRotator Rotation( StartSnapped.X, StartSnapped.Y, StartSnapped.Z ); FSnappingUtils::SnapRotatorToGrid( Rotation ); StartSnapped = FVector( Rotation.Pitch, Rotation.Yaw, Rotation.Roll ); } break; case UE::Widget::WM_TranslateRotateZ: case UE::Widget::WM_2D: FSnappingUtils::SnapPointToGrid( StartSnapped, FVector(GEditor->GetGridSize(),GEditor->GetGridSize(),GEditor->GetGridSize()) ); break; default: break; } // Clear any snapping helpers on new movement const bool bClearImmediatley = true; FSnappingUtils::ClearSnappingHelpers( bClearImmediatley ); End = EndScreen = Start; EndSnapped = StartSnapped; bExternalMovement = false; //no external movement has occurred yet. InViewportClient->Widget->ResetDeltaRotation(); } /** * Called when a mouse button has been released. If there are no other * mouse buttons being held down, the internal information is reset. */ bool FMouseDeltaTracker::EndTracking(FEditorViewportClient* InViewportClient) { DetermineCurrentAxis( InViewportClient ); if (InViewportClient->Widget) { InViewportClient->Widget->SetDragging(false); } InViewportClient->TrackingStopped(); InViewportClient->Widget->ResetDeltaRotation(); Start = StartSnapped = StartScreen = End = EndSnapped = EndScreen = RawDelta = ReductionAmount = FVector::ZeroVector; if (!bIsDeletingDragTool) { // Ending the drag tool may pop up a modal dialog which can cause unwanted reentrancy - protect against this. TGuardValue RecursionGuard(bIsDeletingDragTool, true); // Delete the drag tool if one exists. if (DragTool.IsValid()) { if (DragTool->IsDragging()) { DragTool->EndDrag(); } DragTool.Reset(); return false; } } // Do not fade snapping indicators over time if this viewport is not real time bool bClearImmediately = !InViewportClient->IsRealtime(); FSnappingUtils::ClearSnappingHelpers( bClearImmediately ); return true; } void FMouseDeltaTracker::ConditionalBeginUsingDragTool( FEditorViewportClient* InViewportClient ) { if (UE::Editor::DragTools::UseEditorDragTools()) { return; } const bool LeftMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::LeftMouseButton); const bool RightMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::RightMouseButton); const bool MiddleMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::MiddleMouseButton); const bool bAltDown = InViewportClient->IsAltPressed(); const bool bShiftDown = InViewportClient->IsShiftPressed(); const bool bControlDown = InViewportClient->IsCtrlPressed(); // Has there been enough mouse movement to begin using a drag tool. We don't want to start using a tool for clicks(could have very small mouse movements) bool bEnoughMouseMovement = GetRawDelta().SizeSquared() > MOUSE_CLICK_DRAG_DELTA; if( bEnoughMouseMovement ) { const bool bCanDrag = !DragTool.IsValid() && !RightMouseButtonDown && InViewportClient->CanUseDragTool(); if (bCanDrag && !bHasAttemptedDragTool) { // Create a drag tool. if (!(bAltDown + bShiftDown) && bControlDown && MiddleMouseButtonDown && !LeftMouseButtonDown && !RightMouseButtonDown) { DragTool = InViewportClient->MakeDragTool(EDragTool::ViewportChange); } else { if (InViewportClient->IsOrtho()) { if (LeftMouseButtonDown) { DragTool = InViewportClient->MakeDragTool(EDragTool::BoxSelect); } else if (!(bControlDown + bAltDown + bShiftDown) && MiddleMouseButtonDown) { DragTool = InViewportClient->MakeDragTool(EDragTool::Measure); } } else { if (LeftMouseButtonDown && bControlDown && bAltDown) { DragTool = InViewportClient->MakeDragTool(EDragTool::FrustumSelect); } } } if (DragTool.IsValid()) { DragTool->StartDrag(InViewportClient, GEditor->ClickLocation, FVector2D(StartScreen)); } } // Can not attempt to use a drag tool the rest of this tracking session bHasAttemptedDragTool = true; } } /** * Adds delta movement into the tracker. */ void FMouseDeltaTracker::AddDelta(FEditorViewportClient* InViewportClient, FKey InKey, const int32 InDelta, bool InNudge) { const bool LeftMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::LeftMouseButton); const bool RightMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::RightMouseButton); const bool MiddleMouseButtonDown = InViewportClient->Viewport->KeyState(EKeys::MiddleMouseButton); const bool bAltDown = InViewportClient->IsAltPressed(); const bool bShiftDown = InViewportClient->IsShiftPressed(); const bool bControlDown = InViewportClient->IsCtrlPressed(); if( !LeftMouseButtonDown && !MiddleMouseButtonDown && !RightMouseButtonDown && !InNudge ) { return; } // Accumulate raw delta RawDelta += FVector(InKey == EKeys::MouseX ? InDelta : 0, InKey == EKeys::MouseY ? InDelta : 0, 0); // Note that AddDelta has been called since StartTracking bHasReceivedAddDelta = true; // If we are using a drag tool, the widget isn't involved so set it to having no active axis. This // means we will get unmodified mouse movement returned to us by other functions. const EAxisList::Type SaveAxis = InViewportClient->GetCurrentWidgetAxis(); // If the user isn't dragging with the left mouse button, clear out the axis // as the widget only responds to the left mouse button. // // We allow an exception for dragging with the left and/or right mouse button while holding control // as that simulates moving objects with the gizmo // // We also allow the exception of the middle mouse button when Alt is pressed, or when the current axis is the pivot centre, as it // allows movement of only the pivot const bool bIsOrthoObjectRotation = bControlDown && InViewportClient->IsOrtho(); const bool bUsingDragTool = UsingDragTool(); const bool bUsingAxis = !bUsingDragTool && (LeftMouseButtonDown || (bAltDown && MiddleMouseButtonDown) || (SaveAxis == EAxisList::Screen && MiddleMouseButtonDown) || ((bIsOrthoObjectRotation || bControlDown) && RightMouseButtonDown)); ConditionalBeginUsingDragTool( InViewportClient ); if( bUsingDragTool || !InViewportClient->IsTracking() || !bUsingAxis ) { InViewportClient->SetCurrentWidgetAxis( EAxisList::None ); } FVector Wk = InViewportClient->TranslateDelta( InKey, static_cast(InDelta), InNudge ); EndScreen += Wk; if( InViewportClient->GetCurrentWidgetAxis() != EAxisList::None ) { // Affect input delta by the camera speed UE::Widget::EWidgetMode WidgetMode = InViewportClient->GetWidgetMode(); bool bIsRotation = (WidgetMode == UE::Widget::WM_Rotate) || ( ( WidgetMode == UE::Widget::WM_TranslateRotateZ ) && ( InViewportClient->GetCurrentWidgetAxis() == EAxisList::ZRotation ) ) || ( ( WidgetMode == UE::Widget::WM_2D) && (InViewportClient->GetCurrentWidgetAxis() == EAxisList::Rotate2D ) ); if (bIsRotation) { Wk *= GetDefault()->MouseSensitivty; } else if( WidgetMode == UE::Widget::WM_Scale && !GEditor->UsePercentageBasedScaling() ) { const float ScaleSpeedMultipler = 0.01f; Wk *= ScaleSpeedMultipler; } // Make rotations occur at the same speed, regardless of ortho zoom if( InViewportClient->IsOrtho() ) { if (bIsRotation) { double Scale = 1.0; if( InViewportClient->IsOrtho() ) { Scale = DEFAULT_ORTHOZOOM / InViewportClient->GetOrthoZoom(); } Wk *= Scale; } } //if Absolute Translation, and not just moving the camera around else if (InViewportClient->IsUsingAbsoluteTranslation(false)) { // Compute a view. FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues( InViewportClient->Viewport, InViewportClient->GetScene(), InViewportClient->EngineShowFlags ) .SetRealtimeUpdate( InViewportClient->IsRealtime() )); FSceneView* View = InViewportClient->CalcSceneView( &ViewFamily ); //calculate mouse position check(InViewportClient->Viewport); FVector2D MousePosition(InViewportClient->Viewport->GetMouseX(), InViewportClient->Viewport->GetMouseY()); FVector WidgetPosition = InViewportClient->GetWidgetLocation(); FRotator TempRot; FVector TempScale; InViewportClient->Widget->AbsoluteTranslationConvertMouseMovementToAxisMovement(View, InViewportClient, WidgetPosition, MousePosition, Wk, TempRot, TempScale ); } } End += Wk; EndSnapped = End; if( UsingDragTool() ) { FVector Drag = Wk; if( DragTool->bConvertDelta ) { FRotator Rot; InViewportClient->ConvertMovementToDragRot( Wk, Drag, Rot ); } if ( InViewportClient->IsPerspective() ) { DragTool->AddDelta(Wk); } else { DragTool->AddDelta( Drag ); } InViewportClient->SetCurrentWidgetAxis( SaveAxis ); } else { switch( InViewportClient->GetWidgetMode() ) { case UE::Widget::WM_Translate: FSnappingUtils::SnapPointToGrid( EndSnapped, FVector(GEditor->GetGridSize(),GEditor->GetGridSize(),GEditor->GetGridSize()) ); break; case UE::Widget::WM_Scale: FSnappingUtils::SnapScale( EndSnapped, FVector(GEditor->GetGridSize(),GEditor->GetGridSize(),GEditor->GetGridSize()) ); break; case UE::Widget::WM_Rotate: { FRotator Rotation( EndSnapped.X, EndSnapped.Y, EndSnapped.Z ); FSnappingUtils::SnapRotatorToGrid( Rotation ); EndSnapped = FVector( Rotation.Pitch, Rotation.Yaw, Rotation.Roll ); } break; case UE::Widget::WM_TranslateRotateZ: case UE::Widget::WM_2D: { if (InViewportClient->GetCurrentWidgetAxis() == EAxisList::Rotate2D) { FRotator Rotation( EndSnapped.X, EndSnapped.Y, EndSnapped.Z ); FSnappingUtils::SnapRotatorToGrid( Rotation ); EndSnapped = FVector( Rotation.Pitch, Rotation.Yaw, Rotation.Roll ); } else { //translation (either xy plane or z) FSnappingUtils::SnapPointToGrid( EndSnapped, FVector(GEditor->GetGridSize(),GEditor->GetGridSize(),GEditor->GetGridSize()) ); } } default: break; } } } /** * Returns the raw mouse delta, in pixels. */ const FVector FMouseDeltaTracker::GetRawDelta() const { return RawDelta; } /** * Returns the current delta. */ const FVector FMouseDeltaTracker::GetDelta() const { const FVector Delta( End - Start ); return Delta; } /** * Returns the current snapped delta. */ const FVector FMouseDeltaTracker::GetDeltaSnapped() const { const FVector SnappedDelta( EndSnapped - StartSnapped ); return SnappedDelta; } /** * Returns the absolute delta since dragging started. */ const FVector FMouseDeltaTracker::GetAbsoluteDelta() const { const FVector Delta( End - Start + ReductionAmount ); return Delta; } /** * Returns the absolute snapped delta since dragging started. */ const FVector FMouseDeltaTracker::GetAbsoluteDeltaSnapped() const { const FVector SnappedDelta( EndSnapped - StartSnapped + ReductionAmount ); return SnappedDelta; } /** * Returns the screen space delta since dragging started. */ const FVector FMouseDeltaTracker::GetScreenDelta() const { const FVector Delta( EndScreen - StartScreen ); return Delta; } /** * Converts the delta movement to drag/rotation/scale based on the viewport type or widget axis */ void FMouseDeltaTracker::ConvertMovementDeltaToDragRot(FSceneView* InView, FEditorViewportClient* InViewportClient, FVector& InOutDragDelta, FVector& OutDrag, FRotator& OutRotation, FVector& OutScale) const { OutDrag = FVector::ZeroVector; OutRotation = FRotator::ZeroRotator; OutScale = FVector::ZeroVector; if( InViewportClient->GetCurrentWidgetAxis() != EAxisList::None ) { InViewportClient->Widget->ConvertMouseMovementToAxisMovement( InView, InViewportClient, bUsedDragModifier, InOutDragDelta, OutDrag, OutRotation, OutScale ); if (AxisDisplayInfo::GetAxisDisplayCoordinateSystem() == EAxisList::LeftUpForward) { OutDrag.Y = -OutDrag.Y; } } else { InViewportClient->ConvertMovementToDragRot( InOutDragDelta, OutDrag, OutRotation ); } } /** * Absolute Translation conversion from mouse position on the screen to widget axis movement/rotation. */ void FMouseDeltaTracker::AbsoluteTranslationConvertMouseToDragRot(FSceneView* InView, FEditorViewportClient* InViewportClient,FVector& OutDrag, FRotator& OutRotation, FVector& OutScale ) const { OutDrag = FVector::ZeroVector; OutRotation = FRotator::ZeroRotator; OutScale = FVector::ZeroVector; check ( InViewportClient->GetCurrentWidgetAxis() != EAxisList::None ); //calculate mouse position check(InViewportClient->Viewport); FVector2D MousePosition(InViewportClient->Viewport->GetMouseX(), InViewportClient->Viewport->GetMouseY()); InViewportClient->Widget->AbsoluteTranslationConvertMouseMovementToAxisMovement(InView, InViewportClient, InViewportClient->GetWidgetLocation(), MousePosition, OutDrag, OutRotation, OutScale ); } /** * Subtracts the specified value from End and EndSnapped. */ void FMouseDeltaTracker::ReduceBy(const FVector& In) { End -= In; EndSnapped -= In; ReductionAmount += In; } /** * @return true if a drag tool is being used by the tracker, false otherwise. */ bool FMouseDeltaTracker::UsingDragTool() const { return DragTool.IsValid() && DragTool->IsDragging() ; } /** * Renders the drag tool. Does nothing if no drag tool exists. */ void FMouseDeltaTracker::Render3DDragTool(const FSceneView* View,FPrimitiveDrawInterface* PDI) { if ( DragTool.IsValid() ) { DragTool->Render3D( View, PDI ); } } /** * Renders the drag tool. Does nothing if no drag tool exists. */ void FMouseDeltaTracker::RenderDragTool(const FSceneView* View,FCanvas* Canvas) { if ( DragTool.IsValid() ) { DragTool->Render( View, Canvas ); } } const FVector FMouseDeltaTracker::GetDragStartPos() const { return Start; } const bool FMouseDeltaTracker::GetUsedDragModifier() const { return bUsedDragModifier; } void FMouseDeltaTracker::ResetUsedDragModifier() { bUsedDragModifier = false; } #undef LOCTEXT_NAMESPACE