// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= EditorModeInterpolation : Editor mode for setting up interpolation sequences. =============================================================================*/ #include "EditorModeInterpolation.h" #include "EditorViewportClient.h" #include "Modules/ModuleManager.h" #include "Editor/GroupActor.h" #include "EditorModeManager.h" #include "EditorModes.h" #include "InterpolationHitProxy.h" #include "Editor/Matinee/Public/MatineeModule.h" #include "Editor/Matinee/Public/IMatinee.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "ActorGroupingUtils.h" static const float CurveHandleScale = 0.5f; ////////////////////////////////////////////////////////////////////////// // FEdModeInterpEdit ////////////////////////////////////////////////////////////////////////// FEdModeInterpEdit::FEdModeInterpEdit() { MatineeActor = NULL; InterpEd = NULL; Tools.Add(new FModeTool_InterpEdit()); SetCurrentTool(MT_InterpEdit); bLeavingMode = false; } FEdModeInterpEdit::~FEdModeInterpEdit() { } bool FEdModeInterpEdit::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) { // Enter key drops new key frames if (Key == EKeys::Enter && (Event == IE_Pressed || Event == IE_Repeat) && (!ViewportClient->IsShiftPressed() && !ViewportClient->IsAltPressed() && !ViewportClient->IsCtrlPressed())) { if (InterpEd != NULL) { // Add a new key! InterpEd->AddKey(); } return true; } return FEdMode::InputKey(ViewportClient, Viewport, Key, Event); } void FEdModeInterpEdit::Enter() { FEdMode::Enter(); // Disable Grouping while in InterpEdit mode bGroupingActiveSaved = UActorGroupingUtils::IsGroupingActive(); UActorGroupingUtils::SetGroupingActive(false); } void FEdModeInterpEdit::Exit() { MatineeActor = NULL; // If there is one, close the Interp Editor and clear pointers. if (InterpEd != NULL) { bLeavingMode = true; // This is so the editor being closed doesn't try and change the mode again! InterpEd->Close(true); bLeavingMode = false; } InterpEd = NULL; // Grouping is always disabled while in InterpEdit Mode, re-enable the saved value on exit UActorGroupingUtils::SetGroupingActive(bGroupingActiveSaved); AGroupActor::SelectGroupsInSelection(); FEdMode::Exit(); } // We see if we have void FEdModeInterpEdit::ActorMoveNotify() { if (!InterpEd) return; InterpEd->ActorModified(); } void FEdModeInterpEdit::CamMoveNotify(FEditorViewportClient* ViewportClient) { if (!InterpEd) return; if (ViewportClient->AllowsCinematicControl()) { InterpEd->CamMoved(ViewportClient->GetViewLocation(), ViewportClient->GetViewRotation()); } } void FEdModeInterpEdit::ActorPropChangeNotify() { if (!InterpEd) return; InterpEd->ActorModified(); } void FEdModeInterpEdit::UpdateSelectedActor() { // would this be enough? InterpEd->ActorSelectionChange(); } // Set the currently edited MatineeActor and open timeline window. Should always be called after we change to FBuiltinEditorModes::EM_InterpEdit. void FEdModeInterpEdit::InitInterpMode(AMatineeActor* InMatineeActor) { check(InMatineeActor); check(!InterpEd); MatineeActor = InMatineeActor; EToolkitMode::Type Mode = EToolkitMode::Standalone; IMatineeModule* MatineeModule = &FModuleManager::LoadModuleChecked("Matinee"); InterpEd = &(MatineeModule->CreateMatinee(Mode, TSharedPtr(), InMatineeActor).Get()); } void FEdModeInterpEdit::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) { FEdMode::Render(View, Viewport, PDI); check(GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_InterpEdit)); if (InterpEd && !InterpEd->Hide3DTrackView() && View->Family->EngineShowFlags.Splines) { InterpEd->DrawTracks3D(View, PDI); } } void FEdModeInterpEdit::DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) { FEdMode::DrawHUD(ViewportClient, Viewport, View, Canvas); if (InterpEd) { InterpEd->DrawModeHUD(ViewportClient, Viewport, View, Canvas); } } bool FEdModeInterpEdit::AllowWidgetMove() { FModeTool_InterpEdit* InterpTool = (FModeTool_InterpEdit*)FindTool(MT_InterpEdit); if (InterpTool->bMovingHandle) { return false; } else { return true; } } void FEdModeInterpEdit::ActorSelectionChangeNotify() { FEdMode::ActorSelectionChangeNotify(); if (InterpEd) { InterpEd->ActorSelectionChange(); } } bool FEdModeInterpEdit::IsCompatibleWith(FEditorModeID OtherModeID) const { return OtherModeID == FBuiltinEditorModes::EM_Placement || OtherModeID == FBuiltinEditorModes::EM_MeshPaint; } ////////////////////////////////////////////////////////////////////////// // FModeTool_InterpEdit ////////////////////////////////////////////////////////////////////////// FModeTool_InterpEdit::FModeTool_InterpEdit() { ID = MT_InterpEdit; bMovingHandle = false; DragGroup = NULL; DragTrackIndex = false; DragKeyIndex = false; bDragArriving = false; } FModeTool_InterpEdit::~FModeTool_InterpEdit() { } bool FModeTool_InterpEdit::MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) { check(GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_InterpEdit)); FEdModeInterpEdit* mode = (FEdModeInterpEdit*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_InterpEdit); return 0; } bool FModeTool_InterpEdit::InputAxis(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime) { if (InViewportClient->GetCurrentWidgetAxis() == EAxisList::None && !InViewportClient->Viewport->KeyState(EKeys::MiddleMouseButton)) { // We need to set up a widget axis here to prevent our drag operation being co-opted by box/frustum selection ELevelViewportType ViewportType = InViewportClient->ViewportType; switch (ViewportType) { 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; case LVT_OrthoFreelook: case LVT_Perspective: break; } } return false; } bool FModeTool_InterpEdit::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) { check(GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_InterpEdit)); FEdModeInterpEdit* mode = (FEdModeInterpEdit*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_InterpEdit); if (!mode->InterpEd) { // Abort cleanly on InerpEd not yet being assigned. This can occasionally be the case when receiving // modifier key release events when changing into interp edit mode. return false; } bool bCtrlDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl); bool bAltDown = Viewport->KeyState(EKeys::LeftAlt) || Viewport->KeyState(EKeys::RightAlt); bool bShiftDown = Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift); if (Key == EKeys::LeftMouseButton) { if (Event == IE_Pressed) { int32 HitX = ViewportClient->Viewport->GetMouseX(); int32 HitY = ViewportClient->Viewport->GetMouseY(); HHitProxy* HitResult = ViewportClient->Viewport->GetHitProxy(HitX, HitY); if (HitResult) { if (HitResult->IsA(HInterpTrackKeypointProxy::StaticGetType())) { HInterpTrackKeypointProxy* KeyProxy = (HInterpTrackKeypointProxy*)HitResult; UInterpGroup* Group = KeyProxy->Group; UInterpTrack* Track = KeyProxy->Track; int32 KeyIndex = KeyProxy->KeyIndex; // Using the CTRL modifier invokes multi-select keyframe selection. if (bCtrlDown) { // If key is already selected, deselect the key. if (mode->InterpEd->KeyIsInSelection(Group, Track, KeyIndex)) { mode->InterpEd->RemoveKeyFromSelection(Group, Track, KeyIndex); mode->InterpEd->InvalidateTrackWindowViewports(); } // Otherwise, select the key while preserving previous selection. else { // This will invalidate the display - so we must not access the KeyProxy after this! mode->InterpEd->SelectTrack(Group, Track, false); mode->InterpEd->AddKeyToSelection(Group, Track, KeyIndex, !bShiftDown); } } else { mode->InterpEd->SelectTrack(Group, Track); // NOTE: Clear previously-selected tracks because ctrl is not down. mode->InterpEd->ClearKeySelection(); mode->InterpEd->AddKeyToSelection(Group, Track, KeyIndex, !bShiftDown); } } else if (HitResult->IsA(HInterpTrackKeyHandleProxy::StaticGetType())) { // If we clicked on a 3D track handle, remember which key. HInterpTrackKeyHandleProxy* KeyProxy = (HInterpTrackKeyHandleProxy*)HitResult; DragGroup = KeyProxy->Group; DragTrackIndex = KeyProxy->TrackIndex; DragKeyIndex = KeyProxy->KeyIndex; bDragArriving = KeyProxy->bArriving; bMovingHandle = true; mode->InterpEd->BeginDrag3DHandle(DragGroup, DragTrackIndex); } } } else if (Event == IE_Released) { if (bMovingHandle) { mode->InterpEd->EndDrag3DHandle(); bMovingHandle = false; } } } // Handle keys if (Event == IE_Pressed) { // Swallow 'Delete' key to avoid deleting stuff when trying to interpolate it! if (Key == EKeys::Platform_Delete) { // Can't delete actors if Matinee is open. const FText ErrorMsg = NSLOCTEXT("UnrealEd", "Error_WrongModeForActorDeletion", "Cannot delete actor while Matinee is open"); FNotificationInfo Info(ErrorMsg); FSlateNotificationManager::Get().AddNotification(Info); return true; } else if (mode->InterpEd->ProcessKeyPress(Key, bCtrlDown, bAltDown)) { return true; } } return FModeTool::InputKey(ViewportClient, Viewport, Key, Event); } bool FModeTool_InterpEdit::InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) { check(GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_InterpEdit)); FEdModeInterpEdit* mode = (FEdModeInterpEdit*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_InterpEdit); check(mode->InterpEd); bool bShiftDown = InViewport->KeyState(EKeys::LeftShift) || InViewport->KeyState(EKeys::RightShift); FVector InputDeltaDrag(InDrag); // If we are grabbing a 'handle' on the movement curve, pass that info to Matinee if (bMovingHandle) { mode->InterpEd->Move3DHandle(DragGroup, DragTrackIndex, DragKeyIndex, bDragArriving, InputDeltaDrag * (1.f / CurveHandleScale)); return 1; } // If shift is downOnly do 'move initial position' if dragging the widget else if (bShiftDown && InViewportClient->GetCurrentWidgetAxis() != EAxisList::None) { mode->InterpEd->MoveInitialPosition(InputDeltaDrag, InRot); return 1; } InViewportClient->Viewport->Invalidate(); return 0; } void FModeTool_InterpEdit::SelectNone() { check(GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_InterpEdit)); FEdModeInterpEdit* mode = (FEdModeInterpEdit*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_InterpEdit); }