// Copyright Epic Games, Inc. All Rights Reserved. #include "DialogueStructsCustomizations.h" #include "AssetThumbnail.h" #include "Containers/Array.h" #include "Containers/BitArray.h" #include "Containers/Set.h" #include "Containers/SparseArray.h" #include "CoreTypes.h" #include "Delegates/Delegate.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "DialogueWaveWidgets.h" #include "Fonts/SlateFontInfo.h" #include "HAL/PlatformCrt.h" #include "IDetailChildrenBuilder.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Layout/Children.h" #include "Layout/Geometry.h" #include "Layout/Margin.h" #include "Layout/Visibility.h" #include "Math/Color.h" #include "Math/Vector2D.h" #include "Misc/Attribute.h" #include "Misc/Optional.h" #include "PropertyHandle.h" #include "Serialization/Archive.h" #include "SlateOptMacros.h" #include "SlotBase.h" #include "Sound/DialogueTypes.h" #include "Sound/DialogueVoice.h" #include "Sound/DialogueWave.h" #include "Styling/AppStyle.h" #include "Styling/SlateColor.h" #include "Templates/Casts.h" #include "Templates/TypeHash.h" #include "Templates/UnrealTemplate.h" #include "Types/SlateEnums.h" #include "Types/SlateStructs.h" #include "UObject/Object.h" #include "UObject/ObjectPtr.h" #include "UObject/UnrealType.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SComboBox.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SWrapBox.h" #include "Widgets/Notifications/SErrorHint.h" #include "Widgets/Notifications/SErrorText.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/SNullWidget.h" #include "Widgets/SOverlay.h" #include "Widgets/Text/STextBlock.h" class SWidget; #define LOCTEXT_NAMESPACE "DialogueWaveDetails" TSharedRef FDialogueContextStructCustomization::MakeInstance() { return MakeShareable( new FDialogueContextStructCustomization ); } void FDialogueContextStructCustomization::CustomizeHeader( TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) { if( StructPropertyHandle->IsValidHandle() ) { HeaderRow [ SNew( SBorder ) .BorderImage( FAppStyle::GetBrush("DialogueWaveDetails.HeaderBorder") ) [ SNew( SVerticalBox ) +SVerticalBox::Slot() .HAlign(HAlign_Center) .AutoHeight() [ StructPropertyHandle->CreatePropertyNameWidget() ] +SVerticalBox::Slot() .AutoHeight() [ SNew( SDialogueContextHeaderWidget, StructPropertyHandle, StructCustomizationUtils.GetThumbnailPool().ToSharedRef() ) ] ] ]; } } void FDialogueContextStructCustomization::CustomizeChildren( TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) { if( StructPropertyHandle->IsValidHandle() ) { const TSharedPtr SpeakerPropertyHandle = StructPropertyHandle->GetChildHandle("Speaker"); ChildBuilder.AddProperty(SpeakerPropertyHandle.ToSharedRef()); const TSharedPtr TargetsPropertyHandle = StructPropertyHandle->GetChildHandle("Targets"); ChildBuilder.AddProperty(TargetsPropertyHandle.ToSharedRef()); } } class SSpeakerDropDown : public SCompoundWidget { public: SLATE_BEGIN_ARGS( SSpeakerDropDown ) {} SLATE_END_ARGS() void Construct( const FArguments& InArgs, const TSharedRef& InPropertyHandle, const TSharedRef& InAssetThumbnailPool ); virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) override; private: void OnSelectionChanged(TSharedPtr Speaker, ESelectInfo::Type SelectInfo); TSharedRef MakeComboButtonItemWidget(TSharedPtr Speaker); private: TSharedPtr DialogueWaveParameterPropertyHandle; TSharedPtr AssetThumbnailPool; TSharedPtr< SComboBox< TSharedPtr > > ComboBox; TArray< TSharedPtr > OptionsSource; }; void SSpeakerDropDown::Construct( const FArguments& InArgs, const TSharedRef& InPropertyHandle, const TSharedRef& InAssetThumbnailPool ) { DialogueWaveParameterPropertyHandle = InPropertyHandle; AssetThumbnailPool = InAssetThumbnailPool; const TSharedPtr ContextPropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("Context"); const TSharedPtr SpeakerPropertyHandle = ContextPropertyHandle->GetChildHandle("Speaker"); TSharedRef SpeakerPropertyEditor = SNew( SDialogueVoicePropertyEditor, SpeakerPropertyHandle.ToSharedRef(), InAssetThumbnailPool ) .IsEditable(false) .ShouldCenterThumbnail(true); FSlateFontInfo Font = IDetailLayoutBuilder::GetDetailFont(); ChildSlot [ SNew( SVerticalBox ) +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Center) .AutoHeight() [ SAssignNew( ComboBox, SComboBox< TSharedPtr > ) .ButtonStyle( FAppStyle::Get(), "PropertyEditor.AssetComboStyle" ) .ForegroundColor(FAppStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) .OptionsSource( &OptionsSource ) .OnGenerateWidget( this, &SSpeakerDropDown::MakeComboButtonItemWidget ) .OnSelectionChanged( this, &SSpeakerDropDown::OnSelectionChanged ) [ SpeakerPropertyEditor ] ] +SVerticalBox::Slot() .Padding( 2.0f ) .HAlign(HAlign_Center) .AutoHeight() [ // Voice Description SNew( STextBlock ) .Font( Font ) .Text( SpeakerPropertyEditor, &SDialogueVoicePropertyEditor::GetDialogueVoiceDescription ) ] ]; } void SSpeakerDropDown::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { // Get dialogue wave. const TSharedPtr DialogueWavePropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("DialogueWave"); const UDialogueWave* DialogueWave = NULL; if( DialogueWavePropertyHandle->IsValidHandle() ) { UObject* Object = NULL; DialogueWavePropertyHandle->GetValue(Object); DialogueWave = Cast(Object); } // Get context. const TSharedPtr ContextPropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("Context"); // Get speaker. const TSharedPtr SpeakerPropertyHandle = ContextPropertyHandle->GetChildHandle("Speaker"); const UDialogueVoice* Speaker = NULL; { UObject* Object = NULL; SpeakerPropertyHandle->GetValue(Object); Speaker = Cast(Object); } // Gather unique speaker options. TArray UniqueSpeakers; if(DialogueWave) { for(int32 i = 0; i < DialogueWave->ContextMappings.Num(); ++i) { bool bIsValidSpeaker = ( DialogueWave->ContextMappings[i].Context.Speaker != NULL ); bool bIsValidTargetSet = true; for(int32 j = 0; j < DialogueWave->ContextMappings[i].Context.Targets.Num(); ++j) { bIsValidTargetSet = ( DialogueWave->ContextMappings[i].Context.Targets[j] != NULL ); if( !bIsValidTargetSet ) { break; } } if(bIsValidSpeaker && bIsValidTargetSet) { UniqueSpeakers.AddUnique( DialogueWave->ContextMappings[i].Context.Speaker ); } } } // Check if a refresh is needed. bool bNeedsRefresh = false; if( OptionsSource.Num() == UniqueSpeakers.Num() ) { for(int32 i = 0; i < UniqueSpeakers.Num(); ++i) { if( *(OptionsSource[i]) != UniqueSpeakers[i] ) { bNeedsRefresh = true; break; } } } else { bNeedsRefresh = true; } // Refresh if needed. if(bNeedsRefresh) { OptionsSource.Empty(); if(DialogueWave) { for(int32 i = 0; i < UniqueSpeakers.Num(); ++i) { OptionsSource.Add( MakeShareable( new UDialogueVoice*(UniqueSpeakers[i]) ) ); } } ComboBox->ClearSelection(); ComboBox->RefreshOptions(); } } struct FSpeakerMatcher { const UDialogueVoice* Speaker; bool operator()(const FDialogueContextMapping& Mapping) const { return Mapping.Context.Speaker == Speaker; } }; void SSpeakerDropDown::OnSelectionChanged(TSharedPtr Speaker, ESelectInfo::Type SelectInfo) { const TSharedPtr ContextPropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("Context"); const TSharedPtr SpeakerPropertyHandle = ContextPropertyHandle->GetChildHandle("Speaker"); UDialogueVoice** SpeakerToChange = NULL; { TArray RawData; SpeakerPropertyHandle->AccessRawData(RawData); SpeakerToChange = reinterpret_cast(RawData[0]); } if( SpeakerToChange && Speaker.IsValid() ) { SpeakerPropertyHandle->NotifyPreChange(); *SpeakerToChange = *Speaker; SpeakerPropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); } } TSharedRef SSpeakerDropDown::MakeComboButtonItemWidget(TSharedPtr Speaker) { const uint32 ThumbnailSizeX = 64; const uint32 ThumbnailSizeY = 64; const TSharedPtr AssetThumbnail = MakeShareable( new FAssetThumbnail( *Speaker, ThumbnailSizeX, ThumbnailSizeY, AssetThumbnailPool ) ); return SNew( SVerticalBox ) +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Center) .AutoHeight() [ SNew( SBox ) .Padding(2.0f) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SBox ) .WidthOverride( static_cast(ThumbnailSizeX) ) .HeightOverride( static_cast(ThumbnailSizeY) ) [ AssetThumbnail->MakeThumbnailWidget() ] ] ] +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Center) .AutoHeight() [ // Voice Description SNew( STextBlock ) .Text( ( Speaker.IsValid() && *Speaker ) ? FText::FromString((*Speaker)->GetDesc()) : LOCTEXT("None", "None") ) ]; } class STargetSetDropDown : public SCompoundWidget { public: typedef TArray FTargetSet; SLATE_BEGIN_ARGS( STargetSetDropDown ) {} SLATE_END_ARGS() void Construct( const FArguments& InArgs, const TSharedRef& InPropertyHandle, const TSharedRef& InAssetThumbnailPool ); virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) override; private: void OnSelectionChanged(TSharedPtr TargetSet, ESelectInfo::Type SelectInfo); float GetPreferredWidthForWrapping() const; TSharedRef MakeComboButtonItemWidget(TSharedPtr TargetSet); private: TSharedPtr DialogueWaveParameterPropertyHandle; TSharedPtr AssetThumbnailPool; TSharedPtr< SComboBox< TSharedPtr > > ComboBox; TArray< TSharedPtr > OptionsSource; float AllottedWidth; }; void STargetSetDropDown::Construct( const FArguments& InArgs, const TSharedRef& InPropertyHandle, const TSharedRef& InAssetThumbnailPool ) { DialogueWaveParameterPropertyHandle = InPropertyHandle; AssetThumbnailPool = InAssetThumbnailPool; AllottedWidth = 0.0f; const TSharedPtr ContextPropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("Context") ; const TSharedPtr TargetsPropertyHandle = ContextPropertyHandle->GetChildHandle("Targets") ; TSharedRef TargetsSummaryWidget = SNew( STargetsSummaryWidget, TargetsPropertyHandle.ToSharedRef(), AssetThumbnailPool.ToSharedRef() ) .IsEditable(false) .WrapWidth( this, &STargetSetDropDown::GetPreferredWidthForWrapping); FSlateFontInfo Font = IDetailLayoutBuilder::GetDetailFont(); ChildSlot .HAlign(HAlign_Center) [ SNew( SVerticalBox ) +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .AutoHeight() [ SAssignNew( ComboBox, SComboBox< TSharedPtr > ) .ButtonStyle( FAppStyle::Get(), "PropertyEditor.AssetComboStyle" ) .ForegroundColor(FAppStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) .OptionsSource( &OptionsSource ) .OnGenerateWidget( this, &STargetSetDropDown::MakeComboButtonItemWidget ) .OnSelectionChanged( this, &STargetSetDropDown::OnSelectionChanged ) [ TargetsSummaryWidget ] ] +SVerticalBox::Slot() .Padding( 2.0f ) .HAlign(HAlign_Center) .AutoHeight() [ // Voice Description SNew( STextBlock ) .Font( Font ) .Text( TargetsSummaryWidget, &STargetsSummaryWidget::GetDialogueVoiceDescription ) ] ]; } void STargetSetDropDown::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { AllottedWidth = AllottedGeometry.Size.X; // Get dialogue wave. const TSharedPtr DialogueWavePropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("DialogueWave"); const UDialogueWave* DialogueWave = NULL; if( DialogueWavePropertyHandle->IsValidHandle() ) { UObject* Object = NULL; DialogueWavePropertyHandle->GetValue(Object); DialogueWave = Cast(Object); } // Get context. const TSharedPtr ContextPropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("Context"); // Get speaker. const TSharedPtr SpeakerPropertyHandle = ContextPropertyHandle->GetChildHandle("Speaker"); const UDialogueVoice* Speaker = NULL; { UObject* Object = NULL; SpeakerPropertyHandle->GetValue(Object); Speaker = Cast(Object); } // Get target set. const TSharedPtr TargetsPropertyHandle = ContextPropertyHandle->GetChildHandle("Targets"); FTargetSet* TargetSet = NULL; { TArray RawData; TargetsPropertyHandle->AccessRawData(RawData); TargetSet = reinterpret_cast(RawData[0]); } // Gather unique target set options. TArray UniqueTargetSets; if( DialogueWave && Speaker ) { for(int32 i = 0; i < DialogueWave->ContextMappings.Num(); ++i) { if( DialogueWave->ContextMappings[i].Context.Speaker == Speaker ) { bool bIsValidTargetSet = true; for(int32 j = 0; j < DialogueWave->ContextMappings[i].Context.Targets.Num(); ++j) { bIsValidTargetSet = ( DialogueWave->ContextMappings[i].Context.Targets[j] != NULL ); if( !bIsValidTargetSet ) { break; } } if( bIsValidTargetSet ) { UniqueTargetSets.AddUnique( DialogueWave->ContextMappings[i].Context.Targets ); } } } } // Check if a refresh is needed. bool bNeedsRefresh = false; if( OptionsSource.Num() == UniqueTargetSets.Num() ) { for(int32 i = 0; i < UniqueTargetSets.Num(); ++i) { if( *(OptionsSource[i]) != UniqueTargetSets[i] ) { bNeedsRefresh = true; break; } } } else { bNeedsRefresh = true; } // Refresh if needed. if(bNeedsRefresh) { OptionsSource.Empty(); if(DialogueWave) { for(int32 i = 0; i < UniqueTargetSets.Num(); ++i) { OptionsSource.Add( MakeShareable( new FTargetSet(UniqueTargetSets[i]) ) ); } } ComboBox->ClearSelection(); ComboBox->RefreshOptions(); } } void STargetSetDropDown::OnSelectionChanged(TSharedPtr TargetSet, ESelectInfo::Type SelectInfo) { const TSharedPtr ContextPropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("Context"); const TSharedPtr TargetsPropertyHandle = ContextPropertyHandle->GetChildHandle("Targets"); FTargetSet* TargetSetToChange = NULL; { TArray RawData; TargetsPropertyHandle->AccessRawData(RawData); TargetSetToChange = reinterpret_cast(RawData[0]); } if( TargetSet.IsValid() ) { TargetsPropertyHandle->NotifyPreChange(); *TargetSetToChange = *TargetSet; TargetsPropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); } } float STargetSetDropDown::GetPreferredWidthForWrapping() const { return AllottedWidth; } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION TSharedRef STargetSetDropDown::MakeComboButtonItemWidget(TSharedPtr TargetSet) { TSharedPtr Result; const uint32 ThumbnailSizeX = 64; const uint32 ThumbnailSizeY = 64; FSlateFontInfo Font = IDetailLayoutBuilder::GetDetailFont(); if( TargetSet->Num() > 1 ) { const TSharedRef WrapBox = SNew( SWrapBox ) .PreferredSize( this, &STargetSetDropDown::GetPreferredWidthForWrapping ); for(int32 i = 0; i < TargetSet->Num(); ++i) { const TSharedPtr AssetThumbnail = MakeShareable( new FAssetThumbnail( (*TargetSet)[i], ThumbnailSizeX, ThumbnailSizeY, AssetThumbnailPool ) ); WrapBox->AddSlot() .Padding(2.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew( SBox ) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SBox ) .WidthOverride( static_cast(ThumbnailSizeX) ) .HeightOverride( static_cast(ThumbnailSizeY) ) [ AssetThumbnail->MakeThumbnailWidget() ] ] ]; } Result = SNew( SVerticalBox ) +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Center) .AutoHeight() [ WrapBox ] +SVerticalBox::Slot() .Padding( 2.0f ) .HAlign(HAlign_Center) .AutoHeight() [ // Voice Description SNew( STextBlock ) .Font( Font ) .Text( LOCTEXT("Multiple", "Multiple") ) ]; } else if( TargetSet->Num() == 1 ) { UDialogueVoice* Target = (*TargetSet)[0]; const TSharedPtr AssetThumbnail = MakeShareable( new FAssetThumbnail( Target, ThumbnailSizeX, ThumbnailSizeY, AssetThumbnailPool ) ); SAssignNew( Result, SVerticalBox ) +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Center) .AutoHeight() [ SNew( SBox ) .Padding(2.0f) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SBox ) .WidthOverride( static_cast(ThumbnailSizeX) ) .HeightOverride( static_cast(ThumbnailSizeY) ) [ AssetThumbnail->MakeThumbnailWidget() ] ] ] +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Center) .AutoHeight() [ // Voice Description SNew( STextBlock ) .Font( Font ) .Text( Target ? FText::FromString(Target->GetDesc()) : LOCTEXT("None", "None") ) ]; } else { SAssignNew( Result, SVerticalBox ) +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Center) .AutoHeight() [ SNew( SBox ) .WidthOverride( static_cast(ThumbnailSizeX) ) .HeightOverride( static_cast(ThumbnailSizeY) ) .HAlign(HAlign_Center) .VAlign(VAlign_Center) ] +SVerticalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Center) .AutoHeight() [ // Voice Description SNew( STextBlock ) .Font( Font ) .Text( LOCTEXT( "NoTargets", "No One" ) ) ]; } return Result.ToSharedRef(); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION class SValidatedDialogueContextHeaderWidget : public SCompoundWidget { public: SLATE_BEGIN_ARGS( SValidatedDialogueContextHeaderWidget ) {} SLATE_END_ARGS() void Construct( const FArguments& InArgs, const TSharedRef& InPropertyHandle, const TSharedRef& InAssetThumbnailPool ); virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) override; private: bool IsDialogueWaveValid() const; bool IsSpeakerValid() const; bool IsTargetSetValid() const; bool ShouldSpeakerDropDownBeEnabled() const; EVisibility GetSpeakerErrorVisibility() const; bool ShouldTargetsDropDownBeEnabled() const; EVisibility GetTargetsErrorVisibility() const; private: const UDialogueWave* CurrentDialogueWave; TSharedPtr DialogueWaveParameterPropertyHandle; TSharedPtr ContextErrorHint; TSharedPtr SpeakerErrorHint; TSharedPtr SpeakerErrorText; TSharedPtr TargetsErrorHint; TSharedPtr TargetsErrorText; }; BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SValidatedDialogueContextHeaderWidget::Construct( const FArguments& InArgs, const TSharedRef& InPropertyHandle, const TSharedRef& InAssetThumbnailPool ) { DialogueWaveParameterPropertyHandle = InPropertyHandle; const TSharedPtr DialogueWavePropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("DialogueWave"); CurrentDialogueWave = NULL; { UObject* Object = NULL; DialogueWavePropertyHandle->GetValue(Object); CurrentDialogueWave = Cast(Object); } const TSharedPtr ContextPropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("Context"); if( ContextPropertyHandle->IsValidHandle() ) { const TSharedPtr SpeakerPropertyHandle = ContextPropertyHandle->GetChildHandle("Speaker"); const TSharedPtr TargetsPropertyHandle = ContextPropertyHandle->GetChildHandle("Targets"); ChildSlot [ SNew( SBorder ) .BorderImage( FAppStyle::GetBrush("DialogueWaveDetails.HeaderBorder") ) [ SNew( SVerticalBox ) +SVerticalBox::Slot() .AutoHeight() [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .FillWidth(1.0f) [ SAssignNew( ContextErrorHint, SErrorHint ) ] +SHorizontalBox::Slot() .AutoWidth() [ ContextPropertyHandle->CreatePropertyNameWidget() ] +SHorizontalBox::Slot() .FillWidth(1.0f) [ SNullWidget::NullWidget ] ] +SVerticalBox::Slot() .Padding( FMargin( 4.0f, 2.0f, 4.0f, 4.0f ) ) .AutoHeight() [ 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_Fill) .VAlign(VAlign_Center) .FillHeight(1.0f) [ SNew( SOverlay ) +SOverlay::Slot() [ SNew( SSpeakerDropDown, DialogueWaveParameterPropertyHandle.ToSharedRef(), InAssetThumbnailPool ) .IsEnabled( this, &SValidatedDialogueContextHeaderWidget::ShouldSpeakerDropDownBeEnabled ) ] +SOverlay::Slot() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SBox ) .Visibility( this, &SValidatedDialogueContextHeaderWidget::GetSpeakerErrorVisibility ) [ SAssignNew( SpeakerErrorText, SErrorText ) ] ] ] ] +SHorizontalBox::Slot() .HAlign(HAlign_Center) .VAlign(VAlign_Center) .Padding(4.0f) .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() [ TargetsPropertyHandle->CreatePropertyNameWidget( LOCTEXT("DirectedAt", "Directed At") ) ] +SHorizontalBox::Slot() .FillWidth(1.0f) [ SNullWidget::NullWidget ] ] +SVerticalBox::Slot() .HAlign(HAlign_Fill) .FillHeight(1.0f) [ SNew( SOverlay ) +SOverlay::Slot() [ SNew( STargetSetDropDown, DialogueWaveParameterPropertyHandle.ToSharedRef(), InAssetThumbnailPool ) .IsEnabled( this, &SValidatedDialogueContextHeaderWidget::ShouldTargetsDropDownBeEnabled ) ] +SOverlay::Slot() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew( SBox ) .Visibility( this, &SValidatedDialogueContextHeaderWidget::GetTargetsErrorVisibility ) [ SAssignNew( TargetsErrorText, SErrorText ) ] ] ] ] ] ] ]; } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION void SValidatedDialogueContextHeaderWidget::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { if( DialogueWaveParameterPropertyHandle.IsValid() && DialogueWaveParameterPropertyHandle->IsValidHandle() ) { // Get dialogue wave. const TSharedPtr DialogueWavePropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("DialogueWave"); const UDialogueWave* DialogueWave = NULL; if( DialogueWavePropertyHandle.IsValid() && DialogueWavePropertyHandle->IsValidHandle() ) { UObject* Object = NULL; DialogueWavePropertyHandle->GetValue(Object); DialogueWave = Cast(Object); } // Get context. const TSharedPtr ContextPropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("Context"); // Get speaker. const TSharedPtr SpeakerPropertyHandle = ContextPropertyHandle->GetChildHandle("Speaker"); UDialogueVoice* Speaker = NULL; if( SpeakerPropertyHandle.IsValid() && SpeakerPropertyHandle->IsValidHandle()) { UObject* Object = NULL; SpeakerPropertyHandle->GetValue(Object); Speaker = Cast(Object); } // Get target set. const TSharedPtr TargetsPropertyHandle = ContextPropertyHandle->GetChildHandle("Targets"); STargetSetDropDown::FTargetSet* TargetSet = NULL; if( TargetsPropertyHandle.IsValid() && TargetsPropertyHandle->IsValidHandle()) { TArray RawData; TargetsPropertyHandle->AccessRawData(RawData); TargetSet = reinterpret_cast(RawData[0]); } bool bDidDialogueWaveChange = (CurrentDialogueWave != DialogueWave); if( bDidDialogueWaveChange ) { CurrentDialogueWave = DialogueWave; // Check if the speaker needs to be reset. bool bSpeakerNeedsReset = true; bool bTargetSetNeedsReset = true; if( DialogueWave ) { for(int32 i = 0; i < DialogueWave->ContextMappings.Num(); ++i) { if( bSpeakerNeedsReset ) { bSpeakerNeedsReset = ( DialogueWave->ContextMappings[i].Context.Speaker != Speaker ); } if( bTargetSetNeedsReset && TargetSet ) { bTargetSetNeedsReset = ( ToRawPtrTArrayUnsafe(DialogueWave->ContextMappings[i].Context.Targets) != *TargetSet ); } if( !bSpeakerNeedsReset && !bTargetSetNeedsReset) { break; } } } // Don't try resets if there are no valid contexts. if( DialogueWave && DialogueWave->ContextMappings.Num() > 0) { // Speaker reset if needed and possible. if( bSpeakerNeedsReset && SpeakerPropertyHandle.IsValid() && SpeakerPropertyHandle->IsValidHandle() ) { const UObject* Object = DialogueWave->ContextMappings[0].Context.Speaker; SpeakerPropertyHandle->SetValue( Object ); } // Target set reset if needed. if(bTargetSetNeedsReset) { // Reset if possible. if( TargetSet && TargetsPropertyHandle.IsValid() && TargetsPropertyHandle->IsValidHandle() ) { TargetsPropertyHandle->NotifyPreChange(); *TargetSet = DialogueWave->ContextMappings[0].Context.Targets; TargetsPropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); } } } } } if( !IsDialogueWaveValid() ) { if( ContextErrorHint.IsValid() ) { ContextErrorHint->SetError( LOCTEXT("InvalidDialogueWaveError", "Invalid dialogue wave.") ); } if( SpeakerErrorHint.IsValid() ) { SpeakerErrorHint->SetError( FText::GetEmpty() ); } if( SpeakerErrorText.IsValid() ) { SpeakerErrorText->SetError( LOCTEXT("SelectDialogueWaveError", "Select a valid dialogue wave.") ); } if( TargetsErrorHint.IsValid() ) { TargetsErrorHint->SetError( FText::GetEmpty() ); } if( TargetsErrorText.IsValid() ) { TargetsErrorText->SetError( LOCTEXT("SelectDialogueWaveError", "Select a valid dialogue wave.") ); } } else if( !IsSpeakerValid() ) { if( ContextErrorHint.IsValid() ) { ContextErrorHint->SetError( FText::GetEmpty() ); } if( SpeakerErrorHint.IsValid() ) { SpeakerErrorHint->SetError( LOCTEXT("InvalidSpeakerError", "Invalid speaker for dialogue wave.") ); } if( SpeakerErrorText.IsValid() ) { SpeakerErrorText->SetError( FText::GetEmpty() ); } if( TargetsErrorHint.IsValid() ) { TargetsErrorHint->SetError( FText::GetEmpty() ); } if( TargetsErrorText.IsValid() ) { TargetsErrorText->SetError( LOCTEXT("SelectSpeakerError", "Select a valid speaker.") ); } } else if( !IsTargetSetValid() ) { if( ContextErrorHint.IsValid() ) { ContextErrorHint->SetError( FText::GetEmpty() ); } if( SpeakerErrorHint.IsValid() ) { SpeakerErrorHint->SetError( FText::GetEmpty() ); } if( SpeakerErrorText.IsValid() ) { SpeakerErrorText->SetError( FText::GetEmpty() ); } if( TargetsErrorHint.IsValid() ) { TargetsErrorHint->SetError( LOCTEXT("SelectTargetsError", "Select a valid target set.") ); } if( TargetsErrorText.IsValid() ) { TargetsErrorText->SetError( FText::GetEmpty() ); } } else { if( ContextErrorHint.IsValid() ) { ContextErrorHint->SetError( FText::GetEmpty() ); } if( SpeakerErrorHint.IsValid() ) { SpeakerErrorHint->SetError( FText::GetEmpty() ); } if( SpeakerErrorText.IsValid() ) { SpeakerErrorText->SetError( FText::GetEmpty() ); } if( TargetsErrorHint.IsValid() ) { TargetsErrorHint->SetError( FText::GetEmpty() ); } if( TargetsErrorText.IsValid() ) { TargetsErrorText->SetError( FText::GetEmpty() ); } } } bool SValidatedDialogueContextHeaderWidget::IsDialogueWaveValid() const { const UDialogueWave* DialogueWave = NULL; const TSharedPtr DialogueWavePropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("DialogueWave"); if( DialogueWavePropertyHandle->IsValidHandle() ) { UObject* Object = NULL; DialogueWavePropertyHandle->GetValue(Object); DialogueWave = Cast(Object); } return DialogueWave != NULL && DialogueWave->ContextMappings.Num() > 0; } bool SValidatedDialogueContextHeaderWidget::IsSpeakerValid() const { const TSharedPtr DialogueWavePropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("DialogueWave"); const UDialogueWave* DialogueWave = NULL; { UObject* Object = NULL; DialogueWavePropertyHandle->GetValue(Object); DialogueWave = Cast(Object); } const TSharedPtr ContextPropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("Context"); const TSharedPtr SpeakerPropertyHandle = ContextPropertyHandle->GetChildHandle("Speaker"); const UDialogueVoice* Speaker = NULL; { UObject* Object = NULL; SpeakerPropertyHandle->GetValue(Object); Speaker = Cast(Object); } bool bSpeakerIsValid = false; if( DialogueWave ) { FSpeakerMatcher SpeakerMatcher = { Speaker }; if (DialogueWave->ContextMappings.IndexOfByPredicate(SpeakerMatcher) != INDEX_NONE) { bSpeakerIsValid = true; } } return bSpeakerIsValid; } bool SValidatedDialogueContextHeaderWidget::IsTargetSetValid() const { const TSharedPtr DialogueWavePropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("DialogueWave"); const UDialogueWave* DialogueWave = NULL; { UObject* Object = NULL; DialogueWavePropertyHandle->GetValue(Object); DialogueWave = Cast(Object); } const TSharedPtr ContextPropertyHandle = DialogueWaveParameterPropertyHandle->GetChildHandle("Context"); const FDialogueContext* DialogueContext = NULL; { TArray RawData; ContextPropertyHandle->AccessRawData(RawData); DialogueContext = reinterpret_cast(RawData[0]); } return DialogueWave && DialogueContext && DialogueWave->SupportsContext(*DialogueContext); } bool SValidatedDialogueContextHeaderWidget::ShouldSpeakerDropDownBeEnabled() const { return IsDialogueWaveValid(); } EVisibility SValidatedDialogueContextHeaderWidget::GetSpeakerErrorVisibility() const { return SpeakerErrorText.IsValid() && SpeakerErrorText->HasError() ? EVisibility::Visible : EVisibility::Collapsed; } bool SValidatedDialogueContextHeaderWidget::ShouldTargetsDropDownBeEnabled() const { return IsSpeakerValid(); } EVisibility SValidatedDialogueContextHeaderWidget::GetTargetsErrorVisibility() const { return TargetsErrorText.IsValid() && TargetsErrorText->HasError() ? EVisibility::Visible : EVisibility::Collapsed; } TSharedRef FDialogueWaveParameterStructCustomization::MakeInstance() { return MakeShareable( new FDialogueWaveParameterStructCustomization ); } void FDialogueWaveParameterStructCustomization::CustomizeHeader( TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) { } void FDialogueWaveParameterStructCustomization::CustomizeChildren( TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) { if( StructPropertyHandle->IsValidHandle() ) { const TSharedPtr DialogueWavePropertyHandle = StructPropertyHandle->GetChildHandle("DialogueWave"); ChildBuilder.AddProperty(DialogueWavePropertyHandle.ToSharedRef()); const TSharedPtr ContextPropertyHandle = StructPropertyHandle->GetChildHandle("Context"); ChildBuilder.AddCustomRow( ContextPropertyHandle->GetPropertyDisplayName() ) [ SNew( SValidatedDialogueContextHeaderWidget, StructPropertyHandle, StructCustomizationUtils.GetThumbnailPool().ToSharedRef() ) ]; } } #undef LOCTEXT_NAMESPACE