EM_Task/UnrealEd/Public/SSkeletonWidget.h
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

699 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Input/Reply.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SWidget.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/SWindow.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Views/STableViewBase.h"
#include "Widgets/Views/STableRow.h"
#include "Widgets/Views/SListView.h"
#include "EditorStyleSet.h"
#include "AssetData.h"
#include "Widgets/Layout/SUniformGridPanel.h"
#include "PreviewScene.h"
#include "EditorViewportClient.h"
#include "SEditorViewport.h"
#include "EditorAnimUtils.h"
class UAnimSet;
class USkeletalMesh;
using namespace EditorAnimUtils;
/**
* This below code is to select skeleton from the list
*/
#define LOCTEXT_NAMESPACE "SkeletonWidget"
struct FBoneTrackPair
{
FName Bone1;
FName Bone2;
FBoneTrackPair(FName InBone1, FName InBone2)
: Bone1(InBone1), Bone2(InBone2)
{
}
};
class SBonePairRow: public SMultiColumnTableRow<TSharedPtr<FBoneTrackPair>>
{
public:
SLATE_BEGIN_ARGS(SBonePairRow) {}
SLATE_ARGUMENT(TSharedPtr<FBoneTrackPair>, BonePair)
SLATE_END_ARGS()
/**
* Construct child widgets that comprise this widget.
*
* @param InArgs Declaration from which to construct this widget
*/
void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
this->BonePair = InArgs._BonePair;
SMultiColumnTableRow<TSharedPtr<FBoneTrackPair>>::Construct(FSuperRowType::FArguments(), InOwnerTableView);
}
virtual TSharedRef<SWidget> GenerateWidgetForColumn(const FName& ColumnName) override
{
TSharedPtr<SBorder> Border1, Border2;
if (ColumnName == TEXT("Curretly Selected"))
{
return SAssignNew(Border1, SBorder).Padding(2).Content()[SNew(STextBlock).Text(FText::FromName(BonePair->Bone1))];
}
else
{
if (BonePair->Bone2 == NAME_None)
{
return SAssignNew(Border2, SBorder).Padding(2).ColorAndOpacity(FLinearColor(1.f, 0.f, 0.f)).Content()[SNew(STextBlock).Text(LOCTEXT("MissingBone", "Missing"))];
}
else
{
return SAssignNew(Border2, SBorder).Padding(2).Content()[SNew(STextBlock).Text(FText::FromName(BonePair->Bone2))];
}
}
}
private:
TSharedPtr<FBoneTrackPair> BonePair;
};
class SSkeletonWidget: public SCompoundWidget
{
public:
USkeleton* GetSelectedSkeleton() const
{
return CurSelectedSkeleton;
}
protected:
USkeleton* CurSelectedSkeleton;
};
/** 1 columns - just show bone list **/
class SSkeletonListWidget: public SSkeletonWidget
{
public:
SLATE_BEGIN_ARGS(SSkeletonListWidget) {}
SLATE_END_ARGS()
public:
// WIDGETS
void Construct(const FArguments& InArgs);
void SkeletonSelectionChanged(const FAssetData& AssetData);
TSharedRef<ITableRow> GenerateSkeletonRow(USkeleton* InSkeleton, const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew(STableRow<USkeleton*>, OwnerTable)
.Content()
[SNew(STextBlock)
.Text(FText::FromString(InSkeleton->GetFullName()))];
}
TSharedRef<ITableRow> GenerateSkeletonBoneRow(TSharedPtr<FName> InBoneName, const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew(STableRow<TSharedPtr<FName>>, OwnerTable)
.Content()
[SNew(STextBlock)
.Text(FText::FromName(*InBoneName))];
}
private:
SVerticalBox::FSlot* BoneListSlot;
TArray<TSharedPtr<FName>> BoneList;
};
/** 2 columns - bone pair widget **/
class SSkeletonCompareWidget: public SSkeletonWidget
{
public:
SLATE_BEGIN_ARGS(SSkeletonCompareWidget)
: _Object(NULL)
{}
SLATE_ARGUMENT(UObject*, Object)
SLATE_ARGUMENT(TArray<FName>*, BoneNames)
SLATE_END_ARGS()
public:
// WIDGETS
void Construct(const FArguments& InArgs);
void SkeletonSelectionChanged(const FAssetData& AssetData);
TSharedRef<ITableRow> GenerateBonePairRow(TSharedPtr<FBoneTrackPair> InBonePair, const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew(SBonePairRow, OwnerTable)
.BonePair(InBonePair);
}
private:
TArray<FName> BoneNames;
SVerticalBox::FSlot* BonePairSlot;
TArray<TSharedPtr<FBoneTrackPair>> BonePairList;
};
class SSkeletonSelectorWindow: public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SSkeletonSelectorWindow)
: _Object(NULL)
{}
SLATE_ARGUMENT(UObject*, Object)
SLATE_ARGUMENT(TSharedPtr<SWindow>, WidgetWindow)
SLATE_END_ARGS()
public:
UNREALED_API void Construct(const FArguments& InArgs);
void ConstructWindowFromAnimSet(UAnimSet* InAnimSet);
void ConstructWindowFromMesh(USkeletalMesh* InSkeletalMesh);
void ConstructWindow();
void ConstructButtons(TSharedRef<SVerticalBox> ParentBox)
{
ParentBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
[SNew(SUniformGridPanel)
.SlotPadding(FEditorStyle::GetMargin("StandardDialog.SlotPadding"))
.MinDesiredSlotWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth"))
.MinDesiredSlotHeight(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) +
SUniformGridPanel::Slot(0, 0)
[SNew(SButton)
.Text(LOCTEXT("Accept", "Accept"))
.HAlign(HAlign_Center)
.ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding"))
.OnClicked_Raw(this, &SSkeletonSelectorWindow::OnAccept)] +
SUniformGridPanel::Slot(1, 0)
[SNew(SButton)
.Text(LOCTEXT("Cancel", "Cancel"))
.HAlign(HAlign_Center)
.ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding"))
.OnClicked_Raw(this, &SSkeletonSelectorWindow::OnCancel)]];
}
FReply OnAccept()
{
SelectedSkeleton = SkeletonWidget->GetSelectedSkeleton();
if (SelectedSkeleton != NULL && WidgetWindow.IsValid())
{
WidgetWindow.Pin()->RequestDestroyWindow();
}
return FReply::Handled();
}
FReply OnCancel()
{
if (WidgetWindow.IsValid())
{
WidgetWindow.Pin()->RequestDestroyWindow();
}
return FReply::Handled();
}
USkeleton* GetSelectedSkeleton()
{
return SelectedSkeleton;
}
private:
TSharedPtr<SSkeletonWidget> SkeletonWidget;
TWeakPtr<SWindow> WidgetWindow;
USkeleton* SelectedSkeleton;
};
//////////////////////////
class SBasePoseViewport: public /*SCompoundWidget*/ SEditorViewport
{
public:
SLATE_BEGIN_ARGS(SBasePoseViewport)
{}
SLATE_ARGUMENT(USkeleton*, Skeleton)
SLATE_END_ARGS()
public:
SBasePoseViewport();
void Construct(const FArguments& InArgs);
void SetSkeleton(USkeleton* Skeleton);
protected:
/** SEditorViewport interface */
virtual TSharedRef<FEditorViewportClient> MakeEditorViewportClient() override;
virtual TSharedPtr<SWidget> MakeViewportToolbar() override;
private:
/** Skeleton */
USkeleton* TargetSkeleton;
FPreviewScene PreviewScene;
class UDebugSkelMeshComponent* PreviewComponent;
bool IsVisible() const override;
};
/////////////////////////////////////////////
/**
* Slate panel for choose Skeleton for assets to relink
*/
DECLARE_DELEGATE_SixParams(FOnRetargetAnimation, USkeleton* /*OldSkeleton*/, USkeleton* /*NewSkeleton*/, bool /*bRemapReferencedAssets*/, bool /*bAllowRemapToExisting*/, bool /*bConvertSpaces*/, const FNameDuplicationRule* /*NameRule*/)
class SAnimationRemapSkeleton: public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SAnimationRemapSkeleton)
: _CurrentSkeleton(NULL), _WidgetWindow(NULL), _WarningMessage(), _ShowRemapOption(false), _ShowExistingRemapOption(false)
{}
/** The anim sequences to compress */
SLATE_ARGUMENT(USkeleton*, CurrentSkeleton)
SLATE_ARGUMENT(TSharedPtr<SWindow>, WidgetWindow)
SLATE_ARGUMENT(FText, WarningMessage)
SLATE_ARGUMENT(bool, ShowRemapOption)
SLATE_ARGUMENT(bool, ShowExistingRemapOption)
SLATE_ARGUMENT(bool, ShowConvertSpacesOption)
SLATE_ARGUMENT(bool, ShowCompatibleDisplayOption)
SLATE_ARGUMENT(bool, ShowDuplicateAssetOption)
SLATE_EVENT(FOnRetargetAnimation, OnRetargetDelegate)
SLATE_END_ARGS()
/**
* Constructs this widget
*
* @param InArgs The declaration data for this widget
*/
void Construct(const FArguments& InArgs);
/**
* Old Skeleton that was mapped
* This data is needed to prevent users from selecting same skeleton
*/
USkeleton* OldSkeleton;
/**
* New Skeleton that they would like to map to
*/
USkeleton* NewSkeleton;
private:
/**
* Whether we are remapping assets that are referenced by the assets the user selects to remap
*/
bool bRemapReferencedAssets;
/**
* Whether we allow remapping to existing assets for the new skeleton
*/
bool bAllowRemappingToExistingAssets;
/**
* Whether we are remapping assets that are referenced by the assets the user selects to remap
*/
bool bConvertSpaces;
/**
* Whether to show skeletons with the same rig set up
*/
bool bShowOnlyCompatibleSkeletons;
TSharedPtr<SBasePoseViewport> SourceViewport;
TSharedPtr<SBasePoseViewport> TargetViewport;
TSharedPtr<SBox> AssetPickerBox;
TWeakPtr<SWindow> WidgetWindow;
FOnRetargetAnimation OnRetargetAnimationDelegate;
/** Handlers for check box for remapping assets option */
ECheckBoxState IsRemappingReferencedAssets() const;
void OnRemappingReferencedAssetsChanged(ECheckBoxState InNewRadioState);
/** Handlers for check box for remapping to existing assets */
ECheckBoxState IsRemappingToExistingAssetsChecked() const;
bool IsRemappingToExistingAssetsEnabled() const;
void OnRemappingToExistingAssetsChanged(ECheckBoxState InNewRadioState);
/** Handlers for check box for converting spaces*/
ECheckBoxState IsConvertSpacesChecked() const;
void OnConvertSpacesCheckChanged(ECheckBoxState InNewRadioState);
/** Handlers for check box for converting spaces*/
ECheckBoxState IsShowOnlyCompatibleSkeletonsChecked() const;
bool IsShowOnlyCompatibleSkeletonsEnabled() const;
void OnShowOnlyCompatibleSkeletonsCheckChanged(ECheckBoxState InNewRadioState);
/** should filter asset */
bool OnShouldFilterAsset(const struct FAssetData& AssetData);
/**
* return true if it can apply
*/
bool CanApply() const;
FReply OnApply();
FReply OnCancel();
void CloseWindow();
/** Handler for dialog window close button */
void OnRemapDialogClosed(const TSharedRef<SWindow>& Window);
/**
* Handler for when asset is selected
*/
void OnAssetSelectedFromPicker(const FAssetData& AssetData);
/*
* Refreshes asset picker - call when asset picker option changes
*/
void UpdateAssetPicker();
/*
* Duplicate Name Rule
*/
FNameDuplicationRule NameDuplicateRule;
FText ExampleText;
FText GetPrefixName() const
{
return FText::FromString(NameDuplicateRule.Prefix);
}
void SetPrefixName(const FText& InText)
{
NameDuplicateRule.Prefix = InText.ToString();
UpdateExampleText();
}
FText GetSuffixName() const
{
return FText::FromString(NameDuplicateRule.Suffix);
}
void SetSuffixName(const FText& InText)
{
NameDuplicateRule.Suffix = InText.ToString();
UpdateExampleText();
}
FText GetReplaceFrom() const
{
return FText::FromString(NameDuplicateRule.ReplaceFrom);
}
void SetReplaceFrom(const FText& InText)
{
NameDuplicateRule.ReplaceFrom = InText.ToString();
UpdateExampleText();
}
FText GetReplaceTo() const
{
return FText::FromString(NameDuplicateRule.ReplaceTo);
}
void SetReplaceTo(const FText& InText)
{
NameDuplicateRule.ReplaceTo = InText.ToString();
UpdateExampleText();
}
FText GetExampleText() const
{
return ExampleText;
}
void UpdateExampleText();
FReply ShowFolderOption();
FText GetFolderPath() const
{
return FText::FromString(NameDuplicateRule.FolderPath);
}
public:
/**
* Show window
*
* @param OldSkeleton Old Skeleton to change from
*
* @return true if successfully selected new skeleton
*/
static UNREALED_API void ShowWindow(USkeleton* OldSkeleton, const FText& WarningMessage, bool bDuplicateAssets, FOnRetargetAnimation RetargetDelegate);
static TSharedPtr<SWindow> DialogWindow;
bool bShowDuplicateAssetOption;
};
//////////////////////////////////////////////////////////////////////////
class FDisplayedAssetEntryInfo
{
public:
USkeleton* NewSkeleton;
UObject* AnimAsset;
UObject* RemapAsset;
static TSharedRef<FDisplayedAssetEntryInfo> Make(UObject* InAsset, USkeleton* InNewSkeleton);
protected:
FDisplayedAssetEntryInfo() {};
FDisplayedAssetEntryInfo(UObject* InAsset, USkeleton* InNewSkeleton);
};
class SAssetEntryRow: public SMultiColumnTableRow<TSharedPtr<FDisplayedAssetEntryInfo>>
{
public:
SLATE_BEGIN_ARGS(SAssetEntryRow)
{}
SLATE_ARGUMENT(TSharedPtr<FDisplayedAssetEntryInfo>, DisplayedInfo)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView);
virtual TSharedRef<SWidget> GenerateWidgetForColumn(const FName& ColumnName) override;
protected:
TSharedRef<SWidget> GetRemapMenuContent();
FText GetRemapMenuButtonText() const;
void OnAssetSelected(const FAssetData& AssetData);
bool OnShouldFilterAsset(const FAssetData& AssetData) const;
TSharedPtr<FDisplayedAssetEntryInfo> DisplayedInfo;
TWeakObjectPtr<UObject> RemapAsset;
FString SkeletonExportName;
};
typedef SListView<TSharedPtr<FDisplayedAssetEntryInfo>> SRemapAssetEntryList;
class SAnimationRemapAssets: public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SAnimationRemapAssets)
: _NewSkeleton(nullptr), _RetargetContext(nullptr)
{}
SLATE_ARGUMENT(USkeleton*, NewSkeleton)
SLATE_ARGUMENT(FAnimationRetargetContext*, RetargetContext)
SLATE_ARGUMENT(TSharedPtr<SWindow>, WidgetWindow)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
static UNREALED_API void ShowWindow(FAnimationRetargetContext& RetargetContext, USkeleton* RetargetToSkeleton);
static TSharedPtr<SWindow> DialogWindow;
private:
TSharedRef<ITableRow> OnGenerateMontageReferenceRow(TSharedPtr<FDisplayedAssetEntryInfo> Item, const TSharedRef<STableViewBase>& OwnerTable);
void OnDialogClosed(const TSharedRef<SWindow>& Window);
/** Button Handlers */
FReply OnOkClicked();
FReply OnBestGuessClicked();
/** Best guess functions to try and match asset names */
const FAssetData* FindBestGuessMatch(const FAssetData& AssetName, const TArray<FAssetData>& PossibleAssets) const;
/** The retargetting context we're managing*/
FAnimationRetargetContext* RetargetContext;
/** List of entries to the remap list*/
TArray<TSharedPtr<FDisplayedAssetEntryInfo>> AssetListInfo;
/** Skeleton we're about to retarget to*/
USkeleton* NewSkeleton;
/** The list viewing AssetListInfo*/
TSharedPtr<SRemapAssetEntryList> ListWidget;
};
////////////////////////////////////////////////////
/**
* FDlgRemapSkeleton
*
* Wrapper class for SAnimationRemapSkeleton. This class creates and launches a dialog then awaits the
* result to return to the user.
*/
class UNREALED_API FDlgRemapSkeleton
{
public:
FDlgRemapSkeleton(USkeleton* Skeleton);
/** Shows the dialog box and waits for the user to respond. */
bool ShowModal();
/** New Skeleton that is chosen **/
USkeleton* NewSkeleton;
/** true if you'd like to retarget skeletal meshes as well **/
bool RetargetSkeletalMesh;
private:
/** Cached pointer to the modal window */
TSharedPtr<SWindow> DialogWindow;
/** Cached pointer to the LOD Chain widget */
TSharedPtr<class SAnimationRemapSkeleton> DialogWidget;
};
class SRemapFailures: public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SRemapFailures) {}
SLATE_ARGUMENT(TArray<FText>, FailedRemaps)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
static UNREALED_API void OpenRemapFailuresDialog(const TArray<FText>& InFailedRemaps);
private:
TSharedRef<ITableRow> MakeListViewWidget(TSharedRef<FText> Item, const TSharedRef<STableViewBase>& OwnerTable);
FReply CloseClicked();
private:
TArray<TSharedRef<FText>> FailedRemaps;
};
/////////////////////////////////////////////
/**
* Slate panel for choose displaying bones to remove
*/
class SSkeletonBoneRemoval: public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SSkeletonBoneRemoval) {}
/** The bones to remove (for list display) */
SLATE_ARGUMENT(TArray<FName>, BonesToRemove)
/** The window this panel has been placed in */
SLATE_ARGUMENT(TSharedPtr<SWindow>, WidgetWindow)
/** Message to display to the user */
SLATE_ARGUMENT(FText, WarningMessage)
SLATE_END_ARGS()
/**
* Constructs this widget
*
* @param InArgs The declaration data for this widget
*/
void Construct(const FArguments& InArgs);
/** Reference to our window */
TWeakPtr<SWindow> WidgetWindow;
/** Button Handlers */
FReply OnOk();
FReply OnCancel();
/** Handle closing to dialog window */
void CloseWindow();
/** Create an individual row for the bone name list */
TSharedRef<ITableRow> GenerateSkeletonBoneRow(TSharedPtr<FName> InBoneName, const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew(STableRow<TSharedPtr<FName>>, OwnerTable)
.Content()
[SNew(STextBlock)
.Text(FText::FromName(*InBoneName))];
}
/**
* Show Modal window
*
* @param BonesToRemove List of bones that will be removed
* @param WarningMessage Message to display to the user so they know what is going on
*
* @return true if successfully selected new skeleton
*/
static UNREALED_API bool ShowModal(const TArray<FName> BonesToRemove, const FText& WarningMessage);
/** Did the user choose to continue */
bool bShouldContinue;
/** List of bone names that will be removed */
TArray<TSharedPtr<FName>> BoneNames;
};
class SSelectFolderDlg: public SWindow
{
public:
SLATE_BEGIN_ARGS(SSelectFolderDlg)
{
}
SLATE_ARGUMENT(FText, DefaultAssetPath)
SLATE_END_ARGS()
SSelectFolderDlg()
: UserResponse(EAppReturnType::Cancel)
{
}
void Construct(const FArguments& InArgs);
public:
/** Displays the dialog in a blocking fashion */
EAppReturnType::Type ShowModal();
/** Gets the resulting asset path */
FString GetAssetPath();
protected:
void OnPathChange(const FString& NewPath);
FReply OnButtonClick(EAppReturnType::Type ButtonID);
EAppReturnType::Type UserResponse;
FText AssetPath;
};
#undef LOCTEXT_NAMESPACE