You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
2541 lines
76 KiB
C++
2541 lines
76 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "UnrealEd.h"
|
|
#include "RichCurveEditorCommands.h"
|
|
#include "SCurveEditor.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "SColorGradientEditor.h"
|
|
#include "GenericCommands.h"
|
|
#include "SNumericEntryBox.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "SCurveEditor"
|
|
|
|
const static FVector2D CONST_KeySize = FVector2D(11,11);
|
|
const static FVector2D CONST_TangentSize = FVector2D(7,7);
|
|
const static FVector2D CONST_CurveSize = FVector2D(12,12);
|
|
|
|
const static float CONST_FitMargin = 0.05f;
|
|
const static float CONST_MinViewRange = 0.01f;
|
|
const static float CONST_DefaultZoomRange = 1.0f;
|
|
const static float CONST_KeyTangentOffset = 60.0f;
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// SCurveEditor
|
|
|
|
void SCurveEditor::Construct(const FArguments& InArgs)
|
|
{
|
|
CurveFactory = NULL;
|
|
Commands = TSharedPtr< FUICommandList >(new FUICommandList);
|
|
CurveOwner = NULL;
|
|
|
|
// view input
|
|
ViewMinInput = InArgs._ViewMinInput;
|
|
ViewMaxInput = InArgs._ViewMaxInput;
|
|
// data input - only used when it's set
|
|
DataMinInput = InArgs._DataMinInput;
|
|
DataMaxInput = InArgs._DataMaxInput;
|
|
|
|
ViewMinOutput = InArgs._ViewMinOutput;
|
|
ViewMaxOutput = InArgs._ViewMaxOutput;
|
|
|
|
InputSnap = InArgs._InputSnap;
|
|
OutputSnap = InArgs._OutputSnap;
|
|
bSnappingEnabled = InArgs._SnappingEnabled;
|
|
|
|
bZoomToFitVertical = InArgs._ZoomToFitVertical;
|
|
bZoomToFitHorizontal = InArgs._ZoomToFitHorizontal;
|
|
DesiredSize = InArgs._DesiredSize;
|
|
|
|
GridColor = InArgs._GridColor;
|
|
|
|
bIsUsingSlider = false;
|
|
|
|
// if editor size is set, use it, otherwise, use default value
|
|
if (DesiredSize.Get().IsZero())
|
|
{
|
|
DesiredSize.Set(FVector2D(128, 64));
|
|
}
|
|
|
|
TimelineLength = InArgs._TimelineLength;
|
|
SetInputViewRangeHandler = InArgs._OnSetInputViewRange;
|
|
SetOutputViewRangeHandler = InArgs._OnSetOutputViewRange;
|
|
bDrawCurve = InArgs._DrawCurve;
|
|
bHideUI = InArgs._HideUI;
|
|
bAllowZoomOutput = InArgs._AllowZoomOutput;
|
|
bAlwaysDisplayColorCurves = InArgs._AlwaysDisplayColorCurves;
|
|
bShowZoomButtons = InArgs._ShowZoomButtons;
|
|
bShowCurveSelector = InArgs._ShowCurveSelector;
|
|
bDrawInputGridNumbers = InArgs._ShowInputGridNumbers;
|
|
bDrawOutputGridNumbers = InArgs._ShowOutputGridNumbers;
|
|
bShowCurveToolTips = InArgs._ShowCurveToolTips;
|
|
|
|
OnCreateAsset = InArgs._OnCreateAsset;
|
|
|
|
DragState = EDragState::None;
|
|
DragThreshold = 4;
|
|
|
|
//Simple r/g/b for now
|
|
CurveColors.Add(FLinearColor(1.0f, 0.0f, 0.0f));
|
|
CurveColors.Add(FLinearColor(0.0f, 1.0f, 0.0f));
|
|
CurveColors.Add(FLinearColor(0.05f, 0.05f, 1.0f));
|
|
|
|
TransactionIndex = -1;
|
|
|
|
Commands->MapAction(FGenericCommands::Get().Undo,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::UndoAction));
|
|
|
|
Commands->MapAction(FGenericCommands::Get().Redo,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::RedoAction));
|
|
|
|
Commands->MapAction(FRichCurveEditorCommands::Get().ZoomToFitHorizontal,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::ZoomToFitHorizontal));
|
|
|
|
Commands->MapAction(FRichCurveEditorCommands::Get().ZoomToFitVertical,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::ZoomToFitVertical));
|
|
|
|
Commands->MapAction(FRichCurveEditorCommands::Get().ZoomToFit,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::ZoomToFit));
|
|
|
|
Commands->MapAction(FRichCurveEditorCommands::Get().ToggleSnapping,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::ToggleSnapping),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SCurveEditor::IsSnappingEnabled));
|
|
|
|
Commands->MapAction(FRichCurveEditorCommands::Get().InterpolationConstant,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::OnSelectInterpolationMode, RCIM_Constant, RCTM_Auto),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SCurveEditor::IsInterpolationModeSelected, RCIM_Constant, RCTM_Auto));
|
|
|
|
Commands->MapAction(FRichCurveEditorCommands::Get().InterpolationLinear,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::OnSelectInterpolationMode, RCIM_Linear, RCTM_Auto),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SCurveEditor::IsInterpolationModeSelected, RCIM_Linear, RCTM_Auto));
|
|
|
|
Commands->MapAction(FRichCurveEditorCommands::Get().InterpolationCubicAuto,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::OnSelectInterpolationMode, RCIM_Cubic, RCTM_Auto),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SCurveEditor::IsInterpolationModeSelected, RCIM_Cubic, RCTM_Auto));
|
|
|
|
Commands->MapAction(FRichCurveEditorCommands::Get().InterpolationCubicUser,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::OnSelectInterpolationMode, RCIM_Cubic, RCTM_User),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SCurveEditor::IsInterpolationModeSelected, RCIM_Cubic, RCTM_User));
|
|
|
|
Commands->MapAction(FRichCurveEditorCommands::Get().InterpolationCubicBreak,
|
|
FExecuteAction::CreateSP(this, &SCurveEditor::OnSelectInterpolationMode, RCIM_Cubic, RCTM_Break),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SCurveEditor::IsInterpolationModeSelected, RCIM_Cubic, RCTM_Break));
|
|
|
|
SAssignNew(WarningMessageText, SErrorText);
|
|
|
|
TSharedRef<SBox> CurveSelector = SNew(SBox)
|
|
.VAlign(VAlign_Top)
|
|
.Visibility(this, &SCurveEditor::GetCurveSelectorVisibility)
|
|
[
|
|
CreateCurveSelectionWidget()
|
|
];
|
|
|
|
CurveSelectionWidget = CurveSelector;
|
|
|
|
InputAxisName = InArgs._XAxisName.IsSet() ? FText::FromString(InArgs._XAxisName.GetValue()) : LOCTEXT("Time", "Time");
|
|
OutputAxisName = InArgs._YAxisName.IsSet() ? FText::FromString(InArgs._YAxisName.GetValue()) : LOCTEXT("Value", "Value");
|
|
|
|
this->ChildSlot
|
|
[
|
|
SNew( SHorizontalBox )
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew( SVerticalBox )
|
|
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility( this, &SCurveEditor::GetCurveAreaVisibility )
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(30, 12, 0, 0))
|
|
[
|
|
CurveSelector
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SBorder)
|
|
.VAlign(VAlign_Top)
|
|
.HAlign(HAlign_Left)
|
|
.BorderImage( FEditorStyle::GetBrush("NoBorder") )
|
|
.DesiredSizeScale(FVector2D(256.0f,32.0f))
|
|
.Padding(FMargin(2, 12, 0, 0))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.ToolTipText(LOCTEXT("ZoomToFitHorizontal", "Zoom To Fit Horizontal"))
|
|
.Visibility(this, &SCurveEditor::GetZoomButtonVisibility)
|
|
.OnClicked(this, &SCurveEditor::ZoomToFitHorizontalClicked)
|
|
.ContentPadding(1)
|
|
[
|
|
SNew(SImage)
|
|
.Image( FEditorStyle::GetBrush("CurveEd.FitHorizontal") )
|
|
.ColorAndOpacity( FSlateColor::UseForeground() )
|
|
]
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.ToolTipText(LOCTEXT("ZoomToFitVertical", "Zoom To Fit Vertical"))
|
|
.Visibility(this, &SCurveEditor::GetZoomButtonVisibility)
|
|
.OnClicked(this, &SCurveEditor::ZoomToFitVerticalClicked)
|
|
.ContentPadding(1)
|
|
[
|
|
SNew(SImage)
|
|
.Image( FEditorStyle::GetBrush("CurveEd.FitVertical") )
|
|
.ColorAndOpacity( FSlateColor::UseForeground() )
|
|
]
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FEditorStyle::GetBrush("NoBorder"))
|
|
.Visibility(this, &SCurveEditor::GetEditVisibility)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SNumericEntryBox<float>)
|
|
.IsEnabled(this, &SCurveEditor::GetInputEditEnabled)
|
|
.Font(FEditorStyle::GetFontStyle("CurveEd.InfoFont"))
|
|
.Value(this, &SCurveEditor::OnGetTime)
|
|
.UndeterminedString(LOCTEXT("MultipleValues", "Multiple Values"))
|
|
.OnValueCommitted(this, &SCurveEditor::OnTimeComitted)
|
|
.OnValueChanged(this, &SCurveEditor::OnTimeChanged)
|
|
.OnBeginSliderMovement(this, &SCurveEditor::OnBeginSliderMovement, LOCTEXT("SetValue", "Set New Time"))
|
|
.OnEndSliderMovement(this, &SCurveEditor::OnEndSliderMovement)
|
|
.LabelVAlign(VAlign_Center)
|
|
.AllowSpin(true)
|
|
.MinValue(TOptional<float>())
|
|
.MaxValue(TOptional<float>())
|
|
.MaxSliderValue(TOptional<float>())
|
|
.MinSliderValue(TOptional<float>())
|
|
.Delta(0.001f)
|
|
.MinDesiredValueWidth(60.0f)
|
|
.Label()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(InputAxisName)
|
|
]
|
|
]
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage( FEditorStyle::GetBrush("NoBorder") )
|
|
.Visibility(this, &SCurveEditor::GetEditVisibility)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SNumericEntryBox<float>)
|
|
.Font(FEditorStyle::GetFontStyle("CurveEd.InfoFont"))
|
|
.Value(this, &SCurveEditor::OnGetValue)
|
|
.UndeterminedString(LOCTEXT("MultipleValues", "Multiple Values"))
|
|
.OnValueCommitted(this, &SCurveEditor::OnValueComitted)
|
|
.OnValueChanged(this, &SCurveEditor::OnValueChanged)
|
|
.OnBeginSliderMovement(this, &SCurveEditor::OnBeginSliderMovement, LOCTEXT("SetValue", "Set New Value"))
|
|
.OnEndSliderMovement(this, &SCurveEditor::OnEndSliderMovement)
|
|
.LabelVAlign(VAlign_Center)
|
|
.AllowSpin(true)
|
|
.MinValue(TOptional<float>())
|
|
.MaxValue(TOptional<float>())
|
|
.MaxSliderValue(TOptional<float>())
|
|
.MinSliderValue(TOptional<float>())
|
|
.Delta(.001f)
|
|
.MinDesiredValueWidth(60.0f)
|
|
.Label()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(OutputAxisName)
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
|
|
+ SVerticalBox::Slot()
|
|
.VAlign(VAlign_Bottom)
|
|
.FillHeight(.75f)
|
|
[
|
|
SNew( SBorder )
|
|
.Visibility( this, &SCurveEditor::GetColorGradientVisibility )
|
|
.BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") )
|
|
.BorderBackgroundColor( FLinearColor( .8f, .8f, .8f, .60f ) )
|
|
.Padding(1.0f)
|
|
[
|
|
SAssignNew( GradientViewer, SColorGradientEditor )
|
|
.ViewMinInput( ViewMinInput )
|
|
.ViewMaxInput( ViewMaxInput )
|
|
.IsEditingEnabled( this, &SCurveEditor::IsEditingEnabled )
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
if (GEditor != NULL)
|
|
{
|
|
GEditor->RegisterForUndo(this);
|
|
}
|
|
}
|
|
|
|
FText SCurveEditor::GetIsCurveVisibleToolTip(TSharedPtr<FCurveViewModel> CurveViewModel) const
|
|
{
|
|
return CurveViewModel->bIsVisible ?
|
|
FText::Format(LOCTEXT("HideFormat", "Hide {0} curve"), FText::FromName(CurveViewModel->CurveInfo.CurveName)) :
|
|
FText::Format(LOCTEXT("ShowFormat", "Show {0} curve"), FText::FromName(CurveViewModel->CurveInfo.CurveName));
|
|
}
|
|
|
|
ECheckBoxState SCurveEditor::IsCurveVisible(TSharedPtr<FCurveViewModel> CurveViewModel) const
|
|
{
|
|
return CurveViewModel->bIsVisible ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SCurveEditor::OnCurveIsVisibleChanged(ECheckBoxState NewCheckboxState, TSharedPtr<FCurveViewModel> CurveViewModel)
|
|
{
|
|
if (NewCheckboxState == ECheckBoxState::Checked)
|
|
{
|
|
CurveViewModel->bIsVisible = true;
|
|
}
|
|
else
|
|
{
|
|
CurveViewModel->bIsVisible = false;
|
|
RemoveCurveKeysFromSelection(CurveViewModel);
|
|
}
|
|
}
|
|
|
|
FText SCurveEditor::GetIsCurveLockedToolTip(TSharedPtr<FCurveViewModel> CurveViewModel) const
|
|
{
|
|
return CurveViewModel->bIsLocked ?
|
|
FText::Format(LOCTEXT("UnlockFormat", "Unlock {0} curve for editing"), FText::FromName(CurveViewModel->CurveInfo.CurveName)) :
|
|
FText::Format(LOCTEXT("LockFormat", "Lock {0} curve for editing"), FText::FromName(CurveViewModel->CurveInfo.CurveName));
|
|
}
|
|
|
|
ECheckBoxState SCurveEditor::IsCurveLocked(TSharedPtr<FCurveViewModel> CurveViewModel) const
|
|
{
|
|
return CurveViewModel->bIsLocked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SCurveEditor::OnCurveIsLockedChanged(ECheckBoxState NewCheckboxState, TSharedPtr<FCurveViewModel> CurveViewModel)
|
|
{
|
|
if (NewCheckboxState == ECheckBoxState::Checked)
|
|
{
|
|
CurveViewModel->bIsLocked = true;
|
|
RemoveCurveKeysFromSelection(CurveViewModel);
|
|
}
|
|
else
|
|
{
|
|
CurveViewModel->bIsLocked = false;
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::RemoveCurveKeysFromSelection(TSharedPtr<FCurveViewModel> CurveViewModel)
|
|
{
|
|
TArray<FSelectedCurveKey> SelectedKeysForLockedCurve;
|
|
for (auto SelectedKey : SelectedKeys)
|
|
{
|
|
if (SelectedKey.Curve == CurveViewModel->CurveInfo.CurveToEdit)
|
|
{
|
|
SelectedKeysForLockedCurve.Add(SelectedKey);
|
|
}
|
|
}
|
|
for (auto KeyToDeselect : SelectedKeysForLockedCurve)
|
|
{
|
|
RemoveFromSelection(KeyToDeselect);
|
|
}
|
|
}
|
|
|
|
FText SCurveEditor::GetCurveToolTipNameText() const
|
|
{
|
|
return CurveToolTipNameText;
|
|
}
|
|
|
|
FText SCurveEditor::GetCurveToolTipInputText() const
|
|
{
|
|
return CurveToolTipInputText;
|
|
}
|
|
|
|
FText SCurveEditor::GetCurveToolTipOutputText() const
|
|
{
|
|
return CurveToolTipOutputText;
|
|
}
|
|
|
|
SCurveEditor::~SCurveEditor()
|
|
{
|
|
if (GEditor != NULL)
|
|
{
|
|
GEditor->UnregisterForUndo(this);
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> SCurveEditor::CreateCurveSelectionWidget() const
|
|
{
|
|
TSharedRef<SVerticalBox> CurveBox = SNew(SVerticalBox);
|
|
if (CurveViewModels.Num() > 1)
|
|
{
|
|
// Only create curve controls if there are more than one.
|
|
for (auto CurveViewModel : CurveViewModels)
|
|
{
|
|
CurveBox->AddSlot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(0, 0, 5, 0)
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(FEditorStyle::GetFontStyle("CurveEd.LabelFont"))
|
|
.ColorAndOpacity(CurveViewModel->Color)
|
|
.Text(FText::FromName(CurveViewModel->CurveInfo.CurveName))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(this, &SCurveEditor::IsCurveVisible, CurveViewModel)
|
|
.OnCheckStateChanged(this, &SCurveEditor::OnCurveIsVisibleChanged, CurveViewModel)
|
|
.ToolTipText(this, &SCurveEditor::GetIsCurveVisibleToolTip, CurveViewModel)
|
|
.CheckedImage(FEditorStyle::GetBrush("CurveEd.Visible"))
|
|
.CheckedHoveredImage(FEditorStyle::GetBrush("CurveEd.VisibleHighlight"))
|
|
.CheckedPressedImage(FEditorStyle::GetBrush("CurveEd.Visible"))
|
|
.UncheckedImage(FEditorStyle::GetBrush("CurveEd.Invisible"))
|
|
.UncheckedHoveredImage(FEditorStyle::GetBrush("CurveEd.InvisibleHighlight"))
|
|
.UncheckedPressedImage(FEditorStyle::GetBrush("CurveEd.Invisible"))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(2, 0, 0, 0)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(this, &SCurveEditor::IsCurveLocked, CurveViewModel)
|
|
.OnCheckStateChanged(this, &SCurveEditor::OnCurveIsLockedChanged, CurveViewModel)
|
|
.ToolTipText(this, &SCurveEditor::GetIsCurveLockedToolTip, CurveViewModel)
|
|
.CheckedImage(FEditorStyle::GetBrush("CurveEd.Locked"))
|
|
.CheckedHoveredImage(FEditorStyle::GetBrush("CurveEd.LockedHighlight"))
|
|
.CheckedPressedImage(FEditorStyle::GetBrush("CurveEd.Locked"))
|
|
.UncheckedImage(FEditorStyle::GetBrush("CurveEd.Unlocked"))
|
|
.UncheckedHoveredImage(FEditorStyle::GetBrush("CurveEd.UnlockedHighlight"))
|
|
.UncheckedPressedImage(FEditorStyle::GetBrush("CurveEd.Unlocked"))
|
|
.Visibility(bCanEditTrack ? EVisibility::Visible : EVisibility::Collapsed)
|
|
]
|
|
];
|
|
}
|
|
}
|
|
|
|
TSharedRef<SBorder> Border = SNew(SBorder)
|
|
.Padding(FMargin(3, 2, 2, 2))
|
|
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.BorderBackgroundColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f))
|
|
[
|
|
CurveBox
|
|
];
|
|
|
|
return Border;
|
|
}
|
|
|
|
void SCurveEditor::PushWarningMenu( FVector2D Position, const FText& Message )
|
|
{
|
|
WarningMessageText->SetError(Message);
|
|
|
|
FSlateApplication::Get().PushMenu(
|
|
SharedThis( this ),
|
|
FWidgetPath(),
|
|
WarningMessageText->AsWidget(),
|
|
Position,
|
|
FPopupTransitionEffect( FPopupTransitionEffect::ContextMenu));
|
|
}
|
|
|
|
void SCurveEditor::PushKeyMenu(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
|
|
{
|
|
FMenuBuilder MenuBuilder(true, Commands.ToSharedRef());
|
|
MenuBuilder.BeginSection("CurveEditorInterpolation", LOCTEXT("KeyInterpolationMode", "Key Interpolation"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(FRichCurveEditorCommands::Get().InterpolationCubicAuto);
|
|
MenuBuilder.AddMenuEntry(FRichCurveEditorCommands::Get().InterpolationCubicUser);
|
|
MenuBuilder.AddMenuEntry(FRichCurveEditorCommands::Get().InterpolationCubicBreak);
|
|
MenuBuilder.AddMenuEntry(FRichCurveEditorCommands::Get().InterpolationLinear);
|
|
MenuBuilder.AddMenuEntry(FRichCurveEditorCommands::Get().InterpolationConstant);
|
|
}
|
|
MenuBuilder.EndSection(); //CurveEditorInterpolation
|
|
|
|
FWidgetPath WidgetPath = InMouseEvent.GetEventPath() != nullptr ? *InMouseEvent.GetEventPath() : FWidgetPath();
|
|
FVector2D Position = InMouseEvent.GetScreenSpacePosition();
|
|
FSlateApplication::Get().PushMenu(
|
|
SharedThis( this ),
|
|
WidgetPath,
|
|
MenuBuilder.MakeWidget(),
|
|
Position,
|
|
FPopupTransitionEffect( FPopupTransitionEffect::ContextMenu));
|
|
}
|
|
|
|
|
|
FVector2D SCurveEditor::ComputeDesiredSize( float ) const
|
|
{
|
|
return DesiredSize.Get();
|
|
}
|
|
|
|
EVisibility SCurveEditor::GetCurveAreaVisibility() const
|
|
{
|
|
return AreCurvesVisible() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SCurveEditor::GetCurveSelectorVisibility() const
|
|
{
|
|
return (IsHovered() || (false == bHideUI)) && bShowCurveSelector ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SCurveEditor::GetEditVisibility() const
|
|
{
|
|
return (SelectedKeys.Num() > 0) && (IsHovered() || (false == bHideUI)) ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SCurveEditor::GetColorGradientVisibility() const
|
|
{
|
|
return bIsGradientEditorVisible && IsLinearColorCurve() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SCurveEditor::GetZoomButtonVisibility() const
|
|
{
|
|
return (IsHovered() || (false == bHideUI)) && bShowZoomButtons ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
bool SCurveEditor::GetInputEditEnabled() const
|
|
{
|
|
return (SelectedKeys.Num() == 1);
|
|
}
|
|
|
|
int32 SCurveEditor::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
// Rendering info
|
|
bool bEnabled = ShouldBeEnabled( bParentEnabled );
|
|
ESlateDrawEffect::Type DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
const FSlateBrush* TimelineAreaBrush = FEditorStyle::GetBrush("CurveEd.TimelineArea");
|
|
const FSlateBrush* WhiteBrush = FEditorStyle::GetBrush("WhiteTexture");
|
|
|
|
FGeometry CurveAreaGeometry = AllottedGeometry;
|
|
|
|
// Positioning info
|
|
FTrackScaleInfo ScaleInfo(ViewMinInput.Get(), ViewMaxInput.Get(), ViewMinOutput.Get(), ViewMaxOutput.Get(), CurveAreaGeometry.Size);
|
|
|
|
// Draw background to indicate valid timeline area
|
|
float ZeroInputX = ScaleInfo.InputToLocalX(0.f);
|
|
float ZeroOutputY = ScaleInfo.OutputToLocalY(0.f);
|
|
|
|
// timeline background
|
|
int32 BackgroundLayerId = LayerId;
|
|
float TimelineMaxX = ScaleInfo.InputToLocalX(TimelineLength.Get());
|
|
FSlateDrawElement::MakeBox
|
|
(
|
|
OutDrawElements,
|
|
BackgroundLayerId,
|
|
CurveAreaGeometry.ToPaintGeometry(FVector2D(ZeroInputX, 0), FVector2D(TimelineMaxX - ZeroInputX, CurveAreaGeometry.Size.Y)),
|
|
TimelineAreaBrush,
|
|
MyClippingRect,
|
|
DrawEffects,
|
|
TimelineAreaBrush->GetTint(InWidgetStyle) * InWidgetStyle.GetColorAndOpacityTint()
|
|
);
|
|
|
|
// grid lines.
|
|
int32 GridLineLayerId = BackgroundLayerId + 1;
|
|
PaintGridLines(CurveAreaGeometry, ScaleInfo, OutDrawElements, GridLineLayerId, MyClippingRect, DrawEffects);
|
|
|
|
// time=0 line
|
|
int32 ZeroLineLayerId = GridLineLayerId + 1;
|
|
TArray<FVector2D> ZeroLinePoints;
|
|
ZeroLinePoints.Add( FVector2D( ZeroInputX, 0 ) );
|
|
ZeroLinePoints.Add( FVector2D( ZeroInputX, CurveAreaGeometry.Size.Y ) );
|
|
FSlateDrawElement::MakeLines(
|
|
OutDrawElements,
|
|
ZeroLineLayerId,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
ZeroLinePoints,
|
|
MyClippingRect,
|
|
DrawEffects,
|
|
FLinearColor::White,
|
|
false );
|
|
|
|
// value=0 line
|
|
if( AreCurvesVisible() )
|
|
{
|
|
FSlateDrawElement::MakeBox
|
|
(
|
|
OutDrawElements,
|
|
ZeroLineLayerId,
|
|
CurveAreaGeometry.ToPaintGeometry( FVector2D(0, ZeroOutputY), FVector2D(CurveAreaGeometry.Size.X, 1) ),
|
|
WhiteBrush,
|
|
MyClippingRect,
|
|
DrawEffects,
|
|
WhiteBrush->GetTint( InWidgetStyle ) * InWidgetStyle.GetColorAndOpacityTint()
|
|
);
|
|
}
|
|
|
|
int32 LockedCurveLayerID = ZeroLineLayerId + 1;
|
|
int32 CurveLayerId = LockedCurveLayerID + 1;
|
|
|
|
int32 KeyLayerId = CurveLayerId + 1;
|
|
int32 SelectedKeyLayerId = KeyLayerId + 1;
|
|
|
|
if( AreCurvesVisible() )
|
|
{
|
|
//Paint the curves, unlocked curves will be on top
|
|
for ( auto CurveViewModel : CurveViewModels )
|
|
{
|
|
if (CurveViewModel->bIsVisible)
|
|
{
|
|
PaintCurve(CurveViewModel, CurveAreaGeometry, ScaleInfo, OutDrawElements, CurveViewModel->bIsLocked ? LockedCurveLayerID : CurveLayerId, MyClippingRect, DrawEffects, InWidgetStyle);
|
|
}
|
|
}
|
|
|
|
//Paint the keys on top of the curve
|
|
for (auto CurveViewModel : CurveViewModels)
|
|
{
|
|
if (CurveViewModel->bIsVisible)
|
|
{
|
|
PaintKeys(CurveViewModel, ScaleInfo, OutDrawElements, KeyLayerId, SelectedKeyLayerId, CurveAreaGeometry, MyClippingRect, DrawEffects, InWidgetStyle);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Paint children
|
|
int32 ChildrenLayerId = SelectedKeyLayerId + 1;
|
|
int32 MarqueeLayerId = SCompoundWidget::OnPaint(Args, CurveAreaGeometry, MyClippingRect, OutDrawElements, ChildrenLayerId, InWidgetStyle, bParentEnabled);
|
|
|
|
// Paint marquee
|
|
if (DragState == EDragState::MarqueeSelect)
|
|
{
|
|
PaintMarquee(AllottedGeometry, MyClippingRect, OutDrawElements, MarqueeLayerId);
|
|
}
|
|
|
|
return MarqueeLayerId + 1;
|
|
}
|
|
|
|
void SCurveEditor::PaintCurve(TSharedPtr<FCurveViewModel> CurveViewModel, const FGeometry &AllottedGeometry, FTrackScaleInfo &ScaleInfo, FSlateWindowElementList &OutDrawElements,
|
|
int32 LayerId, const FSlateRect& MyClippingRect, ESlateDrawEffect::Type DrawEffects, const FWidgetStyle &InWidgetStyle )const
|
|
{
|
|
if (CurveViewModel.IsValid())
|
|
{
|
|
if (bDrawCurve)
|
|
{
|
|
FLinearColor Color = InWidgetStyle.GetColorAndOpacityTint() * CurveViewModel->Color;
|
|
|
|
// Fade out curves which are locked.
|
|
if(CurveViewModel->bIsLocked)
|
|
{
|
|
Color *= FLinearColor(1.0f,1.0f,1.0f,0.35f);
|
|
}
|
|
|
|
TArray<FVector2D> LinePoints;
|
|
int32 CurveDrawInterval = 1;
|
|
|
|
FRichCurve* Curve = CurveViewModel->CurveInfo.CurveToEdit;
|
|
if (Curve->GetNumKeys() < 2)
|
|
{
|
|
//Not enough point, just draw flat line
|
|
float Value = Curve->Eval(0.0f);
|
|
float Y = ScaleInfo.OutputToLocalY(Value);
|
|
LinePoints.Add(FVector2D(0.0f, Y));
|
|
LinePoints.Add(FVector2D(AllottedGeometry.Size.X, Y));
|
|
|
|
FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), LinePoints, MyClippingRect, DrawEffects, Color);
|
|
LinePoints.Empty();
|
|
}
|
|
else
|
|
{
|
|
//Add arrive and exit lines
|
|
{
|
|
const FRichCurveKey& FirstKey = Curve->GetFirstKey();
|
|
const FRichCurveKey& LastKey = Curve->GetLastKey();
|
|
|
|
float ArriveX = ScaleInfo.InputToLocalX(FirstKey.Time);
|
|
float ArriveY = ScaleInfo.OutputToLocalY(FirstKey.Value);
|
|
float LeaveY = ScaleInfo.OutputToLocalY(LastKey.Value);
|
|
float LeaveX = ScaleInfo.InputToLocalX(LastKey.Time);
|
|
|
|
//Arrival line
|
|
LinePoints.Add(FVector2D(0.0f, ArriveY));
|
|
LinePoints.Add(FVector2D(ArriveX, ArriveY));
|
|
FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), LinePoints, MyClippingRect, DrawEffects, Color);
|
|
LinePoints.Empty();
|
|
|
|
//Leave line
|
|
LinePoints.Add(FVector2D(AllottedGeometry.Size.X, LeaveY));
|
|
LinePoints.Add(FVector2D(LeaveX, LeaveY));
|
|
FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), LinePoints, MyClippingRect, DrawEffects, Color);
|
|
LinePoints.Empty();
|
|
}
|
|
|
|
|
|
//Add enclosed segments
|
|
TArray<FRichCurveKey> Keys = Curve->GetCopyOfKeys();
|
|
for(int32 i = 0;i<Keys.Num()-1;++i)
|
|
{
|
|
CreateLinesForSegment(Curve, Keys[i], Keys[i+1],LinePoints, ScaleInfo);
|
|
FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), LinePoints, MyClippingRect, DrawEffects, Color);
|
|
LinePoints.Empty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SCurveEditor::CreateLinesForSegment( FRichCurve* Curve, const FRichCurveKey& Key1, const FRichCurveKey& Key2, TArray<FVector2D>& Points, FTrackScaleInfo &ScaleInfo ) const
|
|
{
|
|
switch(Key1.InterpMode)
|
|
{
|
|
case RCIM_Constant:
|
|
{
|
|
//@todo: should really only need 3 points here but something about the line rendering isn't quite behaving as I'd expect, so need extras
|
|
Points.Add(FVector2D(Key1.Time, Key1.Value));
|
|
Points.Add(FVector2D(Key2.Time, Key1.Value));
|
|
Points.Add(FVector2D(Key2.Time, Key1.Value));
|
|
Points.Add(FVector2D(Key2.Time, Key2.Value));
|
|
Points.Add(FVector2D(Key2.Time, Key1.Value));
|
|
}break;
|
|
case RCIM_Linear:
|
|
{
|
|
Points.Add(FVector2D(Key1.Time, Key1.Value));
|
|
Points.Add(FVector2D(Key2.Time, Key2.Value));
|
|
}break;
|
|
case RCIM_Cubic:
|
|
{
|
|
const float StepSize = 1.0f;
|
|
//clamp to screen to avoid massive slowdown when zoomed in
|
|
float StartX = FMath::Max(ScaleInfo.InputToLocalX(Key1.Time), 0.0f) ;
|
|
float EndX = FMath::Min(ScaleInfo.InputToLocalX(Key2.Time),ScaleInfo.WidgetSize.X);
|
|
for(;StartX<EndX; StartX += StepSize)
|
|
{
|
|
float CurveIn = ScaleInfo.LocalXToInput(FMath::Min(StartX,EndX));
|
|
float CurveOut = Curve->Eval(CurveIn);
|
|
Points.Add(FVector2D(CurveIn,CurveOut));
|
|
}
|
|
Points.Add(FVector2D(Key2.Time,Key2.Value));
|
|
|
|
}break;
|
|
}
|
|
|
|
//Transform to screen
|
|
for(auto It = Points.CreateIterator();It;++It)
|
|
{
|
|
FVector2D Vec2D = *It;
|
|
Vec2D.X = ScaleInfo.InputToLocalX(Vec2D.X);
|
|
Vec2D.Y = ScaleInfo.OutputToLocalY(Vec2D.Y);
|
|
*It = Vec2D;
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::PaintKeys(TSharedPtr<FCurveViewModel> CurveViewModel, FTrackScaleInfo &ScaleInfo, FSlateWindowElementList &OutDrawElements, int32 LayerId, int32 SelectedLayerId, const FGeometry &AllottedGeometry, const FSlateRect& MyClippingRect, ESlateDrawEffect::Type DrawEffects, const FWidgetStyle &InWidgetStyle ) const
|
|
{
|
|
FLinearColor KeyColor = CurveViewModel->bIsLocked ? FLinearColor(0.1f,0.1f,0.1f,1.f) : InWidgetStyle.GetColorAndOpacityTint();
|
|
|
|
// Iterate over each key
|
|
ERichCurveInterpMode LastInterpMode = RCIM_Linear;
|
|
FRichCurve* Curve = CurveViewModel->CurveInfo.CurveToEdit;
|
|
for (auto It(Curve->GetKeyHandleIterator()); It; ++It)
|
|
{
|
|
FKeyHandle KeyHandle = It.Key();
|
|
|
|
// Work out where it is
|
|
FVector2D KeyLocation(
|
|
ScaleInfo.InputToLocalX(Curve->GetKeyTime(KeyHandle)),
|
|
ScaleInfo.OutputToLocalY(Curve->GetKeyValue(KeyHandle)));
|
|
FVector2D KeyIconLocation = KeyLocation - (CONST_KeySize / 2);
|
|
|
|
// Get brush
|
|
bool IsSelected = IsKeySelected(FSelectedCurveKey(Curve,KeyHandle));
|
|
const FSlateBrush* KeyBrush = IsSelected ? FEditorStyle::GetBrush("CurveEd.CurveKeySelected") : FEditorStyle::GetBrush("CurveEd.CurveKey");
|
|
int32 LayerToUse = IsSelected ? SelectedLayerId: LayerId;
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
LayerToUse,
|
|
AllottedGeometry.ToPaintGeometry( KeyIconLocation, CONST_KeySize ),
|
|
KeyBrush,
|
|
MyClippingRect,
|
|
DrawEffects,
|
|
KeyBrush->GetTint( InWidgetStyle ) * InWidgetStyle.GetColorAndOpacityTint() * KeyColor
|
|
);
|
|
|
|
//Handle drawing the tangent controls for curve
|
|
if(IsSelected && (Curve->GetKeyInterpMode(KeyHandle) == RCIM_Cubic || LastInterpMode == RCIM_Cubic))
|
|
{
|
|
PaintTangent(ScaleInfo, Curve, KeyHandle, KeyLocation, OutDrawElements, LayerId, AllottedGeometry, MyClippingRect, DrawEffects, LayerToUse, InWidgetStyle);
|
|
}
|
|
|
|
LastInterpMode = Curve->GetKeyInterpMode(KeyHandle);
|
|
}
|
|
}
|
|
|
|
|
|
void SCurveEditor::PaintTangent( FTrackScaleInfo &ScaleInfo, FRichCurve* Curve, FKeyHandle KeyHandle, FVector2D KeyLocation, FSlateWindowElementList &OutDrawElements, int32 LayerId, const FGeometry &AllottedGeometry, const FSlateRect& MyClippingRect, ESlateDrawEffect::Type DrawEffects, int32 LayerToUse, const FWidgetStyle &InWidgetStyle ) const
|
|
{
|
|
FVector2D ArriveTangentLocation, LeaveTangentLocation;
|
|
GetTangentPoints(ScaleInfo, FSelectedCurveKey(Curve,KeyHandle), ArriveTangentLocation, LeaveTangentLocation);
|
|
|
|
FVector2D ArriveTangentIconLocation = ArriveTangentLocation - (CONST_TangentSize / 2);
|
|
FVector2D LeaveTangentIconLocation = LeaveTangentLocation - (CONST_TangentSize / 2);
|
|
|
|
const FSlateBrush* TangentBrush = FEditorStyle::GetBrush("CurveEd.Tangent");
|
|
const FLinearColor TangentColor = FEditorStyle::GetColor("CurveEd.TangentColor");
|
|
|
|
//Add lines from tangent control point to 'key'
|
|
TArray<FVector2D> LinePoints;
|
|
LinePoints.Add(KeyLocation);
|
|
LinePoints.Add(ArriveTangentLocation);
|
|
FSlateDrawElement::MakeLines(
|
|
OutDrawElements,
|
|
LayerId,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
LinePoints,
|
|
MyClippingRect,
|
|
DrawEffects,
|
|
TangentColor
|
|
);
|
|
|
|
LinePoints.Empty();
|
|
LinePoints.Add(KeyLocation);
|
|
LinePoints.Add(LeaveTangentLocation);
|
|
FSlateDrawElement::MakeLines(
|
|
OutDrawElements,
|
|
LayerId,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
LinePoints,
|
|
MyClippingRect,
|
|
DrawEffects,
|
|
TangentColor
|
|
);
|
|
|
|
//Arrive tangent control
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
LayerToUse,
|
|
AllottedGeometry.ToPaintGeometry(ArriveTangentIconLocation, CONST_TangentSize ),
|
|
TangentBrush,
|
|
MyClippingRect,
|
|
DrawEffects,
|
|
TangentBrush->GetTint( InWidgetStyle ) * InWidgetStyle.GetColorAndOpacityTint()
|
|
);
|
|
//Leave tangent control
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
LayerToUse,
|
|
AllottedGeometry.ToPaintGeometry(LeaveTangentIconLocation, CONST_TangentSize ),
|
|
TangentBrush,
|
|
MyClippingRect,
|
|
DrawEffects,
|
|
TangentBrush->GetTint( InWidgetStyle ) * InWidgetStyle.GetColorAndOpacityTint()
|
|
);
|
|
}
|
|
|
|
|
|
float SCurveEditor::CalcGridLineStepDistancePow2(double RawValue)
|
|
{
|
|
return float(double(FMath::RoundUpToPowerOfTwo(uint32(RawValue*1024.0))>>1)/1024.0);
|
|
}
|
|
|
|
float SCurveEditor::GetTimeStep(FTrackScaleInfo &ScaleInfo) const
|
|
{
|
|
const float MaxGridPixelSpacing = 150.0f;
|
|
|
|
const float GridPixelSpacing = FMath::Min(ScaleInfo.WidgetSize.GetMin()/1.5f, MaxGridPixelSpacing);
|
|
|
|
double MaxTimeStep = ScaleInfo.LocalXToInput(ViewMinInput.Get() + GridPixelSpacing) - ScaleInfo.LocalXToInput(ViewMinInput.Get());
|
|
|
|
return CalcGridLineStepDistancePow2(MaxTimeStep);
|
|
}
|
|
|
|
void SCurveEditor::PaintGridLines(const FGeometry &AllottedGeometry, FTrackScaleInfo &ScaleInfo, FSlateWindowElementList &OutDrawElements,
|
|
int32 LayerId, const FSlateRect& MyClippingRect, ESlateDrawEffect::Type DrawEffects )const
|
|
{
|
|
const float MaxGridPixelSpacing = 150.0f;
|
|
|
|
const float GridPixelSpacing = FMath::Min(ScaleInfo.WidgetSize.GetMin()/1.5f, MaxGridPixelSpacing);
|
|
|
|
const FLinearColor GridTextColor = FLinearColor(1.0f,1.0f,1.0f, 0.75f) ;
|
|
|
|
//Vertical grid(time)
|
|
{
|
|
float TimeStep = GetTimeStep(ScaleInfo);
|
|
float ScreenStepTime = ScaleInfo.InputToLocalX(TimeStep) - ScaleInfo.InputToLocalX(0.0f);
|
|
|
|
if(ScreenStepTime >= 1.0f)
|
|
{
|
|
float StartTime = ScaleInfo.LocalXToInput(0.0f);
|
|
TArray<FVector2D> LinePoints;
|
|
float ScaleX = (TimeStep)/(AllottedGeometry.Size.X);
|
|
|
|
//draw vertical grid lines
|
|
float StartOffset = -FMath::Fractional(StartTime / TimeStep)*ScreenStepTime;
|
|
float Time = ScaleInfo.LocalXToInput(StartOffset);
|
|
for(float X = StartOffset;X< AllottedGeometry.Size.X;X+= ScreenStepTime, Time += TimeStep)
|
|
{
|
|
if(SMALL_NUMBER < FMath::Abs(X)) //don't show at 0 to avoid overlapping with center axis
|
|
{
|
|
LinePoints.Add(FVector2D(X, 0.0));
|
|
LinePoints.Add(FVector2D(X, AllottedGeometry.Size.Y));
|
|
FSlateDrawElement::MakeLines(
|
|
OutDrawElements,
|
|
LayerId,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
LinePoints,
|
|
MyClippingRect,
|
|
DrawEffects,
|
|
GridColor,
|
|
false);
|
|
|
|
//Show grid time
|
|
if (bDrawInputGridNumbers)
|
|
{
|
|
FString TimeStr = FString::Printf(TEXT("%.2f"), Time);
|
|
FSlateDrawElement::MakeText(OutDrawElements,LayerId,AllottedGeometry.MakeChild(FVector2D(X, 0.0), FVector2D(1.0f, ScaleX )).ToPaintGeometry(),TimeStr,
|
|
FEditorStyle::GetFontStyle("CurveEd.InfoFont"), MyClippingRect, DrawEffects, GridTextColor );
|
|
}
|
|
|
|
LinePoints.Empty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Horizontal grid(values)
|
|
// This is only useful if the curves are visible
|
|
if( AreCurvesVisible() )
|
|
{
|
|
double MaxValueStep = ScaleInfo.LocalYToOutput(0) - ScaleInfo.LocalYToOutput(GridPixelSpacing) ;
|
|
float ValueStep = CalcGridLineStepDistancePow2(MaxValueStep);
|
|
float ScreenStepValue = ScaleInfo.OutputToLocalY(0.0f) - ScaleInfo.OutputToLocalY(ValueStep);
|
|
if(ScreenStepValue >= 1.0f)
|
|
{
|
|
float StartValue = ScaleInfo.LocalYToOutput(0.0f);
|
|
TArray<FVector2D> LinePoints;
|
|
|
|
float StartOffset = FMath::Fractional(StartValue / ValueStep)*ScreenStepValue;
|
|
float Value = ScaleInfo.LocalYToOutput(StartOffset);
|
|
float ScaleY = (ValueStep)/(AllottedGeometry.Size.Y);
|
|
|
|
for(float Y = StartOffset;Y< AllottedGeometry.Size.Y;Y+= ScreenStepValue, Value-=ValueStep)
|
|
{
|
|
if(SMALL_NUMBER < FMath::Abs(Y)) //don't show at 0 to avoid overlapping with center axis
|
|
{
|
|
LinePoints.Add(FVector2D(0.0f, Y));
|
|
LinePoints.Add(FVector2D(AllottedGeometry.Size.X,Y));
|
|
FSlateDrawElement::MakeLines(
|
|
OutDrawElements,
|
|
LayerId,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
LinePoints,
|
|
MyClippingRect,
|
|
DrawEffects,
|
|
GridColor,
|
|
false);
|
|
|
|
//Show grid value
|
|
if (bDrawOutputGridNumbers)
|
|
{
|
|
FString ValueStr = FString::Printf(TEXT("%.2f"), Value);
|
|
FSlateFontInfo Font = FEditorStyle::GetFontStyle("CurveEd.InfoFont");
|
|
|
|
const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
FVector2D DrawSize = FontMeasureService->Measure(ValueStr, Font);
|
|
|
|
// draw at the start
|
|
FSlateDrawElement::MakeText(OutDrawElements,LayerId,AllottedGeometry.MakeChild(FVector2D(0.0f, Y), FVector2D(ScaleY, 1.0f )).ToPaintGeometry(),ValueStr,
|
|
Font, MyClippingRect, DrawEffects, GridTextColor );
|
|
|
|
// draw at the last since sometimes start can be hidden
|
|
FSlateDrawElement::MakeText(OutDrawElements,LayerId,AllottedGeometry.MakeChild(FVector2D(AllottedGeometry.Size.X-DrawSize.X, Y), FVector2D(ScaleY, 1.0f )).ToPaintGeometry(),ValueStr,
|
|
Font, MyClippingRect, DrawEffects, GridTextColor );
|
|
}
|
|
|
|
LinePoints.Empty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::PaintMarquee(const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const
|
|
{
|
|
FVector2D MarqueTopLeft(
|
|
FMath::Min(MouseDownLocation.X, MouseMoveLocation.X),
|
|
FMath::Min(MouseDownLocation.Y, MouseMoveLocation.Y)
|
|
);
|
|
|
|
FVector2D MarqueBottomRight(
|
|
FMath::Max(MouseDownLocation.X, MouseMoveLocation.X),
|
|
FMath::Max(MouseDownLocation.Y, MouseMoveLocation.Y)
|
|
);
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
LayerId,
|
|
AllottedGeometry.ToPaintGeometry(MarqueTopLeft, MarqueBottomRight - MarqueTopLeft),
|
|
FEditorStyle::GetBrush(TEXT("MarqueeSelection")),
|
|
MyClippingRect
|
|
);
|
|
}
|
|
|
|
void SCurveEditor::SetCurveOwner(FCurveOwnerInterface* InCurveOwner, bool bCanEdit)
|
|
{
|
|
if(InCurveOwner != CurveOwner)
|
|
{
|
|
EmptySelection();
|
|
}
|
|
|
|
GradientViewer->SetCurveOwner(InCurveOwner);
|
|
|
|
CurveOwner = InCurveOwner;
|
|
bCanEditTrack = bCanEdit;
|
|
|
|
|
|
bAreCurvesVisible = !IsLinearColorCurve();
|
|
bIsGradientEditorVisible = IsLinearColorCurve();
|
|
|
|
CurveViewModels.Empty();
|
|
if(CurveOwner != NULL)
|
|
{
|
|
int curveIndex = 0;
|
|
for (auto CurveInfo : CurveOwner->GetCurves())
|
|
{
|
|
CurveViewModels.Add(TSharedPtr<FCurveViewModel>(new FCurveViewModel(CurveInfo, CurveColors[curveIndex % CurveColors.Num()], !bCanEdit)));
|
|
curveIndex++;
|
|
}
|
|
CurveOwner->MakeTransactional();
|
|
}
|
|
|
|
SelectedKeys.Empty();
|
|
|
|
if( bZoomToFitVertical )
|
|
{
|
|
ZoomToFitVertical();
|
|
}
|
|
|
|
if ( bZoomToFitHorizontal )
|
|
{
|
|
ZoomToFitHorizontal();
|
|
}
|
|
|
|
CurveSelectionWidget.Pin()->SetContent(CreateCurveSelectionWidget());
|
|
}
|
|
|
|
void SCurveEditor::SetZoomToFit(bool bNewZoomToFitVertical, bool bNewZoomToFitHorizontal)
|
|
{
|
|
bZoomToFitVertical = bNewZoomToFitVertical;
|
|
bZoomToFitHorizontal = bNewZoomToFitHorizontal;
|
|
}
|
|
|
|
FCurveOwnerInterface* SCurveEditor::GetCurveOwner() const
|
|
{
|
|
return CurveOwner;
|
|
}
|
|
|
|
FRichCurve* SCurveEditor::GetCurve(int32 CurveIndex) const
|
|
{
|
|
if(CurveIndex < CurveViewModels.Num())
|
|
{
|
|
return CurveViewModels[CurveIndex]->CurveInfo.CurveToEdit;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void SCurveEditor::DeleteSelectedKeys()
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("CurveEditor_RemoveKeys", "Delete Key(s)"));
|
|
CurveOwner->ModifyOwner();
|
|
TSet<FRichCurve*> ChangedCurves;
|
|
|
|
// While there are still keys
|
|
while(SelectedKeys.Num() > 0)
|
|
{
|
|
// Pull one out of the selected set
|
|
FSelectedCurveKey Key = SelectedKeys.Pop();
|
|
if(IsValidCurve(Key.Curve))
|
|
{
|
|
// Remove from the curve
|
|
Key.Curve->DeleteKey(Key.KeyHandle);
|
|
ChangedCurves.Add(Key.Curve);
|
|
}
|
|
}
|
|
|
|
TArray<FRichCurveEditInfo> ChangedCurveEditInfos;
|
|
for (auto CurveViewModel : CurveViewModels)
|
|
{
|
|
if (ChangedCurves.Contains(CurveViewModel->CurveInfo.CurveToEdit))
|
|
{
|
|
ChangedCurveEditInfos.Add(CurveViewModel->CurveInfo);
|
|
}
|
|
}
|
|
|
|
CurveOwner->OnCurveChanged(ChangedCurveEditInfos);
|
|
}
|
|
|
|
FReply SCurveEditor::OnMouseButtonDown( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
|
|
{
|
|
const bool bLeftMouseButton = InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton;
|
|
const bool bRightMouseButton = InMouseEvent.GetEffectingButton() == EKeys::RightMouseButton;
|
|
|
|
DragState = EDragState::PreDrag;
|
|
if (bLeftMouseButton || bRightMouseButton)
|
|
{
|
|
MouseDownLocation = InMyGeometry.AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition());
|
|
|
|
// Set keyboard focus to this so that selected text box doesn't try to apply to newly selected keys
|
|
if(!HasKeyboardFocus())
|
|
{
|
|
FSlateApplication::Get().SetKeyboardFocus(SharedThis(this), EFocusCause::SetDirectly);
|
|
}
|
|
|
|
// Always capture mouse if we left or right click on the widget
|
|
return FReply::Handled().CaptureMouse(SharedThis(this));
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void SCurveEditor::AddNewKey(FGeometry InMyGeometry, FVector2D ScreenPosition, TSharedPtr<TArray<TSharedPtr<FCurveViewModel>>> CurvesToAddKeysTo, bool bAddKeysInline)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("CurveEditor_AddKey", "Add Key(s)"));
|
|
CurveOwner->ModifyOwner();
|
|
TArray<FRichCurveEditInfo> ChangedCurveEditInfos;
|
|
for (TSharedPtr<FCurveViewModel> CurveViewModel : *CurvesToAddKeysTo)
|
|
{
|
|
if (!CurveViewModel->bIsLocked)
|
|
{
|
|
FRichCurve* SelectedCurve = CurveViewModel->CurveInfo.CurveToEdit;
|
|
if (IsValidCurve(SelectedCurve))
|
|
{
|
|
FTrackScaleInfo ScaleInfo(ViewMinInput.Get(), ViewMaxInput.Get(), ViewMinOutput.Get(), ViewMaxOutput.Get(), InMyGeometry.Size);
|
|
|
|
FVector2D LocalClickPos = InMyGeometry.AbsoluteToLocal(ScreenPosition);
|
|
|
|
float Input = ScaleInfo.LocalXToInput(LocalClickPos.X);
|
|
float Output;
|
|
if (bAddKeysInline)
|
|
{
|
|
Output = SelectedCurve->Eval(Input);
|
|
}
|
|
else
|
|
{
|
|
Output = ScaleInfo.LocalYToOutput(LocalClickPos.Y);
|
|
}
|
|
FVector2D NewKeyLocation = SnapLocation(FVector2D(Input, Output));
|
|
FKeyHandle NewKeyHandle = SelectedCurve->AddKey(NewKeyLocation.X, NewKeyLocation.Y);
|
|
|
|
EmptySelection();
|
|
AddToSelection(FSelectedCurveKey(SelectedCurve, NewKeyHandle));
|
|
ChangedCurveEditInfos.Add(CurveViewModel->CurveInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ChangedCurveEditInfos.Num() > 0)
|
|
{
|
|
CurveOwner->OnCurveChanged(ChangedCurveEditInfos);
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::OnMouseCaptureLost()
|
|
{
|
|
// if we began a drag transaction we need to finish it to make sure undo doesn't get out of sync
|
|
if (DragState == EDragState::DragKey || DragState == EDragState::DragTangent)
|
|
{
|
|
EndDragTransaction();
|
|
}
|
|
DragState = EDragState::None;
|
|
}
|
|
|
|
FReply SCurveEditor::OnMouseButtonUp( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent )
|
|
{
|
|
if (this->HasMouseCapture())
|
|
{
|
|
if (DragState == EDragState::PreDrag)
|
|
{
|
|
// If the user didn't start dragging, handle the mouse operation as a click.
|
|
ProcessClick(InMyGeometry, InMouseEvent);
|
|
}
|
|
else
|
|
{
|
|
EndDrag(InMyGeometry, InMouseEvent);
|
|
}
|
|
return FReply::Handled().ReleaseMouseCapture();
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void ClampViewRangeToDataIfBound( float& NewViewMin, float& NewViewMax, const TAttribute< TOptional<float> > & DataMin, const TAttribute< TOptional<float> > & DataMax, const float ViewRange)
|
|
{
|
|
// if we have data bound
|
|
const TOptional<float> & Min = DataMin.Get();
|
|
const TOptional<float> & Max = DataMax.Get();
|
|
if ( Min.IsSet() && NewViewMin < Min.GetValue())
|
|
{
|
|
// if we have min data set
|
|
NewViewMin = Min.GetValue();
|
|
NewViewMax = ViewRange;
|
|
}
|
|
else if ( Max.IsSet() && NewViewMax > Max.GetValue() )
|
|
{
|
|
// if we have min data set
|
|
NewViewMin = Max.GetValue() - ViewRange;
|
|
NewViewMax = Max.GetValue();
|
|
}
|
|
}
|
|
|
|
FReply SCurveEditor::OnMouseMove( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent )
|
|
{
|
|
UpdateCurveToolTip(InMyGeometry, InMouseEvent);
|
|
|
|
FRichCurve* Curve = GetCurve(0);
|
|
if( Curve != NULL && this->HasMouseCapture())
|
|
{
|
|
if (DragState == EDragState::PreDrag)
|
|
{
|
|
TryStartDrag(InMyGeometry, InMouseEvent);
|
|
}
|
|
if (DragState != EDragState::None)
|
|
{
|
|
ProcessDrag(InMyGeometry, InMouseEvent);
|
|
}
|
|
MouseMoveLocation = InMyGeometry.AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition());
|
|
return FReply::Handled();
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void SCurveEditor::UpdateCurveToolTip(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
|
|
{
|
|
if (bShowCurveToolTips.Get())
|
|
{
|
|
TSharedPtr<FCurveViewModel> HoveredCurve = HitTestCurves(InMyGeometry, InMouseEvent);
|
|
if (HoveredCurve.IsValid())
|
|
{
|
|
FTrackScaleInfo ScaleInfo(ViewMinInput.Get(), ViewMaxInput.Get(), ViewMinOutput.Get(), ViewMaxOutput.Get(), InMyGeometry.Size);
|
|
const FVector2D HitPosition = InMyGeometry.AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition());
|
|
float Time = ScaleInfo.LocalXToInput(HitPosition.X);
|
|
float Value = HoveredCurve->CurveInfo.CurveToEdit->Eval(Time);
|
|
|
|
FNumberFormattingOptions FormattingOptions;
|
|
FormattingOptions.MaximumFractionalDigits = 2;
|
|
CurveToolTipNameText = FText::FromName(HoveredCurve->CurveInfo.CurveName);
|
|
CurveToolTipInputText = FText::Format(LOCTEXT("CurveToolTipTimeFormat", "{0}: {1}"), InputAxisName, FText::AsNumber(Time, &FormattingOptions));
|
|
CurveToolTipOutputText = FText::Format(LOCTEXT("CurveToolTipValueFormat", "{0}: {1}"), OutputAxisName, FText::AsNumber(Value, &FormattingOptions));
|
|
|
|
if (CurveToolTip.IsValid() == false)
|
|
{
|
|
SetToolTip(
|
|
SAssignNew(CurveToolTip, SToolTip)
|
|
.BorderImage( FCoreStyle::Get().GetBrush( "ToolTip.BrightBackground" ) )
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SCurveEditor::GetCurveToolTipNameText)
|
|
.Font(FCoreStyle::Get().GetFontStyle("ToolTip.LargerFont"))
|
|
.ColorAndOpacity( FLinearColor::Black)
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SCurveEditor::GetCurveToolTipInputText)
|
|
.Font(FCoreStyle::Get().GetFontStyle("ToolTip.LargerFont"))
|
|
.ColorAndOpacity(FLinearColor::Black)
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SCurveEditor::GetCurveToolTipOutputText)
|
|
.Font(FCoreStyle::Get().GetFontStyle("ToolTip.LargerFont"))
|
|
.ColorAndOpacity(FLinearColor::Black)
|
|
]
|
|
]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CurveToolTip.Reset();
|
|
SetToolTip(CurveToolTip);
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply SCurveEditor::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
const float ZoomDelta = -0.1f * MouseEvent.GetWheelDelta();
|
|
|
|
if (bAllowZoomOutput)
|
|
{
|
|
const float OutputViewSize = ViewMaxOutput.Get() - ViewMinOutput.Get();
|
|
const float OutputChange = OutputViewSize * ZoomDelta;
|
|
|
|
const float NewMinOutput = (ViewMinOutput.Get() - (OutputChange * 0.5f));
|
|
const float NewMaxOutput = (ViewMaxOutput.Get() + (OutputChange * 0.5f));
|
|
|
|
SetOutputMinMax(NewMinOutput, NewMaxOutput);
|
|
}
|
|
|
|
{
|
|
const float InputViewSize = ViewMaxInput.Get() - ViewMinInput.Get();
|
|
const float InputChange = InputViewSize * ZoomDelta;
|
|
|
|
const float NewMinInput = ViewMinInput.Get() - (InputChange * 0.5f);
|
|
const float NewMaxInput = ViewMaxInput.Get() + (InputChange * 0.5f);
|
|
|
|
SetInputMinMax(NewMinInput, NewMaxInput);
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply SCurveEditor::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
|
|
{
|
|
if (InKeyEvent.GetKey() == EKeys::Platform_Delete && SelectedKeys.Num() != 0)
|
|
{
|
|
DeleteSelectedKeys();
|
|
return FReply::Handled();
|
|
}
|
|
else
|
|
{
|
|
if( Commands->ProcessCommandBindings( InKeyEvent ) )
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::TryStartDrag(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
|
|
{
|
|
const bool bLeftMouseButton = InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton);
|
|
const bool bRightMouseButton = InMouseEvent.IsMouseButtonDown(EKeys::RightMouseButton);
|
|
const bool bControlDown = InMouseEvent.IsControlDown();
|
|
|
|
FVector2D DragVector = InMyGeometry.AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition()) - MouseDownLocation;
|
|
if (DragVector.Size() >= DragThreshold)
|
|
{
|
|
if (bLeftMouseButton)
|
|
{
|
|
// Check if we should start dragging keys.
|
|
FSelectedCurveKey HitKey = HitTestKeys(InMyGeometry, InMyGeometry.LocalToAbsolute(MouseDownLocation));
|
|
if (HitKey.IsValid())
|
|
{
|
|
if (IsKeySelected(HitKey) == false)
|
|
{
|
|
if (!bControlDown)
|
|
{
|
|
EmptySelection();
|
|
}
|
|
AddToSelection(HitKey);
|
|
}
|
|
|
|
BeginDragTransaction();
|
|
DragState = EDragState::DragKey;
|
|
DraggedKeyHandle = HitKey.KeyHandle;
|
|
PreDragKeyLocations.Empty();
|
|
for (auto selectedKey : SelectedKeys)
|
|
{
|
|
PreDragKeyLocations.Add(selectedKey.KeyHandle, FVector2D
|
|
(
|
|
selectedKey.Curve->GetKeyTime(selectedKey.KeyHandle),
|
|
selectedKey.Curve->GetKeyValue(selectedKey.KeyHandle)
|
|
));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check if we should start dragging a tangent.
|
|
FSelectedTangent Tangent = HitTestCubicTangents(InMyGeometry, InMyGeometry.LocalToAbsolute(MouseDownLocation));
|
|
if (Tangent.IsValid())
|
|
{
|
|
BeginDragTransaction();
|
|
SelectedTangent = Tangent;
|
|
DragState = EDragState::DragTangent;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise if the user left clicked on nothing and start a marquee select.
|
|
DragState = EDragState::MarqueeSelect;
|
|
}
|
|
}
|
|
}
|
|
else if (bRightMouseButton)
|
|
{
|
|
DragState = EDragState::Pan;
|
|
}
|
|
else
|
|
{
|
|
DragState = EDragState::None;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::ProcessDrag(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
|
|
{
|
|
FTrackScaleInfo ScaleInfo(ViewMinInput.Get(), ViewMaxInput.Get(), ViewMinOutput.Get(), ViewMaxOutput.Get(), InMyGeometry.Size);
|
|
FVector2D ScreenDelta = InMouseEvent.GetCursorDelta();
|
|
|
|
FVector2D InputDelta;
|
|
InputDelta.X = ScreenDelta.X / ScaleInfo.PixelsPerInput;
|
|
InputDelta.Y = -ScreenDelta.Y / ScaleInfo.PixelsPerOutput;
|
|
|
|
if (DragState == EDragState::DragKey)
|
|
{
|
|
FVector2D MousePosition = InMyGeometry.AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition());
|
|
MoveSelectedKeys(FVector2D(ScaleInfo.LocalXToInput(MousePosition.X), ScaleInfo.LocalYToOutput(MousePosition.Y)));
|
|
}
|
|
else if (DragState == EDragState::DragTangent)
|
|
{
|
|
FVector2D MousePositionScreen = InMouseEvent.GetScreenSpacePosition();
|
|
MousePositionScreen -= InMyGeometry.AbsolutePosition;
|
|
FVector2D MousePositionCurve(ScaleInfo.LocalXToInput(MousePositionScreen.X), ScaleInfo.LocalYToOutput(MousePositionScreen.Y));
|
|
OnMoveTangent(MousePositionCurve);
|
|
}
|
|
else if (DragState == EDragState::Pan)
|
|
{
|
|
// Output is not clamped.
|
|
const float NewMinOutput = (ViewMinOutput.Get() - InputDelta.Y);
|
|
const float NewMaxOutput = (ViewMaxOutput.Get() - InputDelta.Y);
|
|
|
|
SetOutputMinMax(NewMinOutput, NewMaxOutput);
|
|
|
|
// Input maybe clamped if DataMinInput or DataMaxOutput was set.
|
|
float NewMinInput = ViewMinInput.Get() - InputDelta.X;
|
|
float NewMaxInput = ViewMaxInput.Get() - InputDelta.X;
|
|
ClampViewRangeToDataIfBound(NewMinInput, NewMaxInput, DataMinInput, DataMaxInput, ScaleInfo.ViewInputRange);
|
|
|
|
SetInputMinMax(NewMinInput, NewMaxInput);
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::EndDrag(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
|
|
{
|
|
const bool bControlDown = InMouseEvent.IsControlDown();
|
|
|
|
if (DragState == EDragState::DragKey || DragState == EDragState::DragTangent)
|
|
{
|
|
EndDragTransaction();
|
|
}
|
|
else if (DragState == EDragState::MarqueeSelect)
|
|
{
|
|
FVector2D MarqueTopLeft
|
|
(
|
|
FMath::Min(MouseDownLocation.X, MouseMoveLocation.X),
|
|
FMath::Min(MouseDownLocation.Y, MouseMoveLocation.Y)
|
|
);
|
|
|
|
FVector2D MarqueBottomRight
|
|
(
|
|
FMath::Max(MouseDownLocation.X, MouseMoveLocation.X),
|
|
FMath::Max(MouseDownLocation.Y, MouseMoveLocation.Y)
|
|
);
|
|
|
|
TArray<FSelectedCurveKey> SelectedCurveKeys = GetEditableKeysWithinMarquee(InMyGeometry, MarqueTopLeft, MarqueBottomRight);
|
|
|
|
if (!bControlDown)
|
|
{
|
|
EmptySelection();
|
|
}
|
|
|
|
for (auto SelectedCurveKey : SelectedCurveKeys)
|
|
{
|
|
|
|
if (IsKeySelected(SelectedCurveKey))
|
|
{
|
|
RemoveFromSelection(SelectedCurveKey);
|
|
}
|
|
else
|
|
{
|
|
AddToSelection(SelectedCurveKey);
|
|
}
|
|
}
|
|
}
|
|
DragState = EDragState::None;
|
|
}
|
|
|
|
void SCurveEditor::ProcessClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
|
|
{
|
|
const bool bLeftMouseButton = InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton;
|
|
const bool bRightMouseButton = InMouseEvent.GetEffectingButton() == EKeys::RightMouseButton;
|
|
const bool bControlDown = InMouseEvent.IsControlDown();
|
|
const bool bShiftDown = InMouseEvent.IsShiftDown();
|
|
|
|
FSelectedCurveKey HitKey = HitTestKeys(InMyGeometry, InMouseEvent.GetScreenSpacePosition());
|
|
|
|
if (bLeftMouseButton)
|
|
{
|
|
// If the user left clicked a key, update selection based on modifier key state.
|
|
if (HitKey.IsValid())
|
|
{
|
|
if (!IsKeySelected(HitKey))
|
|
{
|
|
if (!bControlDown)
|
|
{
|
|
EmptySelection();
|
|
}
|
|
AddToSelection(HitKey);
|
|
}
|
|
else if (bControlDown)
|
|
{
|
|
RemoveFromSelection(HitKey);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the user didn't click a key, add a new one if shift is held down, or try to select a curve.
|
|
if (bShiftDown)
|
|
{
|
|
TSharedPtr<TArray<TSharedPtr<FCurveViewModel>>> CurvesToAddKeysTo = MakeShareable(new TArray<TSharedPtr<FCurveViewModel>>());
|
|
TSharedPtr<FCurveViewModel> HoveredCurve = HitTestCurves(InMyGeometry, InMouseEvent);
|
|
bool bAddKeysInline;
|
|
if (HoveredCurve.IsValid())
|
|
{
|
|
CurvesToAddKeysTo->Add(HoveredCurve);
|
|
bAddKeysInline = false;
|
|
}
|
|
else
|
|
{
|
|
if (CurveViewModels.Num() == 1)
|
|
{
|
|
CurvesToAddKeysTo->Add(CurveViewModels[0]);
|
|
bAddKeysInline = true;
|
|
}
|
|
else
|
|
{
|
|
CurvesToAddKeysTo->Append(CurveViewModels);
|
|
bAddKeysInline = false;
|
|
}
|
|
}
|
|
AddNewKey(InMyGeometry, InMouseEvent.GetScreenSpacePosition(), CurvesToAddKeysTo, bAddKeysInline);
|
|
}
|
|
else
|
|
{
|
|
// clicking on background clears key selection
|
|
EmptySelection();
|
|
}
|
|
}
|
|
}
|
|
else if (bRightMouseButton)
|
|
{
|
|
// If the user right clicked, handle opening context menus.
|
|
if (HitKey.IsValid())
|
|
{
|
|
// Make sure key is selected in readiness for context menu
|
|
if (!IsKeySelected(HitKey))
|
|
{
|
|
EmptySelection();
|
|
AddToSelection(HitKey);
|
|
}
|
|
PushKeyMenu(InMyGeometry, InMouseEvent);
|
|
}
|
|
else
|
|
{
|
|
if (!HitTestCubicTangents(InMyGeometry, InMouseEvent.GetScreenSpacePosition()).IsValid())
|
|
{
|
|
CreateContextMenu(InMyGeometry, InMouseEvent);
|
|
}
|
|
else
|
|
{
|
|
EmptySelection();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TOptional<float> SCurveEditor::OnGetTime() const
|
|
{
|
|
if ( SelectedKeys.Num() == 1 )
|
|
{
|
|
return GetKeyTime(SelectedKeys[0]);
|
|
}
|
|
|
|
// Value couldn't be accessed. Return an unset value
|
|
return TOptional<float>();
|
|
}
|
|
|
|
void SCurveEditor::OnTimeComitted(float NewTime, ETextCommit::Type CommitType)
|
|
{
|
|
// Don't digest the number if we just clicked away from the pop-up
|
|
if ( !bIsUsingSlider && ((CommitType == ETextCommit::OnEnter) || ( CommitType == ETextCommit::OnUserMovedFocus )) )
|
|
{
|
|
if ( SelectedKeys.Num() >= 1 )
|
|
{
|
|
auto Key = SelectedKeys[0];
|
|
if ( IsValidCurve(Key.Curve) )
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("CurveEditor_NewTime", "New Time Entered"));
|
|
CurveOwner->ModifyOwner();
|
|
Key.Curve->SetKeyTime(Key.KeyHandle, NewTime);
|
|
TArray<FRichCurveEditInfo> ChangedCurveEditInfos;
|
|
ChangedCurveEditInfos.Add(GetViewModelForCurve(Key.Curve)->CurveInfo);
|
|
CurveOwner->OnCurveChanged(ChangedCurveEditInfos);
|
|
}
|
|
}
|
|
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::OnTimeChanged(float NewTime)
|
|
{
|
|
if ( bIsUsingSlider )
|
|
{
|
|
if ( SelectedKeys.Num() >= 1 )
|
|
{
|
|
auto Key = SelectedKeys[0];
|
|
if ( IsValidCurve(Key.Curve) )
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT( "CurveEditor_NewTime", "New Time Entered" ) );
|
|
CurveOwner->ModifyOwner();
|
|
Key.Curve->SetKeyTime(Key.KeyHandle, NewTime);
|
|
TArray<FRichCurveEditInfo> ChangedCurveEditInfos;
|
|
ChangedCurveEditInfos.Add(GetViewModelForCurve(Key.Curve)->CurveInfo);
|
|
CurveOwner->OnCurveChanged(ChangedCurveEditInfos);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TOptional<float> SCurveEditor::OnGetValue() const
|
|
{
|
|
TOptional<float> Value;
|
|
|
|
// Return the value string if all selected keys have the same output string, otherwise empty
|
|
if ( SelectedKeys.Num() > 0 )
|
|
{
|
|
Value = GetKeyValue(SelectedKeys[0]);
|
|
for ( int32 i=1; i < SelectedKeys.Num(); i++ )
|
|
{
|
|
TOptional<float> NewValue = GetKeyValue(SelectedKeys[i]);
|
|
bool bAreEqual = ( ( !Value.IsSet() && !NewValue.IsSet() ) || ( Value.IsSet() && NewValue.IsSet() && Value.GetValue() == NewValue.GetValue() ) );
|
|
if ( !bAreEqual )
|
|
{
|
|
return TOptional<float>();
|
|
}
|
|
}
|
|
}
|
|
|
|
return Value;
|
|
}
|
|
|
|
void SCurveEditor::OnValueComitted(float NewValue, ETextCommit::Type CommitType)
|
|
{
|
|
// Don't digest the number if we just clicked away from the popup
|
|
if ( !bIsUsingSlider && ((CommitType == ETextCommit::OnEnter) || ( CommitType == ETextCommit::OnUserMovedFocus )) )
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT( "CurveEditor_NewValue", "New Value Entered" ) );
|
|
CurveOwner->ModifyOwner();
|
|
TSet<FRichCurve*> ChangedCurves;
|
|
|
|
// Iterate over selected set
|
|
for ( int32 i=0; i < SelectedKeys.Num(); i++ )
|
|
{
|
|
auto Key = SelectedKeys[i];
|
|
if ( IsValidCurve(Key.Curve) )
|
|
{
|
|
// Fill in each element of this key
|
|
Key.Curve->SetKeyValue(Key.KeyHandle, NewValue);
|
|
ChangedCurves.Add(Key.Curve);
|
|
}
|
|
}
|
|
|
|
TArray<FRichCurveEditInfo> ChangedCurveEditInfos;
|
|
for (auto CurveViewModel : CurveViewModels)
|
|
{
|
|
if (ChangedCurves.Contains(CurveViewModel->CurveInfo.CurveToEdit))
|
|
{
|
|
ChangedCurveEditInfos.Add(CurveViewModel->CurveInfo);
|
|
}
|
|
}
|
|
CurveOwner->OnCurveChanged(ChangedCurveEditInfos);
|
|
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::OnValueChanged(float NewValue)
|
|
{
|
|
if ( bIsUsingSlider )
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("CurveEditor_NewValue", "New Value Entered"));
|
|
TSet<FRichCurve*> ChangedCurves;
|
|
|
|
// Iterate over selected set
|
|
for ( int32 i=0; i < SelectedKeys.Num(); i++ )
|
|
{
|
|
auto Key = SelectedKeys[i];
|
|
if ( IsValidCurve(Key.Curve) )
|
|
{
|
|
CurveOwner->ModifyOwner();
|
|
|
|
// Fill in each element of this key
|
|
Key.Curve->SetKeyValue(Key.KeyHandle, NewValue);
|
|
ChangedCurves.Add(Key.Curve);
|
|
}
|
|
}
|
|
|
|
TArray<FRichCurveEditInfo> ChangedCurveEditInfos;
|
|
for (auto CurveViewModel : CurveViewModels)
|
|
{
|
|
if (ChangedCurves.Contains(CurveViewModel->CurveInfo.CurveToEdit))
|
|
{
|
|
ChangedCurveEditInfos.Add(CurveViewModel->CurveInfo);
|
|
}
|
|
}
|
|
CurveOwner->OnCurveChanged(ChangedCurveEditInfos);
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::OnBeginSliderMovement(FText TransactionName)
|
|
{
|
|
bIsUsingSlider = true;
|
|
|
|
GEditor->BeginTransaction(TransactionName);
|
|
}
|
|
|
|
void SCurveEditor::OnEndSliderMovement(float NewValue)
|
|
{
|
|
bIsUsingSlider = false;
|
|
|
|
GEditor->EndTransaction();
|
|
}
|
|
|
|
|
|
SCurveEditor::FSelectedCurveKey SCurveEditor::HitTestKeys(const FGeometry& InMyGeometry, const FVector2D& HitScreenPosition)
|
|
{
|
|
FSelectedCurveKey SelectedKey(NULL,FKeyHandle());
|
|
|
|
if( AreCurvesVisible() )
|
|
{
|
|
FTrackScaleInfo ScaleInfo(ViewMinInput.Get(), ViewMaxInput.Get(), ViewMinOutput.Get(), ViewMaxOutput.Get(), InMyGeometry.Size);
|
|
|
|
const FVector2D HitPosition = InMyGeometry.AbsoluteToLocal( HitScreenPosition );
|
|
|
|
|
|
for(auto CurveViewModel : CurveViewModels)
|
|
{
|
|
if (!CurveViewModel->bIsLocked && CurveViewModel->bIsVisible)
|
|
{
|
|
FRichCurve* Curve = CurveViewModel->CurveInfo.CurveToEdit;
|
|
if(Curve != NULL)
|
|
{
|
|
for (auto It(Curve->GetKeyHandleIterator()); It; ++It)
|
|
{
|
|
float KeyScreenX = ScaleInfo.InputToLocalX(Curve->GetKeyTime(It.Key()));
|
|
float KeyScreenY = ScaleInfo.OutputToLocalY(Curve->GetKeyValue(It.Key()));
|
|
|
|
if( HitPosition.X > (KeyScreenX - (0.5f * CONST_KeySize.X)) &&
|
|
HitPosition.X < (KeyScreenX + (0.5f * CONST_KeySize.X)) &&
|
|
HitPosition.Y > (KeyScreenY - (0.5f * CONST_KeySize.Y)) &&
|
|
HitPosition.Y < (KeyScreenY + (0.5f * CONST_KeySize.Y)) )
|
|
{
|
|
return FSelectedCurveKey(Curve, It.Key());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return SelectedKey;
|
|
}
|
|
|
|
void SCurveEditor::MoveSelectedKeys(FVector2D InNewLocation)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("CurveEditor_MoveKeys", "Move Keys") );
|
|
CurveOwner->ModifyOwner();
|
|
|
|
// track all unique curves encountered so their tangents can be updated later
|
|
TSet<FRichCurve*> UniqueCurves;
|
|
|
|
FVector2D SnappedNewLocation = SnapLocation(InNewLocation);
|
|
|
|
// The total move distance for all keys is the difference between the current snapped location
|
|
// and the start location of the key which was actually dragged.
|
|
FVector2D TotalMoveDistance = SnappedNewLocation - PreDragKeyLocations[DraggedKeyHandle];
|
|
|
|
for (int32 i = 0; i < SelectedKeys.Num(); i++)
|
|
{
|
|
FSelectedCurveKey OldKey = SelectedKeys[i];
|
|
|
|
if (!IsValidCurve(OldKey.Curve))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FKeyHandle OldKeyHandle = OldKey.KeyHandle;
|
|
FRichCurve* Curve = OldKey.Curve;
|
|
|
|
FVector2D PreDragLocation = PreDragKeyLocations[OldKeyHandle];
|
|
FVector2D NewLocation = PreDragLocation + TotalMoveDistance;
|
|
|
|
// Update the key's value without updating the tangents.
|
|
Curve->SetKeyValue(OldKeyHandle, NewLocation.Y, false);
|
|
|
|
// Changing the time of a key returns a new handle, so make sure to update existing references.
|
|
FKeyHandle KeyHandle = Curve->SetKeyTime(OldKeyHandle, NewLocation.X);
|
|
SelectedKeys[i] = FSelectedCurveKey(Curve, KeyHandle);
|
|
PreDragKeyLocations.Remove(OldKeyHandle);
|
|
PreDragKeyLocations.Add(KeyHandle, PreDragLocation);
|
|
|
|
UniqueCurves.Add(Curve);
|
|
}
|
|
|
|
// update auto tangents for all curves encountered, once each only
|
|
for(TSet<FRichCurve*>::TIterator SetIt(UniqueCurves);SetIt;++SetIt)
|
|
{
|
|
(*SetIt)->AutoSetTangents();
|
|
}
|
|
}
|
|
|
|
TOptional<float> SCurveEditor::GetKeyValue(FSelectedCurveKey Key) const
|
|
{
|
|
if(IsValidCurve(Key.Curve))
|
|
{
|
|
return Key.Curve->GetKeyValue(Key.KeyHandle);
|
|
}
|
|
|
|
return TOptional<float>();
|
|
}
|
|
|
|
TOptional<float> SCurveEditor::GetKeyTime(FSelectedCurveKey Key) const
|
|
{
|
|
if ( IsValidCurve(Key.Curve) )
|
|
{
|
|
return Key.Curve->GetKeyTime(Key.KeyHandle);
|
|
}
|
|
|
|
return TOptional<float>();
|
|
}
|
|
|
|
void SCurveEditor::EmptySelection()
|
|
{
|
|
SelectedKeys.Empty();
|
|
}
|
|
|
|
void SCurveEditor::AddToSelection(FSelectedCurveKey Key)
|
|
{
|
|
SelectedKeys.AddUnique(Key);
|
|
}
|
|
|
|
void SCurveEditor::RemoveFromSelection(FSelectedCurveKey Key)
|
|
{
|
|
SelectedKeys.Remove(Key);
|
|
}
|
|
|
|
bool SCurveEditor::IsKeySelected(FSelectedCurveKey Key) const
|
|
{
|
|
return SelectedKeys.Contains(Key);
|
|
}
|
|
|
|
bool SCurveEditor::AreKeysSelected() const
|
|
{
|
|
return SelectedKeys.Num() > 0;
|
|
}
|
|
|
|
|
|
TArray<FRichCurve*> SCurveEditor::GetCurvesToFit() const
|
|
{
|
|
TArray<FRichCurve*> FitCurves;
|
|
|
|
for(auto CurveViewModel : CurveViewModels)
|
|
{
|
|
if (CurveViewModel->bIsVisible)
|
|
{
|
|
FitCurves.Add(CurveViewModel->CurveInfo.CurveToEdit);
|
|
}
|
|
}
|
|
|
|
return FitCurves;
|
|
}
|
|
|
|
|
|
void SCurveEditor::ZoomToFitHorizontal()
|
|
{
|
|
TArray<FRichCurve*> CurvesToFit = GetCurvesToFit();
|
|
|
|
if(CurveViewModels.Num() > 0)
|
|
{
|
|
float InMin = FLT_MAX;
|
|
float InMax = -FLT_MAX;
|
|
int32 TotalKeys = 0;
|
|
|
|
if (SelectedKeys.Num())
|
|
{
|
|
for (auto SelectedKey : SelectedKeys)
|
|
{
|
|
TotalKeys++;
|
|
float KeyTime = SelectedKey.Curve->GetKeyTime(SelectedKey.KeyHandle);
|
|
InMin = FMath::Min(KeyTime, InMin);
|
|
InMax = FMath::Max(KeyTime, InMax);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (FRichCurve* Curve : CurvesToFit)
|
|
{
|
|
float MinTime, MaxTime;
|
|
Curve->GetTimeRange(MinTime, MaxTime);
|
|
InMin = FMath::Min(MinTime, InMin);
|
|
InMax = FMath::Max(MaxTime, InMax);
|
|
TotalKeys += Curve->GetNumKeys();
|
|
}
|
|
}
|
|
|
|
if (TotalKeys > 0)
|
|
{
|
|
// Clamp the minimum size
|
|
float Size = InMax - InMin;
|
|
if (Size < CONST_MinViewRange)
|
|
{
|
|
InMin -= (0.5f*CONST_MinViewRange);
|
|
InMax += (0.5f*CONST_MinViewRange);
|
|
Size = InMax - InMin;
|
|
}
|
|
|
|
// add margin
|
|
InMin -= CONST_FitMargin*Size;
|
|
InMax += CONST_FitMargin*Size;
|
|
}
|
|
else
|
|
{
|
|
InMin = -CONST_FitMargin*2.0f;
|
|
InMax = (CONST_DefaultZoomRange + CONST_FitMargin)*2.0;
|
|
}
|
|
|
|
SetInputMinMax(InMin, InMax);
|
|
}
|
|
}
|
|
|
|
FReply SCurveEditor::ZoomToFitHorizontalClicked()
|
|
{
|
|
ZoomToFitHorizontal();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
/** Set Default output values when range is too small **/
|
|
void SCurveEditor::SetDefaultOutput(const float MinZoomRange)
|
|
{
|
|
const float NewMinOutput = (ViewMinOutput.Get() - (0.5f*MinZoomRange));
|
|
const float NewMaxOutput = (ViewMaxOutput.Get() + (0.5f*MinZoomRange));
|
|
|
|
SetOutputMinMax(NewMinOutput, NewMaxOutput);
|
|
}
|
|
|
|
void SCurveEditor::ZoomToFitVertical()
|
|
{
|
|
TArray<FRichCurve*> CurvesToFit = GetCurvesToFit();
|
|
|
|
if(CurvesToFit.Num() > 0)
|
|
{
|
|
float InMin = FLT_MAX;
|
|
float InMax = -FLT_MAX;
|
|
int32 TotalKeys = 0;
|
|
|
|
if (SelectedKeys.Num() != 0)
|
|
{
|
|
for (auto SelectedKey : SelectedKeys)
|
|
{
|
|
TotalKeys++;
|
|
float KeyValue = SelectedKey.Curve->GetKeyValue(SelectedKey.KeyHandle);
|
|
InMin = FMath::Min(KeyValue, InMin);
|
|
InMax = FMath::Max(KeyValue, InMax);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (FRichCurve* Curve : CurvesToFit)
|
|
{
|
|
float MinVal, MaxVal;
|
|
Curve->GetValueRange(MinVal, MaxVal);
|
|
InMin = FMath::Min(MinVal, InMin);
|
|
InMax = FMath::Max(MaxVal, InMax);
|
|
TotalKeys += Curve->GetNumKeys();
|
|
}
|
|
}
|
|
|
|
const float MinZoomRange = (TotalKeys > 0 ) ? CONST_MinViewRange: CONST_DefaultZoomRange;
|
|
|
|
// Clamp the minimum size
|
|
float Size = InMax - InMin;
|
|
if( Size < MinZoomRange )
|
|
{
|
|
SetDefaultOutput(MinZoomRange);
|
|
InMin = ViewMinOutput.Get();
|
|
InMax = ViewMaxOutput.Get();
|
|
Size = InMax - InMin;
|
|
}
|
|
|
|
// add margin
|
|
const float NewMinOutput = (InMin - CONST_FitMargin*Size);
|
|
const float NewMaxOutput = (InMax + CONST_FitMargin*Size);
|
|
|
|
SetOutputMinMax(NewMinOutput, NewMaxOutput);
|
|
}
|
|
}
|
|
|
|
FReply SCurveEditor::ZoomToFitVerticalClicked()
|
|
{
|
|
ZoomToFitVertical();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void SCurveEditor::ZoomToFit()
|
|
{
|
|
ZoomToFitHorizontal();
|
|
ZoomToFitVertical();
|
|
}
|
|
|
|
void SCurveEditor::ToggleSnapping()
|
|
{
|
|
if (bSnappingEnabled.IsBound() == false)
|
|
{
|
|
bSnappingEnabled = !bSnappingEnabled.Get();
|
|
}
|
|
}
|
|
|
|
bool SCurveEditor::IsSnappingEnabled()
|
|
{
|
|
return bSnappingEnabled.Get();
|
|
}
|
|
|
|
void SCurveEditor::CreateContextMenu(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
|
|
{
|
|
const FVector2D& ScreenPosition = InMouseEvent.GetScreenSpacePosition();
|
|
|
|
const bool CloseAfterSelection = true;
|
|
FMenuBuilder MenuBuilder( CloseAfterSelection, NULL );
|
|
|
|
MenuBuilder.BeginSection("EditCurveEditorActions", LOCTEXT("Actions", "Actions"));
|
|
{
|
|
FText MenuItemLabel;
|
|
FText MenuItemToolTip;
|
|
TSharedPtr<TArray<TSharedPtr<FCurveViewModel>>> CurvesToAddKeysTo = MakeShareable(new TArray<TSharedPtr<FCurveViewModel>>());
|
|
bool bAddKeysInline;
|
|
|
|
FText AddKeyToCurveLabelFormat = LOCTEXT("AddKeyToCurveLabelFormat", "Add key to {0}");
|
|
FText AddKeyToCurveToolTipFormat = LOCTEXT("AddKeyToCurveToolTipFormat", "Add a new key at the hovered time to the {0} curve. Keys can also be added with Shift + Click.");
|
|
|
|
TSharedPtr<FCurveViewModel> HoveredCurve = HitTestCurves(InMyGeometry, InMouseEvent);
|
|
if (HoveredCurve.IsValid())
|
|
{
|
|
MenuItemLabel = FText::Format(AddKeyToCurveLabelFormat, FText::FromName(HoveredCurve->CurveInfo.CurveName));
|
|
MenuItemToolTip = FText::Format(AddKeyToCurveToolTipFormat, FText::FromName(HoveredCurve->CurveInfo.CurveName));
|
|
CurvesToAddKeysTo->Add(HoveredCurve);
|
|
bAddKeysInline = false;
|
|
}
|
|
else
|
|
{
|
|
if (CurveViewModels.Num() == 1)
|
|
{
|
|
MenuItemLabel = FText::Format(AddKeyToCurveLabelFormat, FText::FromName(CurveViewModels[0]->CurveInfo.CurveName));
|
|
MenuItemToolTip = FText::Format(AddKeyToCurveToolTipFormat, FText::FromName(CurveViewModels[0]->CurveInfo.CurveName));
|
|
CurvesToAddKeysTo->Add(CurveViewModels[0]);
|
|
bAddKeysInline = true;
|
|
}
|
|
else
|
|
{
|
|
MenuItemLabel = LOCTEXT("AddKeyToAllCurves", "Add key to all curves");
|
|
MenuItemToolTip = LOCTEXT("AddKeyToAllCurveToolTip", "Adds a key at the hovered time to all curves. Keys can also be added with Shift + Click.");
|
|
CurvesToAddKeysTo->Append(CurveViewModels);
|
|
bAddKeysInline = false;
|
|
}
|
|
}
|
|
|
|
FVector2D Position = InMouseEvent.GetScreenSpacePosition();
|
|
FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SCurveEditor::AddNewKey, InMyGeometry, Position, CurvesToAddKeysTo, bAddKeysInline));
|
|
MenuBuilder.AddMenuEntry(
|
|
MenuItemLabel,
|
|
MenuItemToolTip,
|
|
FSlateIcon(),
|
|
Action);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("CurveEditorActions", LOCTEXT("CurveAction", "Curve Actions") );
|
|
{
|
|
if( OnCreateAsset.IsBound() && IsEditingEnabled() )
|
|
{
|
|
FUIAction Action = FUIAction( FExecuteAction::CreateSP( this, &SCurveEditor::OnCreateExternalCurveClicked ) );
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
LOCTEXT("CreateExternalCurve", "Create External Curve"),
|
|
LOCTEXT("CreateExternalCurve_ToolTip", "Create an external asset using this internal curve"),
|
|
FSlateIcon(),
|
|
Action
|
|
);
|
|
}
|
|
|
|
if( IsLinearColorCurve() && !bAlwaysDisplayColorCurves )
|
|
{
|
|
FUIAction ShowCurveAction( FExecuteAction::CreateSP( this, &SCurveEditor::OnShowCurveToggled ), FCanExecuteAction(), FIsActionChecked::CreateSP( this, &SCurveEditor::AreCurvesVisible ) );
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
LOCTEXT("ShowCurves","Show Curves"),
|
|
LOCTEXT("ShowCurves_ToolTip", "Toggles displaying the curves for linear colors"),
|
|
FSlateIcon(),
|
|
ShowCurveAction,
|
|
NAME_None,
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
}
|
|
|
|
if( IsLinearColorCurve() )
|
|
{
|
|
FUIAction ShowGradientAction( FExecuteAction::CreateSP( this, &SCurveEditor::OnShowGradientToggled ), FCanExecuteAction(), FIsActionChecked::CreateSP( this, &SCurveEditor::IsGradientEditorVisible ) );
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
LOCTEXT("ShowGradient","Show Gradient"),
|
|
LOCTEXT("ShowGradient_ToolTip", "Toggles displaying the gradient for linear colors"),
|
|
FSlateIcon(),
|
|
ShowGradientAction,
|
|
NAME_None,
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
}
|
|
}
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
FWidgetPath WidgetPath = InMouseEvent.GetEventPath() != nullptr ? *InMouseEvent.GetEventPath() : FWidgetPath();
|
|
FSlateApplication::Get().PushMenu(SharedThis(this), WidgetPath, MenuBuilder.MakeWidget(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu));
|
|
}
|
|
|
|
void SCurveEditor::OnCreateExternalCurveClicked()
|
|
{
|
|
OnCreateAsset.ExecuteIfBound();
|
|
}
|
|
|
|
|
|
UObject* SCurveEditor::CreateCurveObject( TSubclassOf<UCurveBase> CurveType, UObject* PackagePtr, FName& AssetName )
|
|
{
|
|
UObject* NewObj = NULL;
|
|
CurveFactory = Cast<UCurveFactory>(NewObject<UFactory>(GetTransientPackage(), UCurveFactory::StaticClass()));
|
|
if(CurveFactory)
|
|
{
|
|
CurveFactory->CurveClass = CurveType;
|
|
NewObj = CurveFactory->FactoryCreateNew( CurveFactory->GetSupportedClass(), PackagePtr, AssetName, RF_Public|RF_Standalone, NULL, GWarn );
|
|
}
|
|
CurveFactory = NULL;
|
|
return NewObj;
|
|
}
|
|
|
|
bool SCurveEditor::IsEditingEnabled() const
|
|
{
|
|
return bCanEditTrack;
|
|
}
|
|
|
|
void SCurveEditor::AddReferencedObjects( FReferenceCollector& Collector )
|
|
{
|
|
Collector.AddReferencedObject( CurveFactory );
|
|
}
|
|
|
|
TSharedPtr<FUICommandList> SCurveEditor::GetCommands()
|
|
{
|
|
return Commands;
|
|
}
|
|
|
|
bool SCurveEditor::IsValidCurve( FRichCurve* Curve ) const
|
|
{
|
|
bool bIsValid = false;
|
|
if(Curve && CurveOwner)
|
|
{
|
|
for(auto CurveViewModel : CurveViewModels)
|
|
{
|
|
if(CurveViewModel->CurveInfo.CurveToEdit == Curve && CurveOwner->IsValidCurve(CurveViewModel->CurveInfo))
|
|
{
|
|
bIsValid = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return bIsValid;
|
|
}
|
|
|
|
void SCurveEditor::SetInputMinMax(float NewMin, float NewMax)
|
|
{
|
|
if (SetInputViewRangeHandler.IsBound())
|
|
{
|
|
SetInputViewRangeHandler.Execute(NewMin, NewMax);
|
|
}
|
|
else
|
|
{
|
|
//if no delegate and view min input isn't using a delegate just set value directly
|
|
if (ViewMinInput.IsBound() == false)
|
|
{
|
|
ViewMinInput.Set(NewMin);
|
|
}
|
|
|
|
if (ViewMaxInput.IsBound() == false)
|
|
{
|
|
ViewMaxInput.Set(NewMax);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SCurveEditor::SetOutputMinMax(float NewMin, float NewMax)
|
|
{
|
|
if (SetOutputViewRangeHandler.IsBound())
|
|
{
|
|
SetOutputViewRangeHandler.Execute(NewMin, NewMax);
|
|
}
|
|
else
|
|
{
|
|
//if no delegate and view min output isn't using a delegate just set value directly
|
|
if (ViewMinOutput.IsBound() == false)
|
|
{
|
|
ViewMinOutput.Set(NewMin);
|
|
}
|
|
|
|
if (ViewMaxOutput.IsBound() == false)
|
|
{
|
|
ViewMaxOutput.Set(NewMax);
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FCurveViewModel> SCurveEditor::HitTestCurves( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent )
|
|
{
|
|
if( AreCurvesVisible() )
|
|
{
|
|
FTrackScaleInfo ScaleInfo(ViewMinInput.Get(), ViewMaxInput.Get(), ViewMinOutput.Get(), ViewMaxOutput.Get(), InMyGeometry.Size);
|
|
|
|
const FVector2D HitPosition = InMyGeometry.AbsoluteToLocal( InMouseEvent.GetScreenSpacePosition() );
|
|
|
|
TArray<FRichCurve*> CurvesHit;
|
|
|
|
for(auto CurveViewModel : CurveViewModels)
|
|
{
|
|
|
|
FRichCurve* Curve = CurveViewModel->CurveInfo.CurveToEdit;
|
|
if(Curve != NULL)
|
|
{
|
|
float Time = ScaleInfo.LocalXToInput(HitPosition.X);
|
|
float KeyScreenY = ScaleInfo.OutputToLocalY(Curve->Eval(Time));
|
|
|
|
if( HitPosition.Y > (KeyScreenY - (0.5f * CONST_CurveSize.Y)) &&
|
|
HitPosition.Y < (KeyScreenY + (0.5f * CONST_CurveSize.Y)))
|
|
{
|
|
return CurveViewModel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return TSharedPtr<FCurveViewModel>();
|
|
}
|
|
|
|
SCurveEditor::FSelectedTangent SCurveEditor::HitTestCubicTangents( const FGeometry& InMyGeometry, const FVector2D& HitScreenPosition )
|
|
{
|
|
FSelectedTangent Tangent;
|
|
|
|
if( AreCurvesVisible() )
|
|
{
|
|
FTrackScaleInfo ScaleInfo(ViewMinInput.Get(), ViewMaxInput.Get(), ViewMinOutput.Get(), ViewMaxOutput.Get(), InMyGeometry.Size);
|
|
|
|
const FVector2D HitPosition = InMyGeometry.AbsoluteToLocal( HitScreenPosition);
|
|
|
|
for(auto It = SelectedKeys.CreateConstIterator();It;++It)
|
|
{
|
|
FSelectedCurveKey Key = *It;
|
|
if(Key.IsValid())
|
|
{
|
|
float Time = ScaleInfo.LocalXToInput(HitPosition.X);
|
|
float KeyScreenY = ScaleInfo.OutputToLocalY(Key.Curve->Eval(Time));
|
|
|
|
FVector2D Arrive, Leave;
|
|
GetTangentPoints(ScaleInfo, Key, Arrive, Leave);
|
|
|
|
if( HitPosition.Y > (Arrive.Y - (0.5f * CONST_CurveSize.Y)) &&
|
|
HitPosition.Y < (Arrive.Y + (0.5f * CONST_CurveSize.Y)) &&
|
|
HitPosition.X > (Arrive.X - (0.5f * CONST_TangentSize.X)) &&
|
|
HitPosition.X < (Arrive.X + (0.5f * CONST_TangentSize.X)))
|
|
{
|
|
Tangent.Key = Key;
|
|
Tangent.bIsArrival = true;
|
|
break;
|
|
}
|
|
if( HitPosition.Y > (Leave.Y - (0.5f * CONST_CurveSize.Y)) &&
|
|
HitPosition.Y < (Leave.Y + (0.5f * CONST_CurveSize.Y)) &&
|
|
HitPosition.X > (Leave.X - (0.5f * CONST_TangentSize.X)) &&
|
|
HitPosition.X < (Leave.X + (0.5f * CONST_TangentSize.X)))
|
|
{
|
|
Tangent.Key = Key;
|
|
Tangent.bIsArrival = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Tangent;
|
|
}
|
|
|
|
void SCurveEditor::OnSelectInterpolationMode(ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode)
|
|
{
|
|
if(SelectedKeys.Num() > 0)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("CurveEditor_SetInterpolationMode", "Select Interpolation Mode"));
|
|
CurveOwner->ModifyOwner();
|
|
TSet<FRichCurve*> ChangedCurves;
|
|
|
|
for(auto It = SelectedKeys.CreateIterator();It;++It)
|
|
{
|
|
FSelectedCurveKey& Key = *It;
|
|
check(IsValidCurve(Key.Curve));
|
|
Key.Curve->SetKeyInterpMode(Key.KeyHandle,InterpMode );
|
|
Key.Curve->SetKeyTangentMode(Key.KeyHandle,TangentMode );
|
|
}
|
|
|
|
TArray<FRichCurveEditInfo> ChangedCurveEditInfos;
|
|
for (auto CurveViewModel : CurveViewModels)
|
|
{
|
|
if (ChangedCurves.Contains(CurveViewModel->CurveInfo.CurveToEdit))
|
|
{
|
|
ChangedCurveEditInfos.Add(CurveViewModel->CurveInfo);
|
|
}
|
|
}
|
|
CurveOwner->OnCurveChanged(ChangedCurveEditInfos);
|
|
}
|
|
}
|
|
|
|
bool SCurveEditor::IsInterpolationModeSelected(ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode)
|
|
{
|
|
if (SelectedKeys.Num() > 0)
|
|
{
|
|
for (auto SelectedKey : SelectedKeys)
|
|
{
|
|
if (SelectedKey.Curve->GetKeyInterpMode(SelectedKey.KeyHandle) != InterpMode || SelectedKey.Curve->GetKeyTangentMode(SelectedKey.KeyHandle) != TangentMode)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Given a tangent value for a key, calculates the 2D delta vector from that key in curve space */
|
|
static inline FVector2D CalcTangentDir(float Tangent)
|
|
{
|
|
const float Angle = FMath::Atan(Tangent);
|
|
return FVector2D( FMath::Cos(Angle), -FMath::Sin(Angle) );
|
|
}
|
|
|
|
/*Given a 2d delta vector in curve space, calculates a tangent value */
|
|
static inline float CalcTangent(const FVector2D& HandleDelta)
|
|
{
|
|
// Ensure X is positive and non-zero.
|
|
// Tangent is gradient of handle.
|
|
return HandleDelta.Y / FMath::Max<double>(HandleDelta.X, KINDA_SMALL_NUMBER);
|
|
}
|
|
|
|
void SCurveEditor::OnMoveTangent(FVector2D MouseCurvePosition)
|
|
{
|
|
auto& RichKey = SelectedTangent.Key.Curve->GetKey(SelectedTangent.Key.KeyHandle);
|
|
|
|
const FSelectedCurveKey &Key = SelectedTangent.Key;
|
|
|
|
FVector2D KeyPosition( Key.Curve->GetKeyTime(Key.KeyHandle),Key.Curve->GetKeyValue(Key.KeyHandle) );
|
|
|
|
FVector2D Movement = MouseCurvePosition - KeyPosition;
|
|
if(SelectedTangent.bIsArrival)
|
|
{
|
|
Movement *= -1.0f;
|
|
}
|
|
float Tangent = CalcTangent(Movement);
|
|
|
|
if(RichKey.TangentMode != RCTM_Break)
|
|
{
|
|
RichKey.ArriveTangent = Tangent;
|
|
RichKey.LeaveTangent = Tangent;
|
|
|
|
RichKey.TangentMode = RCTM_User;
|
|
}
|
|
else
|
|
{
|
|
if(SelectedTangent.bIsArrival)
|
|
{
|
|
RichKey.ArriveTangent = Tangent;
|
|
}
|
|
else
|
|
{
|
|
RichKey.LeaveTangent = Tangent;
|
|
}
|
|
}
|
|
|
|
}
|
|
void SCurveEditor::GetTangentPoints( FTrackScaleInfo &ScaleInfo, const FSelectedCurveKey &Key, FVector2D& Arrive, FVector2D& Leave ) const
|
|
{
|
|
FVector2D ArriveTangentDir = CalcTangentDir( Key.Curve->GetKey(Key.KeyHandle).ArriveTangent);
|
|
FVector2D LeaveTangentDir = CalcTangentDir( Key.Curve->GetKey(Key.KeyHandle).LeaveTangent);
|
|
|
|
FVector2D KeyPosition( Key.Curve->GetKeyTime(Key.KeyHandle),Key.Curve->GetKeyValue(Key.KeyHandle) );
|
|
|
|
ArriveTangentDir.Y *= -1.0f;
|
|
LeaveTangentDir.Y *= -1.0f;
|
|
FVector2D ArrivePosition = -ArriveTangentDir + KeyPosition;
|
|
|
|
FVector2D LeavePosition = LeaveTangentDir + KeyPosition;
|
|
|
|
Arrive = FVector2D(ScaleInfo.InputToLocalX(ArrivePosition.X), ScaleInfo.OutputToLocalY(ArrivePosition.Y));
|
|
Leave = FVector2D(ScaleInfo.InputToLocalX(LeavePosition.X), ScaleInfo.OutputToLocalY(LeavePosition.Y));
|
|
|
|
FVector2D KeyScreenPosition = FVector2D(ScaleInfo.InputToLocalX(KeyPosition.X), ScaleInfo.OutputToLocalY(KeyPosition.Y));
|
|
|
|
FVector2D ToArrive = Arrive - KeyScreenPosition;
|
|
ToArrive.Normalize();
|
|
|
|
Arrive = KeyScreenPosition + ToArrive*CONST_KeyTangentOffset;
|
|
|
|
FVector2D ToLeave = Leave - KeyScreenPosition;
|
|
ToLeave.Normalize();
|
|
|
|
Leave = KeyScreenPosition + ToLeave*CONST_KeyTangentOffset;
|
|
}
|
|
|
|
TArray<SCurveEditor::FSelectedCurveKey> SCurveEditor::GetEditableKeysWithinMarquee(const FGeometry& InMyGeometry, FVector2D MarqueeTopLeft, FVector2D MarqueeBottomRight) const
|
|
{
|
|
TArray<FSelectedCurveKey> KeysWithinMarquee;
|
|
if (AreCurvesVisible())
|
|
{
|
|
FTrackScaleInfo ScaleInfo(ViewMinInput.Get(), ViewMaxInput.Get(), ViewMinOutput.Get(), ViewMaxOutput.Get(), InMyGeometry.Size);
|
|
for (auto CurveViewModel : CurveViewModels)
|
|
{
|
|
if (!CurveViewModel->bIsLocked && CurveViewModel->bIsVisible)
|
|
{
|
|
FRichCurve* Curve = CurveViewModel->CurveInfo.CurveToEdit;
|
|
if (Curve != NULL)
|
|
{
|
|
for (auto It(Curve->GetKeyHandleIterator()); It; ++It)
|
|
{
|
|
float KeyScreenX = ScaleInfo.InputToLocalX(Curve->GetKeyTime(It.Key()));
|
|
float KeyScreenY = ScaleInfo.OutputToLocalY(Curve->GetKeyValue(It.Key()));
|
|
|
|
if (KeyScreenX >= (MarqueeTopLeft.X - (0.5f * CONST_KeySize.X)) &&
|
|
KeyScreenX <= (MarqueeBottomRight.X + (0.5f * CONST_KeySize.X)) &&
|
|
KeyScreenY >= (MarqueeTopLeft.Y - (0.5f * CONST_KeySize.Y)) &&
|
|
KeyScreenY <= (MarqueeBottomRight.Y + (0.5f * CONST_KeySize.Y)))
|
|
{
|
|
KeysWithinMarquee.Add(FSelectedCurveKey(Curve, It.Key()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return KeysWithinMarquee;
|
|
}
|
|
|
|
void SCurveEditor::BeginDragTransaction()
|
|
{
|
|
TransactionIndex = GEditor->BeginTransaction( LOCTEXT("CurveEditor_Drag", "Mouse Drag") );
|
|
CurveOwner->ModifyOwner();
|
|
}
|
|
|
|
void SCurveEditor::EndDragTransaction()
|
|
{
|
|
if ( TransactionIndex >= 0 )
|
|
{
|
|
TArray<FRichCurveEditInfo> ChangedCurveEditInfos;
|
|
for (auto CurveViewModel : CurveViewModels)
|
|
{
|
|
ChangedCurveEditInfos.Add(CurveViewModel->CurveInfo);
|
|
}
|
|
CurveOwner->OnCurveChanged(ChangedCurveEditInfos);
|
|
GEditor->EndTransaction();
|
|
TransactionIndex = -1;
|
|
}
|
|
}
|
|
|
|
bool SCurveEditor::FSelectedTangent::IsValid() const
|
|
{
|
|
return Key.IsValid();
|
|
}
|
|
|
|
void SCurveEditor::UndoAction()
|
|
{
|
|
GEditor->UndoTransaction();
|
|
}
|
|
|
|
void SCurveEditor::RedoAction()
|
|
{
|
|
GEditor->RedoTransaction();
|
|
}
|
|
|
|
void SCurveEditor::PostUndo(bool bSuccess)
|
|
{
|
|
//remove any invalid keys
|
|
for(int32 i = 0;i<SelectedKeys.Num();++i)
|
|
{
|
|
auto Key = SelectedKeys[i];
|
|
if(!IsValidCurve(Key.Curve) || !Key.IsValid())
|
|
{
|
|
SelectedKeys.RemoveAt(i);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SCurveEditor::IsLinearColorCurve() const
|
|
{
|
|
return CurveOwner && CurveOwner->IsLinearColorCurve();
|
|
}
|
|
|
|
FVector2D SCurveEditor::SnapLocation(FVector2D InLocation)
|
|
{
|
|
if (bSnappingEnabled.Get())
|
|
{
|
|
const float InputSnapNow = InputSnap.Get();
|
|
const float OutputSnapNow = OutputSnap.Get();
|
|
|
|
InLocation.X = InputSnapNow != 0 ? FMath::RoundToInt(InLocation.X / InputSnapNow) * InputSnapNow : InLocation.X;
|
|
InLocation.Y = OutputSnapNow != 0 ? FMath::RoundToInt(InLocation.Y / OutputSnapNow) * OutputSnapNow : InLocation.Y;
|
|
}
|
|
return InLocation;
|
|
}
|
|
|
|
TSharedPtr<FCurveViewModel> SCurveEditor::GetViewModelForCurve(FRichCurve* InCurve)
|
|
{
|
|
for (auto CurveViewModel : CurveViewModels)
|
|
{
|
|
if (InCurve == CurveViewModel->CurveInfo.CurveToEdit)
|
|
{
|
|
return CurveViewModel;
|
|
}
|
|
}
|
|
return TSharedPtr<FCurveViewModel>();
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|