// 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> { public: SLATE_BEGIN_ARGS(SBonePairRow) {} SLATE_ARGUMENT(TSharedPtr, 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& InOwnerTableView) { this->BonePair = InArgs._BonePair; SMultiColumnTableRow>::Construct(FSuperRowType::FArguments(), InOwnerTableView); } virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override { TSharedPtr 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 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 GenerateSkeletonRow(USkeleton* InSkeleton, const TSharedRef& OwnerTable) { return SNew(STableRow, OwnerTable) .Content() [SNew(STextBlock) .Text(FText::FromString(InSkeleton->GetFullName()))]; } TSharedRef GenerateSkeletonBoneRow(TSharedPtr InBoneName, const TSharedRef& OwnerTable) { return SNew(STableRow>, OwnerTable) .Content() [SNew(STextBlock) .Text(FText::FromName(*InBoneName))]; } private: SVerticalBox::FSlot* BoneListSlot; TArray> BoneList; }; /** 2 columns - bone pair widget **/ class SSkeletonCompareWidget: public SSkeletonWidget { public: SLATE_BEGIN_ARGS(SSkeletonCompareWidget) : _Object(NULL) {} SLATE_ARGUMENT(UObject*, Object) SLATE_ARGUMENT(TArray*, BoneNames) SLATE_END_ARGS() public: // WIDGETS void Construct(const FArguments& InArgs); void SkeletonSelectionChanged(const FAssetData& AssetData); TSharedRef GenerateBonePairRow(TSharedPtr InBonePair, const TSharedRef& OwnerTable) { return SNew(SBonePairRow, OwnerTable) .BonePair(InBonePair); } private: TArray BoneNames; SVerticalBox::FSlot* BonePairSlot; TArray> BonePairList; }; class SSkeletonSelectorWindow: public SCompoundWidget { public: SLATE_BEGIN_ARGS(SSkeletonSelectorWindow) : _Object(NULL) {} SLATE_ARGUMENT(UObject*, Object) SLATE_ARGUMENT(TSharedPtr, 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 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 SkeletonWidget; TWeakPtr 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 MakeEditorViewportClient() override; virtual TSharedPtr 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, 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 SourceViewport; TSharedPtr TargetViewport; TSharedPtr AssetPickerBox; TWeakPtr 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& 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 DialogWindow; bool bShowDuplicateAssetOption; }; ////////////////////////////////////////////////////////////////////////// class FDisplayedAssetEntryInfo { public: USkeleton* NewSkeleton; UObject* AnimAsset; UObject* RemapAsset; static TSharedRef Make(UObject* InAsset, USkeleton* InNewSkeleton); protected: FDisplayedAssetEntryInfo() {}; FDisplayedAssetEntryInfo(UObject* InAsset, USkeleton* InNewSkeleton); }; class SAssetEntryRow: public SMultiColumnTableRow> { public: SLATE_BEGIN_ARGS(SAssetEntryRow) {} SLATE_ARGUMENT(TSharedPtr, DisplayedInfo) SLATE_END_ARGS() void Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView); virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override; protected: TSharedRef GetRemapMenuContent(); FText GetRemapMenuButtonText() const; void OnAssetSelected(const FAssetData& AssetData); bool OnShouldFilterAsset(const FAssetData& AssetData) const; TSharedPtr DisplayedInfo; TWeakObjectPtr RemapAsset; FString SkeletonExportName; }; typedef SListView> 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, WidgetWindow) SLATE_END_ARGS() void Construct(const FArguments& InArgs); static UNREALED_API void ShowWindow(FAnimationRetargetContext& RetargetContext, USkeleton* RetargetToSkeleton); static TSharedPtr DialogWindow; private: TSharedRef OnGenerateMontageReferenceRow(TSharedPtr Item, const TSharedRef& OwnerTable); void OnDialogClosed(const TSharedRef& Window); /** Button Handlers */ FReply OnOkClicked(); FReply OnBestGuessClicked(); /** Best guess functions to try and match asset names */ const FAssetData* FindBestGuessMatch(const FAssetData& AssetName, const TArray& PossibleAssets) const; /** The retargetting context we're managing*/ FAnimationRetargetContext* RetargetContext; /** List of entries to the remap list*/ TArray> AssetListInfo; /** Skeleton we're about to retarget to*/ USkeleton* NewSkeleton; /** The list viewing AssetListInfo*/ TSharedPtr 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 DialogWindow; /** Cached pointer to the LOD Chain widget */ TSharedPtr DialogWidget; }; class SRemapFailures: public SCompoundWidget { public: SLATE_BEGIN_ARGS(SRemapFailures) {} SLATE_ARGUMENT(TArray, FailedRemaps) SLATE_END_ARGS() void Construct(const FArguments& InArgs); static UNREALED_API void OpenRemapFailuresDialog(const TArray& InFailedRemaps); private: TSharedRef MakeListViewWidget(TSharedRef Item, const TSharedRef& OwnerTable); FReply CloseClicked(); private: TArray> 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, BonesToRemove) /** The window this panel has been placed in */ SLATE_ARGUMENT(TSharedPtr, 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 WidgetWindow; /** Button Handlers */ FReply OnOk(); FReply OnCancel(); /** Handle closing to dialog window */ void CloseWindow(); /** Create an individual row for the bone name list */ TSharedRef GenerateSkeletonBoneRow(TSharedPtr InBoneName, const TSharedRef& OwnerTable) { return SNew(STableRow>, 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 BonesToRemove, const FText& WarningMessage); /** Did the user choose to continue */ bool bShouldContinue; /** List of bone names that will be removed */ TArray> 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