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

1821 lines
72 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SCurveEditorPanel.h"
#include "Algo/Sort.h"
#include "CommonFrameRates.h"
#include "Containers/Array.h"
#include "CurveEditor.h"
#include "CurveEditorCommands.h"
#include "CurveEditorEditObjectContainer.h"
#include "CurveEditorKeyProxy.h"
#include "CurveEditorSelection.h"
#include "CurveEditorSettings.h"
#include "CurveEditorSnapMetrics.h"
#include "CurveEditorViewRegistry.h"
#include "CurveModel.h"
#include "Curves/KeyHandle.h"
#include "Delegates/Delegate.h"
#include "Editor.h"
#include "Filters/FilterUtils.h"
#include "Filters/CurveEditorBakeFilter.h"
#include "Filters/CurveEditorReduceFilter.h"
#include "Filters/SCurveEditorFilterPanel.h"
#include "Fonts/FontMeasure.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandInfo.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/Docking/TabManager.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/SlateDelegates.h"
#include "HAL/IConsoleManager.h"
#include "HAL/PlatformCrt.h"
#include "ICurveEditorDragOperation.h"
#include "ICurveEditorExtension.h"
#include "ICurveEditorModule.h"
#include "ICurveEditorToolExtension.h"
#include "IPropertyRowGenerator.h"
#include "ISequencerWidgetsModule.h"
#include "Input/Events.h"
#include "InputCoreTypes.h"
#include "Internationalization/Internationalization.h"
#include "Layout/BasicLayoutWidgetSlot.h"
#include "Layout/Children.h"
#include "Layout/Clipping.h"
#include "Layout/Margin.h"
#include "Layout/PaintGeometry.h"
#include "Math/UnrealMathSSE.h"
#include "Math/Vector2D.h"
#include "Misc/Attribute.h"
#include "Misc/EnumClassFlags.h"
#include "Misc/FrameRate.h"
#include "Modules/ModuleManager.h"
#include "Rendering/DrawElements.h"
#include "Rendering/RenderingCommon.h"
#include "Rendering/SlateLayoutTransform.h"
#include "Rendering/SlateRenderer.h"
#include "SCurveEditorToolProperties.h"
#include "SCurveEditorView.h"
#include "SCurveEditorViewContainer.h"
#include "SCurveKeyDetailPanel.h"
#include "SGridLineSpacingList.h"
#include "ScopedTransaction.h"
#include "SlotBase.h"
#include "Filters/CurveEditorEulerFilter.h"
#include "Filters/PromotedFilterContainer.h"
#include "Misc/ResizeParamUtils.h"
#include "Modification/Utils/ScopedSelectionTransaction.h"
#include "Styling/AppStyle.h"
#include "Styling/CoreStyle.h"
#include "Styling/ISlateStyle.h"
#include "Styling/SlateColor.h"
#include "Templates/Casts.h"
#include "Templates/Tuple.h"
#include "Templates/TypeHash.h"
#include "Types/SlateEnums.h"
#include "UObject/NameTypes.h"
#include "UObject/Object.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SScrollBar.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Layout/SSplitter.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SFrameRatePicker.h"
#include "Widgets/SNullWidget.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Text/STextBlock.h"
class FPaintArgs;
class FSlateRect;
class FWidgetStyle;
class SWidget;
class SWindow;
struct FSlateBrush;
#define LOCTEXT_NAMESPACE "SCurveEditorPanel"
int32 GCurveEditorPinnedViews = 0;
static FAutoConsoleVariableRef CVarCurveEditorPinnedViews(
TEXT("CurveEditor.PinnedViews"),
GCurveEditorPinnedViews,
TEXT("Whether pinning a curve should also cause it to be exclusively added to a pinned view or not (default: off), rather than simply always remain visible.")
);
int32 GCurveEditorMaxCurvesPerPinnedView = 0;
static FAutoConsoleVariableRef CVarCurveEditorMaxCurvesPerPinnedView(
TEXT("CurveEditor.MaxCurvesPerPinnedView"),
GCurveEditorMaxCurvesPerPinnedView,
TEXT("When CurveEditor.PinnedViews is 1, defines the maximum number of curves allowed on a pinned view (0 for no maximum).")
);
/**
* Implemented as a friend struct to SCurveEditorView to ensure that SCurveEditorPanel is the only thing that can add/remove curves from views
* whilst disallowing access to any other private members;
*/
struct FCurveEditorPanelViewTracker
{
static void AddCurveToView(SCurveEditorView* View, FCurveModelID InCurveID)
{
View->AddCurve(InCurveID);
}
static void RemoveCurveFromView(SCurveEditorView* View, FCurveModelID InCurveID)
{
View->RemoveCurve(InCurveID);
}
};
SCurveEditorPanel::SCurveEditorPanel()
: PendingFocus(FPendingWidgetFocus::MakeNoTextEdit())
, bNeedsRefresh(true)
, CachedActiveCurvesSerialNumber(-1)
{
EditObjects = MakeUnique<FCurveEditorEditObjectContainer>();
bSelectionSupportsWeightedTangents = false;
}
SCurveEditorPanel::~SCurveEditorPanel()
{
// Attempt to close a dialog if it's open. It has a weak reference to us and doesn't work well when it's invalid.
SCurveEditorFilterPanel::CloseDialog();
}
void SCurveEditorPanel::Construct(const FArguments& InArgs, TSharedRef<FCurveEditor> InCurveEditor)
{
GridLineTintAttribute = InArgs._GridLineTint;
DisabledTimeSnapTooltipAttribute = InArgs._DisabledTimeSnapTooltip;
WeakTabManager = InArgs._TabManager;
CachedSelectionSerialNumber = 0;
CurveEditor = InCurveEditor;
CurveEditor->SetPanel(SharedThis(this));
CurveEditor->BindCommands();
CurveEditor->SetTimeSliderController(InArgs._ExternalTimeSliderController);
CurveEditor->OnActiveToolChangedDelegate.AddSP(this, &SCurveEditorPanel::OnCurveEditorToolChanged);
CommandList = MakeShared<FUICommandList>();
CommandList->Append(InCurveEditor->GetCommands().ToSharedRef());
ToolbarPromotedFilterBinder = MakeUnique<UE::CurveEditor::FPromotedFilterCommandBinder>(
GetCurveEditor()->GetToolbarPromotedFilters().ToSharedRef(), CommandList.ToSharedRef(), CurveEditor.ToSharedRef()
);
BindCommands();
ColumnFillCoefficients[0] = 0.3f;
ColumnFillCoefficients[1] = 0.7f;
if (CurveEditor->GetSettings())
{
ColumnFillCoefficients[0] = CurveEditor->GetSettings()->GetTreeViewWidth();
ColumnFillCoefficients[1] = 1.f - CurveEditor->GetSettings()->GetTreeViewWidth();
}
TAttribute<float> FillCoefficient_0, FillCoefficient_1;
{
FillCoefficient_0.Bind(TAttribute<float>::FGetter::CreateSP(this, &SCurveEditorPanel::GetColumnFillCoefficient, 0));
FillCoefficient_1.Bind(TAttribute<float>::FGetter::CreateSP(this, &SCurveEditorPanel::GetColumnFillCoefficient, 1));
}
// Create some Widgets
ISequencerWidgetsModule& SequencerWidgets = FModuleManager::Get().LoadModuleChecked<ISequencerWidgetsModule>("SequencerWidgets");
TSharedPtr<SWidget> TopTimeSlider = SNullWidget::NullWidget;
if (InArgs._ExternalTimeSliderController)
{
TopTimeSlider = SequencerWidgets.CreateTimeSlider(InArgs._ExternalTimeSliderController.ToSharedRef(), false /*bMirrorLabels*/);
}
TSharedRef<SScrollBar> ScrollBar = SNew(SScrollBar).Thickness(FVector2D(5.f, 5.f));
TSharedRef<SWidget> MainContent =
SNew(SOverlay)
// The main editing area
+ SOverlay::Slot()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
// Top Time Slider
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.BorderBackgroundColor(FLinearColor(.50f, .50f, .50f, 1.0f))
.Padding(0)
.Clipping(EWidgetClipping::ClipToBounds)
[
TopTimeSlider.ToSharedRef()
]
]
+ SVerticalBox::Slot()
[
SNew(SOverlay)
+ SOverlay::Slot()
[
SAssignNew(ScrollBox, SScrollBox)
.ExternalScrollbar(ScrollBar)
+ SScrollBox::Slot()
[
// Main Curve View Area. The contents of this are dynamically filled based
// on your current views.
SAssignNew(CurveViewsContainer, SCurveEditorViewContainer, InCurveEditor)
.ExternalTimeSliderController(InArgs._ExternalTimeSliderController)
.MinimumPanelHeight(InArgs._MinimumViewPanelHeight)
]
]
+ SOverlay::Slot()
.HAlign(HAlign_Right)
[
ScrollBar
]
+ SOverlay::Slot()
.Padding(10.0f, 10.0f)
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
[
SAssignNew(ToolPropertiesPanel, SCurveEditorToolProperties, InCurveEditor, FCurveEditorToolID::Unset())
]
]
]
// An overlay for the main area which lets us put system-wide overlays
+ SOverlay::Slot()
[
SNew(SOverlay)
.Visibility(this, &SCurveEditorPanel::ShouldInstructionOverlayBeVisible)
// Darker background
+ SOverlay::Slot()
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.BorderBackgroundColor(FLinearColor::Black.CopyWithNewOpacity(0.35f))
]
// Text
+ SOverlay::Slot()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("CurveEditorTutorialOverlay", "Select a curve on the left to begin editing."))
.Font(FCoreStyle::Get().GetFontStyle("FontAwesome.13"))
.ColorAndOpacity(FLinearColor::White)
]
]
];
if (InArgs._TreeContent.Widget != SNullWidget::NullWidget)
{
ChildSlot
[
SAssignNew(TreeViewSplitter, SSplitter)
.Orientation(Orient_Horizontal)
.Style(FAppStyle::Get(), "SplitterDark")
.PhysicalSplitterHandleSize(3.0f)
.OnSplitterFinishedResizing(this, &SCurveEditorPanel::OnSplitterFinishedResizing)
+ SSplitter::Slot()
.Value(FillCoefficient_0)
.MinSize(1.f)
.OnSlotResized(SSplitter::FOnSlotResized::CreateSP(this, &SCurveEditorPanel::OnColumnFillCoefficientChanged, 0))
[
InArgs._TreeContent.Widget
]
+ SSplitter::Slot()
.MinSize(1.f)
.Value(FillCoefficient_1)
.OnSlotResized(SSplitter::FOnSlotResized::CreateSP(this, &SCurveEditorPanel::OnColumnFillCoefficientChanged, 1))
[
MainContent
]
];
}
else
{
ChildSlot
[
MainContent
];
}
KeyDetailsView = SNew(SCurveKeyDetailPanel, CurveEditor.ToSharedRef())
.IsEnabled(this, &SCurveEditorPanel::IsInlineEditPanelEditable);
UpdateEditBox();
UpdateAxisSnapping();
// Initializes our Curve Views on the next Tick
SetViewMode(ECurveEditorViewID::Absolute);
}
TArrayView<const TSharedPtr<SCurveEditorView>> SCurveEditorPanel::GetViews() const
{
return CurveViewsContainer->GetViews();
}
void SCurveEditorPanel::ScrollBy(float Amount)
{
ScrollBox->SetScrollOffset(ScrollBox->GetScrollOffset() + Amount);
}
void SCurveEditorPanel::BindCommands()
{
// Interpolation and tangents
{
FExecuteAction SetConstant = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetKeyAttributes, FKeyAttributes().SetInterpMode(RCIM_Constant).SetTangentMode(RCTM_Auto), LOCTEXT("SetInterpConstant", "Set Interp Constant"));
FExecuteAction SetLinear = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetKeyAttributes, FKeyAttributes().SetInterpMode(RCIM_Linear).SetTangentMode(RCTM_Auto), LOCTEXT("SetInterpLinear", "Set Interp Linear"));
FExecuteAction SetCubicAuto = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetKeyAttributes, FKeyAttributes().SetInterpMode(RCIM_Cubic).SetTangentMode(RCTM_Auto), LOCTEXT("SetInterpCubic", "Set Interp Auto"));
FExecuteAction SetCubicSmartAuto = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetKeyAttributes, FKeyAttributes().SetInterpMode(RCIM_Cubic).SetTangentMode(RCTM_SmartAuto), LOCTEXT("SetInterpSmartAuto", "Set Interp Smart Auto"));
FExecuteAction SetCubicUser = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetKeyAttributes, FKeyAttributes().SetInterpMode(RCIM_Cubic).SetTangentMode(RCTM_User), LOCTEXT("SetInterpUser", "Set Interp User"));
FExecuteAction SetCubicBreak = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetKeyAttributes, FKeyAttributes().SetInterpMode(RCIM_Cubic).SetTangentMode(RCTM_Break), LOCTEXT("SetInterpBreak", "Set Interp Break"));
FExecuteAction ToggleWeighted = FExecuteAction::CreateSP(this, &SCurveEditorPanel::ToggleWeightedTangents);
FCanExecuteAction CanToggleWeighted = FCanExecuteAction::CreateSP(this, &SCurveEditorPanel::CanToggleWeightedTangents);
FIsActionChecked IsConstantCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonInterpolationMode, RCIM_Constant);
FIsActionChecked IsLinearCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonInterpolationMode, RCIM_Linear);
FIsActionChecked IsCubicAutoCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonTangentMode, RCIM_Cubic, RCTM_Auto);
FIsActionChecked IsCubicSmartAutoCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonTangentMode, RCIM_Cubic, RCTM_SmartAuto);
FIsActionChecked IsCubicUserCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonTangentMode, RCIM_Cubic, RCTM_User);
FIsActionChecked IsCubicBreakCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonTangentMode, RCIM_Cubic, RCTM_Break);
FIsActionChecked IsCubicWeightCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonTangentWeightMode, RCIM_Cubic, RCTWM_WeightedBoth);
FCanExecuteAction CanSetKeyTangent = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CanSetKeyInterpolation);
int32 SupportedTangentTypes = CurveEditor->GetSupportedTangentTypes();
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationCubicSmartAuto)
{
CommandList->MapAction(FCurveEditorCommands::Get().InterpolationCubicSmartAuto, SetCubicSmartAuto, CanSetKeyTangent, IsCubicSmartAutoCommon);
};
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationCubicAuto)
{
CommandList->MapAction(FCurveEditorCommands::Get().InterpolationCubicAuto, SetCubicAuto, CanSetKeyTangent, IsCubicAutoCommon);
};
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationCubicUser)
{
CommandList->MapAction(FCurveEditorCommands::Get().InterpolationCubicUser, SetCubicUser, CanSetKeyTangent, IsCubicUserCommon);
}
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationCubicBreak)
{
CommandList->MapAction(FCurveEditorCommands::Get().InterpolationCubicBreak, SetCubicBreak, CanSetKeyTangent, IsCubicBreakCommon);
}
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationLinear)
{
CommandList->MapAction(FCurveEditorCommands::Get().InterpolationLinear, SetLinear, CanSetKeyTangent, IsLinearCommon);
}
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationConstant)
{
CommandList->MapAction(FCurveEditorCommands::Get().InterpolationConstant, SetConstant, CanSetKeyTangent, IsConstantCommon);
}
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationCubicWeighted)
{
CommandList->MapAction(FCurveEditorCommands::Get().InterpolationToggleWeighted, ToggleWeighted, CanToggleWeighted, IsCubicWeightCommon);
}
}
const FCanExecuteAction CanExtrapolate = FCanExecuteAction::CreateLambda([this](){ return CanSetKeyInterpolation(); });
// Pre Extrapolation Modes
{
FExecuteAction SetCycle = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetCurveAttributes, FCurveAttributes().SetPreExtrapolation(RCCE_Cycle), LOCTEXT("SetPreExtrapCycle", "Set Pre Extrapolation (Cycle)"));
FExecuteAction SetCycleWithOffset = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetCurveAttributes, FCurveAttributes().SetPreExtrapolation(RCCE_CycleWithOffset), LOCTEXT("SetPreExtrapCycleWithOffset", "Set Pre Extrapolation (Cycle With Offset)"));
FExecuteAction SetOscillate = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetCurveAttributes, FCurveAttributes().SetPreExtrapolation(RCCE_Oscillate), LOCTEXT("SetPreExtrapOscillate", "Set Pre Extrapolation (Oscillate)"));
FExecuteAction SetLinear = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetCurveAttributes, FCurveAttributes().SetPreExtrapolation(RCCE_Linear), LOCTEXT("SetPreExtrapLinear", "Set Pre Extrapolation (Linear)"));
FExecuteAction SetConstant = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetCurveAttributes, FCurveAttributes().SetPreExtrapolation(RCCE_Constant), LOCTEXT("SetPreExtrapConstant", "Set Pre Extrapolation (Constant)"));
FIsActionChecked IsCycleCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonPreExtrapolationMode, RCCE_Cycle);
FIsActionChecked IsCycleWithOffsetCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonPreExtrapolationMode, RCCE_CycleWithOffset);
FIsActionChecked IsOscillateCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonPreExtrapolationMode, RCCE_Oscillate);
FIsActionChecked IsLinearCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonPreExtrapolationMode, RCCE_Linear);
FIsActionChecked IsConstantCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonPreExtrapolationMode, RCCE_Constant);
CommandList->MapAction(FCurveEditorCommands::Get().SetPreInfinityExtrapCycle, SetCycle, CanExtrapolate, IsCycleCommon);
CommandList->MapAction(FCurveEditorCommands::Get().SetPreInfinityExtrapCycleWithOffset, SetCycleWithOffset, CanExtrapolate, IsCycleWithOffsetCommon);
CommandList->MapAction(FCurveEditorCommands::Get().SetPreInfinityExtrapOscillate, SetOscillate, CanExtrapolate, IsOscillateCommon);
CommandList->MapAction(FCurveEditorCommands::Get().SetPreInfinityExtrapLinear, SetLinear, CanExtrapolate, IsLinearCommon);
CommandList->MapAction(FCurveEditorCommands::Get().SetPreInfinityExtrapConstant, SetConstant, CanExtrapolate, IsConstantCommon);
}
// Post Extrapolation Modes
{
FExecuteAction SetCycle = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetCurveAttributes, FCurveAttributes().SetPostExtrapolation(RCCE_Cycle), LOCTEXT("SetPostExtrapCycle", "Set Post Extrapolation (Cycle)"));
FExecuteAction SetCycleWithOffset = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetCurveAttributes, FCurveAttributes().SetPostExtrapolation(RCCE_CycleWithOffset), LOCTEXT("SetPostExtrapCycleWithOffset", "Set Post Extrapolation (Cycle With Offset)"));
FExecuteAction SetOscillate = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetCurveAttributes, FCurveAttributes().SetPostExtrapolation(RCCE_Oscillate), LOCTEXT("SetPostExtrapOscillate", "Set Post Extrapolation (Oscillate)"));
FExecuteAction SetLinear = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetCurveAttributes, FCurveAttributes().SetPostExtrapolation(RCCE_Linear), LOCTEXT("SetPostExtrapLinear", "Set Post Extrapolation (Linear)"));
FExecuteAction SetConstant = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetCurveAttributes, FCurveAttributes().SetPostExtrapolation(RCCE_Constant), LOCTEXT("SetPostExtrapConstant", "Set Post Extrapolation (Constant)"));
FIsActionChecked IsCycleCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonPostExtrapolationMode, RCCE_Cycle);
FIsActionChecked IsCycleWithOffsetCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonPostExtrapolationMode, RCCE_CycleWithOffset);
FIsActionChecked IsOscillateCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonPostExtrapolationMode, RCCE_Oscillate);
FIsActionChecked IsLinearCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonPostExtrapolationMode, RCCE_Linear);
FIsActionChecked IsConstantCommon = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareCommonPostExtrapolationMode, RCCE_Constant);
CommandList->MapAction(FCurveEditorCommands::Get().SetPostInfinityExtrapCycle, SetCycle, CanExtrapolate, IsCycleCommon);
CommandList->MapAction(FCurveEditorCommands::Get().SetPostInfinityExtrapCycleWithOffset, SetCycleWithOffset, CanExtrapolate, IsCycleWithOffsetCommon);
CommandList->MapAction(FCurveEditorCommands::Get().SetPostInfinityExtrapOscillate, SetOscillate, CanExtrapolate, IsOscillateCommon);
CommandList->MapAction(FCurveEditorCommands::Get().SetPostInfinityExtrapLinear, SetLinear, CanExtrapolate, IsLinearCommon);
CommandList->MapAction(FCurveEditorCommands::Get().SetPostInfinityExtrapConstant, SetConstant, CanExtrapolate, IsConstantCommon);
}
// Absolute, Stacked and Normalized views.
{
FExecuteAction SetViewAbsolute = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetViewMode, ECurveEditorViewID::Absolute);
FIsActionChecked IsViewModeAbsolute = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareViewMode, ECurveEditorViewID::Absolute);
FExecuteAction SetViewStacked = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetViewMode, ECurveEditorViewID::Stacked);
FIsActionChecked IsViewModeStacked = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareViewMode, ECurveEditorViewID::Stacked);
FExecuteAction SetViewNormalized = FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetViewMode, ECurveEditorViewID::Normalized);
FIsActionChecked IsViewModeNormalized = FIsActionChecked::CreateSP(this, &SCurveEditorPanel::CompareViewMode, ECurveEditorViewID::Normalized);
CommandList->MapAction(FCurveEditorCommands::Get().SetViewModeAbsolute, SetViewAbsolute, FCanExecuteAction(), IsViewModeAbsolute);
CommandList->MapAction(FCurveEditorCommands::Get().SetViewModeStacked, SetViewStacked, FCanExecuteAction(), IsViewModeStacked);
CommandList->MapAction(FCurveEditorCommands::Get().SetViewModeNormalized, SetViewNormalized, FCanExecuteAction(), IsViewModeNormalized);
}
{
// Deselect Current Keys
TSharedPtr<FCurveEditor> LocalCurveEditor = CurveEditor;
FExecuteAction DeselectAllAction = FExecuteAction::CreateLambda([LocalCurveEditor] { if (LocalCurveEditor.IsValid())
{
const UE::CurveEditor::FScopedSelectionTransaction Transaction(LocalCurveEditor, LOCTEXT("DeselectAllKeys", "Deselect all keys"));
LocalCurveEditor->GetSelection().Clear();
}
});
CommandList->MapAction(FCurveEditorCommands::Get().DeselectAllKeys, DeselectAllAction);
}
// Presets for Bake and Reduce
CommandList->MapAction(FCurveEditorCommands::Get().BakeCurve, FExecuteAction::CreateSP(this, &SCurveEditorPanel::ShowCurveFilterUI, TSubclassOf<UCurveEditorFilterBase>(UCurveEditorBakeFilter::StaticClass())));
CommandList->MapAction(FCurveEditorCommands::Get().ReduceCurve, FExecuteAction::CreateSP(this, &SCurveEditorPanel::ShowCurveFilterUI, TSubclassOf<UCurveEditorFilterBase>(UCurveEditorReduceFilter::StaticClass())));
// User Implementable Filter just defaults to Bake since we know it exists...
CommandList->MapAction(FCurveEditorCommands::Get().OpenUserImplementableFilterWindow, FExecuteAction::CreateSP(this, &SCurveEditorPanel::ShowCurveFilterUI, TSubclassOf<UCurveEditorFilterBase>(UCurveEditorBakeFilter::StaticClass())));
// Axis Snapping
{
FUIAction SetSnappingNoneAction(FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetAxisSnapping, ECurveEditorSnapAxis::CESA_None));
FUIAction SetSnappingHorizontalAction(FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetAxisSnapping, ECurveEditorSnapAxis::CESA_X));
FUIAction SetSnappingVerticalAction(FExecuteAction::CreateSP(this, &SCurveEditorPanel::SetAxisSnapping, ECurveEditorSnapAxis::CESA_Y));
CommandList->MapAction(FCurveEditorCommands::Get().SetAxisSnappingNone, SetSnappingNoneAction);
CommandList->MapAction(FCurveEditorCommands::Get().SetAxisSnappingHorizontal, SetSnappingHorizontalAction);
CommandList->MapAction(FCurveEditorCommands::Get().SetAxisSnappingVertical, SetSnappingVerticalAction);
}
}
void SCurveEditorPanel::SetViewMode(const ECurveEditorViewID NewViewMode)
{
DefaultViewID = NewViewMode;
bNeedsRefresh = true;
}
bool SCurveEditorPanel::CompareViewMode(const ECurveEditorViewID InViewMode) const
{
return DefaultViewID == InViewMode;
}
void SCurveEditorPanel::SetAxisSnapping(ECurveEditorSnapAxis InAxis)
{
CurveEditor->GetSettings()->SetSnapAxis(InAxis);
UpdateAxisSnapping();
}
void SCurveEditorPanel::UpdateAxisSnapping()
{
FCurveEditorAxisSnap Snap = CurveEditor->GetAxisSnap();
Snap.RestrictedAxisList = CurveEditor->GetSettings()->GetSnapAxis();
CurveEditor->SetAxisSnap(Snap);
}
void SCurveEditorPanel::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
if (bNeedsRefresh || CachedActiveCurvesSerialNumber != CurveEditor->GetActiveCurvesSerialNumber())
{
RebuildCurveViews();
if (CurveEditor->ShouldAutoFrame())
{
CurveEditor->ZoomToFitCurves(CurveEditor->GetEditedCurves().Array());
}
bNeedsRefresh = false;
CachedActiveCurvesSerialNumber = CurveEditor->GetActiveCurvesSerialNumber();
}
UpdateCommonCurveInfo();
UpdateEditBox();
UpdateTime();
CachedSelectionSerialNumber = CurveEditor->Selection.GetSerialNumber();
}
void SCurveEditorPanel::ResetMinMaxes()
{
//only reset the min/max if we have views since we will then get these values from them
//otherwise if we didn't we would end up with everything back to 0,1 again.
if (CurveViews.IsEmpty() == false)
{
LastOutputMin = DBL_MAX;
LastOutputMax = DBL_MIN;
}
}
void SCurveEditorPanel::RemoveCurveFromViews(FCurveModelID InCurveID)
{
for (auto It = CurveViews.CreateKeyIterator(InCurveID); It; ++It)
{
SCurveEditorView* View = &It.Value().Get();
//cache these so we can re-use it on reconstruction
if (View)
{
if (View->GetOutputMin() < LastOutputMin)
{
LastOutputMin = View->GetOutputMin();
}
if (View->GetOutputMax() > LastOutputMax)
{
LastOutputMax = View->GetOutputMax();
}
}
FCurveEditorPanelViewTracker::RemoveCurveFromView(View, InCurveID);
It.RemoveCurrent();
}
}
void SCurveEditorPanel::PostUndo()
{
EditObjects->CurveIDToKeyProxies.Empty();
// Force the edit box to update (ie. the value of the keys might have changed)
CachedSelectionSerialNumber = 0;
UpdateEditBox();
// Reset the selection serial number so that time doesn't change since selection didn't really change on undo
CachedSelectionSerialNumber = CurveEditor->Selection.GetSerialNumber();
}
void SCurveEditorPanel::AddView(TSharedRef<SCurveEditorView> ViewToAdd)
{
ExternalViews.Add(ViewToAdd);
bNeedsRefresh = true;
}
void SCurveEditorPanel::RemoveView(TSharedRef<SCurveEditorView> ViewToRemove)
{
ExternalViews.Remove(ViewToRemove);
bNeedsRefresh = true;
}
TSharedPtr<SCurveEditorView> SCurveEditorPanel::CreateViewOfType(FCurveModelID CurveModelID, ECurveEditorViewID ViewTypeID, bool bPinned)
{
for (auto It = FreeViewsByType.CreateKeyIterator(ViewTypeID); It; ++It)
{
TSharedRef<SCurveEditorView> View = It.Value();
if (!GCurveEditorPinnedViews || View->bPinned == bPinned)
{
FCurveEditorPanelViewTracker::AddCurveToView(&View.Get(), CurveModelID);
CurveViews.Add(CurveModelID, View);
if (!View->HasCapacity())
{
It.RemoveCurrent();
}
if (LastOutputMin != DBL_MAX && LastOutputMax != DBL_MIN)
{
View->SetOutputBounds(LastOutputMin, LastOutputMax);
}
return View;
}
}
TSharedPtr<SCurveEditorView> View = FCurveEditorViewRegistry::Get().ConstructView(ViewTypeID, CurveEditor);
if (View)
{
if (GCurveEditorPinnedViews && bPinned)
{
// Pinned views are always a fixed height
View->bPinned = true;
View->MaximumCapacity = View->MaximumCapacity == 0 ? GCurveEditorMaxCurvesPerPinnedView : FMath::Min(View->MaximumCapacity, GCurveEditorMaxCurvesPerPinnedView);
if (!View->FixedHeight.IsSet())
{
View->FixedHeight = 100.f;
}
}
View->ViewTypeID = ViewTypeID;
FCurveEditorPanelViewTracker::AddCurveToView(View.Get(), CurveModelID);
CurveViews.Add(CurveModelID, View.ToSharedRef());
if (View->HasCapacity())
{
FreeViewsByType.Add(ViewTypeID, View.ToSharedRef());
}
}
return View;
}
void SCurveEditorPanel::RebuildCurveViews()
{
TSet<TSharedRef<SCurveEditorView>> Views = ExternalViews;
for (const TTuple<FCurveModelID, TUniquePtr<FCurveModel>>& CurvePair : CurveEditor->GetCurves())
{
FCurveModel* Curve = CurvePair.Value.Get();
FCurveModelID CurveID = CurvePair.Key;
const bool bIsPinned = CurveEditor->IsCurvePinned(CurveID);
bool bNeedsView = true;
for (auto ViewIt = CurveViews.CreateKeyIterator(CurveID); ViewIt; ++ViewIt)
{
TSharedRef<SCurveEditorView> View = ViewIt.Value();
// @todo: pinning - This code causes curves that have changed their pinned state to be re-added to a correctly (un)pinned views
if (GCurveEditorPinnedViews && View->bPinned != bIsPinned)
{
// No longer the same pinned status as the view it's in - remove it so that it can get added to the correct view (or removed entirely)
FCurveEditorPanelViewTracker::RemoveCurveFromView(&View.Get(), CurveID);
ViewIt.RemoveCurrent();
}
else if (View->ViewTypeID == DefaultViewID || !EnumHasAnyFlags(View->ViewTypeID, ECurveEditorViewID::ANY_BUILT_IN))
{
// Keep this view if it is the default view or any other custom view
Views.Add(View);
bNeedsView = false;
}
else
{
// Built in view which is no longer the selected mode - remove it
FCurveEditorPanelViewTracker::RemoveCurveFromView(&View.Get(), CurveID);
ViewIt.RemoveCurrent();
}
}
if (bNeedsView)
{
ECurveEditorViewID SupportedViews = Curve->GetSupportedViews();
// Add to the default view if supported, else use the first supported view we can find.
// @todo: this may require extra work if curves are ever to support multiple views but it's fine for now
if (EnumHasAnyFlags(SupportedViews, DefaultViewID))
{
TSharedPtr<SCurveEditorView> NewView = CreateViewOfType(CurveID, DefaultViewID, bIsPinned);
if (NewView)
{
Views.Add(NewView.ToSharedRef());
}
continue;
}
ECurveEditorViewID CustomView = ECurveEditorViewID::CUSTOM_START;
while (CustomView >= ECurveEditorViewID::CUSTOM_START)
{
if (EnumHasAnyFlags(SupportedViews, ECurveEditorViewID(CustomView)))
{
TSharedPtr<SCurveEditorView> NewView = CreateViewOfType(CurveID, CustomView, bIsPinned);
if (NewView)
{
Views.Add(NewView.ToSharedRef());
}
}
CustomView = ECurveEditorViewID((__underlying_type(ECurveEditorViewID))(CustomView) << 1);
}
}
}
// Remove any empty views
for (auto It = FreeViewsByType.CreateIterator(); It; ++It)
{
if (!It.Value()->bAllowEmpty && It.Value()->NumCurves() == 0)
{
It.RemoveCurrent();
}
}
// Sort by pinned, then capacity
TArray<TSharedRef<SCurveEditorView>> SortedViews = Views.Array();
Algo::Sort(SortedViews, [](const TSharedPtr<SCurveEditorView>& A, const TSharedPtr<SCurveEditorView>& B)
{
if (A->SortBias == B->SortBias)
{
if (A->bPinned == B->bPinned)
{
return A->RelativeOrder < B->RelativeOrder;
}
return A->bPinned != 0;
}
return A->SortBias > B->SortBias;
}
);
CurveViewsContainer->Clear();
for (TSharedPtr<SCurveEditorView> View : SortedViews)
{
CurveViewsContainer->AddView(View.ToSharedRef());
}
OnPostRebuildCurveViewsDelegate.Broadcast();
}
void SCurveEditorPanel::UpdateCommonCurveInfo()
{
// Gather up common extended curve info for the current set of curves
TOptional<FCurveAttributes> AccumulatedCurveAttributes;
for (const TTuple<FCurveModelID, FKeyHandleSet>& Pair : CurveEditor->Selection.GetAll())
{
FCurveAttributes Attributes;
FCurveModel* Curve = CurveEditor->FindCurve(Pair.Key);
if (Curve)
{
Curve->GetCurveAttributes(Attributes);
// Some curves don't support extrapolation. We don't count them for determine the accumulated state.
if (Attributes.HasPreExtrapolation() && Attributes.GetPreExtrapolation() == RCCE_None && Attributes.HasPostExtrapolation() && Attributes.GetPostExtrapolation() == RCCE_None)
{
continue;
}
if (!AccumulatedCurveAttributes.IsSet())
{
AccumulatedCurveAttributes = Attributes;
}
else
{
AccumulatedCurveAttributes = FCurveAttributes::MaskCommon(AccumulatedCurveAttributes.GetValue(), Attributes);
}
}
}
// Reset the common curve and key info
bSelectionSupportsWeightedTangents = false;
CachedCommonCurveAttributes = AccumulatedCurveAttributes.Get(FCurveAttributes());
TOptional<FKeyAttributes> AccumulatedKeyAttributes;
TArray<FKeyAttributes> AllKeyAttributes;
for (const TTuple<FCurveModelID, FKeyHandleSet>& Pair : CurveEditor->Selection.GetAll())
{
FCurveModel* Curve = CurveEditor->FindCurve(Pair.Key);
if (Curve)
{
AllKeyAttributes.Reset();
AllKeyAttributes.SetNum(Pair.Value.Num());
Curve->GetKeyAttributes(Pair.Value.AsArray(), AllKeyAttributes);
for (const FKeyAttributes& Attributes : AllKeyAttributes)
{
if (Attributes.HasTangentWeightMode())
{
bSelectionSupportsWeightedTangents = true;
}
if (!AccumulatedKeyAttributes.IsSet())
{
AccumulatedKeyAttributes = Attributes;
}
else
{
AccumulatedKeyAttributes = FKeyAttributes::MaskCommon(AccumulatedKeyAttributes.GetValue(), Attributes);
}
}
}
}
// Reset the common curve and key info
CachedCommonKeyAttributes = AccumulatedKeyAttributes.Get(FKeyAttributes());
}
void SCurveEditorPanel::OnCurveEditorToolChanged(FCurveEditorToolID InToolId)
{
ToolPropertiesPanel->OnToolChanged(InToolId);
}
void SCurveEditorPanel::UpdateTime()
{
const FCurveEditorSelection& Selection = CurveEditor->Selection;
if (CachedSelectionSerialNumber == Selection.GetSerialNumber())
{
return;
}
if (CurveEditor->GetSettings()->GetSnapTimeToSelection())
{
CurveEditor->SnapToSelectedKey();
}
}
void SCurveEditorPanel::UpdateEditBox()
{
const FCurveEditorSelection& Selection = CurveEditor->Selection;
for (auto& OuterPair : EditObjects->CurveIDToKeyProxies)
{
const FKeyHandleSet* SelectedKeys = Selection.FindForCurve(OuterPair.Key);
if(SelectedKeys)
{
for (auto& InnerPair : OuterPair.Value)
{
if (ICurveEditorKeyProxy* Proxy = Cast<ICurveEditorKeyProxy>(InnerPair.Value))
{
Proxy->UpdateValuesFromRawData();
}
}
}
}
if (CachedSelectionSerialNumber == Selection.GetSerialNumber())
{
return;
}
TArray<FKeyHandle> KeyHandleScratch;
TArray<UObject*> NewProxiesScratch;
TArray<UObject*> AllEditObjects;
for (const TTuple<FCurveModelID, FKeyHandleSet>& Pair : Selection.GetAll())
{
FCurveModel* Curve = CurveEditor->FindCurve(Pair.Key);
if (!Curve)
{
continue;
}
KeyHandleScratch.Reset();
NewProxiesScratch.Reset();
auto& KeyHandleToEditObject = EditObjects->CurveIDToKeyProxies.FindOrAdd(Pair.Key);
for (FKeyHandle Handle : Pair.Value.AsArray())
{
if (UObject* Existing = KeyHandleToEditObject.FindRef(Handle))
{
AllEditObjects.Add(Existing);
}
else
{
KeyHandleScratch.Add(Handle);
}
}
if (KeyHandleScratch.Num() > 0)
{
NewProxiesScratch.SetNum(KeyHandleScratch.Num());
Curve->CreateKeyProxies(KeyHandleScratch, NewProxiesScratch);
for (int32 Index = 0; Index < KeyHandleScratch.Num(); ++Index)
{
if (UObject* NewObject = NewProxiesScratch[Index])
{
KeyHandleToEditObject.Add(KeyHandleScratch[Index], NewObject);
AllEditObjects.Add(NewObject);
// Update the proxy immediately after adding it so that it doesn't have the wrong values for 1 tick.
if (ICurveEditorKeyProxy* Proxy = Cast<ICurveEditorKeyProxy>(NewObject))
{
Proxy->UpdateValuesFromRawData();
}
}
}
}
}
KeyDetailsView->GetPropertyRowGenerator()->SetObjects(AllEditObjects);
}
TSharedRef<SWidget> SCurveEditorPanel::MakeTangentModeMenu()
{
FMenuBuilder MenuBuilder(true, GetCommands());
const int32 SupportedTangentTypes = CurveEditor->GetSupportedTangentTypes();
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationCubicSmartAuto)
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().InterpolationCubicSmartAuto);
};
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationCubicAuto)
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().InterpolationCubicAuto);
};
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationCubicUser)
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().InterpolationCubicUser);
}
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationCubicBreak)
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().InterpolationCubicBreak);
}
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationLinear)
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().InterpolationLinear);
}
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationConstant)
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().InterpolationConstant);
}
return MenuBuilder.MakeWidget();
}
FText SCurveEditorPanel::GetTangentModeLabel() const
{
switch (DetermineTangentMode())
{
case ETangentModeComboState::NoSelection: return LOCTEXT("TangentMode.NoSelection.Label", "Mode");
case ETangentModeComboState::Constant: return LOCTEXT("TangentMode.Constant.Label", "Constant");
case ETangentModeComboState::Linear: return LOCTEXT("TangentMode.Linear.Label", "Linear");
case ETangentModeComboState::CubicAuto: return LOCTEXT("TangentMode.CubicAuto.Label", "Cubic (auto)");
case ETangentModeComboState::CubicSmartAuto: return LOCTEXT("TangentMode.CubicSmartAuto.Label", "Cubic (smart)");
case ETangentModeComboState::CubicUser: return LOCTEXT("TangentMode.CubicUser.Label", "Cubic (flat)");
case ETangentModeComboState::CubicBreak: return LOCTEXT("TangentMode.CubicBreak.Label", "Cubic (broken)");
case ETangentModeComboState::Mixed: return LOCTEXT("TangentMode.Mixed.Label", "Mixed");
default: checkNoEntry(); return FText::GetEmpty();
}
}
FText SCurveEditorPanel::GetTangentModeTooltip() const
{
switch (DetermineTangentMode())
{
case ETangentModeComboState::NoSelection: return LOCTEXT("TangentMode.NoSelection.Tooltip", "Select keys to set tangent mode");
case ETangentModeComboState::Constant: return FCurveEditorCommands::Get().InterpolationConstant->GetDescription();
case ETangentModeComboState::Linear: return FCurveEditorCommands::Get().InterpolationLinear->GetDescription();
case ETangentModeComboState::CubicAuto: return FCurveEditorCommands::Get().InterpolationCubicAuto->GetDescription();
case ETangentModeComboState::CubicSmartAuto: return FCurveEditorCommands::Get().InterpolationCubicSmartAuto->GetDescription();
case ETangentModeComboState::CubicUser: return FCurveEditorCommands::Get().InterpolationCubicUser->GetDescription();
case ETangentModeComboState::CubicBreak: return FCurveEditorCommands::Get().InterpolationCubicBreak->GetDescription();
case ETangentModeComboState::Mixed: return LOCTEXT("TangentMode.Mixed.Tooltip", "The selected keys have different tangent modes.");
default: checkNoEntry(); return FText::GetEmpty();
}
}
FSlateIcon SCurveEditorPanel::GetTangentModeIcon() const
{
switch (DetermineTangentMode())
{
case ETangentModeComboState::NoSelection: return FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GenericCurveEditor.InterpolationNoSelection");
case ETangentModeComboState::Constant: return FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GenericCurveEditor.InterpolationConstant");
case ETangentModeComboState::Linear: return FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GenericCurveEditor.InterpolationLinear");
case ETangentModeComboState::CubicAuto: return FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GenericCurveEditor.InterpolationCubicAuto");
case ETangentModeComboState::CubicSmartAuto: return FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GenericCurveEditor.InterpolationCubicSmartAuto");
case ETangentModeComboState::CubicUser: return FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GenericCurveEditor.InterpolationCubicUser");
case ETangentModeComboState::CubicBreak: return FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GenericCurveEditor.InterpolationCubicBreak");
case ETangentModeComboState::Mixed: return FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GenericCurveEditor.InterpolationMixed");
default: checkNoEntry(); return FSlateIcon();
}
}
bool SCurveEditorPanel::IsTangentModeComboEnabled() const
{
const ETangentModeComboState ComboState = DetermineTangentMode();
return ComboState != ETangentModeComboState::NoSelection;
}
SCurveEditorPanel::ETangentModeComboState SCurveEditorPanel::DetermineTangentMode() const
{
if (CurveEditor->Selection.IsEmpty())
{
return ETangentModeComboState::NoSelection;
}
const bool bIsConstantCommon = CompareCommonInterpolationMode(RCIM_Constant);
const bool bIsLinearCommon = CompareCommonInterpolationMode(RCIM_Linear);
const bool bIsCubicAutoCommon = CompareCommonTangentMode(RCIM_Cubic, RCTM_Auto);
const bool bIsCubicSmartAutoCommon = CompareCommonTangentMode(RCIM_Cubic, RCTM_SmartAuto);
const bool bIsCubicUserCommon = CompareCommonTangentMode(RCIM_Cubic, RCTM_User);
const bool bIsCubicBreakCommon = CompareCommonTangentMode(RCIM_Cubic, RCTM_Break);
if (bIsConstantCommon)
{
return ETangentModeComboState::Constant;
}
if (bIsLinearCommon)
{
return ETangentModeComboState::Linear;
}
if (bIsCubicAutoCommon)
{
return ETangentModeComboState::CubicAuto;
}
if (bIsCubicSmartAutoCommon)
{
return ETangentModeComboState::CubicSmartAuto;
}
if (bIsCubicUserCommon)
{
return ETangentModeComboState::CubicUser;
}
if (bIsCubicBreakCommon)
{
return ETangentModeComboState::CubicBreak;
}
return ETangentModeComboState::Mixed;
}
EVisibility SCurveEditorPanel::GetSplitterVisibility() const
{
return EVisibility::Visible;
}
void SCurveEditorPanel::SetKeyAttributes(FKeyAttributes KeyAttributes, FText Description)
{
FScopedTransaction Transaction(Description);
for (const TTuple<FCurveModelID, FKeyHandleSet>& Pair : CurveEditor->Selection.GetAll())
{
if (FCurveModel* Curve = CurveEditor->FindCurve(Pair.Key))
{
Curve->Modify();
Curve->SetKeyAttributes(Pair.Value.AsArray(), KeyAttributes);
}
}
}
void SCurveEditorPanel::SetCurveAttributes(FCurveAttributes CurveAttributes, FText Description)
{
FScopedTransaction Transaction(Description);
for (const TTuple<FCurveModelID, FKeyHandleSet>& Pair : CurveEditor->Selection.GetAll())
{
if (FCurveModel* Curve = CurveEditor->FindCurve(Pair.Key))
{
Curve->Modify();
Curve->SetCurveAttributes(CurveAttributes);
}
}
}
void SCurveEditorPanel::ToggleWeightedTangents()
{
FScopedTransaction Transaction(LOCTEXT("ToggleWeightedTangents_Transaction", "Toggle Weighted Tangents"));
TMap<FCurveModelID, TArray<FKeyAttributes>> KeyAttributesPerCurve;
const TMap<FCurveModelID, FKeyHandleSet>& Selection = CurveEditor->GetSelection().GetAll();
// Disable weights unless we find something that doesn't have weights, then add them
FKeyAttributes KeyAttributesToAssign = FKeyAttributes().SetTangentWeightMode(RCTWM_WeightedNone);
// Gather current key attributes
for (const TTuple<FCurveModelID, FKeyHandleSet>& Pair : Selection)
{
FCurveModel* Curve = CurveEditor->FindCurve(Pair.Key);
if (Curve)
{
TArray<FKeyAttributes>& KeyAttributes = KeyAttributesPerCurve.Add(Pair.Key);
KeyAttributes.SetNum(Pair.Value.Num());
Curve->GetKeyAttributes(Pair.Value.AsArray(), KeyAttributes);
// Check all the key attributes if they support tangent weights, but don't have any. If we find any such keys, we'll enable weights on all.
if (KeyAttributesToAssign.GetTangentWeightMode() == RCTWM_WeightedNone)
{
for (const FKeyAttributes& Attributes : KeyAttributes)
{
if (Attributes.HasTangentWeightMode() && !(Attributes.HasArriveTangentWeight() || Attributes.HasLeaveTangentWeight()))
{
KeyAttributesToAssign.SetTangentWeightMode(RCTWM_WeightedBoth);
break;
}
}
}
}
}
// Assign the new key attributes to all the selected curves
for (TTuple<FCurveModelID, TArray<FKeyAttributes>>& Pair : KeyAttributesPerCurve)
{
FCurveModel* Curve = CurveEditor->FindCurve(Pair.Key);
if (Curve)
{
for (FKeyAttributes& Attributes : Pair.Value)
{
Attributes = KeyAttributesToAssign;
}
TArrayView<const FKeyHandle> KeyHandles = Selection.FindChecked(Pair.Key).AsArray();
Curve->Modify();
Curve->SetKeyAttributes(KeyHandles, Pair.Value);
}
}
}
bool SCurveEditorPanel::CanToggleWeightedTangents() const
{
return bSelectionSupportsWeightedTangents && CanSetKeyInterpolation();
}
bool SCurveEditorPanel::CanSetKeyInterpolation() const
{
return CurveEditor->GetSelection().Count() > 0;
}
FReply SCurveEditorPanel::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
if (InKeyEvent.GetKey() == EKeys::Escape)
{
const UE::CurveEditor::FScopedSelectionTransaction Transaction(GetCurveEditor(), LOCTEXT("EscapeKeys", "Press escape"));
CurveEditor->Selection.Clear();
return FReply::Handled();
}
else if (CommandList->ProcessCommandBindings(InKeyEvent))
{
return FReply::Handled();
}
return FReply::Unhandled();
}
void SCurveEditorPanel::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
SCompoundWidget::OnMouseEnter(MyGeometry, MouseEvent);
PendingFocus.SetPendingFocusIfNeeded(AsWeak());
}
void SCurveEditorPanel::OnMouseLeave(const FPointerEvent& MouseEvent)
{
SCompoundWidget::OnMouseLeave(MouseEvent);
PendingFocus.ResetPendingFocus();
}
void SCurveEditorPanel::EnablePendingFocusOnHovering(const bool InEnabled)
{
PendingFocus.Enable(InEnabled);
}
TSharedRef<SWidget> SCurveEditorPanel::MakeCurveEditorCurveViewOptionsMenu(TSharedPtr<FExtender> InExtender)
{
FMenuBuilder MenuBuilder(true, CurveEditor->GetCommands(), InExtender);
// Framing
MenuBuilder.BeginSection("Framing", LOCTEXT("FrameHeader", "Frame"));
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().ZoomToFit);
}
MenuBuilder.EndSection();
// Viewing mode
MenuBuilder.BeginSection("CurveViewMode", LOCTEXT("CurveViewModeHeader", "Curve View Mode"));
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetViewModeAbsolute);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetViewModeStacked);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetViewModeNormalized);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("TangentVisibility", LOCTEXT("CurveEditorMenuTangentVisibilityHeader", "Tangent Visibility"));
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetAllTangentsVisibility);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetSelectedKeysTangentVisibility);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetNoTangentsVisibility);
}
MenuBuilder.EndSection();
MenuBuilder.AddMenuSeparator();
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().ToggleAutoFrameCurveEditor);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().ToggleSnapTimeToSelection);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().ToggleShowBufferedCurves);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().ToggleShowBars);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().ToggleShowCurveEditorCurveToolTips);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().ToggleShowValueIndicatorLines);
MenuBuilder.BeginSection("Organize", LOCTEXT("CurveEditorMenuOrganizeHeader", "Organize"));
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().ToggleExpandCollapseNodes);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().ToggleExpandCollapseNodesAndDescendants);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("CurveColors", LOCTEXT("CurveColorsHeader", "Curve Colors"));
{
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetRandomCurveColorsForSelected);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetCurveColorsForSelected);
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
TSharedRef<SWidget> SCurveEditorPanel::MakeCurvesMenu(TSharedPtr<FExtender> InExtender)
{
FMenuBuilder MenuBuilder(true, CurveEditor->GetCommands(), InExtender);
AddPreInfinityToMenu(MenuBuilder);
AddPostInfinityToMenu(MenuBuilder);
return MenuBuilder.MakeWidget();
}
void SCurveEditorPanel::AddPreInfinityToMenu(FMenuBuilder& InMenuBuilder)
{
InMenuBuilder.BeginSection("PreInfinity", LOCTEXT("CurveEditorMenuPreInfinityHeader", "Pre-Infinity"));
{
InMenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetPreInfinityExtrapConstant);
InMenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetPreInfinityExtrapCycle);
InMenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetPreInfinityExtrapCycleWithOffset);
InMenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetPreInfinityExtrapLinear);
InMenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetPreInfinityExtrapOscillate);
}
InMenuBuilder.EndSection();
}
void SCurveEditorPanel::AddPostInfinityToMenu(FMenuBuilder& InMenuBuilder)
{
InMenuBuilder.BeginSection("PostInfinity", LOCTEXT("CurveEditorMenuPostInfinityHeader", "Post-Infinity"));
{
InMenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetPostInfinityExtrapConstant);
InMenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetPostInfinityExtrapCycle);
InMenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetPostInfinityExtrapCycleWithOffset);
InMenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetPostInfinityExtrapLinear);
InMenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetPostInfinityExtrapOscillate);
}
InMenuBuilder.EndSection();
}
FSlateIcon SCurveEditorPanel::GetCurveExtrapolationPreIcon() const
{
// We check to see if pre/post share a extrapolation mode and return a shared icon, otherwise mixed.
if (CompareCommonPreExtrapolationMode(RCCE_Constant))
{
return FCurveEditorCommands::Get().SetPreInfinityExtrapConstant->GetIcon();
}
else if (CompareCommonPreExtrapolationMode(RCCE_Cycle))
{
return FCurveEditorCommands::Get().SetPreInfinityExtrapCycle->GetIcon();
}
else if (CompareCommonPreExtrapolationMode(RCCE_CycleWithOffset))
{
return FCurveEditorCommands::Get().SetPreInfinityExtrapCycleWithOffset->GetIcon();
}
else if (CompareCommonPreExtrapolationMode(RCCE_Linear))
{
return FCurveEditorCommands::Get().SetPreInfinityExtrapLinear->GetIcon();
}
else if (CompareCommonPreExtrapolationMode(RCCE_Oscillate))
{
return FCurveEditorCommands::Get().SetPreInfinityExtrapOscillate->GetIcon();
}
else
{
return FSlateIcon(FAppStyle::GetAppStyleSetName(), "GenericCurveEditor.PreInfinityMixed");
}
}
FSlateIcon SCurveEditorPanel::GetCurveExtrapolationPostIcon() const
{
// We check to see if pre/post share a extrapolation mode and return a shared icon, otherwise mixed.
if (CompareCommonPostExtrapolationMode(RCCE_Constant))
{
return FCurveEditorCommands::Get().SetPostInfinityExtrapConstant->GetIcon();
}
else if (CompareCommonPostExtrapolationMode(RCCE_Cycle))
{
return FCurveEditorCommands::Get().SetPostInfinityExtrapCycle->GetIcon();
}
else if (CompareCommonPostExtrapolationMode(RCCE_CycleWithOffset))
{
return FCurveEditorCommands::Get().SetPostInfinityExtrapCycleWithOffset->GetIcon();
}
else if (CompareCommonPostExtrapolationMode(RCCE_Linear))
{
return FCurveEditorCommands::Get().SetPostInfinityExtrapLinear->GetIcon();
}
else if (CompareCommonPostExtrapolationMode(RCCE_Oscillate))
{
return FCurveEditorCommands::Get().SetPostInfinityExtrapOscillate->GetIcon();
}
else
{
return FSlateIcon(FAppStyle::GetAppStyleSetName(), "GenericCurveEditor.PostInfinityMixed");
}
}
void SCurveEditorPanel::ShowCurveFilterUI(TSubclassOf<UCurveEditorFilterBase> FilterClass)
{
TSharedPtr<FTabManager> TabManager = WeakTabManager.Pin();
TSharedPtr<SDockTab> OwnerTab = TabManager.IsValid() ? TabManager->GetOwnerTab() : TSharedPtr<SDockTab>();
TSharedPtr<SWindow> RootWindow = OwnerTab.IsValid() ? OwnerTab->GetParentWindow() : TSharedPtr<SWindow>();
FilterPanel = SCurveEditorFilterPanel::OpenDialog(RootWindow, CurveEditor.ToSharedRef(), FilterClass);
FilterPanel->OnFilterClassChanged.BindRaw(this, &SCurveEditorPanel::FilterClassChanged);
FilterClassChanged();
}
void SCurveEditorPanel::FilterClassChanged()
{
OnFilterClassChanged.ExecuteIfBound();
}
const FGeometry& SCurveEditorPanel::GetScrollPanelGeometry() const
{
return ScrollBox->GetCachedGeometry();
}
const FGeometry& SCurveEditorPanel::GetViewContainerGeometry() const
{
return CurveViewsContainer->GetCachedGeometry();
}
namespace UE::CurveEditor::PanelDetail
{
static void FillToolbar(
FToolBarBuilder& ToolBarBuilder,
TSharedRef<SCurveKeyDetailPanel> InKeyDetailsPanel,
TSharedRef<SCurveEditorPanel> InEditorPanel,
TSharedRef<FPromotedFilterContainer> InToolbarPromotedFilters,
TSharedPtr<FExtender> InExtender
)
{
}
}
TSharedPtr<FExtender> SCurveEditorPanel::GetToolbarExtender()
{
using namespace UE::CurveEditor::PanelDetail;
// We're going to create a new Extender and add the main Curve Editor icons to it.
// We combine this with the extender provided by the Curve Editor Module as that extender has been extended by tools
TSharedPtr<FExtender> Extender = CombineEditorExtensions();
Extender->AddToolBarExtension(
"Asset", EExtensionHook::After, GetCommands(),
FToolBarExtensionDelegate::CreateSP(this, &SCurveEditorPanel::BuildToolbar, Extender)
);
return Extender;
}
TSharedPtr<FExtender> SCurveEditorPanel::CombineEditorExtensions() const
{
const TSharedRef<FUICommandList>& CommandListRef = GetCommands().ToSharedRef();
ICurveEditorModule& CurveEditorModule = FModuleManager::Get().LoadModuleChecked<ICurveEditorModule>("CurveEditor");
TArray<TSharedPtr<FExtender>> ToolbarExtenders;
for (ICurveEditorModule::FCurveEditorMenuExtender& ExtenderCallback : CurveEditorModule.GetAllToolBarMenuExtenders())
{
ToolbarExtenders.Add(ExtenderCallback.Execute(CommandListRef));
}
for (const TSharedRef<ICurveEditorExtension>& Extension : CurveEditor->GetEditorExtensions())
{
if (const TSharedPtr<FExtender> OptionalExtender = Extension->MakeToolbarExtender(CommandListRef))
{
ToolbarExtenders.Add(OptionalExtender);
}
}
return FExtender::Combine(ToolbarExtenders);
}
void SCurveEditorPanel::BuildToolbar(FToolBarBuilder& InToolBarBuilder, TSharedPtr<FExtender> InBaseExtender)
{
using namespace UE::CurveEditor;
InToolBarBuilder.BeginSection("View");
InToolBarBuilder.BeginStyleOverride("CurveEditorToolbar");
{
InToolBarBuilder.SetLabelVisibility(EVisibility::Visible); // Show label..
InToolBarBuilder.AddComboButton(
FUIAction(),
FOnGetContent::CreateSP(this, &SCurveEditorPanel::MakeCurveEditorCurveViewOptionsMenu, InBaseExtender),
LOCTEXT("CurveEditor.ViewOptions.Label", "View"),
LOCTEXT("CurveEditor.ViewOptions.ToolTip", "View Options"),
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "Icons.Visibility"),
false, NAME_None, {}, {}, {}, EUserInterfaceActionType::Button,
MakeResizeParams(TEXT("View"))
);
InToolBarBuilder.SetLabelVisibility(EVisibility::Collapsed); // ... but don't affect the other entries
}
InToolBarBuilder.EndSection();
InToolBarBuilder.BeginSection("Tools");
{
InToolBarBuilder.BeginStyleOverride("CurveEditorToolBar.ToolsCombo"); // Min size for combo button so size stays consistent independent of label size
InToolBarBuilder.SetLabelVisibility(EVisibility::Visible); // Show label..
InToolBarBuilder.AddComboButton(
FUIAction(),
FOnGetContent::CreateSP(this, &SCurveEditorPanel::MakeToolsComboMenu, InBaseExtender),
TAttribute<FText>(this, &SCurveEditorPanel::GetCurrentToolLabel),
FText::GetEmpty(),
TAttribute<FSlateIcon>(this, &SCurveEditorPanel::GetCurrentToolIcon),
false, NAME_None, {}, {}, {}, EUserInterfaceActionType::Button,
MakeResizeParams(TEXT("Tools"))
);
InToolBarBuilder.SetLabelVisibility(EVisibility::Collapsed); // ... but don't affect the other entries
InToolBarBuilder.EndStyleOverride();
}
InToolBarBuilder.EndSection();
InToolBarBuilder.BeginSection("Key Details");
{
InToolBarBuilder.AddWidget(KeyDetailsView.ToSharedRef(), {}, NAME_None, true, {}, {}, MakeResizeParams(TEXT("KeyDetails")));
}
InToolBarBuilder.EndSection();
InToolBarBuilder.BeginSection("Adjustment");
{
// Dropdown Menu for choosing your axis snapping for tool movement
TAttribute<FSlateIcon> AxisSnappingModeIcon;
AxisSnappingModeIcon.Bind(TAttribute<FSlateIcon>::FGetter::CreateLambda([this] {
switch (this->GetCurveEditor()->GetAxisSnap().RestrictedAxisList)
{
case ECurveEditorSnapAxis::CESA_X:
return FCurveEditorCommands::Get().SetAxisSnappingHorizontal->GetIcon();
case ECurveEditorSnapAxis::CESA_Y:
return FCurveEditorCommands::Get().SetAxisSnappingVertical->GetIcon();
default: // ECurveEditorSnapAxis::CESA_None
return FCurveEditorCommands::Get().SetAxisSnappingNone->GetIcon();
}
}));
InToolBarBuilder.AddComboButton(
FUIAction(),
FOnGetContent::CreateSP(this, &SCurveEditorPanel::MakeAxisSnapMenu),
LOCTEXT("AxisSnappingOptions", "Axis Snapping"),
LOCTEXT("AxisSnappingOptionsToolTip", "Choose which axes movement tools are locked to."),
AxisSnappingModeIcon,
false, NAME_None, {}, {}, {}, EUserInterfaceActionType::Button,
MakeResizeParams(TEXT("AxisSnapping"))
);
// Toggle Button for Time Snapping
InToolBarBuilder.AddToolBarButton(
FCurveEditorCommands::Get().ToggleInputSnapping,
NAME_None, {}, {}, {}, NAME_None, {}, {}, {},
MakeResizeParams(TEXT("ToggleInputSnapping"))
);
// Dropdown Menu to choose the snapping scale.
FUIAction TimeSnapMenuAction(FExecuteAction(), FCanExecuteAction::CreateLambda([this] { return !this->DisabledTimeSnapTooltipAttribute.IsSet(); }));
InToolBarBuilder.AddComboButton(
TimeSnapMenuAction,
FOnGetContent::CreateSP(this, &SCurveEditorPanel::MakeTimeSnapMenu),
LOCTEXT("TimeSnappingOptions", "Time Snapping"),
TAttribute<FText>(this, &SCurveEditorPanel::GetTimeSnapMenuTooltip),
TAttribute<FSlateIcon>(),
true,
NAME_None, {}, {}, {}, EUserInterfaceActionType::Button,
MakeResizeParams(TEXT("TimeSnapping"))
);
// Toggle Button for Value Snapping
InToolBarBuilder.AddToolBarButton(FCurveEditorCommands::Get().ToggleOutputSnapping,
NAME_None, {}, {}, {}, NAME_None, {}, {}, {},
MakeResizeParams(TEXT("ToggleOutputSnapping"))
);
// Dropdown Menu to choose the snapping scale.
InToolBarBuilder.AddComboButton(
FUIAction(),
FOnGetContent::CreateSP(this, &SCurveEditorPanel::MakeGridSpacingMenu),
LOCTEXT("GridSnappingOptions", "Grid Snapping"),
LOCTEXT("GridSnappingOptionsToolTip", "Choose the spacing between horizontal grid lines."),
TAttribute<FSlateIcon>(),
true,
NAME_None, {}, {}, {}, EUserInterfaceActionType::Button,
MakeResizeParams(TEXT("GridSnapping"))
);
}
InToolBarBuilder.EndSection();
InToolBarBuilder.BeginSection("Tangents");
{
InToolBarBuilder.BeginStyleOverride("CurveEditorToolBar.TangentMode"); // Min size for combo button so size stays consistent independent of label size
InToolBarBuilder.SetLabelVisibility(EVisibility::Visible); // Show label...
InToolBarBuilder.AddComboButton(
FUIAction(FExecuteAction(), FCanExecuteAction::CreateSP(this, &SCurveEditorPanel::IsTangentModeComboEnabled)),
FOnGetContent::CreateSP(this, &SCurveEditorPanel::MakeTangentModeMenu),
TAttribute<FText>::CreateSP(this, &SCurveEditorPanel::GetTangentModeLabel),
TAttribute<FText>::CreateSP(this, &SCurveEditorPanel::GetTangentModeTooltip),
TAttribute<FSlateIcon>::CreateSP(this, &SCurveEditorPanel::GetTangentModeIcon),
false, NAME_None, {}, {}, {}, EUserInterfaceActionType::Button,
MakeResizeParams(TEXT("TangentMenu"))
);
InToolBarBuilder.SetLabelVisibility(EVisibility::Collapsed); // ... but don't affect the other entries
InToolBarBuilder.EndStyleOverride();
const int32 SupportedTangentTypes = this->CurveEditor->GetSupportedTangentTypes();
if (SupportedTangentTypes & (int32)ECurveEditorTangentTypes::InterpolationCubicWeighted)
{
InToolBarBuilder.AddToolBarButton(FCurveEditorCommands::Get().InterpolationToggleWeighted,
NAME_None, {}, {}, {}, NAME_None, {}, {}, {},
MakeResizeParams(TEXT("InterpolationToggleWeighted"))
);
}
InToolBarBuilder.AddToolBarButton(FCurveEditorCommands::Get().FlattenTangents,
NAME_None, {}, {}, {}, NAME_None, {}, {}, {},
MakeResizeParams(TEXT("FlattenTangents"))
);
InToolBarBuilder.AddToolBarButton(FCurveEditorCommands::Get().StraightenTangents,
NAME_None, {}, {}, {}, NAME_None, {}, {}, {},
MakeResizeParams(TEXT("StraightenTangents"))
);
}
InToolBarBuilder.EndSection();
InToolBarBuilder.BeginSection("Curves");
{
InToolBarBuilder.SetLabelVisibility(EVisibility::Visible); // Show label...
InToolBarBuilder.AddComboButton(
FUIAction(),
FOnGetContent::CreateSP(this, &SCurveEditorPanel::MakeCurvesMenu, InBaseExtender),
LOCTEXT("CurveEditor.CurvesCombo.Label", "Curves"),
LOCTEXT("CurveEditor.CurvesCombo.ToolTip", "Curves"),
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GenericCurveEditor.Curves")
);
InToolBarBuilder.SetLabelVisibility(EVisibility::Collapsed); // ... but don't affect the other entries
GetCurveEditor()->GetToolbarPromotedFilters()->AppendToBuilder(InToolBarBuilder);
InToolBarBuilder.AddToolBarButton(FCurveEditorCommands::Get().OpenUserImplementableFilterWindow);
}
InToolBarBuilder.EndStyleOverride();
}
TSharedRef<SWidget> SCurveEditorPanel::MakeTimeSnapMenu()
{
TSharedRef<SWidget> InputSnapWidget =
SNew(SFrameRatePicker)
.Value_Lambda([this] { return this->CurveEditor->InputSnapRateAttribute.Get(); })
.OnValueChanged_Lambda([this](FFrameRate InFrameRate) { this->CurveEditor->InputSnapRateAttribute = InFrameRate; })
.PresetValues({
// We re-use the common frame rates but omit some of them.
FCommonFrameRateInfo{ FCommonFrameRates::FPS_12(), LOCTEXT("Snap_Input_Twelve", "82ms (1/12s)"), LOCTEXT("Snap_Input_Description_Twelve", "Snap time values to one twelfth of a second (ie: 12fps)") },
FCommonFrameRateInfo{ FCommonFrameRates::FPS_15(), LOCTEXT("Snap_Input_Fifteen", "66ms (1/15s)"), LOCTEXT("Snap_Input_Description_Fifteen", "Snap time values to one fifteenth of a second (ie: 15fps)") },
FCommonFrameRateInfo{ FCommonFrameRates::FPS_24(), LOCTEXT("Snap_Input_TwentyFour", "42ms (1/24s)"), LOCTEXT("Snap_Input_Description_TwentyFour", "Snap time values to one twenty-fourth of a second (ie: 24fps)") },
FCommonFrameRateInfo{ FCommonFrameRates::FPS_25(), LOCTEXT("Snap_Input_TwentyFive", "40ms (1/25s)"), LOCTEXT("Snap_Input_Description_TwentyFive", "Snap time values to one twenty-fifth of a second (ie: 25fps)") },
FCommonFrameRateInfo{ FCommonFrameRates::FPS_30(), LOCTEXT("Snap_Input_Thirty", "33ms (1/30s)"), LOCTEXT("Snap_Input_Description_Thirty", "Snap time values to one thirtieth of a second (ie: 30fps)") },
FCommonFrameRateInfo{ FCommonFrameRates::FPS_48(), LOCTEXT("Snap_Input_FourtyEight", "21ms (1/48s)"), LOCTEXT("Snap_Input_Description_FourtyEight", "Snap time values to one fourth-eight of a second (ie: 48fps)") },
FCommonFrameRateInfo{ FCommonFrameRates::FPS_50(), LOCTEXT("Snap_Input_Fifty", "20ms (1/50s)"), LOCTEXT("Snap_Input_Description_Fifty", "Snap time values to one fiftieth of a second (ie: 50fps)") },
FCommonFrameRateInfo{ FCommonFrameRates::FPS_60(), LOCTEXT("Snap_Input_Sixty", "16ms (1/60s)"), LOCTEXT("Snap_Input_Description_Sixty", "Snap time values to one sixtieth of a second (ie: 60fps)") },
FCommonFrameRateInfo{ FCommonFrameRates::FPS_100(), LOCTEXT("Snap_Input_OneHundred", "10ms (1/100s)"), LOCTEXT("Snap_Input_Description_OneHundred", "Snap time values to one one-hundredth of a second (ie: 100fps)") },
FCommonFrameRateInfo{ FCommonFrameRates::FPS_120(), LOCTEXT("Snap_Input_OneHundredTwenty", "8ms (1/120s)"), LOCTEXT("Snap_Input_Description_OneHundredTwenty", "Snap time values to one one-hundred-twentieth of a second (ie: 120fps)") },
FCommonFrameRateInfo{ FCommonFrameRates::FPS_240(), LOCTEXT("Snap_Input_TwoHundredFourty", "4ms (1/240s)"), LOCTEXT("Snap_Input_Description_TwoHundredFourty", "Snap time values to one two-hundred-fourtieth of a second (ie: 240fps)") }
});
return InputSnapWidget;
}
FText SCurveEditorPanel::GetTimeSnapMenuTooltip() const
{
// If this is specified then the time snap menu is disabled
if (DisabledTimeSnapTooltipAttribute.IsSet())
{
return DisabledTimeSnapTooltipAttribute.Get();
}
return LOCTEXT("TimeSnappingOptionsToolTip", "Choose what precision the Time axis is snapped to while moving keys.");
}
TSharedRef<SWidget> SCurveEditorPanel::MakeGridSpacingMenu()
{
TArray<SGridLineSpacingList::FNamedValue> SpacingAmounts;
// SnapValues.Add( SNumericDropDown<float>::FNamedValue( 0.001f, LOCTEXT( "Snap_OneThousandth", "0.001" ), LOCTEXT( "SnapDescription_OneThousandth", "Set snap to 1/1000th" ) ) );
//SnapValues.Add( SNumericDropDown<float>::FNamedValue( 0.01f, LOCTEXT( "Snap_OneHundredth", "0.01" ), LOCTEXT( "SnapDescription_OneHundredth", "Set snap to 1/100th" ) ) );
SpacingAmounts.Add(SGridLineSpacingList::FNamedValue(0.1f, LOCTEXT("OneTenth", "0.1"), LOCTEXT("Description_OneTenth", "Set grid spacing to 1/10th")));
SpacingAmounts.Add(SGridLineSpacingList::FNamedValue(0.5f, LOCTEXT("OneHalf", "0.5"), LOCTEXT("Description_OneHalf", "Set grid spacing to 1/2")));
SpacingAmounts.Add(SGridLineSpacingList::FNamedValue(1.0f, LOCTEXT("One", "1"), LOCTEXT("Description_One", "Set grid spacing to 1")));
SpacingAmounts.Add(SGridLineSpacingList::FNamedValue(2.0f, LOCTEXT("Two", "2"), LOCTEXT("Description_Two", "Set grid spacing to 2")));
SpacingAmounts.Add(SGridLineSpacingList::FNamedValue(5.0f, LOCTEXT("Five", "5"), LOCTEXT("Description_Five", "Set grid spacing to 5")));
SpacingAmounts.Add(SGridLineSpacingList::FNamedValue(10.0f, LOCTEXT("Ten", "10"), LOCTEXT("Description_Ten", "Set grid spacing to 10")));
SpacingAmounts.Add(SGridLineSpacingList::FNamedValue(50.0f, LOCTEXT("Fifty", "50"), LOCTEXT("Description_50", "Set grid spacing to 50")));
SpacingAmounts.Add(SGridLineSpacingList::FNamedValue(100.0f, LOCTEXT("OneHundred", "100"), LOCTEXT("Description_OneHundred", "Set grid spacing to 100")));
SpacingAmounts.Add(SGridLineSpacingList::FNamedValue(TOptional<float>(), LOCTEXT("Automatic", "Automatic"), LOCTEXT("Description_Automatic", "Set grid spacing to automatic")));
TSharedRef<SWidget> OutputSnapWidget =
SNew(SGridLineSpacingList)
.DropDownValues(SpacingAmounts)
.MinDesiredValueWidth(60)
.Value_Lambda([this]() -> TOptional<float> { return this->CurveEditor->FixedGridSpacingAttribute.Get(); })
.OnValueChanged_Lambda([this](TOptional<float> InNewOutputSnap) { this->CurveEditor->FixedGridSpacingAttribute = InNewOutputSnap; })
.HeaderText(LOCTEXT("CurveEditorMenuGridSpacingHeader", "Grid Spacing"));
return OutputSnapWidget;
}
TSharedRef<SWidget> SCurveEditorPanel::MakeFlipCurveMenu(FCurveEditor::ECurveFlipDirection Direction)
{
FMenuBuilder MenuBuilder(true, nullptr);
MenuBuilder.BeginSection("Header", LOCTEXT("CurveEditorMenuCurveFlipgHeader", "Curve Flip Settings"));
FCurveEditor::FCurveFlipRangeSettings* RangeSetting = (Direction == FCurveEditor::ECurveFlipDirection::Horizontal) ? &CurveEditor->HorizontalCurveFlipRangeSettings : &CurveEditor->VerticalCurveFlipRangeSettings;
// For the x axis, the range of keys and the range of curve should be the same
if (Direction == FCurveEditor::ECurveFlipDirection::Horizontal)
{
// Add menu entry: "Use key/curve range"
MenuBuilder.AddMenuEntry(
LOCTEXT("UseKeyCurveRange", "Use Key/Curve Range"),
LOCTEXT("UseKeyCurveRangeTooltip", "Flip curve within the range of keys/curve"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([RangeSetting]()
{
RangeSetting->RangeType = FCurveEditor::ECurveFlipRangeType::CurveRange;
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([RangeSetting]() { return RangeSetting->RangeType == FCurveEditor::ECurveFlipRangeType::CurveRange; })
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
}
// For the y axis, the range of curve can be different than range of keys if tangent is set
if (Direction == FCurveEditor::ECurveFlipDirection::Vertical)
{
// Add menu entry: "Use key range"
MenuBuilder.AddMenuEntry(
LOCTEXT("UseKeyRange", "Use Key Range"),
LOCTEXT("UseKeyRangeTooltip", "Flip curve within the range of the keys"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([RangeSetting]()
{
RangeSetting->RangeType = FCurveEditor::ECurveFlipRangeType::KeyRange;
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([RangeSetting]() { return RangeSetting->RangeType == FCurveEditor::ECurveFlipRangeType::KeyRange; })
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
// Add menu entry: "Use curve range"
MenuBuilder.AddMenuEntry(
LOCTEXT("UseCurveRange", "Use Curve Range"),
LOCTEXT("UseCurveRangeTooltip", "Flip curve within the range of the curve"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([RangeSetting]()
{
RangeSetting->RangeType = FCurveEditor::ECurveFlipRangeType::CurveRange;
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([RangeSetting]() { return RangeSetting->RangeType == FCurveEditor::ECurveFlipRangeType::CurveRange; })
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
}
// Add "Custom Range" menu entry
MenuBuilder.AddMenuEntry(
LOCTEXT("CustomRange", "Use Custom Range"),
LOCTEXT("CustomRangeTooltip", "Flip curve within the range of float inputs (Default range is 0 to 1)"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([RangeSetting]()
{
RangeSetting->RangeType = FCurveEditor::ECurveFlipRangeType::CustomRange;
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([RangeSetting]() { return RangeSetting->RangeType == FCurveEditor::ECurveFlipRangeType::CustomRange; })
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
// Create a custom widget that includes two input boxes: one for "Min" and one for "Max"
TSharedRef<SWidget> CustomRangeWidget =
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0, 6, 0, 0))
[
SNew(STextBlock)
.Text(LOCTEXT("MinRangeLabel", "Min:"))
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SEditableTextBox)
.MinDesiredWidth(60.0f)
.Text_Lambda([RangeSetting]() -> FText {
return FText::AsNumber(RangeSetting->MinRange);
})
.OnTextCommitted_Lambda([RangeSetting](const FText& InText, ETextCommit::Type CommitType)
{
RangeSetting->MinRange = FCString::Atof(*InText.ToString());
})
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0, 8, 0, 0))
[
SNew(STextBlock)
.Text(LOCTEXT("MaxRangeLabel", "Max:"))
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SEditableTextBox)
.MinDesiredWidth(60.0f)
.Text_Lambda([RangeSetting]() -> FText {
return FText::AsNumber(RangeSetting->MaxRange);
})
.OnTextCommitted_Lambda([RangeSetting](const FText& InText, ETextCommit::Type CommitType)
{
RangeSetting->MaxRange = FCString::Atof(*InText.ToString());
})
];
// Only enable the custom range input widget when "Custom Range" is selected.
CustomRangeWidget->SetEnabled(TAttribute<bool>::CreateLambda([RangeSetting]() { return RangeSetting->RangeType == FCurveEditor::ECurveFlipRangeType::CustomRange; }));
MenuBuilder.AddWidget(CustomRangeWidget, LOCTEXT("CustomRangeInputs", "Range"));
return MenuBuilder.MakeWidget();
}
TSharedRef<SWidget> SCurveEditorPanel::MakeAxisSnapMenu()
{
FMenuBuilder MenuBuilder(true, GetCommands());
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetAxisSnappingNone);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetAxisSnappingHorizontal);
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().SetAxisSnappingVertical);
return MenuBuilder.MakeWidget();
}
bool SCurveEditorPanel::IsInlineEditPanelEditable() const
{
return CurveEditor->GetSelection().Count() > 0;
}
EVisibility SCurveEditorPanel::ShouldInstructionOverlayBeVisible() const
{
// The instruction overlay is visible if they have no selection in the tree.
const bool bCurvesAreVisible = CurveEditor->GetTreeSelection().Num() > 0 || CurveEditor->GetPinnedCurves().Num() > 0;
return bCurvesAreVisible ? EVisibility::Hidden : EVisibility::HitTestInvisible;
}
TSharedRef<SWidget> SCurveEditorPanel::MakeToolsComboMenu(TSharedPtr<FExtender> InExtender)
{
FMenuBuilder MenuBuilder(true, CurveEditor->GetCommands(), InExtender);
MenuBuilder.BeginSection(TEXT("Tools"));
MenuBuilder.AddMenuEntry(FCurveEditorCommands::Get().DeactivateCurrentTool);
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
FText SCurveEditorPanel::GetCurrentToolLabel() const
{
const ICurveEditorToolExtension* CurrentTool = GetCurveEditor()->GetCurrentTool();
return CurrentTool ? CurrentTool->GetLabel() : FCurveEditorCommands::Get().DeactivateCurrentTool->GetLabel();
}
FText SCurveEditorPanel::GetCurrentToolDescription() const
{
const ICurveEditorToolExtension* CurrentTool = GetCurveEditor()->GetCurrentTool();
return CurrentTool ? CurrentTool->GetDescription() : FCurveEditorCommands::Get().DeactivateCurrentTool->GetDescription();
}
FSlateIcon SCurveEditorPanel::GetCurrentToolIcon() const
{
const ICurveEditorToolExtension* CurrentTool = GetCurveEditor()->GetCurrentTool();
return CurrentTool ? CurrentTool->GetIcon() : FCurveEditorCommands::Get().DeactivateCurrentTool->GetIcon();
}
void SCurveEditorPanel::OnSplitterFinishedResizing()
{
SSplitter::FSlot const& LeftSplitterSlot = TreeViewSplitter->SlotAt(0);
SSplitter::FSlot const& RightSplitterSlot = TreeViewSplitter->SlotAt(1);
OnColumnFillCoefficientChanged(LeftSplitterSlot.GetSizeValue(), 0);
OnColumnFillCoefficientChanged(RightSplitterSlot.GetSizeValue(), 1);
CurveEditor->GetSettings()->SetTreeViewWidth(LeftSplitterSlot.GetSizeValue());
}
void SCurveEditorPanel::OnColumnFillCoefficientChanged(float FillCoefficient, int32 ColumnIndex)
{
ColumnFillCoefficients[ColumnIndex] = FillCoefficient;
}
#undef LOCTEXT_NAMESPACE