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

1422 lines
42 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CurveTableEditor.h"
#include "Containers/ArrayView.h"
#include "CurveEditor.h"
#include "CurveModel.h"
#include "CurveTableEditorCommands.h"
#include "CurveTableEditorHandle.h"
#include "CurveTableEditorModule.h"
#include "Curves/KeyHandle.h"
#include "Curves/SimpleCurve.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "EditorReimportHandler.h"
#include "Engine/CompositeCurveTable.h"
#include "Engine/CurveTable.h"
#include "Fonts/FontMeasure.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/GenericCommands.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/MultiBox/MultiBoxDefs.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/Text/TextLayout.h"
#include "Framework/Views/ITypedTableView.h"
#include "ICurveEditorModule.h"
#include "Internationalization/Internationalization.h"
#include "Layout/BasicLayoutWidgetSlot.h"
#include "Layout/Margin.h"
#include "Math/UnrealMathSSE.h"
#include "Math/Vector2D.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "Modules/ModuleManager.h"
#include "RealCurveModel.h"
#include "Rendering/SlateRenderer.h"
#include "RichCurveEditorModel.h"
#include "SCurveEditorPanel.h"
#include "SPositiveActionButton.h"
#include "ScopedTransaction.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Styling/ISlateStyle.h"
#include "Styling/SlateColor.h"
#include "Styling/SlateTypes.h"
#include "Styling/StyleColors.h"
#include "Templates/Casts.h"
#include "Templates/Tuple.h"
#include "Templates/UniquePtr.h"
#include "Textures/SlateIcon.h"
#include "Toolkits/AssetEditorToolkit.h"
#include "Tree/CurveEditorTree.h"
#include "Tree/CurveEditorTreeFilter.h"
#include "Tree/CurveEditorTreeTraits.h"
#include "Tree/ICurveEditorTreeItem.h"
#include "Tree/SCurveEditorTree.h"
#include "Tree/SCurveEditorTreePin.h"
#include "Tree/SCurveEditorTreeSelect.h"
#include "Tree/SCurveEditorTreeTextFilter.h"
#include "Types/SlateStructs.h"
#include "UObject/ObjectMacros.h"
#include "UObject/UnrealNames.h"
#include "UObject/UnrealType.h"
#include "UObject/WeakObjectPtrTemplates.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/Input/SSegmentedControl.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScrollBar.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Layout/SSplitter.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SNullWidget.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/Views/SListView.h"
class ITableRow;
class SWidget;
class UObject;
struct FRichCurve;
#define LOCTEXT_NAMESPACE "CurveTableEditor"
const FName FCurveTableEditor::CurveTableTabId("CurveTableEditor_CurveTable");
struct FCurveTableEditorColumnHeaderData
{
/** Unique ID used to identify this column */
FName ColumnId;
/** Display name of this column */
FText DisplayName;
/** The calculated width of this column taking into account the cell data for each row */
float DesiredColumnWidth;
/** The evaluated key time **/
float KeyTime;
};
namespace {
FName MakeUniqueCurveName( UCurveTable* Table )
{
check(Table != nullptr);
int incr = 0;
FName TestName = FName("Curve", incr);
const TMap<FName, FRealCurve*>& RowMap = Table->GetRowMap();
while (RowMap.Contains(TestName))
{
TestName = FName("Curve", ++incr);
}
return TestName;
}
}
/*
* FCurveTableEditorItem
*
* FCurveTableEditorItem uses and extends the CurveEditorTreeItem to be used in both our TableView and the CurveEditorTree.
* The added GenerateTableViewCell handles the table columns unknown to the standard CurveEditorTree.
*
*/
class FCurveTableEditorItem : public ICurveEditorTreeItem, public TSharedFromThis<FCurveTableEditorItem>
{
struct CachedKeyInfo
{
CachedKeyInfo(FKeyHandle& InKeyHandle, FText InDisplayValue) :
KeyHandle(InKeyHandle)
, DisplayValue(InDisplayValue) {}
FKeyHandle KeyHandle;
FText DisplayValue;
};
public:
FCurveTableEditorItem (TWeakPtr<FCurveTableEditor> InCurveTableEditor, const FCurveEditorTreeItemID& InTreeID, const FName& InRowId, FCurveTableEditorHandle InRowHandle, const TArray<FCurveTableEditorColumnHeaderDataPtr>& InColumns)
: CurveTableEditor(InCurveTableEditor)
, TreeID(InTreeID)
, RowId(InRowId)
, RowHandle(InRowHandle)
, Columns(InColumns)
{
DisplayName = FText::FromName(InRowId);
CacheKeys();
}
TSharedPtr<SWidget> GenerateCurveEditorTreeWidget(const FName& InColumnName, TWeakPtr<FCurveEditor> InCurveEditor, FCurveEditorTreeItemID InTreeItemID, const TSharedRef<ITableRow>& InTableRow) override
{
if (InColumnName == ColumnNames.Label)
{
return SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.Padding(FMargin(4.f))
.VAlign(VAlign_Center)
.HAlign(HAlign_Right)
.AutoWidth()
[
SAssignNew(InlineRenameWidget, SInlineEditableTextBlock)
.Text(DisplayName)
.ColorAndOpacity(FSlateColor::UseForeground())
.MaximumLength(NAME_SIZE-1)
.OnTextCommitted(this, &FCurveTableEditorItem::HandleNameCommitted)
.OnVerifyTextChanged(this, &FCurveTableEditorItem::VerifyNameChanged)
];
}
else if (InColumnName == ColumnNames.SelectHeader)
{
return SNew(SCurveEditorTreeSelect, InCurveEditor, InTreeItemID, InTableRow);
}
else if (InColumnName == ColumnNames.PinHeader)
{
return SNew(SCurveEditorTreePin, InCurveEditor, InTreeItemID, InTableRow);
}
return GenerateTableViewCell(InColumnName, InCurveEditor, InTreeItemID, InTableRow);
}
TSharedPtr<SWidget> GenerateTableViewCell(const FName& InColumnId, TWeakPtr<FCurveEditor> InCurveEditor, FCurveEditorTreeItemID InTreeItemID, const TSharedRef<ITableRow>& InTableRow)
{
if (!RowHandle.HasRichCurves())
{
FRealCurve* Curve = RowHandle.GetCurve();
FKeyHandle& KeyHandle = CellDataMap[InColumnId].KeyHandle;
return SNew(SNumericEntryBox<float>)
.EditableTextBoxStyle( &FAppStyle::Get().GetWidgetStyle<FEditableTextBoxStyle>("CurveTableEditor.Cell.Text") )
.Value_Lambda([this, KeyHandle] () {
if (FRealCurve* Curve = RowHandle.GetCurve())
{
return Curve->GetKeyValue(KeyHandle);
}
return 0.0f;
})
.OnValueChanged_Lambda([this, KeyHandle] (float NewValue)
{
if (FRealCurve* Curve = RowHandle.GetCurve())
{
FScopedTransaction Transaction(LOCTEXT("SetKeyValues", "Set Key Values"));
RowHandle.ModifyOwner();
Curve->SetKeyValue(KeyHandle, NewValue);
}
})
.Justification(ETextJustify::Right)
;
}
return SNullWidget::NullWidget;
}
void CreateCurveModels(TArray<TUniquePtr<FCurveModel>>& OutCurveModels) override
{
if (RowHandle.HasRichCurves())
{
if (FRichCurve* RichCurve = RowHandle.GetRichCurve())
{
const UCurveTable* Table = RowHandle.CurveTable.Get();
UCurveTable* RawTable = const_cast<UCurveTable*>(Table);
TUniquePtr<FRichCurveEditorModelRaw> NewCurve = MakeUnique<FRichCurveEditorModelRaw>(RichCurve, RawTable);
NewCurve->SetShortDisplayName(DisplayName);
NewCurve->SetColor(FStyleColors::AccentOrange.GetSpecifiedColor());
OutCurveModels.Add(MoveTemp(NewCurve));
}
}
else
{
const UCurveTable* Table = RowHandle.CurveTable.Get();
UCurveTable* RawTable = const_cast<UCurveTable*>(Table);
TUniquePtr<FRealCurveModel> NewCurveModel = MakeUnique<FRealCurveModel>(RowHandle.GetCurve(), RawTable);
NewCurveModel->SetShortDisplayName(DisplayName);
OutCurveModels.Add(MoveTemp(NewCurveModel));
}
}
bool PassesFilter(const FCurveEditorTreeFilter* InFilter) const override
{
if (InFilter->GetType() == ECurveEditorTreeFilterType::Text)
{
FString DisplayNameAsString = DisplayName.ToString();
const FCurveEditorTreeTextFilter* Filter = static_cast<const FCurveEditorTreeTextFilter*>(InFilter);
for (const FCurveEditorTreeTextFilterTerm& Term : Filter->GetTerms())
{
if (!Term.Match(DisplayNameAsString).IsTotalMatch())
{
return false;
}
}
return true;
}
return false;
}
void CacheKeys()
{
if (!RowHandle.HasRichCurves())
{
if (FRealCurve* Curve = RowHandle.GetCurve())
{
for (auto Col : Columns)
{
FKeyHandle KeyHandle = Curve->FindKey(Col->KeyTime);
float KeyValue = Curve->GetKeyValue(KeyHandle);
CellDataMap.Add(Col->ColumnId, CachedKeyInfo(KeyHandle, FText::AsNumber(KeyValue)));
}
}
}
}
void EnterRenameMode()
{
InlineRenameWidget->EnterEditingMode();
}
bool VerifyNameChanged(const FText& InText, FText& OutErrorMessage)
{
FName CheckName = FName(*InText.ToString());
if (CheckName == RowId)
{
return true;
}
if (RowHandle.CurveTable.IsValid())
{
UCurveTable* Table = RowHandle.CurveTable.Get();
const TMap<FName, FRealCurve*>& RowMap = Table->GetRowMap();
if (RowMap.Contains(CheckName))
{
OutErrorMessage = LOCTEXT("NameAlreadyUsed", "Row Names Must Be Unique");
return false;
}
return true;
}
return false;
}
void HandleNameCommitted(const FText& CommittedText, ETextCommit::Type CommitInfo)
{
if (CommitInfo == ETextCommit::OnEnter)
{
TSharedPtr<FCurveTableEditor> TableEditorPtr = CurveTableEditor.Pin();
if (TableEditorPtr != nullptr)
{
FName OldName = RowId;
FName NewName = *CommittedText.ToString();
DisplayName = CommittedText;
InlineRenameWidget->SetText(DisplayName);
RowHandle.RowName = NewName;
RowId = NewName;
TableEditorPtr->HandleCurveRename(TreeID, OldName, NewName);
TSharedPtr<FCurveEditor> CurveEditor = TableEditorPtr->GetCurveEditor();
FCurveEditorTreeItem& TreeItem = CurveEditor->GetTreeItem(TreeID);
for (FCurveModelID ModelID : TreeItem.GetCurves())
{
if (FCurveModel* CurveModel = CurveEditor->FindCurve(ModelID))
{
CurveModel->SetShortDisplayName(DisplayName);
}
}
}
}
}
/** Hold onto a weak ptr to the CurveTableEditor specifically for deleting and renaming */
TWeakPtr<FCurveTableEditor> CurveTableEditor;
/** The CurveEditor's Unique ID for the TreeItem this item is attached to (SetStrongItem) */
FCurveEditorTreeItemID TreeID;
/** Unique ID used to identify this row */
FName RowId;
/** Display name of this row */
FText DisplayName;
/** Array corresponding to each cell in this row */
TMap<FName, CachedKeyInfo> CellDataMap;
/** Handle to the row */
FCurveTableEditorHandle RowHandle;
/** A Reference to the available columns in the TableView */
const TArray<FCurveTableEditorColumnHeaderDataPtr>& Columns;
/** Inline editable text box for renaming */
TSharedPtr<SInlineEditableTextBlock> InlineRenameWidget;
};
void FCurveTableEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_CurveTableEditor", "Curve Table Editor"));
InTabManager->RegisterTabSpawner( CurveTableTabId, FOnSpawnTab::CreateSP(this, &FCurveTableEditor::SpawnTab_CurveTable) )
.SetDisplayName( LOCTEXT("CurveTableTab", "Curve Table") )
.SetGroup( WorkspaceMenuCategory.ToSharedRef() );
}
void FCurveTableEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
InTabManager->UnregisterTabSpawner( CurveTableTabId );
}
FCurveTableEditor::~FCurveTableEditor()
{
FReimportManager::Instance()->OnPostReimport().RemoveAll(this);
if (UCurveTable* CurveTable = GetCurveTable())
{
CurveTable->OnCurveTableChanged().RemoveAll(this);
}
}
void FCurveTableEditor::InitCurveTableEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UCurveTable* Table )
{
const TSharedRef< FTabManager::FLayout > StandaloneDefaultLayout = InitCurveTableLayout();
FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, FCurveTableEditorModule::CurveTableEditorAppIdentifier, StandaloneDefaultLayout, ShouldCreateDefaultStandaloneMenu(), ShouldCreateDefaultToolbar(), Table );
BindCommands();
ExtendMenu();
ExtendToolbar();
RegenerateMenusAndToolbars();
FReimportManager::Instance()->OnPostReimport().AddSP(this, &FCurveTableEditor::OnPostReimport);
if (Table)
{
Table->OnCurveTableChanged().AddSP(this, &FCurveTableEditor::RefreshTableRows);
}
GEditor->RegisterForUndo(this);
}
TSharedRef< FTabManager::FLayout > FCurveTableEditor::InitCurveTableLayout()
{
return FTabManager::NewLayout("Standalone_CurveTableEditor_Layout_v1.1")
->AddArea
(
FTabManager::NewPrimaryArea()
->Split
(
FTabManager::NewStack()
->AddTab(CurveTableTabId, ETabState::OpenedTab)
->SetHideTabWell(true)
)
);
}
void FCurveTableEditor::BindCommands()
{
FCurveTableEditorCommands::Register();
ToolkitCommands->MapAction(FGenericCommands::Get().Undo, FExecuteAction::CreateLambda([]{ GEditor->UndoTransaction(); }));
ToolkitCommands->MapAction(FGenericCommands::Get().Redo, FExecuteAction::CreateLambda([]{ GEditor->RedoTransaction(); }));
ToolkitCommands->MapAction(
FCurveTableEditorCommands::Get().CurveViewToggle,
FExecuteAction::CreateSP(this, &FCurveTableEditor::ToggleViewMode),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FCurveTableEditor::IsCurveViewChecked)
);
ToolkitCommands->MapAction(
FCurveTableEditorCommands::Get().AppendKeyColumn,
FExecuteAction::CreateSP(this, &FCurveTableEditor::OnAddNewKeyColumn)
);
ToolkitCommands->MapAction(
FCurveTableEditorCommands::Get().RenameSelectedCurve,
FExecuteAction::CreateSP(this, &FCurveTableEditor::OnRenameCurve)
);
ToolkitCommands->MapAction(
FCurveTableEditorCommands::Get().DeleteSelectedCurves,
FExecuteAction::CreateSP(this, &FCurveTableEditor::OnDeleteCurves)
);
}
bool FCurveTableEditor::IsReadOnly() const
{
/* Currently, the only read-only tables are composite curve tables */
return GetCurveTable()->IsA<UCompositeCurveTable>();
}
void FCurveTableEditor::ExtendMenu()
{
MenuExtender = MakeShareable(new FExtender);
struct Local
{
static void ExtendMenu(FMenuBuilder& MenuBuilder)
{
MenuBuilder.BeginSection("CurveTableEditor", LOCTEXT("CurveTableEditor", "Curve Table"));
{
MenuBuilder.AddMenuEntry(FCurveTableEditorCommands::Get().CurveViewToggle);
}
MenuBuilder.EndSection();
}
};
MenuExtender->AddMenuExtension(
"WindowLayout",
EExtensionHook::After,
GetToolkitCommands(),
FMenuExtensionDelegate::CreateStatic(&Local::ExtendMenu)
);
AddMenuExtender(MenuExtender);
FCurveTableEditorModule& CurveTableEditorModule = FModuleManager::LoadModuleChecked<FCurveTableEditorModule>("CurveTableEditor");
AddMenuExtender(CurveTableEditorModule.GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
}
void FCurveTableEditor::ExtendToolbar()
{
ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
GetToolkitCommands(),
FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ParentToolbarBuilder)
{
ParentToolbarBuilder.BeginSection("CurveTable");
ParentToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &FCurveTableEditor::Reimport_Execute, GetEditingObject())),
NAME_None,
FText::GetEmpty(),
LOCTEXT("Reimport_Tooltip", "Reimport the Curve Table from the source file. All changes will be lost. This action cannot be undone."),
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "Icons.Toolbar.Reimport")
);
bool HasRichCurves = GetCurveTable()->HasRichCurves();
ParentToolbarBuilder.AddWidget(
SNew(SSegmentedControl<ECurveTableViewMode>)
.Visibility(HasRichCurves ? EVisibility::Collapsed : EVisibility::Visible)
.OnValueChanged_Lambda([this] (ECurveTableViewMode InMode) {if (InMode != GetViewMode()) ToggleViewMode(); } )
.Value(this, &FCurveTableEditor::GetViewMode)
+SSegmentedControl<ECurveTableViewMode>::Slot(ECurveTableViewMode::CurveTable)
.Icon(FAppStyle::Get().GetBrush("CurveTableEditor.CurveView"))
+SSegmentedControl<ECurveTableViewMode>::Slot(ECurveTableViewMode::Grid)
.Icon(FAppStyle::Get().GetBrush("CurveTableEditor.TableView"))
);
if (!IsReadOnly())
{
ParentToolbarBuilder.AddToolBarButton(
FCurveTableEditorCommands::Get().AppendKeyColumn,
NAME_None,
FText::GetEmpty(),
TAttribute<FText>(),
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "Sequencer.KeyTriangle20"));
}
ParentToolbarBuilder.EndSection();
})
);
AddToolbarExtender(ToolbarExtender);
}
FName FCurveTableEditor::GetToolkitFName() const
{
return FName("CurveTableEditor");
}
FText FCurveTableEditor::GetBaseToolkitName() const
{
return LOCTEXT( "AppLabel", "CurveTable Editor" );
}
FString FCurveTableEditor::GetWorldCentricTabPrefix() const
{
return LOCTEXT("WorldCentricTabPrefix", "CurveTable ").ToString();
}
FLinearColor FCurveTableEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor( 0.0f, 0.0f, 0.2f, 0.5f );
}
void FCurveTableEditor::PreChange(const UCurveTable* Changed, FCurveTableEditorUtils::ECurveTableChangeInfo Info)
{
}
void FCurveTableEditor::PostUndo(bool bSuccess)
{
RefreshCachedCurveTable();
}
void FCurveTableEditor::PostRedo(bool bSuccess)
{
RefreshCachedCurveTable();
}
void FCurveTableEditor::PostChange(const UCurveTable* Changed, FCurveTableEditorUtils::ECurveTableChangeInfo Info)
{
const UCurveTable* Table = GetCurveTable();
if (Changed == Table)
{
HandlePostChange();
}
}
UCurveTable* FCurveTableEditor::GetCurveTable() const
{
return Cast<UCurveTable>(GetEditingObject());
}
void FCurveTableEditor::HandlePostChange()
{
RefreshCachedCurveTable();
}
TSharedRef<SDockTab> FCurveTableEditor::SpawnTab_CurveTable( const FSpawnTabArgs& Args )
{
check( Args.GetTabId().TabType == CurveTableTabId );
bUpdatingTableViewSelection = false;
bool bTableIsReadOnly = IsReadOnly();
TSharedRef<SScrollBar> VerticalScrollBar = SNew(SScrollBar)
.Orientation(Orient_Vertical);
ColumnNamesHeaderRow = SNew(SHeaderRow)
.Visibility(this, &FCurveTableEditor::GetTableViewControlsVisibility);
CurveEditor = MakeShared<FCurveEditor>();
FCurveEditorInitParams CurveEditorInitParams;
CurveEditor->InitCurveEditor(CurveEditorInitParams);
// We want this editor to handle undo, not the CurveEditor because
// the PostUndo fixes up the selection and in the case of a CurveTable,
// the curves have been rebuilt on undo and thus need special handling to restore the selection
GEditor->UnregisterForUndo(CurveEditor.Get());
CurveEditorTree = SNew(SCurveEditorTree, CurveEditor.ToSharedRef())
.OnTreeViewScrolled(this, &FCurveTableEditor::OnCurveTreeViewScrolled)
.OnMouseButtonDoubleClick(this, &FCurveTableEditor::OnRequestCurveRename)
.OnContextMenuOpening(this, &FCurveTableEditor::OnOpenCurveMenu);
TSharedRef<SCurveEditorPanel> CurveEditorPanel = SNew(SCurveEditorPanel, CurveEditor.ToSharedRef());
TableView = SNew(SListView<FCurveEditorTreeItemID>)
.IsEnabled(!bTableIsReadOnly)
.ListItemsSource(&EmptyItems)
.OnListViewScrolled(this, &FCurveTableEditor::OnTableViewScrolled)
.HeaderRow(ColumnNamesHeaderRow)
.OnGenerateRow(CurveEditorTree.Get(), &SCurveEditorTree::GenerateRow)
.ExternalScrollbar(VerticalScrollBar)
.SelectionMode(ESelectionMode::Multi)
.OnSelectionChanged_Lambda(
[this](TListTypeTraits<FCurveEditorTreeItemID>::NullableType InItemID, ESelectInfo::Type Type)
{
this->OnTableViewSelectionChanged(InItemID, Type);
}
);
CurveEditor->GetTree()->Events.OnItemsChanged.AddSP(this, &FCurveTableEditor::RefreshTableRows);
CurveEditor->GetTree()->Events.OnSelectionChanged.AddSP(this, &FCurveTableEditor::RefreshTableRowsSelection);
ViewMode = GetCurveTable()->HasRichCurves() ? ECurveTableViewMode::CurveTable : ECurveTableViewMode::Grid;
RefreshCachedCurveTable();
return SNew(SDockTab)
.Label( LOCTEXT("CurveTableTitle", "Curve Table") )
.TabColorScale( GetTabColorScale() )
[
SNew(SBorder)
.Padding(2)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(8, 0))
[
MakeToolbar(CurveEditorPanel)
]
+SVerticalBox::Slot()
[
SNew(SSplitter)
+SSplitter::Slot()
.Value(.2)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.Padding(0, 0, 0, 1) // adjusting padding so as to line up the rows in the cell view
.AutoHeight()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.f, 0.f, 4.f, 0.0)
[
SNew(SPositiveActionButton)
.Icon(FAppStyle::Get().GetBrush("Icons.Plus"))
.Text(LOCTEXT("Curve", "Curve"))
.OnClicked(this, &FCurveTableEditor::OnAddCurveClicked)
.Visibility(bTableIsReadOnly ? EVisibility::Collapsed : EVisibility::Visible)
]
+SHorizontalBox::Slot()
[
SNew(SCurveEditorTreeTextFilter, CurveEditor)
]
]
+SVerticalBox::Slot()
[
CurveEditorTree.ToSharedRef()
]
]
+SSplitter::Slot()
[
SNew(SHorizontalBox)
.Visibility(this, &FCurveTableEditor::GetTableViewControlsVisibility)
+SHorizontalBox::Slot()
[
SNew(SScrollBox)
.Orientation(Orient_Horizontal)
+SScrollBox::Slot()
[
TableView.ToSharedRef()
]
]
+SHorizontalBox::Slot()
.AutoWidth()
[
VerticalScrollBar
]
]
+SSplitter::Slot()
[
SNew(SBox)
.Visibility(this, &FCurveTableEditor::GetCurveViewControlsVisibility)
.IsEnabled(!bTableIsReadOnly)
[
CurveEditorPanel
]
]
]
]
];
}
void FCurveTableEditor::RefreshTableRows()
{
if (TableView && CurveEditor)
{
TableView->RequestListRefresh();
}
}
void FCurveTableEditor::RefreshTableRowsSelection()
{
if(bUpdatingTableViewSelection == false && TableView && CurveEditor)
{
TGuardValue<bool> SelectionGuard(bUpdatingTableViewSelection, true);
TArray<FCurveEditorTreeItemID> CurrentTreeWidgetSelection;
TableView->GetSelectedItems(CurrentTreeWidgetSelection);
const TMap<FCurveEditorTreeItemID, ECurveEditorTreeSelectionState>& CurrentCurveEditorTreeSelection = CurveEditor->GetTreeSelection();
TArray<FCurveEditorTreeItemID> NewTreeWidgetSelection;
for (const TPair<FCurveEditorTreeItemID, ECurveEditorTreeSelectionState>& CurveEditorTreeSelectionEntry : CurrentCurveEditorTreeSelection)
{
if (CurveEditorTreeSelectionEntry.Value != ECurveEditorTreeSelectionState::None)
{
NewTreeWidgetSelection.Add(CurveEditorTreeSelectionEntry.Key);
CurrentTreeWidgetSelection.RemoveSwap(CurveEditorTreeSelectionEntry.Key);
}
}
TableView->SetItemSelection(CurrentTreeWidgetSelection, false, ESelectInfo::Direct);
TableView->SetItemSelection(NewTreeWidgetSelection, true, ESelectInfo::Direct);
}
}
void FCurveTableEditor::OnTableViewSelectionChanged(FCurveEditorTreeItemID ItemID, ESelectInfo::Type)
{
if (bUpdatingTableViewSelection == false && TableView && CurveEditor)
{
TGuardValue<bool> SelectionGuard(bUpdatingTableViewSelection, true);
CurveEditor->GetTree()->SetDirectSelection(TableView->GetSelectedItems(), CurveEditor.Get());
}
}
void FCurveTableEditor::RefreshCachedCurveTable()
{
if (!CurveEditor)
{
return;
}
// This will trigger to remove any cached widgets in the TableView while we rebuild the model from the source CurveTable
const TSet<FCurveModelID>& Pinned = CurveEditor->GetPinnedCurves();
TSet<FName> PinnedCurves;
for (auto PinnedCurveID : Pinned)
{
FCurveEditorTreeItemID TreeID = CurveEditor->GetTreeIDFromCurveID(PinnedCurveID);
if (RowIDMap.Contains(TreeID))
{
PinnedCurves.Add(RowIDMap[TreeID]);
}
}
TSet<FName> SelectedCurves;
const TMap<FCurveEditorTreeItemID, ECurveEditorTreeSelectionState>& Selected = CurveEditor->GetTreeSelection();
for (const TPair<FCurveEditorTreeItemID, ECurveEditorTreeSelectionState>& SelectionEntry: Selected)
{
if (SelectionEntry.Value != ECurveEditorTreeSelectionState::None)
{
if (RowIDMap.Contains(SelectionEntry.Key))
{
SelectedCurves.Add(RowIDMap[SelectionEntry.Key]);
}
}
}
// New Selection
TArray<FCurveEditorTreeItemID> NewSelectedItems;
if (TableView)
{
TableView->SetItemsSource(&EmptyItems);
}
CurveEditor->RemoveAllTreeItems();
ColumnNamesHeaderRow->ClearColumns();
AvailableColumns.Empty();
RowIDMap.Empty();
UCurveTable* Table = GetCurveTable();
if (!Table || Table->GetRowMap().Num() == 0)
{
return;
}
TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
const FTextBlockStyle& CellTextStyle = FAppStyle::GetWidgetStyle<FTextBlockStyle>("DataTableEditor.CellText");
static const float CellPadding = 10.0f;
if (Table->HasRichCurves())
{
InterpMode = RCIM_Cubic;
for (const TPair<FName, FRichCurve*>& CurveRow : Table->GetRichCurveRowMap())
{
// Setup the CurveEdtiorTree
const FName& CurveName = CurveRow.Key;
FCurveEditorTreeItem* TreeItem = CurveEditor->AddTreeItem(FCurveEditorTreeItemID());
TreeItem->SetStrongItem(MakeShared<FCurveTableEditorItem>(SharedThis(this), TreeItem->GetID(), CurveName, FCurveTableEditorHandle(Table, CurveName), AvailableColumns));
RowIDMap.Add(TreeItem->GetID(), CurveName);
if (SelectedCurves.Contains(CurveName))
{
NewSelectedItems.Add(TreeItem->GetID());
}
if (PinnedCurves.Contains(CurveName))
{
for (auto ModelID : TreeItem->GetCurves())
{
CurveEditor->PinCurve(ModelID);
}
}
}
}
else
{
// Find unique column titles and setup columns
TArray<float> UniqueColumns;
for (const TPair<FName, FRealCurve*>& CurveRow : Table->GetRowMap())
{
FRealCurve* Curve = CurveRow.Value;
for (auto CurveIt(Curve->GetKeyHandleIterator()); CurveIt; ++CurveIt)
{
UniqueColumns.AddUnique(Curve->GetKeyTime(*CurveIt));
}
}
UniqueColumns.Sort();
for (const float& ColumnTime : UniqueColumns)
{
const FText ColumnText = FText::AsNumber(ColumnTime);
FCurveTableEditorColumnHeaderDataPtr CachedColumnData = MakeShareable(new FCurveTableEditorColumnHeaderData());
CachedColumnData->ColumnId = *ColumnText.ToString();
CachedColumnData->DisplayName = ColumnText;
CachedColumnData->DesiredColumnWidth = FontMeasure->Measure(CachedColumnData->DisplayName, CellTextStyle.Font).X + CellPadding;
CachedColumnData->KeyTime = ColumnTime;
AvailableColumns.Add(CachedColumnData);
ColumnNamesHeaderRow->AddColumn( GenerateHeaderColumnForKey(CachedColumnData) );
}
// Setup the CurveEditorTree
// Store the default Interpolation Mode
InterpMode = RCIM_None;
for (const TPair<FName, FSimpleCurve*>& CurveRow : Table->GetSimpleCurveRowMap())
{
if (InterpMode == RCIM_None)
{
InterpMode = CurveRow.Value->GetKeyInterpMode();
}
const FName& CurveName = CurveRow.Key;
FCurveEditorTreeItem* TreeItem = CurveEditor->AddTreeItem(FCurveEditorTreeItemID());
TSharedPtr<FCurveTableEditorItem> NewItem = MakeShared<FCurveTableEditorItem>(SharedThis(this), TreeItem->GetID(), CurveName, FCurveTableEditorHandle(Table, CurveName), AvailableColumns);
OnColumnsChanged.AddSP(NewItem.ToSharedRef(), &FCurveTableEditorItem::CacheKeys);
TreeItem->SetStrongItem(NewItem);
RowIDMap.Add(TreeItem->GetID(), CurveName);
if (SelectedCurves.Contains(CurveName))
{
NewSelectedItems.Add(TreeItem->GetID());
}
if (PinnedCurves.Contains(CurveName))
{
for (auto ModelID : TreeItem->GetOrCreateCurves(CurveEditor.Get()))
{
CurveEditor->PinCurve(ModelID);
}
}
}
}
if (TableView)
{
TableView->SetItemsSource(&CurveEditorTree->GetSourceItems());
}
TGuardValue<bool> SelectionGuard(bUpdatingTableViewSelection, true);
CurveEditor->SetTreeSelection(MoveTemp(NewSelectedItems));
}
void FCurveTableEditor::OnCurveTreeViewScrolled(double InScrollOffset)
{
// Synchronize the list views
if (TableView)
{
TableView->SetScrollOffset(InScrollOffset);
}
}
void FCurveTableEditor::OnTableViewScrolled(double InScrollOffset)
{
// Synchronize the list views
CurveEditorTree->SetScrollOffset(InScrollOffset);
}
void FCurveTableEditor::OnPostReimport(UObject* InObject, bool)
{
const UCurveTable* Table = GetCurveTable();
if (Table && Table == InObject)
{
RefreshCachedCurveTable();
}
}
EVisibility FCurveTableEditor::GetTableViewControlsVisibility() const
{
return ViewMode == ECurveTableViewMode::CurveTable ? EVisibility::Collapsed : EVisibility::Visible;
}
EVisibility FCurveTableEditor::GetCurveViewControlsVisibility() const
{
return ViewMode == ECurveTableViewMode::Grid ? EVisibility::Collapsed : EVisibility::Visible;
}
void FCurveTableEditor::ToggleViewMode()
{
ViewMode = (ViewMode == ECurveTableViewMode::CurveTable) ? ECurveTableViewMode::Grid : ECurveTableViewMode::CurveTable;
}
bool FCurveTableEditor::IsCurveViewChecked() const
{
return (ViewMode == ECurveTableViewMode::CurveTable);
}
TSharedRef<SWidget> FCurveTableEditor::MakeToolbar(TSharedRef<SCurveEditorPanel>& InEditorPanel)
{
FSlimHorizontalToolBarBuilder ToolBarBuilder(InEditorPanel->GetCommands(), FMultiBoxCustomization::None, InEditorPanel->GetToolbarExtender(), true);
ToolBarBuilder.BeginSection("Asset");
ToolBarBuilder.EndSection();
// We just use all of the extenders as our toolbar, we don't have a need to create a separate toolbar.
bool bHasRichCurves = GetCurveTable()->HasRichCurves();
bool bTableIsReadOnly = IsReadOnly();
return SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SBox)
.Visibility(this, &FCurveTableEditor::GetCurveViewControlsVisibility)
[
ToolBarBuilder.MakeWidget()
]
];
}
FReply FCurveTableEditor::OnAddCurveClicked()
{
FScopedTransaction Transaction(LOCTEXT("AddCurve", "Add Curve"));
UCurveTable* Table = Cast<UCurveTable>(GetEditingObject());
check(Table != nullptr);
Table->Modify();
if (Table->HasRichCurves())
{
FName NewCurveUnique = MakeUniqueCurveName(Table);
FRichCurve& NewCurve = Table->AddRichCurve(NewCurveUnique);
FCurveEditorTreeItem* TreeItem = CurveEditor->AddTreeItem(FCurveEditorTreeItemID());
TreeItem->SetStrongItem(MakeShared<FCurveTableEditorItem>(SharedThis(this), TreeItem->GetID(), NewCurveUnique, FCurveTableEditorHandle(Table, NewCurveUnique), AvailableColumns));
RowIDMap.Add(TreeItem->GetID(), NewCurveUnique);
}
else
{
FName NewCurveUnique = MakeUniqueCurveName(Table);
FSimpleCurve& RealCurve = Table->AddSimpleCurve(NewCurveUnique);
RealCurve.SetKeyInterpMode(InterpMode);
// Also add a default key for each column
for (auto Column : AvailableColumns)
{
RealCurve.AddKey(Column->KeyTime, 0.0);
}
FCurveEditorTreeItem* TreeItem = CurveEditor->AddTreeItem(FCurveEditorTreeItemID());
TSharedPtr<FCurveTableEditorItem> NewItem = MakeShared<FCurveTableEditorItem>(SharedThis(this), TreeItem->GetID(), NewCurveUnique, FCurveTableEditorHandle(Table, NewCurveUnique), AvailableColumns);
OnColumnsChanged.AddSP(NewItem.ToSharedRef(), &FCurveTableEditorItem::CacheKeys);
TreeItem->SetStrongItem(NewItem);
RowIDMap.Add(TreeItem->GetID(), NewCurveUnique);
}
return FReply::Handled();
}
void FCurveTableEditor::OnAddNewKeyColumn()
{
UCurveTable* Table = Cast<UCurveTable>(GetEditingObject());
check(Table != nullptr);
if (!Table->HasRichCurves())
{
// Compute a new keytime based on the last columns
float NewKeyTime = 1.0;
if (AvailableColumns.Num() > 1)
{
float LastKeyTime = AvailableColumns[AvailableColumns.Num() - 1]->KeyTime;
float PrevKeyTime = AvailableColumns[AvailableColumns.Num() - 2]->KeyTime;
NewKeyTime = 2.*LastKeyTime - PrevKeyTime;
}
else if (AvailableColumns.Num() > 0)
{
float LastKeyTime = AvailableColumns[AvailableColumns.Num() - 1]->KeyTime;
NewKeyTime = LastKeyTime + 1;
}
AddNewKeyColumn(NewKeyTime);
}
}
void FCurveTableEditor::AddNewKeyColumn(float NewKeyTime)
{
UCurveTable* Table = Cast<UCurveTable>(GetEditingObject());
check(Table != nullptr);
if (!Table->HasRichCurves())
{
FScopedTransaction Transaction(LOCTEXT("AddKeyColumn", "AddKeyColumn"));
Table->Modify();
// Make sure we don't already have a key at this time
// 1. Add new keys to every curve
for (const TPair<FName, FRealCurve*>& CurveRow : Table->GetRowMap())
{
FRealCurve* Curve = CurveRow.Value;
Curve->UpdateOrAddKey(NewKeyTime, Curve->Eval(NewKeyTime));
}
// 2. Add Column to our Table
FCurveTableEditorColumnHeaderDataPtr ColumnData = MakeShareable(new FCurveTableEditorColumnHeaderData());
const FText ColumnText = FText::AsNumber(NewKeyTime);
ColumnData->ColumnId = *ColumnText.ToString();
ColumnData->DisplayName = ColumnText;
ColumnData->KeyTime = NewKeyTime;
TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
const FTextBlockStyle& CellTextStyle = FAppStyle::GetWidgetStyle<FTextBlockStyle>("DataTableEditor.CellText");
ColumnData->DesiredColumnWidth = FontMeasure->Measure(ColumnData->DisplayName, CellTextStyle.Font).X + 10.f;
AvailableColumns.Add(ColumnData);
// 3. Let the CurveTreeItems know they need to recache
OnColumnsChanged.Broadcast();
ColumnNamesHeaderRow->AddColumn( GenerateHeaderColumnForKey(ColumnData) );
}
}
void FCurveTableEditor::OnRequestCurveRename(FCurveEditorTreeItemID TreeItemId)
{
const FCurveEditorTreeItem* TreeItem = CurveEditor->FindTreeItem(TreeItemId);
if (TreeItem != nullptr)
{
TSharedPtr<ICurveEditorTreeItem> CurveEditorTreeItem = TreeItem->GetItem();
if (CurveEditorTreeItem.IsValid())
{
TSharedPtr<FCurveTableEditorItem> CurveTableEditorItem = StaticCastSharedPtr<FCurveTableEditorItem>(CurveEditorTreeItem);
CurveTableEditorItem->EnterRenameMode();
}
}
}
void FCurveTableEditor::HandleCurveRename(FCurveEditorTreeItemID& TreeID, FName& CurrentCurve, FName& NewCurveName)
{
// Update the underlying Curve Data Asset itself
UCurveTable* Table = Cast<UCurveTable>(GetEditingObject());
check(Table != nullptr);
FScopedTransaction Transaction(LOCTEXT("RenameCurve", "Rename Curve"));
Table->SetFlags(RF_Transactional);
Table->Modify();
Table->RenameRow(CurrentCurve, NewCurveName);
FPropertyChangedEvent PropertyChangeStruct(nullptr, EPropertyChangeType::ValueSet);
Table->PostEditChangeProperty(PropertyChangeStruct);
// Update our internal map of TreeIDs to FNames
RowIDMap[TreeID] = NewCurveName;
}
void FCurveTableEditor::OnRenameCurve()
{
const TMap<FCurveEditorTreeItemID, ECurveEditorTreeSelectionState>& SelectedRows = CurveEditor->GetTreeSelection();
if (SelectedRows.Num() == 1)
{
for (auto Item : SelectedRows)
{
OnRequestCurveRename(Item.Key);
}
}
}
void FCurveTableEditor::OnDeleteCurves()
{
UCurveTable* Table = Cast<UCurveTable>(GetEditingObject());
check(Table != nullptr);
const TMap<FCurveEditorTreeItemID, ECurveEditorTreeSelectionState>& SelectedRows = CurveEditor->GetTreeSelection();
if (SelectedRows.Num() >= 1)
{
FScopedTransaction Transaction(LOCTEXT("DeleteCurveRow", "Delete Curve Rows"));
Table->SetFlags(RF_Transactional);
Table->Modify();
for (auto Item : SelectedRows)
{
CurveEditor->RemoveTreeItem(Item.Key);
FName& CurveName = RowIDMap[Item.Key];
Table->DeleteRow(CurveName);
RowIDMap.Remove(Item.Key);
}
FPropertyChangedEvent PropertyChangeStruct(nullptr, EPropertyChangeType::ValueSet);
Table->PostEditChangeProperty(PropertyChangeStruct);
}
}
TSharedPtr<SWidget> FCurveTableEditor::OnOpenCurveMenu()
{
int32 SelectedRowCount = CurveEditor->GetTreeSelection().Num();
if (SelectedRowCount > 0 && !IsReadOnly())
{
FMenuBuilder MenuBuilder(true /*auto close*/, ToolkitCommands);
MenuBuilder.BeginSection("Edit");
if (SelectedRowCount == 1)
{
MenuBuilder.AddMenuEntry(
FCurveTableEditorCommands::Get().RenameSelectedCurve,
NAME_None,
TAttribute<FText>(),
TAttribute<FText>(),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Edit")
);
}
MenuBuilder.AddMenuEntry(
FCurveTableEditorCommands::Get().DeleteSelectedCurves,
NAME_None,
TAttribute<FText>(),
TAttribute<FText>(),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Delete")
);
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
return SNullWidget::NullWidget;
}
void FCurveTableEditor::OnDeleteKeyColumn(float KeyTime)
{
UCurveTable* Table = Cast<UCurveTable>(GetEditingObject());
check(Table != nullptr);
if (!Table->HasRichCurves())
{
// First find the column data associated with the original keytime
int FoundIndex = -1;
for (int i = 0; i < AvailableColumns.Num(); i++)
{
if (FMath::IsNearlyEqual(KeyTime, AvailableColumns[i]->KeyTime, KINDA_SMALL_NUMBER))
{
FoundIndex = i;
break;
}
}
if (FoundIndex < 0)
{
return;
}
FCurveTableEditorColumnHeaderDataPtr ColumnData = AvailableColumns[FoundIndex];
if (ColumnData.IsValid())
{
// Remove the column from the ui
AvailableColumns.RemoveAt(FoundIndex);
ColumnNamesHeaderRow->RemoveColumn(ColumnData->ColumnId);
// Remove the keys from all curve rows is the data table
FScopedTransaction Transaction(LOCTEXT("DeleteKeyColumn", "Delete Key Column"));
Table->Modify();
for (const TPair<FName, FRealCurve*>& CurveRow : Table->GetRowMap())
{
FRealCurve* Curve = CurveRow.Value;
FKeyHandle KeyHandle = Curve->FindKey(KeyTime);
if (KeyHandle != FKeyHandle::Invalid())
{
Curve->DeleteKey(KeyHandle);
}
}
FPropertyChangedEvent PropertyChangeStruct(nullptr, EPropertyChangeType::ValueSet);
Table->PostEditChangeProperty(PropertyChangeStruct);
// Let the CurveTreeItems (row ui) know they need to recache
OnColumnsChanged.Broadcast();
}
}
}
bool FCurveTableEditor::VerifyValidRetime(const FText& InText, FText& OutErrorMessage, float OriginalTime)
{
if (!InText.IsNumeric())
{
OutErrorMessage = LOCTEXT("KeysMustBeNumeric", "Key Times must be numeric.");
return false;
}
float NewTime = 0.0f;
LexFromString(NewTime, *InText.ToString());
// do we already have a column with this time?
for (auto Col : AvailableColumns)
{
if (FMath::IsNearlyEqual(NewTime, Col->KeyTime, KINDA_SMALL_NUMBER))
{
OutErrorMessage = LOCTEXT("KeyAlreadyExists", "Key times must be unique!");
return false;
}
}
return true;
}
void FCurveTableEditor::HandleRetimeCommitted(const FText& InText, ETextCommit::Type CommitInfo, float OriginalKeyTime)
{
// First find the column data associated with the original keytime
int FoundIndex = -1;
for (int i = 0; i < AvailableColumns.Num(); i++)
{
if (FMath::IsNearlyEqual(OriginalKeyTime, AvailableColumns[i]->KeyTime, KINDA_SMALL_NUMBER))
{
FoundIndex = i;
break;
}
}
if (FoundIndex < 0)
{
return;
}
FCurveTableEditorColumnHeaderDataPtr CachedColumnData = AvailableColumns[FoundIndex];
if (CachedColumnData.IsValid())
{
// 1. Remove the UI associated with this column (ColumnData and the SHeaderRow::FColumn)
AvailableColumns.RemoveAt(FoundIndex);
ColumnNamesHeaderRow->RemoveColumn(CachedColumnData->ColumnId);
float NewTime = 0.0f;
LexFromString(NewTime, *InText.ToString());
// 2. Adjust the key times for each of the curve table rows
UCurveTable* Table = Cast<UCurveTable>(GetEditingObject());
check(Table != nullptr);
FScopedTransaction Transaction(LOCTEXT("RetimeKeyColumn", "Retime Key Column"));
Table->Modify();
for (const TPair<FName, FRealCurve*>& CurveRow : Table->GetRowMap())
{
FRealCurve* Curve = CurveRow.Value;
FKeyHandle KeyHandle = Curve->FindKey(OriginalKeyTime);
if (KeyHandle != FKeyHandle::Invalid())
{
Curve->SetKeyTime(KeyHandle, NewTime);
}
}
FPropertyChangedEvent PropertyChangeStruct(nullptr, EPropertyChangeType::ValueSet);
Table->PostEditChangeProperty(PropertyChangeStruct);
// 3. Update the ColumnData and re-insert the ColumnData and SHeaderRow::FColumn into the
// correct places in order of the key times
int NewIndex = 0;
while (NewIndex < AvailableColumns.Num() && NewTime > AvailableColumns[NewIndex]->KeyTime )
{
NewIndex++;
}
const FText ColumnText = FText::AsNumber(NewTime);
CachedColumnData->ColumnId = *ColumnText.ToString();
CachedColumnData->DisplayName = ColumnText;
CachedColumnData->KeyTime = NewTime;
AvailableColumns.Insert(CachedColumnData, NewIndex);
// Let the CurveTreeItems know they need to recache
// note we do this before adding the column to the header so the rows already have their
// data in place and are prepared to draw
OnColumnsChanged.Broadcast();
ColumnNamesHeaderRow->InsertColumn( GenerateHeaderColumnForKey(CachedColumnData), NewIndex) ;
}
}
SHeaderRow::FColumn::FArguments FCurveTableEditor::GenerateHeaderColumnForKey(FCurveTableEditorColumnHeaderDataPtr ColumnData)
{
TSharedRef<SInlineEditableTextBlock> KeyTimeWidget = SNew(SInlineEditableTextBlock)
.Text(ColumnData->DisplayName)
.Justification(ETextJustify::Center)
.ColorAndOpacity(FSlateColor::UseForeground())
.OnTextCommitted(this, &FCurveTableEditor::HandleRetimeCommitted, ColumnData->KeyTime)
.OnVerifyTextChanged(this, &FCurveTableEditor::VerifyValidRetime, ColumnData->KeyTime);
// Create the Column Header's R-Click Menu
FMenuBuilder MenuBuilder(true /*Auto close*/, ToolkitCommands);
MenuBuilder.BeginSection("Edit");
MenuBuilder.AddMenuEntry(
FText::Format(LOCTEXT("RetimeKeysColumn", "Retime Keys at {0}"), FText::AsNumber(ColumnData->KeyTime)),
FText::Format(LOCTEXT("RetimeKeysColumn_Tooltip", "Retimes this column and all keys at {0}"), FText::AsNumber(ColumnData->KeyTime)),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Edit"),
FUIAction(FExecuteAction::CreateSP(KeyTimeWidget, &SInlineEditableTextBlock::EnterEditingMode))
);
MenuBuilder.AddMenuEntry(
FText::Format(LOCTEXT("DeleteKeysColumn", "Delete Keys at {0}"), FText::AsNumber(ColumnData->KeyTime)),
FText::Format(LOCTEXT("DeleteKeysColumn_Tooltip", "Deletes this column and all keys at {0}"), FText::AsNumber(ColumnData->KeyTime)),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Delete"),
FUIAction(FExecuteAction::CreateSP(this, &FCurveTableEditor::OnDeleteKeyColumn, ColumnData->KeyTime))
);
MenuBuilder.EndSection();
return SHeaderRow::Column(ColumnData->ColumnId)
.DefaultLabel(ColumnData->DisplayName)
.FixedWidth(ColumnData->DesiredColumnWidth + 40)
.HAlignHeader(HAlign_Fill)
.MenuContent()
[
MenuBuilder.MakeWidget()
]
.HeaderContent()
[
SNew(SBox)
.HeightOverride(22.f)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
KeyTimeWidget
]
];
}
#undef LOCTEXT_NAMESPACE