// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "MatineeModule.h" #include "Matinee.h" #include "Runtime/Engine/Public/InterpolationHitProxy.h" #include "Runtime/Engine/Public/Slate/SceneViewport.h" #include "Matinee/MatineeActor.h" #include "Matinee/InterpTrackMove.h" #include "Matinee/InterpTrackEvent.h" #include "Engine/InterpCurveEdSetup.h" /*----------------------------------------------------------------------------- FMatineeViewportClient -----------------------------------------------------------------------------*/ FMatineeViewportClient::FMatineeViewportClient( class FMatinee* InMatinee ) : FEditorViewportClient(nullptr) { InterpEd = InMatinee; // This window will be 2D/canvas only, so set the viewport type to None ViewportType = LVT_None; // Set defaults for members. These should be initialized by the owner after construction. bIsDirectorTrackWindow = false; bWantTimeline = false; // Scroll bar starts at the top of the list! ThumbPos_Vert = 0; OldMouseX = 0; OldMouseY = 0; DistanceDragged = 0; BoxStartX = 0; BoxStartY = 0; BoxEndX = 0; BoxEndY = 0; bPanning = false; bMouseDown = false; bGrabbingHandle = false; bBoxSelecting = false; bTransactionBegun = false; bNavigating = false; bGrabbingMarker = false; DragObject = NULL; SetRealtime( false ); // Cache the font to use for drawing labels. LabelFont = GEditor->EditorFont; CamLockedIcon = Cast(StaticLoadObject( UTexture2D::StaticClass(), NULL, TEXT("/Engine/EditorMaterials/MatineeGroups/MAT_Groups_View_On.MAT_Groups_View_On"), NULL, LOAD_None, NULL )); CamUnlockedIcon = Cast(StaticLoadObject( UTexture2D::StaticClass(), NULL, TEXT("/Engine/EditorMaterials/MatineeGroups/MAT_Groups_View_Off.MAT_Groups_View_Off"), NULL, LOAD_None, NULL )); ForwardEventOnTex = Cast(StaticLoadObject( UTexture2D::StaticClass(), NULL, TEXT("/Engine/EditorMaterials/MatineeGroups/MAT_Groups_Right_On.MAT_Groups_Right_On"), NULL, LOAD_None, NULL )); ForwardEventOffTex = Cast(StaticLoadObject( UTexture2D::StaticClass(), NULL, TEXT("/Engine/EditorMaterials/MatineeGroups/MAT_Groups_Right_Off.MAT_Groups_Right_Off"), NULL, LOAD_None, NULL )); BackwardEventOnTex = Cast(StaticLoadObject( UTexture2D::StaticClass(), NULL, TEXT("/Engine/EditorMaterials/MatineeGroups/MAT_Groups_Left_On.MAT_Groups_Left_On"), NULL, LOAD_None, NULL )); BackwardEventOffTex = Cast(StaticLoadObject( UTexture2D::StaticClass(), NULL, TEXT("/Engine/EditorMaterials/MatineeGroups/MAT_Groups_Left_Off.MAT_Groups_Left_Off"), NULL, LOAD_None, NULL )); DisableTrackTex = Cast(StaticLoadObject( UTexture2D::StaticClass(), NULL, TEXT("/Engine/EditorMaterials/Cascade/CASC_ModuleEnable.CASC_ModuleEnable"), NULL, LOAD_None, NULL )); GraphOnTex = Cast(StaticLoadObject( UTexture2D::StaticClass(), NULL, TEXT("/Engine/EditorMaterials/MatineeGroups/MAT_Groups_Graph_On.MAT_Groups_Graph_On"), NULL, LOAD_None, NULL )); GraphOffTex = Cast(StaticLoadObject( UTexture2D::StaticClass(), NULL, TEXT("/Engine/EditorMaterials/MatineeGroups/MAT_Groups_Graph_Off.MAT_Groups_Graph_Off"), NULL, LOAD_None, NULL )); TrajectoryOnTex = Cast(StaticLoadObject( UTexture2D::StaticClass(), NULL, TEXT("/Engine/EditorMaterials/MatineeGroups/MAT_Groups_Graph_On.MAT_Groups_Graph_On"), NULL, LOAD_None, NULL )); } FMatineeViewportClient::~FMatineeViewportClient() { } void FMatineeViewportClient::SetParentTab(TWeakPtr InParentTab ) { ParentTab = InParentTab; } void FMatineeViewportClient::AddKeysFromHitProxy( HHitProxy* HitProxy, TArray& Selections ) { // Find how much (in time) 1.5 pixels represents on the screen. const float PixelTime = 1.5f/InterpEd->PixelsPerSec; if( !HitProxy ) { return; } if( HitProxy->IsA( HInterpTrackSubGroupKeypointProxy::StaticGetType() ) ) { HInterpTrackSubGroupKeypointProxy* KeyProxy = ( ( HInterpTrackSubGroupKeypointProxy* )HitProxy ); float KeyTime = KeyProxy->KeyTime; UInterpTrack* Track = KeyProxy->Track; UInterpGroup* Group = Track->GetOwningGroup(); int32 GroupIndex = KeyProxy->GroupIndex; // add all keyframes at the specified time if( Track->SubTracks.Num() > 0 ) { if( GroupIndex == INDEX_NONE ) { // The keyframe was drawn on the parent track, add all keyframes in all groups at the specified time for( int32 SubGroupIndex = 0; SubGroupIndex < Track->SubTrackGroups.Num(); ++SubGroupIndex ) { FSubTrackGroup& SubTrackGroup = Track->SubTrackGroups[ SubGroupIndex ]; for( int32 TrackIndex = 0; TrackIndex < SubTrackGroup.TrackIndices.Num(); ++TrackIndex ) { UInterpTrack* SubTrack = Track->SubTracks[ SubTrackGroup.TrackIndices[ TrackIndex ] ]; // Get the keyframe index from the specified time. We cant directly store the index as each subtrack may have a keyframe with the same time at a different index int32 KeyIndex = SubTrack->GetKeyframeIndex( KeyTime ); if( KeyIndex != INDEX_NONE ) { Selections.AddUnique( FInterpEdSelKey(Group, SubTrack , KeyIndex) ); } } } } else { // The keyframe was drawn on a sub track group, select all keyframes in that group's tracks at the specified time. FSubTrackGroup& SubTrackGroup = Track->SubTrackGroups[ GroupIndex ]; for( int32 TrackIndex = 0; TrackIndex < SubTrackGroup.TrackIndices.Num(); ++TrackIndex ) { UInterpTrack* SubTrack = Track->SubTracks[ SubTrackGroup.TrackIndices[ TrackIndex ] ]; // Get the keyframe index from the specified time. We cant directly store the index as each subtrack may have a keyframe with the same time at a different index int32 KeyIndex = SubTrack->GetKeyframeIndex( KeyTime ); if( KeyIndex != INDEX_NONE ) { Selections.AddUnique( FInterpEdSelKey(Group, SubTrack , KeyIndex) ); } } } } } else if( HitProxy->IsA( HInterpTrackKeypointProxy::StaticGetType() ) ) { HInterpTrackKeypointProxy* KeyProxy = ( ( HInterpTrackKeypointProxy* )HitProxy ); UInterpGroup* Group = KeyProxy->Group; UInterpTrack* Track = KeyProxy->Track; const int32 KeyIndex = KeyProxy->KeyIndex; // Because AddKeyToSelection might invalidate the display, we just remember all the keys here and process them together afterwards. Selections.AddUnique( FInterpEdSelKey(Group, Track , KeyIndex) ); // Slight hack here. We select any other keys on the same track which are within 1.5 pixels of this one. const float SelKeyTime = Track->GetKeyframeTime(KeyIndex); for(int32 i=0; iGetNumKeyframes(); i++) { const float KeyTime = Track->GetKeyframeTime(i); if( FMath::Abs(KeyTime - SelKeyTime) < PixelTime ) { Selections.AddUnique( FInterpEdSelKey(Group, Track, i) ); } } } } bool FMatineeViewportClient::InputKey(FViewport* Viewport, int32 ControllerId, FKey Key, EInputEvent Event,float /*AmountDepressed*/,bool /*Gamepad*/) { UpdateAndApplyCursorVisibility(); const bool bCtrlDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl); const bool bShiftDown = Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift); const bool bAltDown = Viewport->KeyState(EKeys::LeftAlt) || Viewport->KeyState(EKeys::RightAlt); const bool bCmdDown = Viewport->KeyState(EKeys::LeftCommand) || Viewport->KeyState(EKeys::RightCommand); const bool bCapsDown = Viewport->KeyState(EKeys::CapsLock); const int32 HitX = Viewport->GetMouseX(); const int32 HitY = Viewport->GetMouseY(); bool bClickedTrackViewport = false; if( Key == EKeys::LeftMouseButton ) { switch( Event ) { case IE_Pressed: { if(DragObject == NULL) { HHitProxy* HitResult = Viewport->GetHitProxy(HitX,HitY); if(HitResult) { if(HitResult->IsA(HMatineeGroupTitle::StaticGetType())) { UInterpGroup* Group = ((HMatineeGroupTitle*)HitResult)->Group; if( bCtrlDown && !InterpEd->HasATrackSelected() ) { if( InterpEd->IsGroupSelected(Group) ) { // Deselect a selected group when ctrl + clicking it. InterpEd->DeselectGroup(Group); } else { // Otherwise, just select the group, but don't deselect other selected tracks. InterpEd->SelectGroup(Group, false, true); } } else { // Since ctrl is not down, select this group only. InterpEd->SelectGroup(Group, true, true); } } else if(HitResult->IsA(HMatineeGroupCollapseBtn::StaticGetType())) { UInterpGroup* Group = ((HMatineeGroupCollapseBtn*)HitResult)->Group; // Deselect existing selected groups only if ctrl is not down. InterpEd->SelectGroup(Group, !bCtrlDown, true); Group->bCollapsed = !Group->bCollapsed; // A group has been expanded or collapsed, so we need to update our scroll bar InterpEd->UpdateTrackWindowScrollBars(); } else if(HitResult->IsA(HMatineeTrackCollapseBtn::StaticGetType())) { // A collapse widget on a track with subtracks was hit HMatineeTrackCollapseBtn* Proxy = ((HMatineeTrackCollapseBtn*)HitResult); UInterpTrack* Track = Proxy->Track; // GroupIndex indicates what sub group to collapse. INDEX_NONE means collapse the whole track int32 GroupIndex = Proxy->SubTrackGroupIndex; if( GroupIndex != INDEX_NONE ) { // A subgroup was collapsed FSubTrackGroup& Group = Track->SubTrackGroups[ GroupIndex ]; Group.bIsCollapsed = !Group.bIsCollapsed; } else { // A track was collapsed InterpEd->SelectTrack( Track->GetOwningGroup(), Track, !bCtrlDown ); Track->bIsCollapsed = !Track->bIsCollapsed; } // Recompute the track window scroll bar position. InterpEd->UpdateTrackWindowScrollBars(); } else if(HitResult->IsA(HMatineeGroupLockCamBtn::StaticGetType())) { UInterpGroup* Group = ((HMatineeGroupLockCamBtn*)HitResult)->Group; if(Group == InterpEd->CamViewGroup) { InterpEd->LockCamToGroup(NULL); } else { InterpEd->LockCamToGroup(Group); } } else if(HitResult->IsA(HMatineeTrackTitle::StaticGetType())) { HMatineeTrackTitle* HitProxy = (HMatineeTrackTitle*)HitResult; UInterpGroup* Group = HitProxy->Group; UInterpTrack* TrackToSelect = HitProxy->Track; check( TrackToSelect ); if( bCtrlDown && !InterpEd->HasAGroupSelected() ) { if( TrackToSelect->IsSelected() ) { // Deselect a selected track when ctrl + clicking it. InterpEd->DeselectTrack( Group, TrackToSelect ); } else { // Otherwise, select the track, but don't deselect any other selected tracks. InterpEd->SelectTrack( Group, TrackToSelect, false); } } else { // Since ctrl is not down, just select this track only. InterpEd->SelectTrack( Group, TrackToSelect, true); } } else if( HitResult->IsA(HMatineeSubGroupTitle::StaticGetType()) ) { // A track sub group was hit HMatineeSubGroupTitle* HitProxy = ( (HMatineeSubGroupTitle*)HitResult ); UInterpTrack* Track = HitProxy->Track; UInterpGroup* TrackGroup = Track->GetOwningGroup(); int32 SubGroupIndex = HitProxy->SubGroupIndex; if( bCtrlDown && !InterpEd->HasAGroupSelected() ) { // Get the sub group from the hit proxy index FSubTrackGroup& SubGroup = Track->SubTrackGroups[ SubGroupIndex ]; if( SubGroup.bIsSelected ) { // SubGroup was already selected, unselect it and all of the tracks in the group. SubGroup.bIsSelected = false; for( int32 TrackIndex = 0; TrackIndex < SubGroup.TrackIndices.Num(); ++TrackIndex ) { InterpEd->DeselectTrack( TrackGroup, Track->SubTracks[ SubGroup.TrackIndices[ TrackIndex ] ] ); } } else { // SubGroup was not selected, select it and all of the tracks in the group. SubGroup.bIsSelected = true; for( int32 TrackIndex = 0; TrackIndex < SubGroup.TrackIndices.Num(); ++TrackIndex ) { InterpEd->SelectTrack( TrackGroup, Track->SubTracks[ SubGroup.TrackIndices[ TrackIndex ] ], false ); } } } else { // control is not down, we should empty our selection InterpEd->DeselectAllTracks(); for( int32 GroupIndex = 0; GroupIndex < Track->SubTrackGroups.Num(); ++GroupIndex ) { FSubTrackGroup& SubGroup = Track->SubTrackGroups[ GroupIndex ]; SubGroup.bIsSelected = false; } // Now select the group and all of its tracks FSubTrackGroup& SubGroup = Track->SubTrackGroups[ SubGroupIndex ]; SubGroup.bIsSelected = true; for( int32 TrackIndex = 0; TrackIndex < SubGroup.TrackIndices.Num(); ++TrackIndex ) { InterpEd->SelectTrack( TrackGroup, Track->SubTracks[ SubGroup.TrackIndices[ TrackIndex ] ], false ); } } } // Did the user select the space in the track viewport associated to a given track? else if( HitResult->IsA(HMatineeTrackTimeline::StaticGetType()) ) { // When the user first clicks in this space, Matinee should interpret this as if the // user clicked on the empty space in the track viewport that doesn't belong to any // track. This enables panning and box-selecting in addition to the ability to select a // track just by clicking on the space in the track viewport associated to a given track. bClickedTrackViewport = true; } else if(HitResult->IsA(HMatineeTrackTrajectoryButton::StaticGetType())) { UInterpGroup* Group = ((HMatineeTrackTrajectoryButton*)HitResult)->Group; UInterpTrack* Track = ((HMatineeTrackTrajectoryButton*)HitResult)->Track; // Should always be a movement track UInterpTrackMove* MovementTrack = Cast( Track ); if( MovementTrack != NULL ) { // Toggle the 3D trajectory for this track InterpEd->InterpEdTrans->BeginSpecial( NSLOCTEXT("UnrealEd", "InterpEd_Undo_ToggleTrajectory", "Toggle 3D Trajectory for Track") ); MovementTrack->Modify(); MovementTrack->bHide3DTrack = !MovementTrack->bHide3DTrack; InterpEd->InterpEdTrans->EndSpecial(); } } else if(HitResult->IsA(HMatineeTrackGraphPropBtn::StaticGetType())) { UInterpGroup* Group = ((HMatineeTrackGraphPropBtn*)HitResult)->Group; UInterpTrack* Track = ((HMatineeTrackGraphPropBtn*)HitResult)->Track; // create an array of tracks that we're interested in - either subtracks or just the main track TArray TracksArray; int32 SubTrackGroupIndex = ((HMatineeTrackGraphPropBtn*)HitResult)->SubTrackGroupIndex; if( SubTrackGroupIndex != -1 ) { FSubTrackGroup& SubTrackGroup = Track->SubTrackGroups[ SubTrackGroupIndex ]; for( int32 Index = 0; Index < SubTrackGroup.TrackIndices.Num(); ++Index ) { int32 SubTrackIndex = SubTrackGroup.TrackIndices[ Index ]; TracksArray.Add( Track->SubTracks[ SubTrackIndex ] ); } } else { if( Track->SubTracks.Num() > 0 ) { // If the track has subtracks, add all subtracks instead for( int32 SubTrackIndex = 0; SubTrackIndex < Track->SubTracks.Num(); ++SubTrackIndex ) { TracksArray.Add( Track->SubTracks[ SubTrackIndex ] ); } } else { TracksArray.Add( Track ); } } // find out whether or not ALL of the tracks are currently shown in the curve editor // start by assuming that all are - then make this false if any of them aren't bool bAllSubtracksShown = true; for( int32 Index = 0; Index < TracksArray.Num(); ++Index ) { if( !InterpEd->IData->CurveEdSetup->ShowingCurve( TracksArray[ Index ] ) ) { bAllSubtracksShown = false; break; } } // toggle tracks ON if NONE or SOME of them are currently shown // toggle tracks OFF if ALL of them are currently shown bool bToggleTracksOn = ( !bAllSubtracksShown ); // add tracks to the curve editor for( int32 Index = 0; Index < TracksArray.Num(); ++Index ) { InterpEd->AddTrackToCurveEd( *Group->GroupName.ToString(), Group->GroupColor, TracksArray[ Index ], bToggleTracksOn ); } } else if(HitResult->IsA(HMatineeEventDirBtn::StaticGetType())) { UInterpGroup* Group = ((HMatineeEventDirBtn*)HitResult)->Group; const int32 TrackIndex = ((HMatineeEventDirBtn*)HitResult)->TrackIndex; EMatineeEventDirection::Type Dir = ((HMatineeEventDirBtn*)HitResult)->Dir; UInterpTrackEvent* EventTrack = CastChecked( Group->InterpTracks[TrackIndex] ); if(Dir == EMatineeEventDirection::IED_Forward) { EventTrack->bFireEventsWhenForwards = !EventTrack->bFireEventsWhenForwards; } else { EventTrack->bFireEventsWhenBackwards = !EventTrack->bFireEventsWhenBackwards; } } else if( ( HitResult->IsA( HInterpTrackKeypointProxy::StaticGetType() ) ) ) { TArray NewSelection; AddKeysFromHitProxy( HitResult, NewSelection ); for( int32 Index = 0; Index < NewSelection.Num(); Index++ ) { FInterpEdSelKey& SelKey = NewSelection[ Index ]; UInterpGroup* Group = SelKey.Group; UInterpTrack* Track = SelKey.Track; const int32 KeyIndex = SelKey.KeyIndex; if(!bCtrlDown) { // NOTE: Clear previously-selected tracks because ctrl is not down. InterpEd->SelectTrack( Group, Track ); InterpEd->ClearKeySelection(); InterpEd->AddKeyToSelection(Group, Track, KeyIndex, !bShiftDown); } } } else if( HitResult->IsA( HInterpTrackSubGroupKeypointProxy::StaticGetType() ) ) { TArray NewSelection; AddKeysFromHitProxy( HitResult, NewSelection ); if( !bCtrlDown ) { InterpEd->DeselectAllTracks(); InterpEd->ClearKeySelection(); } for( int32 Index = 0; Index < NewSelection.Num(); Index++ ) { FInterpEdSelKey& SelKey = NewSelection[ Index ]; UInterpGroup* Group = SelKey.Group; UInterpTrack* Track = SelKey.Track; const int32 KeyIndex = SelKey.KeyIndex; if( !InterpEd->KeyIsInSelection( Group, Track, KeyIndex ) ) { InterpEd->SelectTrack( Group, Track, false ); InterpEd->AddKeyToSelection( Group, Track, KeyIndex, !bShiftDown ); } } if(InterpEd->Opt->SelectedKeys.Num() > 1) { InterpEd->Opt->bAdjustingGroupKeyframes = true; } } else if(HitResult->IsA(HMatineeTrackBkg::StaticGetType())) { InterpEd->DeselectAll(); } else if(HitResult->IsA(HMatineeTimelineBkg::StaticGetType())) { float NewTime = InterpEd->ViewStartTime + ((HitX - InterpEd->LabelWidth) / InterpEd->PixelsPerSec); if( InterpEd->bSnapToFrames && InterpEd->bSnapTimeToFrames ) { NewTime = InterpEd->SnapTimeToNearestFrame( NewTime ); } // When jumping to location by clicking, stop playback. InterpEd->MatineeActor->Stop(); SetRealtime( false ); InterpEd->SetAudioRealtimeOverride( false ); //make sure to turn off recording InterpEd->StopRecordingInterpValues(); // Move to clicked on location InterpEd->SetInterpPosition(NewTime); // Act as if we grabbed the handle as well. bGrabbingHandle = true; } else if(HitResult->IsA(HMatineeNavigatorBackground::StaticGetType())) { // Clicked on the navigator background, so jump directly to the position under the // mouse cursor and wait for a drag const float JumpToTime = ((HitX - InterpEd->LabelWidth)/InterpEd->NavPixelsPerSecond); const float ViewWindow = (InterpEd->ViewEndTime - InterpEd->ViewStartTime); InterpEd->ViewStartTime = JumpToTime - (0.5f * ViewWindow); InterpEd->ViewEndTime = JumpToTime + (0.5f * ViewWindow); InterpEd->SyncCurveEdView(); bNavigating = true; } else if(HitResult->IsA(HMatineeNavigator::StaticGetType())) { // Clicked on the navigator foreground, so just start the drag immediately without // jumping the timeline bNavigating = true; } else if(HitResult->IsA(HMatineeMarker::StaticGetType())) { InterpEd->GrabbedMarkerType = ((HMatineeMarker*)HitResult)->Type; InterpEd->BeginMoveMarker(); bGrabbingMarker = true; } else if(HitResult->IsA(HMatineeTrackDisableTrackBtn::StaticGetType())) { HMatineeTrackDisableTrackBtn* TrackProxy = ((HMatineeTrackDisableTrackBtn*)HitResult); if(TrackProxy->Group != NULL && TrackProxy->Track != NULL ) { UInterpTrack* Track = TrackProxy->Track; InterpEd->InterpEdTrans->BeginSpecial( NSLOCTEXT("UnrealEd", "InterpEd_Undo_ToggleTrackEnabled", "Enable/Disable Track") ); //if only one per group is allowed to be active (And this is ABOUT to become active), ensure that no other is active if (Track->bOnePerGroup && (Track->IsDisabled() == true)) { InterpEd->DisableTracksOfClass(TrackProxy->Group, Track->GetClass()); } Track->Modify(); Track->EnableTrack( Track->IsDisabled() ? true : false, true ); InterpEd->InterpEdTrans->EndSpecial(); // Update the preview and actor states InterpEd->MatineeActor->RecaptureActorState(); } } else if(HitResult->IsA(HInterpEdInputInterface::StaticGetType())) { HInterpEdInputInterface* Proxy = ((HInterpEdInputInterface*)HitResult); DragObject = Proxy->ClickedObject; DragData = Proxy->InputData; DragData.PixelsPerSec = InterpEd->PixelsPerSec; DragData.MouseStart = FIntPoint(HitX, HitY); DragData.bCtrlDown = bCtrlDown; DragData.bAltDown = bAltDown; DragData.bShiftDown = bShiftDown; DragData.bCmdDown = bCmdDown; Proxy->ClickedObject->BeginDrag(DragData); } } else { // The user clicked on empty space that doesn't have a hit proxy associated to it. bClickedTrackViewport = true; } // When the user clicks on empty, non-hit proxy space, allow // features such as box-selection and panning of the viewport. if( bClickedTrackViewport ) { // Enable box selection if CTRL + ALT held down. if(bCtrlDown && bAltDown) { BoxStartX = BoxEndX = HitX; BoxStartY = BoxEndY = HitY; bBoxSelecting = true; } // The last option is to simply pan the viewport else { bPanning = true; } } Viewport->LockMouseToViewport(true); bMouseDown = true; OldMouseX = HitX; OldMouseY = HitY; DistanceDragged = 0; } } break; case IE_DoubleClick: { Viewport->InvalidateHitProxy(); HHitProxy* HitResult = Viewport->GetHitProxy(HitX,HitY); if(HitResult) { if(HitResult->IsA(HMatineeGroupTitle::StaticGetType())) { UInterpGroup* Group = ((HInterpTrackKeypointProxy*)HitResult)->Group; Group->bCollapsed = !Group->bCollapsed; // A group has been expanded or collapsed, so we need to update our scroll bar InterpEd->UpdateTrackWindowScrollBars(); } } } break; case IE_Released: { Viewport->InvalidateHitProxy(); if(bBoxSelecting) { const int32 MinX = FMath::Min(BoxStartX, BoxEndX); const int32 MinY = FMath::Min(BoxStartY, BoxEndY); const int32 MaxX = FMath::Max(BoxStartX, BoxEndX); const int32 MaxY = FMath::Max(BoxStartY, BoxEndY); const int32 TestSizeX = MaxX - MinX + 1; const int32 TestSizeY = MaxY - MinY + 1; // Find how much (in time) 1.5 pixels represents on the screen. const float PixelTime = 1.5f/InterpEd->PixelsPerSec; // We read back the hit proxy map for the required region. TArray ProxyMap; Viewport->GetHitProxyMap(FIntRect(MinX, MinY, MaxX + 1, MaxY + 1), ProxyMap); TArray NewSelection; // Find any keypoint hit proxies in the region - add the keypoint to selection. for( int32 Y = 0; Y < TestSizeY; Y++ ) { for( int32 X = 0; X < TestSizeX; X++ ) { AddKeysFromHitProxy( ProxyMap[ Y * TestSizeX + X ], NewSelection ); } } // If the SHIFT key is down, then the user wants to preserve // the current selection in Matinee during box selection. if(!bShiftDown) { // NOTE: This will clear all keys InterpEd->DeselectAllTracks(); } for(int32 i=0; iSelectTrack( Group, TrackToSelect, false ); InterpEd->AddKeyToSelection( Group, TrackToSelect, NewSelection[i].KeyIndex, false ); } } else if(DragObject) { HHitProxy* HitResult = Viewport->GetHitProxy(HitX,HitY); if(HitResult) { if(HitResult->IsA(HInterpEdInputInterface::StaticGetType())) { HInterpEdInputInterface* Proxy = ((HInterpEdInputInterface*)HitResult); //@todo: Do dropping. } } DragData.PixelsPerSec = InterpEd->PixelsPerSec; DragData.MouseCurrent = FIntPoint(HitX, HitY); DragObject->EndDrag(DragData); DragObject = NULL; } else if(DistanceDragged < 4) { HHitProxy* HitResult = Viewport->GetHitProxy(HitX,HitY); // If mouse didn't really move since last time, and we released over empty space, deselect everything. if(!HitResult) { InterpEd->ClearKeySelection(); } // Allow track selection if the user selects the space in the track viewport that is associated to a given track. else if(HitResult->IsA(HMatineeTrackTimeline::StaticGetType())) { UInterpGroup* Group = ((HMatineeTrackTimeline*)HitResult)->Group; UInterpTrack* TrackToSelect = ((HMatineeTrackTimeline*)HitResult)->Track; if( bCtrlDown && !InterpEd->HasAGroupSelected() ) { if( TrackToSelect->IsSelected() ) { // Deselect a selected track when ctrl + clicking it. InterpEd->DeselectTrack( Group, TrackToSelect ); } else { // Otherwise, select the track, but don't deselect any other selected tracks. InterpEd->SelectTrack( Group, TrackToSelect, false); } } else { // Always clear the key selection when single-clicking on the track viewport background. // If the user is CTRL-clicking, we don't want to clear the key selection of other tracks. InterpEd->ClearKeySelection(); // Since ctrl is not down, just select this track only. InterpEd->SelectTrack( Group, TrackToSelect, true); } } else if(bCtrlDown && HitResult->IsA(HInterpTrackKeypointProxy::StaticGetType())) { HInterpTrackKeypointProxy* KeyProxy = ((HInterpTrackKeypointProxy*)HitResult); UInterpGroup* Group = KeyProxy->Group; UInterpTrack* Track = KeyProxy->Track; const int32 KeyIndex = KeyProxy->KeyIndex; const bool bAlreadySelected = InterpEd->KeyIsInSelection(Group, Track, KeyIndex); if(bAlreadySelected) { InterpEd->RemoveKeyFromSelection(Group, Track, KeyIndex); } else { // NOTE: Do not clear previously-selected tracks because ctrl is down. InterpEd->SelectTrack( Group, Track, false ); InterpEd->AddKeyToSelection(Group, Track, KeyIndex, !bShiftDown); } } } if(bTransactionBegun) { InterpEd->EndMoveSelectedKeys(); bTransactionBegun = false; } if(bGrabbingMarker) { InterpEd->EndMoveMarker(); bGrabbingMarker = false; } Viewport->LockMouseToViewport(false); DistanceDragged = 0; bPanning = false; bMouseDown = false; bGrabbingHandle = false; bNavigating = false; bBoxSelecting = false; } break; } } else if( Key == EKeys::RightMouseButton ) { switch( Event ) { case IE_Pressed: { HHitProxy* HitResult = Viewport->GetHitProxy(HitX,HitY); if(HitResult) { // User right-click somewhere in the track editor TSharedPtr Menu = InterpEd->CreateContextMenu( Viewport, HitResult, bIsDirectorTrackWindow ); if (Menu.IsValid()) { // Redraw the viewport so the user can see which object was right clicked on Viewport->Draw(); FlushRenderingCommands(); TSharedPtr< SWindow > Parent = FSlateApplication::Get().GetActiveTopLevelWindow(); if ( Parent.IsValid() ) { FSlateApplication::Get().PushMenu( Parent.ToSharedRef(), FWidgetPath(), Menu.ToSharedRef(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu)); } } } } break; case IE_Released: { Viewport->InvalidateHitProxy(); } break; } } if(Event == IE_Pressed) { if(Key == EKeys::MouseScrollDown) { InterpEd->ZoomView( FMatinee::InterpEditor_ZoomIncrement, InterpEd->bZoomToScrubPos ); } else if(Key == EKeys::MouseScrollUp) { InterpEd->ZoomView( 1.0f / FMatinee::InterpEditor_ZoomIncrement, InterpEd->bZoomToScrubPos ); } FModifierKeysState ModKeys(bShiftDown, bShiftDown, bCtrlDown, bCtrlDown, bAltDown, bAltDown, bCmdDown, bCmdDown, bCapsDown); FKeyEvent KeyEvent(Key, ModKeys, 0/*UserIndex*/, false, 0, 0); InterpEd->ProcessCommandBindings(KeyEvent); } // Handle viewport screenshot. InputTakeScreenshot( Viewport, Key, Event ); return true; } // X and Y here are the new screen position of the cursor. void FMatineeViewportClient::MouseMove(FViewport* Viewport, int32 X, int32 Y) { bool bCtrlDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl); int32 DeltaX = OldMouseX - X; int32 DeltaY = OldMouseY - Y; if(bMouseDown) { DistanceDragged += ( FMath::Abs(DeltaX) + FMath::Abs(DeltaY) ); } OldMouseX = X; OldMouseY = Y; if(bMouseDown) { if(DragObject != NULL) { DragData.PixelsPerSec = InterpEd->PixelsPerSec; DragData.MouseCurrent = FIntPoint(X, Y); DragObject->ObjectDragged(DragData); } else if(bGrabbingHandle) { float NewTime = InterpEd->ViewStartTime + ((X - InterpEd->LabelWidth) / InterpEd->PixelsPerSec); if( InterpEd->bSnapToFrames && InterpEd->bSnapTimeToFrames ) { NewTime = InterpEd->SnapTimeToNearestFrame( NewTime ); } InterpEd->SetInterpPosition( NewTime, true ); } else if(bBoxSelecting) { BoxEndX = X; BoxEndY = Y; } else if( bCtrlDown && InterpEd->Opt->SelectedKeys.Num() > 0 ) { if(DistanceDragged > 4) { if(!bTransactionBegun) { InterpEd->BeginMoveSelectedKeys(); bTransactionBegun = true; } float DeltaTime = -DeltaX / InterpEd->PixelsPerSec; InterpEd->MoveSelectedKeys(DeltaTime); } } else if(bNavigating) { float DeltaTime = -DeltaX / InterpEd->NavPixelsPerSecond; InterpEd->ViewStartTime += DeltaTime; InterpEd->ViewEndTime += DeltaTime; InterpEd->SyncCurveEdView(); } else if(bGrabbingMarker) { float DeltaTime = -DeltaX / InterpEd->PixelsPerSec; InterpEd->UnsnappedMarkerPos += DeltaTime; if(InterpEd->GrabbedMarkerType == EMatineeMarkerType::ISM_SeqEnd) { InterpEd->SetInterpEnd( InterpEd->SnapTime(InterpEd->UnsnappedMarkerPos, false) ); } else if(InterpEd->GrabbedMarkerType == EMatineeMarkerType::ISM_LoopStart || InterpEd->GrabbedMarkerType == EMatineeMarkerType::ISM_LoopEnd) { InterpEd->MoveLoopMarker( InterpEd->SnapTime(InterpEd->UnsnappedMarkerPos, false), InterpEd->GrabbedMarkerType == EMatineeMarkerType::ISM_LoopStart ); } } else if(bPanning) { const bool bInvertPanning = InterpEd->IsInvertPanToggled(); float DeltaTime = (bInvertPanning ? -DeltaX : DeltaX) / InterpEd->PixelsPerSec; InterpEd->ViewStartTime -= DeltaTime; InterpEd->ViewEndTime -= DeltaTime; // Handle vertical scrolling if the user moved the mouse up or down. // Vertical scrolling is handled by modifying the scroll bar attributes // because the scroll bar drives the vertical position of the viewport. if( DeltaY != 0 ) { // Account for the 'Drag Moves Canvas' option, which determines if panning // is inverted, when figuring out the desired destination thumb position. const int32 TargetThumbPosition = bInvertPanning ? ThumbPos_Vert - DeltaY : ThumbPos_Vert + DeltaY; // Figure out which window we are panning TSharedPtr WindowToPan = bIsDirectorTrackWindow ? InterpEd->DirectorTrackWindow : InterpEd->TrackWindow; // Determine the maximum scroll position in order to prevent the user from scrolling beyond the // valid scroll range. The max scroll position is not equivalent to the max range of the scroll // bar. We must subtract the size of the thumb because the thumb position is the top of the thumb. int32 MaxThumbPosition = ComputeGroupListContentHeight(); // Make sure the max thumb position is not negative. This is possible if the amount // of scrollable space is less than the scroll bar (i.e. no scrolling possible). MaxThumbPosition = FMath::Max( 0, MaxThumbPosition ); // For some reason, the thumb position is always negated. So, instead // of clamping from zero - max, we clamp from -max to zero. ThumbPos_Vert = FMath::Clamp( TargetThumbPosition, -MaxThumbPosition, 0 ); // Redraw the scroll bar so that it is at its new position. WindowToPan->AdjustScrollBar(); } InterpEd->SyncCurveEdView(); } } } bool FMatineeViewportClient::InputAxis(FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad) { if ( Key == EKeys::MouseX || Key == EKeys::MouseY ) { int32 X = Viewport->GetMouseX(); int32 Y = Viewport->GetMouseY(); MouseMove(Viewport, X, Y); return true; } return false; } EMouseCursor::Type FMatineeViewportClient::GetCursor(FViewport* Viewport,int32 X,int32 Y) { EMouseCursor::Type Result = EMouseCursor::Crosshairs; if(DragObject==NULL) { HHitProxy* HitProxy = Viewport->GetHitProxy(X,Y); if(HitProxy) { Result = HitProxy->GetMouseCursor(); } } else { Result = EMouseCursor::Default; } return Result; } void FMatineeViewportClient::Tick(float DeltaSeconds) { // Only the main track window is allowed to tick the root object. We never want the InterpEd object to be // ticked more than once per frame. if( !bIsDirectorTrackWindow ) { InterpEd->TickInterp(DeltaSeconds); } // If curve editor is shown - sync us with it. if(InterpEd->CurveEd->GetVisibility() == EVisibility::Visible) { InterpEd->ViewStartTime = InterpEd->CurveEd->GetStartIn(); InterpEd->ViewEndTime = InterpEd->CurveEd->GetEndIn(); } if(bNavigating || bPanning) { const int32 ScrollBorderSize = 20; const float ScrollBorderSpeed = 500.f; const int32 PosX = Viewport->GetMouseX(); const int32 PosY = Viewport->GetMouseY(); const int32 SizeX = Viewport->GetSizeXY().X; const int32 SizeY = Viewport->GetSizeXY().Y; float DeltaTime = FMath::Clamp(DeltaSeconds, 0.01f, 1.0f); if(PosX < ScrollBorderSize) { ScrollAccum.X += (1.f - ((float)PosX/(float)ScrollBorderSize)) * ScrollBorderSpeed * DeltaTime; } else if(PosX > SizeX - ScrollBorderSize) { ScrollAccum.X -= ((float)(PosX - (SizeX - ScrollBorderSize))/(float)ScrollBorderSize) * ScrollBorderSpeed * DeltaTime; } else { ScrollAccum.X = 0.f; } // Apply integer part of ScrollAccum to the curve editor view position. const int32 DeltaX = FMath::FloorToInt(ScrollAccum.X); ScrollAccum.X -= DeltaX; if(bNavigating) { DeltaTime = -DeltaX / InterpEd->NavPixelsPerSecond; InterpEd->ViewStartTime += DeltaTime; InterpEd->ViewEndTime += DeltaTime; InterpEd->SyncCurveEdView(); } else { DeltaTime = -DeltaX / InterpEd->PixelsPerSec; InterpEd->ViewStartTime -= DeltaTime; InterpEd->ViewEndTime -= DeltaTime; InterpEd->SyncCurveEdView(); } } Viewport->Draw(); //check to see if we need to update the scrollbars due to a size change int32 CurrentHeight = Viewport->GetSizeXY().Y; if (CurrentHeight != PrevViewportHeight) { PrevViewportHeight = CurrentHeight; InterpEd->UpdateTrackWindowScrollBars(); } } void FMatineeViewportClient::Serialize(FArchive& Ar) { // Drag object may be a instance of UObject, so serialize it if it is. if(DragObject && DragObject->GetUObject()) { UObject* DragUObject = DragObject->GetUObject(); Ar << DragUObject; } } /** Exec handler */ void FMatineeViewportClient::Exec(const TCHAR* Cmd) { const TCHAR* Str = Cmd; if(FParse::Command(&Str, TEXT("MATINEE"))) { if(FParse::Command(&Str, TEXT("Undo"))) { InterpEd->InterpEdUndo(); } else if(FParse::Command(&Str, TEXT("Redo"))) { InterpEd->InterpEdRedo(); } else if(FParse::Command(&Str, TEXT("Cut"))) { InterpEd->CopySelectedGroupOrTrack(true); } else if(FParse::Command(&Str, TEXT("Copy"))) { InterpEd->CopySelectedGroupOrTrack(false); } else if(FParse::Command(&Str, TEXT("Paste"))) { InterpEd->PasteSelectedGroupOrTrack(); } else if(FParse::Command(&Str, TEXT("Play"))) { InterpEd->StartPlaying( false, true ); } else if(FParse::Command(&Str, TEXT("PlayReverse"))) { InterpEd->StartPlaying( false, false ); } else if(FParse::Command(&Str, TEXT("Stop"))) { if(InterpEd->MatineeActor->bIsPlaying) { InterpEd->StopPlaying(); } } else if(FParse::Command(&Str, TEXT("Rewind"))) { InterpEd->SetInterpPosition(0.f); } else if(FParse::Command(&Str, TEXT("TogglePlayPause"))) { if(InterpEd->MatineeActor->bIsPlaying) { InterpEd->StopPlaying(); } else { // Start playback and retain whatever direction we were already playing InterpEd->StartPlaying( false, true ); } } else if( FParse::Command( &Str, TEXT( "ZoomIn" ) ) ) { const bool bZoomToTimeCursorPos = true; InterpEd->ZoomView( 1.0f / FMatinee::InterpEditor_ZoomIncrement, bZoomToTimeCursorPos ); } else if( FParse::Command( &Str, TEXT( "ZoomOut" ) ) ) { const bool bZoomToTimeCursorPos = true; InterpEd->ZoomView( FMatinee::InterpEditor_ZoomIncrement, bZoomToTimeCursorPos ); } else if(FParse::Command(&Str, TEXT("DeleteSelection"))) { InterpEd->DeleteSelection(); } else if(FParse::Command(&Str, TEXT("MarkInSection"))) { InterpEd->MoveLoopMarker(InterpEd->MatineeActor->InterpPosition, true); } else if(FParse::Command(&Str, TEXT("MarkOutSection"))) { InterpEd->MoveLoopMarker(InterpEd->MatineeActor->InterpPosition, false); } else if(FParse::Command(&Str, TEXT("CropAnimationBeginning"))) { InterpEd->CropAnimKey(true); } else if(FParse::Command(&Str, TEXT("CropAnimationEnd"))) { InterpEd->CropAnimKey(false); } else if(FParse::Command(&Str, TEXT("IncrementPosition"))) { InterpEd->IncrementSelection(); } else if(FParse::Command(&Str, TEXT("DecrementPosition"))) { InterpEd->DecrementSelection(); } else if(FParse::Command(&Str, TEXT("MoveToNextKey"))) { InterpEd->SelectNextKey(); } else if(FParse::Command(&Str, TEXT("MoveToPrevKey"))) { InterpEd->SelectPreviousKey(); } else if(FParse::Command(&Str, TEXT("SplitAnimKey"))) { InterpEd->SplitAnimKey(); } else if(FParse::Command(&Str, TEXT("ToggleSnap"))) { InterpEd->SetSnapEnabled(!InterpEd->bSnapEnabled); } else if(FParse::Command(&Str, TEXT("ToggleSnapTimeToFrames"))) { InterpEd->SetSnapTimeToFrames(!InterpEd->bSnapTimeToFrames); } else if(FParse::Command(&Str, TEXT("ToggleFixedTimeStepPlayback"))) { InterpEd->SetFixedTimeStepPlayback( !InterpEd->bFixedTimeStepPlayback ); } else if(FParse::Command(&Str, TEXT("TogglePreferFrameNumbers"))) { InterpEd->SetPreferFrameNumbers( !InterpEd->bPreferFrameNumbers ); } else if(FParse::Command(&Str, TEXT("ToggleShowTimeCursorPosForAllKeys"))) { InterpEd->SetShowTimeCursorPosForAllKeys( !InterpEd->bShowTimeCursorPosForAllKeys ); } else if(FParse::Command(&Str, TEXT("MoveActiveUp"))) { InterpEd->MoveActiveUp(); } else if(FParse::Command(&Str, TEXT("MoveActiveDown"))) { InterpEd->MoveActiveDown(); } else if(FParse::Command(&Str, TEXT("AddKey"))) { InterpEd->AddKey(); } else if(FParse::Command(&Str, TEXT("DuplicateSelectedKeys")) ) { InterpEd->DuplicateSelectedKeys(); } else if(FParse::Command(&Str, TEXT("ViewFitSequence")) ) { InterpEd->ViewFitSequence(); } else if(FParse::Command(&Str, TEXT("ViewFitToSelected")) ) { InterpEd->ViewFitToSelected(); } else if(FParse::Command(&Str, TEXT("ViewFitLoop")) ) { InterpEd->ViewFitLoop(); } else if(FParse::Command(&Str, TEXT("ViewFitLoopSequence")) ) { InterpEd->ViewFitLoopSequence(); } else if(FParse::Command(&Str, TEXT("ViewEndOfTrack")) ) { InterpEd->ViewEndOfTrack(); } else if(FParse::Command(&Str, TEXT("ChangeKeyInterpModeAUTO")) ) { InterpEd->ChangeKeyInterpMode(CIM_CurveAuto); } else if(FParse::Command(&Str, TEXT("ChangeKeyInterpModeAUTOCLAMPED")) ) { InterpEd->ChangeKeyInterpMode(CIM_CurveAutoClamped); } else if(FParse::Command(&Str, TEXT("ChangeKeyInterpModeUSER")) ) { InterpEd->ChangeKeyInterpMode(CIM_CurveUser); } else if(FParse::Command(&Str, TEXT("ChangeKeyInterpModeBREAK")) ) { InterpEd->ChangeKeyInterpMode(CIM_CurveBreak); } else if(FParse::Command(&Str, TEXT("ChangeKeyInterpModeLINEAR")) ) { InterpEd->ChangeKeyInterpMode(CIM_Linear); } else if(FParse::Command(&Str, TEXT("ChangeKeyInterpModeCONSTANT")) ) { InterpEd->ChangeKeyInterpMode(CIM_Constant); } } }