// Copyright Epic Games, Inc. All Rights Reserved. #include "DialogueWaveWidgets.h" #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/IAssetRegistry.h" #include "AssetThumbnail.h" #include "Containers/UnrealString.h" #include "Delegates/Delegate.h" #include "DetailLayoutBuilder.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "Fonts/SlateFontInfo.h" #include "HAL/PlatformCrt.h" #include "HAL/PlatformMath.h" #include "Input/Events.h" #include "Input/Reply.h" #include "InputCoreTypes.h" #include "Internationalization/Internationalization.h" #include "Layout/Children.h" #include "Layout/Geometry.h" #include "Layout/Margin.h" #include "Math/Color.h" #include "Math/Vector2D.h" #include "Modules/ModuleManager.h" #include "PropertyHandle.h" #include "SAssetDropTarget.h" #include "Selection.h" #include "SlateOptMacros.h" #include "SlotBase.h" #include "Sound/DialogueVoice.h" #include "Styling/AppStyle.h" #include "Styling/SlateColor.h" #include "Templates/Casts.h" #include "Types/SlateStructs.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/UObjectGlobals.h" #include "UObject/WeakObjectPtr.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SSpacer.h" #include "Widgets/Layout/SWrapBox.h" #include "Widgets/Notifications/SErrorHint.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SNullWidget.h" #include "Widgets/SWidget.h" #include "Widgets/Text/STextBlock.h" class FDragDropEvent; class UClass; #define LOCTEXT_NAMESPACE "DialogueWaveDetails" BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SDialogueVoicePropertyEditor::Construct( const FArguments& InArgs, const TSharedRef& InPropertyHandle, const TSharedRef& InAssetThumbnailPool ) { DialogueVoicePropertyHandle = InPropertyHandle; AssetThumbnailPool = InAssetThumbnailPool; IsEditable = InArgs._IsEditable; OnShouldFilterAsset = InArgs._OnShouldFilterAsset; if( DialogueVoicePropertyHandle->IsValidHandle() ) { const UDialogueVoice* DialogueVoice = NULL; if( DialogueVoicePropertyHandle->IsValidHandle() ) { UObject* Object = NULL; DialogueVoicePropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } const uint32 ThumbnailSizeX = 64; const uint32 ThumbnailSizeY = 64; AssetThumbnail = MakeShareable( new FAssetThumbnail( DialogueVoice, ThumbnailSizeX, ThumbnailSizeY, AssetThumbnailPool ) ); TSharedRef AssetWidget = SNew( SAssetDropTarget ) .ToolTipText( this, &SDialogueVoicePropertyEditor::OnGetToolTip ) .OnAreAssetsAcceptableForDrop( this, &SDialogueVoicePropertyEditor::OnIsAssetAcceptableForDrop ) .OnAssetsDropped( this, &SDialogueVoicePropertyEditor::OnAssetDropped ) [ SNew( SBox ) .WidthOverride( static_cast(ThumbnailSizeX) ) .HeightOverride( static_cast(ThumbnailSizeY) ) .HAlign(HAlign_Fill) .VAlign(VAlign_Fill) .Padding(0.0f) [ AssetThumbnail->MakeThumbnailWidget() ] ]; if(IsEditable) { const TSharedRef UseButton = PropertyCustomizationHelpers::MakeUseSelectedButton( FSimpleDelegate::CreateSP( this, &SDialogueVoicePropertyEditor::OnUseSelectedDialogueVoice ) ); UseButton->SetEnabled( TAttribute::Create( TAttribute::FGetter::CreateSP( this, &SDialogueVoicePropertyEditor::CanUseSelectedAsset) ) ); const TSharedRef BrowseButton = PropertyCustomizationHelpers::MakeBrowseButton( FSimpleDelegate::CreateSP( this, &SDialogueVoicePropertyEditor::OnBrowseToDialogueVoice ) ); BrowseButton->SetEnabled( TAttribute::Create( TAttribute::FGetter::CreateSP( this, &SDialogueVoicePropertyEditor::CanBrowseToAsset) ) ); TSharedRef ButtonsColumnWidget = SNew( SVerticalBox ) + SVerticalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Center) .Padding(1.0f) .AutoHeight() [ UseButton ] + SVerticalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Center) .Padding(1.0f) .AutoHeight() [ BrowseButton ]; struct Local { static FVector2D GetDesiredSize(TSharedRef Widget) { return Widget->GetDesiredSize(); } }; TSharedRef HorizontalBox = SNew( SHorizontalBox ); if(InArgs._ShouldCenterThumbnail) { HorizontalBox->AddSlot() .HAlign(HAlign_Right) .VAlign(VAlign_Fill) .FillWidth(1.0f) [ SNew( SSpacer ) .Size_Static( &Local::GetDesiredSize, ButtonsColumnWidget ) ]; } // Thumbnail HorizontalBox->AddSlot() .HAlign(HAlign_Center) .VAlign(VAlign_Center) .AutoWidth() [ SAssignNew( ComboButton, SComboButton ) .ToolTipText( this, &SDialogueVoicePropertyEditor::OnGetToolTip ) .ButtonStyle( FAppStyle::Get(), "PropertyEditor.AssetComboStyle" ) .ForegroundColor(FAppStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) .OnGetMenuContent( this, &SDialogueVoicePropertyEditor::OnGetMenuContent ) .ContentPadding(2.0f) .ButtonContent() [ AssetWidget ] ]; // Path Property Buttons if(InArgs._ShouldCenterThumbnail) { HorizontalBox->AddSlot() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .FillWidth(1.0f) [ // Redundant Horizontal Box Slot exists to AutoWidth the contents here - avoids squishing nested button images. SNew( SHorizontalBox ) +SHorizontalBox::Slot() .Padding(0.0f) // Don't influence positioning - we're only here to correct sizing. .AutoWidth() [ ButtonsColumnWidget ] ]; } else { HorizontalBox->AddSlot() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .AutoWidth() [ ButtonsColumnWidget ]; } ChildSlot .VAlign(VAlign_Fill) [ HorizontalBox ]; } else { ChildSlot .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ AssetWidget ]; } } } void SDialogueVoicePropertyEditor::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { UDialogueVoice* CurrentDialogueVoice = NULL; if( DialogueVoicePropertyHandle->IsValidHandle() ) { UObject* Object = NULL; DialogueVoicePropertyHandle->GetValue(Object); CurrentDialogueVoice = Cast(Object); } if( AssetThumbnail->GetAsset() != CurrentDialogueVoice ) { AssetThumbnail->SetAsset( CurrentDialogueVoice ); } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION FText SDialogueVoicePropertyEditor::OnGetToolTip() const { FText ToolTipText; const FAssetData& AssetData = AssetThumbnail->GetAssetData(); if( AssetData.IsValid() ) { ToolTipText = FText::FromName(AssetData.PackageName); } return ToolTipText; } TSharedRef SDialogueVoicePropertyEditor::OnGetMenuContent() { TArray AllowedClasses; AllowedClasses.Add(UDialogueVoice::StaticClass()); UDialogueVoice* DialogueVoice = NULL; { UObject* Object = NULL; DialogueVoicePropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( DialogueVoice, false, AllowedClasses, PropertyCustomizationHelpers::GetNewAssetFactoriesForClasses(AllowedClasses), OnShouldFilterAsset, FOnAssetSelected::CreateSP(this, &SDialogueVoicePropertyEditor::OnAssetSelectedFromPicker), FSimpleDelegate::CreateSP(this, &SDialogueVoicePropertyEditor::CloseMenu)); } void SDialogueVoicePropertyEditor::CloseMenu() { ComboButton->SetIsOpen(false); } bool SDialogueVoicePropertyEditor::OnIsAssetAcceptableForDrop( TArrayView InAssets ) const { // Only dialogue voice can be dropped return Cast( InAssets[0].GetAsset() ) != nullptr; } void SDialogueVoicePropertyEditor::OnAssetDropped( const FDragDropEvent&, TArrayView InAssets ) { ReplaceDialogueVoice( CastChecked(InAssets[0].GetAsset()) ); } FText SDialogueVoicePropertyEditor::GetDialogueVoiceDescription() const { UDialogueVoice* DialogueVoice = NULL; { UObject* Object = NULL; DialogueVoicePropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } return DialogueVoice ? FText::FromString(DialogueVoice->GetDesc()) : LOCTEXT("None", "None"); } bool SDialogueVoicePropertyEditor::CanUseSelectedAsset() { bool Result = false; // Load selected assets FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); // Get the first dialogue voice selected USelection* DialogueVoiceSelection = GEditor->GetSelectedObjects(); if (DialogueVoiceSelection && DialogueVoiceSelection->Num() == 1) { UDialogueVoice* DialogueVoiceToAssign = DialogueVoiceSelection->GetTop(); if( DialogueVoiceToAssign && ( !OnShouldFilterAsset.IsBound() || !OnShouldFilterAsset.Execute(DialogueVoiceToAssign) ) ) { Result = true; } } return Result; } void SDialogueVoicePropertyEditor::OnUseSelectedDialogueVoice() { // Load selected assets FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); // Get the first dialogue voice selected USelection* DialogueVoiceSelection = GEditor->GetSelectedObjects(); if (DialogueVoiceSelection && DialogueVoiceSelection->Num() == 1) { UDialogueVoice* DialogueVoiceToAssign = DialogueVoiceSelection->GetTop(); if( DialogueVoiceToAssign ) { ReplaceDialogueVoice( DialogueVoiceToAssign ); } } } void SDialogueVoicePropertyEditor::ReplaceDialogueVoice( const UDialogueVoice* const NewDialogueVoice ) { if( !OnShouldFilterAsset.IsBound() || !OnShouldFilterAsset.Execute(NewDialogueVoice) ) { const UDialogueVoice* PrevDialogueVoice = NULL; const UDialogueVoice* DialogueVoice = NULL; { UObject* Object = NULL; DialogueVoicePropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } if( DialogueVoice ) { PrevDialogueVoice = DialogueVoice; } if( NewDialogueVoice != PrevDialogueVoice ) { // Replace the dialogue voice DialogueVoicePropertyHandle->SetValue( NewDialogueVoice ); } } } bool SDialogueVoicePropertyEditor::CanBrowseToAsset() { const UDialogueVoice* DialogueVoice = NULL; { UObject* Object = NULL; DialogueVoicePropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } return DialogueVoice != NULL; } void SDialogueVoicePropertyEditor::OnBrowseToDialogueVoice() { UDialogueVoice* DialogueVoice = NULL; { UObject* Object = NULL; DialogueVoicePropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } if( DialogueVoice ) { // Find the item in the content browser GoToAssetInContentBrowser(MakeWeakObjectPtr(DialogueVoice)); } } void SDialogueVoicePropertyEditor::OnAssetSelectedFromPicker( const FAssetData& InAssetData ) { UDialogueVoice* NewDialogueVoice = Cast(InAssetData.GetAsset()); if( NewDialogueVoice ) { ReplaceDialogueVoice( NewDialogueVoice ); } } /** * Called to get the dialogue voice path that should be displayed */ FText SDialogueVoicePropertyEditor::GetDialogueVoicePath() const { const UDialogueVoice* EdittingDialogueVoice = NULL; { UObject* Object = NULL; DialogueVoicePropertyHandle->GetValue(Object); EdittingDialogueVoice = Cast(Object); } if( EdittingDialogueVoice ) { return FText::FromString( EdittingDialogueVoice->GetOuter()->GetPathName() ); } else { return FText::GetEmpty(); } } /** * Called when the dialogue voice path is changed by a user */ void SDialogueVoicePropertyEditor::OnDialogueVoicePathChanged( const FText& NewText, ETextCommit::Type TextCommitType ) { const FString& NewString = NewText.ToString(); if( !NewText.EqualTo( GetDialogueVoicePath() ) && NewString.Len() < NAME_SIZE ) { const UDialogueVoice* PrevDialogueVoice = NULL; const UDialogueVoice* DialogueVoice = NULL; { UObject* Object = NULL; DialogueVoicePropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } if( DialogueVoice ) { PrevDialogueVoice = DialogueVoice; } const UDialogueVoice* DialogueVoiceToAssign = NULL; if( !NewString.IsEmpty() ) { if( NewString.Contains( TEXT(".") ) ) { // Formatted text string, use the exact path instead of any package DialogueVoiceToAssign = FindObject(nullptr, *NewString); } else { DialogueVoiceToAssign = FindFirstObject(*NewString, EFindFirstObjectOptions::NativeFirst | EFindFirstObjectOptions::EnsureIfAmbiguous); } if( !DialogueVoiceToAssign ) { DialogueVoiceToAssign = Cast( StaticLoadObject( UDialogueVoice::StaticClass(), NULL, *NewString ) ); } if( !DialogueVoiceToAssign ) { // If we still don't have the dialogue voice attempt to find it via the asset registry FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); // Collect a full list of assets with the specified class TArray AssetData; AssetRegistryModule.Get().GetAssetsByPackageName( FName(*NewString), AssetData ); if( AssetData.Num() > 0 ) { // There should really only be one dialogue voice found DialogueVoiceToAssign = Cast( AssetData[0].GetAsset() ); } } } if( NewString.IsEmpty() || DialogueVoiceToAssign ) { ReplaceDialogueVoice( DialogueVoiceToAssign ); } } } void SDialogueVoicePropertyEditor::GoToAssetInContentBrowser( TWeakObjectPtr Object ) { if( Object.IsValid() ) { TArray< UObject* > Objects; Objects.Add( Object.Get() ); GEditor->SyncBrowserToObjects( Objects ); } } void STargetsSummaryWidget::Construct( const FArguments& InArgs, const TSharedRef& InPropertyHandle, const TSharedRef& InAssetThumbnailPool ) { TargetsPropertyHandle = InPropertyHandle; AssetThumbnailPool = InAssetThumbnailPool; IsEditable = InArgs._IsEditable; WrapWidth = InArgs._WrapWidth; AllottedWidth = 0.0f; GenerateContent(); } float STargetsSummaryWidget::GetPreferredWidthForWrapping() const { return WrapWidth.IsBound() ? WrapWidth.Get() : AllottedWidth; } void STargetsSummaryWidget::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { AllottedWidth = AllottedGeometry.Size.X; if( TargetsPropertyHandle->IsValidHandle() ) { uint32 TargetCount; TargetsPropertyHandle->GetNumChildren(TargetCount); if( TargetCount != DisplayedTargets.Num() ) { // The array sizes differ so we need to refresh the list GenerateContent(); } } } FText STargetsSummaryWidget::GetDialogueVoiceDescription() const { FText Result = LOCTEXT( "NoTargets", "No One" ); if( TargetsPropertyHandle->IsValidHandle() ) { uint32 TargetCount; TargetsPropertyHandle->GetNumChildren(TargetCount); if( TargetCount > 1 ) { Result = LOCTEXT("Multiple", "Multiple"); } else if( TargetCount == 1 ) { const TSharedPtr SingleTargetPropertyHandle = TargetsPropertyHandle->GetChildHandle(0); UDialogueVoice* DialogueVoice = NULL; if( SingleTargetPropertyHandle->IsValidHandle() ) { UObject* Object = NULL; SingleTargetPropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } Result = DialogueVoice ? FText::FromString(DialogueVoice->GetDesc()) : LOCTEXT("None", "None"); } } return Result; } bool STargetsSummaryWidget::FilterTargets( const struct FAssetData& InAssetData ) { bool ShouldAssetBeFilteredOut = false; if( TargetsPropertyHandle->IsValidHandle() ) { uint32 TargetCount; TargetsPropertyHandle->GetNumChildren(TargetCount); // Show tiles only. for(uint32 i = 0; i < TargetCount; ++i) { const TSharedPtr TargetPropertyHandle = TargetsPropertyHandle->GetChildHandle(i); const UDialogueVoice* DialogueVoice = NULL; if( TargetPropertyHandle->IsValidHandle() ) { UObject* Object = NULL; TargetPropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } if( DialogueVoice == InAssetData.GetAsset() ) { ShouldAssetBeFilteredOut = true; break; } } } return ShouldAssetBeFilteredOut; } class SRemovableDialogueVoicePropertyEditor : public SDialogueVoicePropertyEditor { public: SRemovableDialogueVoicePropertyEditor() : bIsPressed(false) { } protected: virtual FReply OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; virtual FReply OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) override; virtual FReply OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; private: void DoRemove(); private: bool bIsPressed; }; FReply SRemovableDialogueVoicePropertyEditor::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { FReply Reply = FReply::Unhandled(); if ( MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton ) { bIsPressed = true; //we need to capture the mouse for MouseUp events Reply = FReply::Handled().CaptureMouse(AsShared()).SetUserFocus(AsShared(), EFocusCause::Mouse); } //return the constructed reply return Reply; } FReply SRemovableDialogueVoicePropertyEditor::OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) { return OnMouseButtonDown( InMyGeometry, InMouseEvent ); } FReply SRemovableDialogueVoicePropertyEditor::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { FReply Reply = FReply::Unhandled(); if ( MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton ) { bIsPressed = false; const bool bIsUnderMouse = MyGeometry.IsUnderLocation(MouseEvent.GetScreenSpacePosition()); if( bIsUnderMouse ) { // If we were asked to allow the button to be clicked on mouse up, regardless of whether the user // pressed the button down first, then we'll allow the click to proceed without an active capture if( HasMouseCapture() ) { DoRemove(); Reply = FReply::Handled(); } } //If the user of the button didn't handle this click, then the button's //default behavior handles it. if(Reply.IsEventHandled() == false) { Reply = FReply::Handled(); } //If the user hasn't requested a new mouse captor, then the default //behavior of the button is to release mouse capture. if(Reply.GetMouseCaptor().IsValid() == false) { Reply.ReleaseMouseCapture(); } } return Reply; } void SRemovableDialogueVoicePropertyEditor::DoRemove() { if( IsEditable ) { const TSharedPtr ParentPropertyHandle = DialogueVoicePropertyHandle->GetParentHandle(); const TSharedPtr ParentPropertyArrayHandle = ParentPropertyHandle->AsArray(); ParentPropertyArrayHandle->DeleteItem( DialogueVoicePropertyHandle->GetIndexInArray() ); } } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void STargetsSummaryWidget::GenerateContent() { if( TargetsPropertyHandle->IsValidHandle() ) { DisplayedTargets.Empty(); uint32 TargetCount; TargetsPropertyHandle->GetNumChildren(TargetCount); FSlateFontInfo Font = IDetailLayoutBuilder::GetDetailFont(); if( TargetCount > 1 ) { const TSharedRef WrapBox = SNew( SWrapBox ) .PreferredSize(this, &STargetsSummaryWidget::GetPreferredWidthForWrapping); // Show tiles only. for(uint32 i = 0; i < TargetCount; ++i) { const TSharedPtr TargetPropertyHandle = TargetsPropertyHandle->GetChildHandle(i); const UDialogueVoice* DialogueVoice = NULL; if( TargetPropertyHandle->IsValidHandle() ) { UObject* Object = NULL; TargetPropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } DisplayedTargets.Add(DialogueVoice); WrapBox->AddSlot() .Padding(2.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew( SRemovableDialogueVoicePropertyEditor, TargetPropertyHandle.ToSharedRef(), AssetThumbnailPool.ToSharedRef() ) .IsEditable( IsEditable ) .OnShouldFilterAsset( this, &STargetsSummaryWidget::FilterTargets) ]; } ChildSlot .HAlign(HAlign_Center) [ WrapBox ]; } else if( TargetCount == 1 ) { const TSharedPtr SingleTargetPropertyHandle = TargetsPropertyHandle->GetChildHandle(0); const UDialogueVoice* DialogueVoice = NULL; if( SingleTargetPropertyHandle->IsValidHandle() ) { UObject* Object = NULL; SingleTargetPropertyHandle->GetValue(Object); DialogueVoice = Cast(Object); } DisplayedTargets.Add(DialogueVoice); TSharedRef TargetPropertyEditor = SNew( SRemovableDialogueVoicePropertyEditor, SingleTargetPropertyHandle.ToSharedRef(), AssetThumbnailPool.ToSharedRef() ) .IsEditable( IsEditable ) .OnShouldFilterAsset( this, &STargetsSummaryWidget::FilterTargets) .ShouldCenterThumbnail(true); ChildSlot .HAlign(HAlign_Center) [ SNew( SBox ) .Padding(2.0f) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ TargetPropertyEditor ] ]; } else { const float ThumbnailSizeX = 64.0f; const float ThumbnailSizeY = 64.0f; ChildSlot .HAlign(HAlign_Center) [ SNew( SBox ) .Padding(2.0f) .WidthOverride( ThumbnailSizeX ) .HeightOverride( ThumbnailSizeY ) .HAlign(HAlign_Center) .VAlign(VAlign_Center) ]; } } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SDialogueContextHeaderWidget::Construct( const FArguments& InArgs, const TSharedRef& InPropertyHandle, const TSharedRef& InAssetThumbnailPool ) { ContextPropertyHandle = InPropertyHandle; if( ContextPropertyHandle->IsValidHandle() ) { FSlateFontInfo Font = IDetailLayoutBuilder::GetDetailFont(); const TSharedPtr SpeakerPropertyHandle = ContextPropertyHandle->GetChildHandle("Speaker"); const TSharedPtr TargetsPropertyHandle = ContextPropertyHandle->GetChildHandle("Targets"); TSharedRef AddButton = PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateSP( this, &SDialogueContextHeaderWidget::AddTargetButton_OnClick ) ); TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton( FSimpleDelegate::CreateSP( this, &SDialogueContextHeaderWidget::RemoveTargetButton_OnClick ) ); TSharedRef EmptyButton = PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateSP( this, &SDialogueContextHeaderWidget::EmptyTargetsButton_OnClick ) ); TSharedRef SpeakerPropertyEditor = SNew( SDialogueVoicePropertyEditor, SpeakerPropertyHandle.ToSharedRef(), InAssetThumbnailPool ) .IsEditable(true) .ShouldCenterThumbnail(true); TSharedRef TargetsSummaryWidget = SNew( STargetsSummaryWidget, TargetsPropertyHandle.ToSharedRef(), InAssetThumbnailPool ); ChildSlot [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew( SVerticalBox ) +SVerticalBox::Slot() .AutoHeight() [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .FillWidth(1.0f) [ SAssignNew( SpeakerErrorHint, SErrorHint ) ] +SHorizontalBox::Slot() .AutoWidth() [ SpeakerPropertyHandle->CreatePropertyNameWidget() ] +SHorizontalBox::Slot() .FillWidth(1.0f) [ SNullWidget::NullWidget ] ] +SVerticalBox::Slot() .HAlign(HAlign_Center) .VAlign(VAlign_Center) .FillHeight(1.0f) [ SNew( SVerticalBox ) +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Fill) .AutoHeight() [ SpeakerPropertyEditor ] +SVerticalBox::Slot() .Padding( 2.0f ) .HAlign(HAlign_Center) .AutoHeight() [ // Voice Description SNew( STextBlock ) .Font( Font ) .Text( SpeakerPropertyEditor, &SDialogueVoicePropertyEditor::GetDialogueVoiceDescription ) ] ] ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew( SImage ) .Image( FAppStyle::GetBrush("DialogueWaveDetails.SpeakerToTarget") ) .ColorAndOpacity( FSlateColor::UseForeground() ) ] +SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew( SVerticalBox ) +SVerticalBox::Slot() .AutoHeight() [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .FillWidth(1.0f) [ SAssignNew( TargetsErrorHint, SErrorHint ) ] +SHorizontalBox::Slot() .AutoWidth() [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .AutoWidth() [ TargetsPropertyHandle->CreatePropertyNameWidget( LOCTEXT("DirectedAt", "Directed At") ) ] +SHorizontalBox::Slot() .AutoWidth() [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .Padding(1.0f) .VAlign(VAlign_Center) .AutoWidth() [ AddButton ] +SHorizontalBox::Slot() .Padding(1.0f) .VAlign(VAlign_Center) .AutoWidth() [ RemoveButton ] +SHorizontalBox::Slot() .Padding(1.0f) .VAlign(VAlign_Center) .AutoWidth() [ EmptyButton ] ] ] +SHorizontalBox::Slot() .FillWidth(1.0f) [ SNullWidget::NullWidget ] ] +SVerticalBox::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Center) .FillHeight(1.0f) [ SNew( SVerticalBox ) +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Fill) .AutoHeight() [ TargetsSummaryWidget ] +SVerticalBox::Slot() .Padding( 2.0f ) .HAlign(HAlign_Center) .AutoHeight() [ // Voice Description SNew( STextBlock ) .Font( Font ) .Text( TargetsSummaryWidget, &STargetsSummaryWidget::GetDialogueVoiceDescription ) ] ] ] ]; } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION void SDialogueContextHeaderWidget::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { if( !IsSpeakerValid() ) { if( SpeakerErrorHint.IsValid() ) { SpeakerErrorHint->SetError( LOCTEXT("NullSpeakerError", "Speaker can not be \"None\".") ); } } else { if( SpeakerErrorHint.IsValid() ) { SpeakerErrorHint->SetError( FText::GetEmpty() ); } } if( !IsTargetSetValid() ) { if( TargetsErrorHint.IsValid() ) { TargetsErrorHint->SetError( LOCTEXT("NullTargetError", "Target set can not contain \"None\".") ); } } else { if( TargetsErrorHint.IsValid() ) { TargetsErrorHint->SetError( FText::GetEmpty() ); } } } bool SDialogueContextHeaderWidget::IsSpeakerValid() const { bool Result = false; if( ContextPropertyHandle.IsValid() && ContextPropertyHandle->IsValidHandle() ) { const TSharedPtr SpeakerPropertyHandle = ContextPropertyHandle->GetChildHandle("Speaker"); const UDialogueVoice* Speaker = nullptr; if( SpeakerPropertyHandle.IsValid() && SpeakerPropertyHandle->IsValidHandle() ) { UObject* Object = nullptr; SpeakerPropertyHandle->GetValue(Object); Speaker = Cast(Object); } Result = ( Speaker != nullptr ); } return Result; } bool SDialogueContextHeaderWidget::IsTargetSetValid() const { bool Result = false; if( ContextPropertyHandle.IsValid() && ContextPropertyHandle->IsValidHandle() ) { Result = true; const TSharedPtr TargetsPropertyHandle = ContextPropertyHandle->GetChildHandle("Targets"); const TSharedPtr TargetsArrayPropertyHandle = TargetsPropertyHandle->AsArray(); uint32 TargetCount; TargetsArrayPropertyHandle->GetNumElements(TargetCount); for(uint32 i = 0; i < TargetCount; ++i) { TSharedPtr TargetPropertyHandle = TargetsArrayPropertyHandle->GetElement(i); const UDialogueVoice* Target = nullptr; if( TargetPropertyHandle.IsValid() && TargetPropertyHandle->IsValidHandle() ) { UObject* Object = nullptr; TargetPropertyHandle->GetValue(Object); Target = Cast(Object); } if ( Target == nullptr ) { Result = false; break; } } } return Result; } void SDialogueContextHeaderWidget::AddTargetButton_OnClick() { if( ContextPropertyHandle->IsValidHandle() ) { const TSharedPtr TargetsPropertyHandle = ContextPropertyHandle->GetChildHandle("Targets"); const TSharedPtr TargetsArrayPropertyHandle = TargetsPropertyHandle->AsArray(); TargetsArrayPropertyHandle->AddItem(); } } void SDialogueContextHeaderWidget::RemoveTargetButton_OnClick() { if( ContextPropertyHandle->IsValidHandle() ) { const TSharedPtr TargetsPropertyHandle = ContextPropertyHandle->GetChildHandle("Targets"); const TSharedPtr TargetsArrayPropertyHandle = TargetsPropertyHandle->AsArray(); uint32 TargetCount; TargetsArrayPropertyHandle->GetNumElements(TargetCount); if( TargetCount > 0 ) { TargetsArrayPropertyHandle->DeleteItem( TargetCount - 1 ); } } } void SDialogueContextHeaderWidget::EmptyTargetsButton_OnClick() { if( ContextPropertyHandle->IsValidHandle() ) { const TSharedPtr TargetsPropertyHandle = ContextPropertyHandle->GetChildHandle("Targets"); const TSharedPtr TargetsArrayPropertyHandle = TargetsPropertyHandle->AsArray(); TargetsArrayPropertyHandle->EmptyArray(); } } #undef LOCTEXT_NAMESPACE