// Copyright Epic Games, Inc. All Rights Reserved. #include "CurveAssetEditor.h" #include "Containers/Array.h" #include "Containers/ArrayView.h" #include "CoreGlobals.h" #include "CurveAssetEditorModule.h" #include "CurveEditor.h" #include "CurveEditorCommands.h" #include "CurveEditorTypes.h" #include "CurveModel.h" #include "Curves/CurveBase.h" #include "Curves/CurveLinearColor.h" #include "Curves/RichCurve.h" #include "Delegates/Delegate.h" #include "DetailsViewArgs.h" #include "Framework/Docking/TabManager.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/MultiBox/MultiBoxDefs.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "ICurveEditorBounds.h" #include "ICurveEditorModule.h" #include "IDetailsView.h" #include "Internationalization/Internationalization.h" #include "Layout/Margin.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "Modules/ModuleManager.h" #include "PropertyEditorModule.h" #include "RichCurveEditorModel.h" #include "SColorGradientCurveEditorView.h" #include "SColorGradientEditor.h" #include "SCurveEditorPanel.h" #include "SlotBase.h" #include "Styling/AppStyle.h" #include "Styling/SlateColor.h" #include "Templates/Casts.h" #include "Templates/UniquePtr.h" #include "Templates/UnrealTemplate.h" #include "Textures/SlateIcon.h" #include "Toolkits/AssetEditorToolkit.h" #include "Trace/Detail/Channel.h" #include "Tree/CurveEditorTree.h" #include "Tree/ICurveEditorTreeItem.h" #include "Tree/SCurveEditorTree.h" #include "Tree/SCurveEditorTreePin.h" #include "Tree/SCurveEditorTreeSelect.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Docking/SDockTab.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Text/STextBlock.h" class FExtender; class ITableRow; class SWidget; #define LOCTEXT_NAMESPACE "CurveAssetEditor" const FName FCurveAssetEditor::CurveTabId( TEXT( "CurveAssetEditor_Curve" ) ); const FName FCurveAssetEditor::ColorCurveEditorTabId(TEXT("CurveAssetEditor_ColorCurveEditor")); struct FCurveAssetEditorTreeItem : public ICurveEditorTreeItem { FCurveAssetEditorTreeItem(TWeakObjectPtr InCurveOwner, const FRichCurveEditInfo& InEditInfo) : CurveOwner(InCurveOwner) , EditInfo(InEditInfo) { if (CurveOwner.IsValid()) { CurveName = FText::FromName(EditInfo.CurveName); CurveColor = CurveOwner->GetCurveColor(EditInfo); } } virtual TSharedPtr GenerateCurveEditorTreeWidget(const FName& InColumnName, TWeakPtr InCurveEditor, FCurveEditorTreeItemID InTreeItemID, const TSharedRef& TableRow) override { if (InColumnName == ColumnNames.Label) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(FMargin(4.f)) .VAlign(VAlign_Center) .HAlign(HAlign_Right) .AutoWidth() [ SNew(STextBlock) .Text(CurveName) .ColorAndOpacity(FSlateColor(CurveColor)) ]; } else if (InColumnName == ColumnNames.SelectHeader) { return SNew(SCurveEditorTreeSelect, InCurveEditor, InTreeItemID, TableRow); } else if (InColumnName == ColumnNames.PinHeader) { return SNew(SCurveEditorTreePin, InCurveEditor, InTreeItemID, TableRow); } return nullptr; } virtual void CreateCurveModels(TArray>& OutCurveModels) override { if (!CurveOwner.IsValid()) { return; } TUniquePtr NewCurve = MakeUnique(static_cast(EditInfo.CurveToEdit), CurveOwner.Get()); NewCurve->SetShortDisplayName(CurveName); NewCurve->SetColor(CurveColor, false); OutCurveModels.Add(MoveTemp(NewCurve)); } private: TWeakObjectPtr CurveOwner; FRichCurveEditInfo EditInfo; FText CurveName; FLinearColor CurveColor; }; void FCurveAssetEditor::RegisterTabSpawners(const TSharedRef& InTabManager) { WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_CurveAssetEditor", "Curve Asset Editor")); FAssetEditorToolkit::RegisterTabSpawners(InTabManager); InTabManager->RegisterTabSpawner( CurveTabId, FOnSpawnTab::CreateSP(this, &FCurveAssetEditor::SpawnTab_CurveAsset) ) .SetDisplayName( LOCTEXT("CurveTab", "Curve") ) .SetGroup(WorkspaceMenuCategory.ToSharedRef()) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.CurveBase")); if (ColorCurveDetailsView) { InTabManager->RegisterTabSpawner(ColorCurveEditorTabId, FOnSpawnTab::CreateSP(this, &FCurveAssetEditor::SpawnTab_ColorCurveEditor)) .SetDisplayName(LOCTEXT("ColorCurveEditorTab", "Color Curve Editor")) .SetGroup(WorkspaceMenuCategory.ToSharedRef()) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.CurveBase")); } } void FCurveAssetEditor::UnregisterTabSpawners(const TSharedRef& InTabManager) { InTabManager->UnregisterTabSpawner( CurveTabId ); InTabManager->UnregisterTabSpawner(ColorCurveEditorTabId); } void FCurveAssetEditor::InitCurveAssetEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UCurveBase* CurveToEdit ) { TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_CurveAssetEditor_Layout_v2") ->AddArea ( FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.9f) ->SetHideTabWell(true) ->AddTab(CurveTabId, ETabState::OpenedTab) ) ); UCurveLinearColor* ColorCurve = Cast(CurveToEdit); if (ColorCurve) { StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_CurveAssetEditor_Layout_ColorCurvev3") ->AddArea ( FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.8f) ->SetHideTabWell(true) ->AddTab(CurveTabId, ETabState::OpenedTab) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.2f) ->SetHideTabWell(true) ->AddTab(ColorCurveEditorTabId, ETabState::OpenedTab) ) ) ); FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.bAllowSearch = false; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; ColorCurveDetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs); } const bool bCreateDefaultStandaloneMenu = true; const bool bCreateDefaultToolbar = true; const bool bToolbarFocusable = false; const bool bUseSmallIcons = true; FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, FCurveAssetEditorModule::CurveAssetEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, CurveToEdit, bToolbarFocusable, bUseSmallIcons); FCurveAssetEditorModule& CurveAssetEditorModule = FModuleManager::LoadModuleChecked( "CurveAssetEditor" ); AddMenuExtender(CurveAssetEditorModule.GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); AddToolbarExtender(GetToolbarExtender()); // @todo toolkit world centric editing /*// Setup our tool's layout if( IsWorldCentricAssetEditor() ) { const FString TabInitializationPayload(TEXT("")); // NOTE: Payload not currently used for table properties SpawnToolkitTab( CurveTabId, TabInitializationPayload, EToolkitTabSpot::Details ); }*/ if (CurveEditor.IsValid()) { RegenerateMenusAndToolbars(); } if (ColorCurve) { ColorCurveDetailsView->SetObject(ColorCurve); } } FName FCurveAssetEditor::GetToolkitFName() const { return FName("CurveAssetEditor"); } FText FCurveAssetEditor::GetBaseToolkitName() const { return LOCTEXT( "AppLabel", "Curve Asset Editor" ); } FString FCurveAssetEditor::GetWorldCentricTabPrefix() const { return LOCTEXT("WorldCentricTabPrefix", "CurveAsset ").ToString(); } FLinearColor FCurveAssetEditor::GetWorldCentricTabColorScale() const { return FLinearColor( 0.0f, 0.0f, 0.2f, 0.5f ); } TSharedRef FCurveAssetEditor::SpawnTab_CurveAsset( const FSpawnTabArgs& Args ) { check( Args.GetTabId().TabType == CurveTabId ); CurveEditor = MakeShared(); FCurveEditorInitParams InitParams; CurveEditor->InitCurveEditor(InitParams); CurveEditor->GridLineLabelFormatXAttribute = LOCTEXT("GridXLabelFormat", "{0}"); // Initialize our bounds at slightly larger than default to avoid clipping the tabs on the color widget. TUniquePtr EditorBounds = MakeUnique(); EditorBounds->SetInputBounds(-1.05, 1.05); CurveEditor->SetBounds(MoveTemp(EditorBounds)); CurveEditorPanel = SNew(SCurveEditorPanel, CurveEditor.ToSharedRef()) .TreeContent() [ SNew(SCurveEditorTree, CurveEditor) ]; UCurveBase* Curve = Cast(GetEditingObject()); if (Curve) { check(CurveEditor.IsValid()); if (Curve->HasRichCurves()) { for (const FRichCurveEditInfo& CurveData : Curve->GetCurves()) { TSharedPtr TreeItem = MakeShared(Curve, CurveData); // Add the channel to the tree-item and let it manage the lifecycle of the tree item. FCurveEditorTreeItem* NewItem = CurveEditor->AddTreeItem(FCurveEditorTreeItemID::Invalid()); NewItem->SetStrongItem(TreeItem); // Pin all of the created curves by default for now so that they're visible when you open the // editor. Since there's only ever up to 4 channels we don't have to worry about overwhelming // amounts of curves. for (const FCurveModelID& CurveModel : NewItem->GetOrCreateCurves(CurveEditor.Get())) { CurveEditor->PinCurve(CurveModel); } } } else { UE_LOG(LogTemp, Error, TEXT("Non-rich curves are not supported in the Curve Asset editor at this time.")); } } TSharedRef NewDockTab = SNew(SDockTab) .Label(FText::Format(LOCTEXT("CurveAssetEditorTitle", "{0} Curve Asset"), FText::FromString(GetTabPrefix()))) .TabColorScale(GetTabColorScale()) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(0.0f) [ CurveEditorPanel.ToSharedRef() ] ]; // Insert a widget for editing the curve as a Color Gradient if it's a color curve we're editing. UCurveLinearColor* ColorCurve = Cast(GetEditingObject()); if (ColorCurve) { // We want to register an additional color editing view. This is effectively a "pinned" view who's visibility is controlled by the editor. TSharedRef ColorGradientView = SNew(SColorGradientCurveEditorView, CurveEditor.ToSharedRef()) .ViewMinInput(this, &FCurveAssetEditor::GetColorGradientViewMin) .ViewMaxInput(this, &FCurveAssetEditor::GetColorGradientViewMax) .IsEditingEnabled(true); ColorGradientView->GetGradientEditor()->SetCurveOwner(ColorCurve); CurveEditorPanel->AddView(ColorGradientView); } return NewDockTab; } TSharedRef FCurveAssetEditor::SpawnTab_ColorCurveEditor(const FSpawnTabArgs& Args) { check(Args.GetTabId().TabType == ColorCurveEditorTabId); TSharedRef NewDockTab = SNew(SDockTab) .Label(LOCTEXT("ColorCurveEditor", "Color Curve Editor")) .TabColorScale(GetTabColorScale()) [ ColorCurveDetailsView.ToSharedRef() ]; return NewDockTab; } TSharedPtr FCurveAssetEditor::GetToolbarExtender() { // Use the Curve Editor Panel's extenders which already has all of the icons listed in the right order. return CurveEditorPanel->GetToolbarExtender(); } EOrientation FCurveAssetEditor::GetSnapLabelOrientation() const { return FMultiBoxSettings::UseSmallToolBarIcons.Get() ? EOrientation::Orient_Horizontal : EOrientation::Orient_Vertical; } TSharedRef FCurveAssetEditor::MakeCurveEditorCurveOptionsMenu() { struct FExtrapolationMenus { static void MakePreInfinityExtrapSubMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection( "Pre-Infinity Extrapolation", LOCTEXT( "CurveEditorMenuPreInfinityExtrapHeader", "Extrapolation" ) ); { MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().SetPreInfinityExtrapCycle); MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().SetPreInfinityExtrapCycleWithOffset); MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().SetPreInfinityExtrapOscillate); MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().SetPreInfinityExtrapLinear); MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().SetPreInfinityExtrapConstant); } MenuBuilder.EndSection(); } static void MakePostInfinityExtrapSubMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection( "Post-Infinity Extrapolation", LOCTEXT( "CurveEditorMenuPostInfinityExtrapHeader", "Extrapolation" ) ); { MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().SetPostInfinityExtrapCycle); MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().SetPostInfinityExtrapCycleWithOffset); MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().SetPostInfinityExtrapOscillate); MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().SetPostInfinityExtrapLinear); MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().SetPostInfinityExtrapConstant); } MenuBuilder.EndSection(); } }; FMenuBuilder MenuBuilder( true, CurveEditor->GetCommands()); MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().BakeCurve); MenuBuilder.AddMenuEntry( FCurveEditorCommands::Get().ReduceCurve); MenuBuilder.AddSubMenu( LOCTEXT( "PreInfinitySubMenu", "Pre-Infinity" ), LOCTEXT( "PreInfinitySubMenuToolTip", "Pre-Infinity Extrapolation" ), FNewMenuDelegate::CreateStatic( &FExtrapolationMenus::MakePreInfinityExtrapSubMenu ) ); MenuBuilder.AddSubMenu( LOCTEXT( "PostInfinitySubMenu", "Post-Infinity" ), LOCTEXT( "PostInfinitySubMenuToolTip", "Post-Infinity Extrapolation" ), FNewMenuDelegate::CreateStatic( &FExtrapolationMenus::MakePostInfinityExtrapSubMenu ) ); return MenuBuilder.MakeWidget(); } float FCurveAssetEditor::GetColorGradientViewMin() const { double MinBounds, MaxBounds; CurveEditor->GetBounds().GetInputBounds(MinBounds, MaxBounds); return MinBounds; } float FCurveAssetEditor::GetColorGradientViewMax() const { double MinBounds, MaxBounds; CurveEditor->GetBounds().GetInputBounds(MinBounds, MaxBounds); return MaxBounds; } #undef LOCTEXT_NAMESPACE