// Copyright Epic Games, Inc. All Rights Reserved. #include "SSingleProperty.h" #include "AssetThumbnail.h" #include "DetailPropertyRow.h" #include "IPropertyUtilities.h" #include "Modules/ModuleManager.h" #include "ObjectPropertyNode.h" #include "PropertyEditorHelpers.h" #include "PropertyEditorModule.h" #include "PropertyNode.h" #include "SResetToDefaultPropertyEditor.h" #include "SStandaloneCustomizedValueWidget.h" #include "Engine/Engine.h" #include "Presentation/PropertyEditor/PropertyEditor.h" #include "ThumbnailRendering/ThumbnailManager.h" #include "UObject/UnrealType.h" #include "Widgets/Colors/SColorPicker.h" #include "Widgets/Text/STextBlock.h" #include "StructurePropertyNode.h" class FSinglePropertyUtilities : public IPropertyUtilities { public: FSinglePropertyUtilities( const TWeakPtr< SSingleProperty >& InView, bool bInShouldDisplayThumbnail ) : View( InView ) , bShouldHideAssetThumbnail(bInShouldDisplayThumbnail) { } virtual class FNotifyHook* GetNotifyHook() const override { TSharedPtr< SSingleProperty > PinnedView = View.Pin(); return PinnedView->GetNotifyHook(); } virtual void CreateColorPickerWindow( const TSharedRef< class FPropertyEditor >& PropertyEditor, bool bUseAlpha ) const override { TSharedPtr< SSingleProperty > PinnedView = View.Pin(); if ( PinnedView.IsValid() ) { PinnedView->CreateColorPickerWindow( PropertyEditor, bUseAlpha ); } } virtual void EnqueueDeferredAction( FSimpleDelegate DeferredAction ) override { // not implemented } virtual bool AreFavoritesEnabled() const override { // not implemented return false; } virtual void ToggleFavorite( const TSharedRef< class FPropertyEditor >& PropertyEditor ) const override { // not implemented } virtual bool IsPropertyEditingEnabled() const override { return true; } virtual void ForceRefresh() override {} virtual void RequestRefresh() override {} virtual void RequestForceRefresh() override {} virtual TSharedPtr GetThumbnailPool() const override { return bShouldHideAssetThumbnail ? nullptr : UThumbnailManager::Get().GetSharedThumbnailPool(); } virtual void NotifyFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) override {} virtual void NotifyStartedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) override {} virtual bool DontUpdateValueWhileEditing() const override { return false; } virtual const TArray>& GetClassViewerFilters() const override { // not implemented static TArray> NotImplemented; return NotImplemented; } const TArray>& GetSelectedObjects() const override { static TArray> Empty; return Empty; } virtual bool HasClassDefaultObject() const override { return false; } private: TWeakPtr< SSingleProperty > View; bool bShouldHideAssetThumbnail = false; }; void SSingleProperty::Construct( const FArguments& InArgs ) { PropertyName = InArgs._PropertyName; NameOverride = InArgs._NameOverride; NamePlacement = InArgs._NamePlacement; NotifyHook = InArgs._NotifyHook; PropertyFont = InArgs._PropertyFont; bShouldHideResetToDefault = InArgs._bShouldHideResetToDefault; PropertyUtilities = MakeShareable( new FSinglePropertyUtilities( SharedThis( this ), InArgs._bShouldHideAssetThumbnail ) ); if (InArgs._Object != nullptr) { SetObject( InArgs._Object ); } else if(InArgs._StructData.IsValid()) { SetStruct(InArgs._StructData); } } void SSingleProperty::SetObject( UObject* InObject ) { if( !RootPropertyNode.IsValid() || RootPropertyNode->GetPropertyType() != FComplexPropertyNode::EPT_Object) { RootPropertyNode = MakeShareable( new FObjectPropertyNode ); } FObjectPropertyNode* RootObjectPropertyNode = static_cast(RootPropertyNode.Get()); RootObjectPropertyNode->RemoveAllObjects(); ValueNode.Reset(); if( InObject ) { RootObjectPropertyNode->AddObject( InObject ); } if( !GeneratePropertyCustomization() ) { // invalid or missing property RootObjectPropertyNode->RemoveAllObjects(); RootPropertyNode.Reset(); } } void SSingleProperty::SetStruct(const TSharedPtr& InStruct) { if (!RootPropertyNode.IsValid() || RootPropertyNode->GetPropertyType() != FComplexPropertyNode::EPT_StandaloneStructure) { RootPropertyNode = MakeShareable(new FStructurePropertyNode); RootPropertyNode->SetNodeFlags(EPropertyNodeFlags::RequiresValidation, true); } FStructurePropertyNode* RootStructPropertyNode = (FStructurePropertyNode*)RootPropertyNode.Get(); RootStructPropertyNode->RemoveStructure(); ValueNode.Reset(); if (InStruct) { RootStructPropertyNode->SetStructure(InStruct); } if( !GeneratePropertyCustomization() ) { // invalid or missing property RootStructPropertyNode->RemoveStructure(); RootPropertyNode.Reset(); } } bool SSingleProperty::GeneratePropertyCustomization() { DestroyColorPicker(); FPropertyNodeInitParams InitParams; InitParams.ParentNode = NULL; InitParams.Property = NULL; InitParams.ArrayOffset = 0; InitParams.ArrayIndex = INDEX_NONE; // we'll generate the children InitParams.bAllowChildren = false; InitParams.bForceHiddenPropertyVisibility = false; RootPropertyNode->InitNode( InitParams ); ValueNode = RootPropertyNode->GenerateSingleChild( PropertyName ); bool bIsAcceptableProperty = false; FProperty* Property = nullptr; // valid criteria for standalone properties if( ValueNode.IsValid() ) { //TODO MaterialLayers: Remove below commenting bIsAcceptableProperty = true; Property = ValueNode->GetProperty(); check(Property); // not an array property (dynamic or static) //bIsAcceptableProperty &= !( Property->IsA( FArrayProperty::StaticClass() ) || (Property->ArrayDim > 1 && ValueNode->GetArrayIndex() == INDEX_NONE) ); // not a struct property unless its a built in type like a vector //bIsAcceptableProperty &= ( !Property->IsA( FStructProperty::StaticClass() ) || PropertyEditorHelpers::IsBuiltInStructProperty( Property ) ); } if( bIsAcceptableProperty ) { ValueNode->RebuildChildren(); TSharedRef< FPropertyEditor > PropertyEditor = FPropertyEditor::Create( ValueNode.ToSharedRef(), TSharedPtr< IPropertyUtilities >( PropertyUtilities ).ToSharedRef() ); ValueNode->SetDisplayNameOverride( NameOverride ); PropertyHandle = PropertyEditorHelpers::GetPropertyHandle(ValueNode.ToSharedRef(), NotifyHook, PropertyUtilities); TSharedPtr HorizontalBox; ChildSlot [ SAssignNew( HorizontalBox, SHorizontalBox ) ]; if( NamePlacement != EPropertyNamePlacement::Hidden ) { HorizontalBox->AddSlot() .Padding(4.0f, 0.0f) .AutoWidth() .VAlign( VAlign_Center ) [ SNew( SPropertyNameWidget, PropertyEditor ) ]; } // For structs and other properties with a customized header FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); if (const FPropertyTypeLayoutCallback LayoutCallback = PropertyEditorModule.GetPropertyTypeCustomization( Property, *PropertyHandle, FCustomPropertyTypeLayoutMap() ); LayoutCallback.IsValid() ) { TSharedRef CustomizationInstance = LayoutCallback.GetCustomizationInstance(); HorizontalBox->AddSlot() .Padding( 4.0f, 0.0f) .FillWidth(1.0f) .VAlign( VAlign_Center ) [ SNew( SStandaloneCustomizedValueWidget, CustomizationInstance, PropertyHandle.ToSharedRef()) ]; } else // For properties without customization { HorizontalBox->AddSlot() .Padding( 4.0f, 0.0f) .FillWidth(1.0f) .VAlign( VAlign_Center ) [ SNew( SPropertyValueWidget, PropertyEditor, PropertyUtilities.ToSharedRef() ) ]; } if (!PropertyEditor->GetPropertyHandle()->HasMetaData(TEXT("NoResetToDefault")) && !bShouldHideResetToDefault) { HorizontalBox->AddSlot() .Padding( 2.0f ) .AutoWidth() .VAlign( VAlign_Center ) [ SNew( SResetToDefaultPropertyEditor, PropertyEditor->GetPropertyHandle() ) ]; } } else { if (ValueNode.IsValid()) { // Still create a PropertyHandle, though it may not be fully initialized as the ValueNode will not have had children rebuilt PropertyHandle = PropertyEditorHelpers::GetPropertyHandle(ValueNode.ToSharedRef(), NotifyHook, PropertyUtilities); } ChildSlot [ SNew(STextBlock) .Font(PropertyFont) .Text(NSLOCTEXT("PropertyEditor", "SinglePropertyInvalidType", "Cannot Edit Inline")) .ToolTipText(NSLOCTEXT("PropertyEditor", "SinglePropertyInvalidType_Tooltip", "Properties of this type cannot be edited inline; edit it elsewhere")) ]; ValueNode.Reset(); } return bIsAcceptableProperty; } void SSingleProperty::SetOnPropertyValueChanged( const FSimpleDelegate& InOnPropertyValueChanged ) { if( HasValidProperty() ) { ValueNode->OnPropertyValueChanged().Add( InOnPropertyValueChanged ); } } void SSingleProperty::ReplaceObjects( const TMap& OldToNewObjectMap ) { if( HasValidProperty() && RootPropertyNode->GetPropertyType() == FComplexPropertyNode::EPT_Object ) { TArray NewObjectList; bool bObjectsReplaced = false; FObjectPropertyNode* RootObjectPropertyNode = static_cast(RootPropertyNode.Get()); // Scan all objects and look for objects which need to be replaced for ( TPropObjectIterator Itor(RootObjectPropertyNode->ObjectIterator() ); Itor; ++Itor ) { UObject* Replacement = OldToNewObjectMap.FindRef( Itor->Get(true) ); if( Replacement ) { bObjectsReplaced = true; NewObjectList.Add( Replacement ); } else { NewObjectList.Add( Itor->Get() ); } } // if any objects were replaced update the observed objects if( bObjectsReplaced ) { SetObject( NewObjectList[0] ); } } } void SSingleProperty::RemoveDeletedObjects( const TArray& DeletedObjects ) { if( HasValidProperty() && RootPropertyNode->GetPropertyType() == FComplexPropertyNode::EPT_Object ) { FObjectPropertyNode* RootObjectPropertyNode = static_cast(RootPropertyNode.Get()); // Scan all objects and look for objects which need to be replaced for ( TPropObjectIterator Itor(RootObjectPropertyNode->ObjectIterator() ); Itor; ++Itor ) { if( DeletedObjects.Contains( Itor->Get() ) ) { SetObject( NULL ); break; } } } } void SSingleProperty::CreateColorPickerWindow( const TSharedRef< class FPropertyEditor >& PropertyEditor, bool bUseAlpha ) { if( HasValidProperty() ) { TSharedRef Node = PropertyEditor->GetPropertyNode(); check( &Node.Get() == ValueNode.Get() ); FProperty* Property = Node->GetProperty(); check(Property); FReadAddressList ReadAddresses; Node->GetReadAddress( false, ReadAddresses, false ); // Use the first address for the initial color TOptional DefaultColor; bool bClampValue = false; if( ReadAddresses.Num() ) { const uint8* Addr = ReadAddresses.GetAddress(0); if( Addr ) { if( CastField(Property)->Struct->GetFName() == NAME_Color ) { DefaultColor = *reinterpret_cast(Addr); bClampValue = true; } else { check( CastField(Property)->Struct->GetFName() == NAME_LinearColor ); DefaultColor = *reinterpret_cast(Addr); } } } if (DefaultColor.IsSet()) { FColorPickerArgs PickerArgs = FColorPickerArgs(DefaultColor.GetValue(), FOnLinearColorValueChanged::CreateSP(this, &SSingleProperty::SetColorPropertyFromColorPicker)); PickerArgs.ParentWidget = AsShared(); PickerArgs.bUseAlpha = bUseAlpha; PickerArgs.bClampValue = bClampValue; PickerArgs.DisplayGamma = TAttribute::Create(TAttribute::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); OpenColorPicker(PickerArgs); } } } void SSingleProperty::SetColorPropertyFromColorPicker(FLinearColor NewColor) { if( HasValidProperty() ) { FProperty* NodeProperty = ValueNode->GetProperty(); check(NodeProperty); check(GetPropertyHandle()); if (CastField(NodeProperty)->Struct->GetFName() == NAME_Color) { const bool bSRGB = true; FColor NewFColor = NewColor.ToFColor(bSRGB); ensure(GetPropertyHandle()->SetValueFromFormattedString(NewFColor.ToString(), EPropertyValueSetFlags::DefaultFlags) == FPropertyAccess::Result::Success); } else { ensure(GetPropertyHandle()->SetValueFromFormattedString(NewColor.ToString(), EPropertyValueSetFlags::DefaultFlags) == FPropertyAccess::Result::Success); } } }