Files
UnrealEngine/Engine/Source/Editor/Persona/Private/AnimTimeline/AnimTimelineTrack_Curves.cpp
2025-05-18 13:04:45 +08:00

348 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimTimeline/AnimTimelineTrack_Curves.h"
#include "PersonaUtils.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Input/SSearchBox.h"
#include "AnimSequenceTimelineCommands.h"
#include "IEditableSkeleton.h"
#include "SAnimCurvePicker.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Animation/AnimSequenceBase.h"
#include "Widgets/Input/STextEntryPopup.h"
#include "Framework/Application/SlateApplication.h"
#include "ScopedTransaction.h"
#include "Animation/AnimMontage.h"
#include "AnimTimeline/SAnimOutlinerItem.h"
#include "Preferences/PersonaOptions.h"
#include "SListViewSelectorDropdownMenu.h"
#include "Animation/AnimData/IAnimationDataModel.h"
#define LOCTEXT_NAMESPACE "FAnimTimelineTrack_Notifies"
ANIMTIMELINE_IMPLEMENT_TRACK(FAnimTimelineTrack_Curves);
FAnimTimelineTrack_Curves::FAnimTimelineTrack_Curves(const TSharedRef<FAnimModel>& InModel)
: FAnimTimelineTrack(LOCTEXT("CurvesRootTrackLabel", "Curves"), LOCTEXT("CurvesRootTrackToolTip", "Curve data contained in this asset"), InModel)
{
}
TSharedRef<SWidget> FAnimTimelineTrack_Curves::GenerateContainerWidgetForOutliner(const TSharedRef<SAnimOutlinerItem>& InRow)
{
TSharedPtr<SBorder> OuterBorder;
TSharedPtr<SHorizontalBox> InnerHorizontalBox;
OutlinerWidget = GenerateStandardOutlinerWidget(InRow, false, OuterBorder, InnerHorizontalBox);
OuterBorder->SetBorderBackgroundColor(FAppStyle::GetColor("AnimTimeline.Outliner.HeaderColor"));
InnerHorizontalBox->AddSlot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
.Padding(2.0f, 1.0f)
.AutoWidth()
[
SNew(STextBlock)
.TextStyle(&FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("AnimTimeline.Outliner.Label"))
.Text(this, &FAnimTimelineTrack_Curves::GetLabel)
.HighlightText(InRow->GetHighlightText())
];
InnerHorizontalBox->AddSlot()
.FillWidth(1.0f)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(5.0f, 1.0f)
[
SNew(STextBlock)
.TextStyle(&FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("TinyText"))
.Text_Lambda([this]()
{
UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();
return FText::Format(LOCTEXT("CurveCountFormat", "({0})"), FText::AsNumber(AnimSequenceBase->GetDataModel()->GetNumberOfFloatCurves()));
})
];
UAnimMontage* AnimMontage = Cast<UAnimMontage>(GetModel()->GetAnimSequenceBase());
if(!(AnimMontage && AnimMontage->HasParentAsset()))
{
InnerHorizontalBox->AddSlot()
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(OutlinerRightPadding, 1.0f)
[
PersonaUtils::MakeTrackButton(LOCTEXT("EditCurvesButtonText", "Curves"), FOnGetContent::CreateSP(this, &FAnimTimelineTrack_Curves::BuildCurvesSubMenu), MakeAttributeSP(this, &FAnimTimelineTrack_Curves::IsHovered))
];
}
return OutlinerWidget.ToSharedRef();
}
void FAnimTimelineTrack_Curves::DeleteAllCurves()
{
UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();
IAnimationDataController& Controller = AnimSequenceBase->GetController();
Controller.RemoveAllCurvesOfType(ERawCurveTrackTypes::RCT_Float);
}
TSharedRef<SWidget> FAnimTimelineTrack_Curves::BuildCurvesSubMenu()
{
FMenuBuilder MenuBuilder(true, GetModel()->GetCommandList());
MenuBuilder.BeginSection("Curves", LOCTEXT("CurvesMenuSection", "Curves"));
{
MenuBuilder.AddSubMenu(
FAnimSequenceTimelineCommands::Get().AddCurve->GetLabel(),
FAnimSequenceTimelineCommands::Get().AddCurve->GetDescription(),
FNewMenuDelegate::CreateSP(this, &FAnimTimelineTrack_Curves::FillVariableCurveMenu)
);
MenuBuilder.AddSubMenu(
FAnimSequenceTimelineCommands::Get().AddMetadata->GetLabel(),
FAnimSequenceTimelineCommands::Get().AddMetadata->GetDescription(),
FNewMenuDelegate::CreateSP(this, &FAnimTimelineTrack_Curves::FillMetadataEntryMenu)
);
UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();
if(AnimSequenceBase->GetDataModel()->GetNumberOfFloatCurves() > 0)
{
MenuBuilder.AddMenuEntry(
FAnimSequenceTimelineCommands::Get().RemoveAllCurves->GetLabel(),
FAnimSequenceTimelineCommands::Get().RemoveAllCurves->GetDescription(),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &FAnimTimelineTrack_Curves::DeleteAllCurves))
);
}
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("Options", LOCTEXT("OptionsMenuSection", "Options"));
{
MenuBuilder.AddMenuEntry(
FAnimSequenceTimelineCommands::Get().ShowCurveKeys->GetLabel(),
FAnimSequenceTimelineCommands::Get().ShowCurveKeys->GetDescription(),
FAnimSequenceTimelineCommands::Get().ShowCurveKeys->GetIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &FAnimTimelineTrack_Curves::HandleShowCurvePoints),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FAnimTimelineTrack_Curves::IsShowCurvePointsEnabled)
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
FAnimSequenceTimelineCommands::Get().UseTreeView->GetLabel(),
FAnimSequenceTimelineCommands::Get().UseTreeView->GetDescription(),
FAnimSequenceTimelineCommands::Get().UseTreeView->GetIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &FAnimTimelineTrack_Curves::HandleUseTreeView),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FAnimTimelineTrack_Curves::IsUseTreeViewEnabled)
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
void FAnimTimelineTrack_Curves::FillMetadataEntryMenu(FMenuBuilder& Builder)
{
// Add new metadata entry menu button
{
const FText Description = LOCTEXT("NewMetadataCreateNew_ToolTip", "Create a new metadata entry");
const FText Label = LOCTEXT("NewMetadataCreateNew_Label","Create Metadata Entry");
FUIAction UIAction;
UIAction.ExecuteAction.BindRaw(this, &FAnimTimelineTrack_Curves::CreateNewMetadataEntryClicked);
Builder.AddMenuEntry(Label, Description, FSlateIcon(), UIAction);
}
Builder.BeginSection(NAME_None, LOCTEXT("MetadataMenu_ListHeading", "Available Names"));
// Add existing curve to timeline using curve picker
{
const TSharedRef<SWidget> CurvePickerWidget = SNew(SAnimCurvePicker, &GetModel()->GetEditableSkeleton()->GetSkeleton())
.OnCurvesPicked_Lambda([this](const TConstArrayView<FName> InCurves)
{
check(InCurves.Num() == 1);
return OnMetadataCurveNamePicked(InCurves[0]);
})
.IsCurveNameMarkedForExclusion(this, &FAnimTimelineTrack_Curves::IsCurveMarkedForExclusion);
Builder.AddWidget(CurvePickerWidget, FText::GetEmpty(), true);
}
Builder.EndSection();
}
void FAnimTimelineTrack_Curves::FillVariableCurveMenu(FMenuBuilder& Builder)
{
// Menu entry to create a new curve
{
FText Description = LOCTEXT("NewVariableCurveCreateNew_ToolTip", "Create a new variable curve");
FText Label = LOCTEXT("NewVariableCurveCreateNew_Label", "Create Curve");
FUIAction UIAction;
UIAction.ExecuteAction.BindRaw(this, &FAnimTimelineTrack_Curves::CreateNewCurveClicked);
Builder.AddMenuEntry(Label, Description, FSlateIcon(), UIAction);
}
Builder.BeginSection(NAME_None, LOCTEXT("VariableMenu_ListHeading", "Available Names"));
// Add existing curve to timeline using curve picker
{
const TSharedRef<SWidget> CurvePickerWidget = SNew(SAnimCurvePicker, &GetModel()->GetEditableSkeleton()->GetSkeleton())
.OnCurvesPicked_Lambda([this](const TConstArrayView<FName> InCurves)
{
check(InCurves.Num() == 1);
OnVariableCurveNamePicked(InCurves[0]);
})
.IsCurveNameMarkedForExclusion(this, &FAnimTimelineTrack_Curves::IsCurveMarkedForExclusion);
Builder.AddWidget(CurvePickerWidget, FText::GetEmpty(), true);
}
Builder.EndSection();
}
void FAnimTimelineTrack_Curves::AddMetadataEntry(const FName& InCurveName)
{
UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();
IAnimationDataController& Controller = AnimSequenceBase->GetController();
IAnimationDataController::FScopedBracket ScopedBracket(Controller, LOCTEXT("AddCurveMetadata", "Add Curve Metadata"));
const FAnimationCurveIdentifier MetadataCurveId(InCurveName, ERawCurveTrackTypes::RCT_Float);
Controller.AddCurve(MetadataCurveId, AACF_Metadata);
Controller.SetCurveKeys(MetadataCurveId, { FRichCurveKey(0.f, 1.f) });
}
void FAnimTimelineTrack_Curves::CreateNewMetadataEntryClicked()
{
TSharedRef<STextEntryPopup> TextEntry =
SNew(STextEntryPopup)
.Label(LOCTEXT("NewMetadataCurveEntryLabal", "Metadata Name"))
.OnTextCommitted(this, &FAnimTimelineTrack_Curves::CreateNewMetadataEntry);
FSlateApplication& SlateApp = FSlateApplication::Get();
SlateApp.PushMenu(
OutlinerWidget.ToSharedRef(),
FWidgetPath(),
TextEntry,
SlateApp.GetCursorPos(),
FPopupTransitionEffect::TypeInPopup
);
}
void FAnimTimelineTrack_Curves::CreateNewMetadataEntry(const FText& CommittedText, ETextCommit::Type CommitType)
{
FSlateApplication::Get().DismissAllMenus();
if(CommitType == ETextCommit::OnEnter)
{
// Add the name to the skeleton and then add the new curve to the sequence
UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();
USkeleton* Skeleton = AnimSequenceBase->GetSkeleton();
if(Skeleton && !CommittedText.IsEmpty())
{
AddMetadataEntry(FName(*CommittedText.ToString()));
}
}
}
void FAnimTimelineTrack_Curves::CreateNewCurveClicked()
{
TSharedRef<STextEntryPopup> TextEntry =
SNew(STextEntryPopup)
.Label(LOCTEXT("NewCurveEntryLabal", "Curve Name"))
.OnTextCommitted(this, &FAnimTimelineTrack_Curves::CreateTrack);
FSlateApplication& SlateApp = FSlateApplication::Get();
SlateApp.PushMenu(
OutlinerWidget.ToSharedRef(),
FWidgetPath(),
TextEntry,
SlateApp.GetCursorPos(),
FPopupTransitionEffect::TypeInPopup
);
}
void FAnimTimelineTrack_Curves::CreateTrack(const FText& ComittedText, ETextCommit::Type CommitInfo)
{
if ( CommitInfo == ETextCommit::OnEnter )
{
UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();
USkeleton* Skeleton = AnimSequenceBase->GetSkeleton();
if(Skeleton && !ComittedText.IsEmpty())
{
const FScopedTransaction Transaction(LOCTEXT("AnimCurve_AddTrack", "Add New Curve"));
AddVariableCurve(FName(*ComittedText.ToString()));
}
FSlateApplication::Get().DismissAllMenus();
}
}
void FAnimTimelineTrack_Curves::AddVariableCurve(const FName& InCurveName)
{
FScopedTransaction Transaction(LOCTEXT("AddCurve", "Add Curve"));
UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();
AnimSequenceBase->Modify();
IAnimationDataController& Controller = AnimSequenceBase->GetController();
const FAnimationCurveIdentifier FloatCurveId(InCurveName, ERawCurveTrackTypes::RCT_Float);
Controller.AddCurve(FloatCurveId);
}
void FAnimTimelineTrack_Curves::HandleShowCurvePoints()
{
GetMutableDefault<UPersonaOptions>()->bTimelineDisplayCurveKeys = !GetDefault<UPersonaOptions>()->bTimelineDisplayCurveKeys;
}
bool FAnimTimelineTrack_Curves::IsShowCurvePointsEnabled() const
{
return GetDefault<UPersonaOptions>()->bTimelineDisplayCurveKeys;
}
void FAnimTimelineTrack_Curves::HandleUseTreeView()
{
GetMutableDefault<UPersonaOptions>()->bUseTreeViewForAnimationCurves = !GetDefault<UPersonaOptions>()->bUseTreeViewForAnimationCurves;
GetModel()->RefreshTracks();
}
bool FAnimTimelineTrack_Curves::IsUseTreeViewEnabled() const
{
return GetDefault<UPersonaOptions>()->bUseTreeViewForAnimationCurves;
}
void FAnimTimelineTrack_Curves::OnMetadataCurveNamePicked(const FName& InCurveName)
{
FSlateApplication::Get().DismissAllMenus();
if(InCurveName != NAME_None)
{
AddMetadataEntry(InCurveName);
}
}
void FAnimTimelineTrack_Curves::OnVariableCurveNamePicked(const FName& InCurveName)
{
FSlateApplication::Get().DismissAllMenus();
if(InCurveName != NAME_None)
{
AddVariableCurve(InCurveName);
}
}
bool FAnimTimelineTrack_Curves::IsCurveMarkedForExclusion(const FName& InCurveName)
{
return GetModel()->GetAnimSequenceBase()->GetDataModel()->FindFloatCurve(FAnimationCurveIdentifier(InCurveName, ERawCurveTrackTypes::RCT_Float)) != nullptr;
}
#undef LOCTEXT_NAMESPACE