// 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" #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 FWidget::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 FWidget::WM_Translate: case FWidget::WM_TranslateRotateZ: case FWidget::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 FWidget::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() == FWidget::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 FWidget::WM_Translate: FSnappingUtils::SnapPointToGrid(StartSnapped, FVector(GEditor->GetGridSize(), GEditor->GetGridSize(), GEditor->GetGridSize())); break; case FWidget::WM_Scale: FSnappingUtils::SnapScale(StartSnapped, FVector(GEditor->GetGridSize(), GEditor->GetGridSize(), GEditor->GetGridSize())); break; case FWidget::WM_Rotate: { FRotator Rotation(StartSnapped.X, StartSnapped.Y, StartSnapped.Z); FSnappingUtils::SnapRotatorToGrid(Rotation); StartSnapped = FVector(Rotation.Pitch, Rotation.Yaw, Rotation.Roll); } break; case FWidget::WM_TranslateRotateZ: case FWidget::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) { 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, InDelta, InNudge); EndScreen += Wk; if (InViewportClient->GetCurrentWidgetAxis() != EAxisList::None) { // Affect input delta by the camera speed FWidget::EWidgetMode WidgetMode = InViewportClient->GetWidgetMode(); bool bIsRotation = (WidgetMode == FWidget::WM_Rotate) || ((WidgetMode == FWidget::WM_TranslateRotateZ) && (InViewportClient->GetCurrentWidgetAxis() == EAxisList::ZRotation)) || ((WidgetMode == FWidget::WM_2D) && (InViewportClient->GetCurrentWidgetAxis() == EAxisList::Rotate2D)); if (bIsRotation) { Wk *= GetDefault()->MouseSensitivty; } else if (WidgetMode == FWidget::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) { float Scale = 1.0f; if (InViewportClient->IsOrtho()) { Scale = DEFAULT_ORTHOZOOM / (float)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 FWidget::WM_Translate: FSnappingUtils::SnapPointToGrid(EndSnapped, FVector(GEditor->GetGridSize(), GEditor->GetGridSize(), GEditor->GetGridSize())); break; case FWidget::WM_Scale: FSnappingUtils::SnapScale(EndSnapped, FVector(GEditor->GetGridSize(), GEditor->GetGridSize(), GEditor->GetGridSize())); break; case FWidget::WM_Rotate: { FRotator Rotation(EndSnapped.X, EndSnapped.Y, EndSnapped.Z); FSnappingUtils::SnapRotatorToGrid(Rotation); EndSnapped = FVector(Rotation.Pitch, Rotation.Yaw, Rotation.Roll); } break; case FWidget::WM_TranslateRotateZ: case FWidget::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); } 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