// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UObject/Object.h" #include "UObject/UnrealType.h" #include "PropertyPath.h" #include "PropertyEditorModule.h" #include "EditConditionParser.h" class FCategoryPropertyNode; class FComplexPropertyNode; class FDetailTreeNode; class FEditConditionContext; class FEditConditionExpression; class FItemPropertyNode; class FNotifyHook; class FObjectPropertyNode; class FPropertyItemValueDataTrackerSlate; class FPropertyNode; class FPropertyRestriction; class FStructurePropertyNode; namespace PropertyEditorPolicy { class IEditConstPolicy; class IArchetypePolicy; } DECLARE_LOG_CATEGORY_EXTERN(LogPropertyNode, Log, All); namespace EPropertyNodeFlags { typedef uint32 Type; inline const Type IsSeen = 1 << 0; /** true if this node can be seen based on current parent expansion. Does not take into account clipping*/ inline const Type IsSeenDueToFiltering = 1 << 1; /** true if this node has been accepted by the filter*/ inline const Type IsSeenDueToChildFiltering = 1 << 2; /** true if this node or one of it's children is seen due to filtering. It will then be forced on as well.*/ inline const Type IsParentSeenDueToFiltering = 1 << 3; /** True if the parent was visible due to filtering*/ inline const Type IsSeenDueToChildFavorite = 1 << 4; /** True if this node is seen to it having a favorite as a child */ inline const Type Expanded = 1 << 5; /** true if this node should display its children*/ inline const Type CanBeExpanded = 1 << 6; /** true if this node is able to be expanded */ inline const Type EditInlineNew = 1 << 7; /** true if the property can be expanded into the property window. */ inline const Type SingleSelectOnly = 1 << 8; /** true if only a single object is selected. */ inline const Type ShowCategories = 1 << 9; /** true if this node should show categories. Different*/ inline const Type HasEverBeenExpanded = 1 << 10; /** true if expand has ever been called on this node */ inline const Type IsBeingFiltered = 1 << 11; /** true if the node is being filtered. If this is true, seen flags should be checked for visibility. If this is false the node has no filter and is visible */ inline const Type IsFavorite = 1 << 12; /** true if this item has been dubbed a favorite by the user */ inline const Type NoChildrenDueToCircularReference= 1 << 13; /** true if this node has no children (but normally would) due to circular referencing */ inline const Type AutoExpanded = 1 << 14; /** true if this node was autoexpanded due to being filtered */ inline const Type ShouldShowHiddenProperties = 1 << 15; /** true if this node should all properties not just those with the correct flag(s) to be shown in the editor */ inline const Type IsAdvanced = 1 << 16; /** true if the property node is advanced (i.e it only shows up in advanced sections) */ inline const Type IsCustomized = 1 << 17; /** true if this node's visual representation has been customized by the editor */ inline const Type RequiresValidation = 1 << 18; /** true if this node could unexpectedly change (array changes, editinlinenew changes) */ inline const Type ShouldShowDisableEditOnInstance = 1 << 19; /** true if this node should show child properties marked CPF_DisableEditOnInstance */ inline const Type IsReadOnly = 1 << 20; /** true if this node is overridden to appear as read-only */ inline const Type SkipChildValidation = 1 << 21; /** true if this node should skip child validation */ inline const Type ShowInnerObjectProperties = 1 << 22; inline const Type HasCustomResetToDefault = 1 << 23; /** true if this node's visual representation of reset to default has been customized*/ inline const Type IsSparseClassData = 1 << 24; /** true if the property on this node is part of a sparse class data structure */ inline const Type ShouldShowInViewport = 1 << 25; /** true if the property should be shown in the viewport context menu */ inline const Type ResolveInstanceDataObjects = 1 << 26; /** Object properties will resolve to the instance data objects if they exist */ inline const Type SupportsDynamicInstancing = 1 << 27; /** This property could be point to a reference, an instance or null */ inline const Type DynamicInstance = 1 << 28; /** This property is pointing to an instance object. Convenience and optimization without requiring inspecting the outer of the child Objects */ inline const Type NoFlags = 0; }; namespace FPropertyNodeConstants { inline const int32 NoDepthRestrictions = -1; /** Character used to deliminate sub-categories in category path names */ inline const TCHAR CategoryDelimiterChar = TCHAR( '|' ); }; class FPropertySettings { public: static FPropertySettings& Get(); bool ShowFriendlyPropertyNames() const { return bShowFriendlyPropertyNames; } bool ShowHiddenProperties() const { return bShowHiddenProperties; } bool ExpandDistributions() const { return bExpandDistributions; } private: FPropertySettings(); private: bool bShowFriendlyPropertyNames; bool bExpandDistributions; bool bShowHiddenProperties; }; struct FAddressPair { FAddressPair(const UObject* InObject, uint8* Address, bool bInIsStruct) : Object( InObject) , ReadAddress( Address ) , bIsStruct(bInIsStruct) {} TWeakObjectPtr Object; uint8* ReadAddress; bool bIsStruct; }; template<> struct TIsPODType { enum { Value = true }; }; struct FReadAddressListData { public: FReadAddressListData() : bAllValuesTheSame( false ) , bRequiresCache( true ) { } void Add(const UObject* Object, uint8* Address, bool bIsStruct = false) { ReadAddresses.Add(FAddressPair(Object, Address, bIsStruct)); } int32 Num() const { return ReadAddresses.Num(); } uint8* GetAddress( int32 Index ) { const FAddressPair& Pair = ReadAddresses[Index]; return (Pair.Object.IsValid() || Pair.bIsStruct) ? Pair.ReadAddress : 0; } const UObject* GetObject(int32 Index) { const FAddressPair& Pair = ReadAddresses[Index]; return Pair.Object.Get(); } bool IsValidIndex( int32 Index ) const { return ReadAddresses.IsValidIndex(Index); } void Reset() { ReadAddresses.Reset(); bAllValuesTheSame = false; bRequiresCache = true; } bool bAllValuesTheSame; bool bRequiresCache; private: TArray ReadAddresses; }; /** * A list of read addresses for a property node which contains the address for the nodes FProperty on each object */ class FReadAddressList { friend class FPropertyNode; public: FReadAddressList() : ReadAddressListData(nullptr) {} int32 Num() const { return (ReadAddressListData != nullptr) ? ReadAddressListData->Num() : 0; } uint8* GetAddress( int32 Index ) { return ReadAddressListData->GetAddress( Index ); } const UObject* GetObject(int32 Index) { return ReadAddressListData->GetObject(Index); } bool IsValidIndex( int32 Index ) const { return ReadAddressListData->IsValidIndex( Index ); } void Reset() { if (ReadAddressListData != nullptr) { ReadAddressListData->Reset(); } } private: FReadAddressListData* ReadAddressListData; }; /** * Parameters for initializing a property node */ struct FPropertyNodeInitParams { enum class EIsSparseDataProperty : uint8 { False, True, Inherit, }; /** The parent of the property node */ TSharedPtr ParentNode; /** The property that the node observes and modifies*/ FProperty* Property; /** Offset to the property data within either a fixed array or a dynamic array */ int32 ArrayOffset; /** Index of the property in its array parent */ int32 ArrayIndex; /** Whether or not to create any children */ bool bAllowChildren; /** Whether or not to allow hidden properties (ones without CPF_Edit) to be visible */ bool bForceHiddenPropertyVisibility; /** Whether or not to create category nodes (note: this setting is only valid for the root node of a property tree. The setting will propagate to children) */ bool bCreateCategoryNodes; /** Whether or not to create nodes for properties marked CPF_DisableEditOnInstance */ bool bCreateDisableEditOnInstanceNodes; /** Resolve Objects to their IDOs */ bool bResolveInstanceDataObjects; /** Whether or not this property is sparse data */ EIsSparseDataProperty IsSparseProperty; FPropertyNodeInitParams() : ParentNode(nullptr) , Property(nullptr) , ArrayOffset(0) , ArrayIndex( INDEX_NONE ) , bAllowChildren( true ) , bForceHiddenPropertyVisibility( false ) , bCreateCategoryNodes( true ) , bCreateDisableEditOnInstanceNodes( true ) , bResolveInstanceDataObjects(false) , IsSparseProperty( EIsSparseDataProperty::Inherit ) {} }; /** Describes in which way an array property change has happend. This is used for propagation of array property changes to the instances of archetype objects. */ struct EPropertyArrayChangeType { enum Type { /** A value was added to the array */ Add, /** The array was cleared */ Clear, /** A new item has been inserted. */ Insert, /** An item has been deleted */ Delete, /** An item has been duplicated */ Duplicate, /** Two items have been swapped */ Swap, }; }; enum EPropertyDataValidationResult : uint8 { /** The object(s) being viewed are now invalid */ ObjectInvalid, /** Non dynamic array property nodes were added or removed that would require a refresh */ PropertiesChanged, /** An edit inline new value changed, In the tree this rebuilds the nodes, in the details view we don't need to do this */ EditInlineNewValueChanged, /** The size of an array changed (delete,insert,add) */ ArraySizeChanged, /** An internal node's children were rebuilt for some reason */ ChildrenRebuilt, /** All data is valid */ DataValid, }; /** Helper class for modifying property values with setters and getters */ class FPropertyNodeEditStack { struct FMemoryFrame { FMemoryFrame() = default; FMemoryFrame(const FProperty* InProperty, uint8* InMemory) : Property(InProperty) , Memory(InMemory) { } /** Property that points to the memory in this frame */ const FProperty* Property = nullptr; /** Property address */ uint8* Memory = nullptr; }; public: /** * Constructs property stack for the specified node * InNode Property node to construct the stack for * InObj Optional Object instance that contains the property being modified (if not provided the root container pointer will be acquired from the provided node hierarchy) */ FPropertyNodeEditStack(const FPropertyNode* InNode, const UObject* InObj = nullptr); FPropertyNodeEditStack() = default; ~FPropertyNodeEditStack(); FPropertyNodeEditStack& operator = (const FPropertyNodeEditStack& Other) = delete; FPropertyNodeEditStack(const FPropertyNodeEditStack& Other) = delete; /** * Initializes property stack for the specified node * InNode Property node to construct the stack for * InObj Object instance that contains the property being modified */ FPropertyAccess::Result Initialize(const FPropertyNode* InNode, const UObject* InObj); /** * Returns the address of the property being modified. * If anywhere in the property stack is a property with a setter or getter this will point to a temporarily allocated memory. */ uint8* GetDirectPropertyAddress() { return MemoryStack.Last().Memory; } /** * Commits all modifications to temporarily allocated property values back to the actual member variables using setters and getters where available */ void CommitChanges(); /** Checks if this edit stack is valid */ bool IsValid() const { return MemoryStack.Num() > 0; } private: FPropertyAccess::Result InitializeInternal(const FPropertyNode* InNode, const UObject* InObj); void Cleanup(); TArray MemoryStack; }; /** * The base class for all property nodes */ class FPropertyNode : public TSharedFromThis { public: FPropertyNode(); virtual ~FPropertyNode(); /** * Init Tree Node internally (used only derived classes to pass through variables that are common to all nodes * @param InitParams Parameters for how the node should be initialized */ void InitNode(const FPropertyNodeInitParams& InitParams); /** * Indicates that children of this node should be rebuilt next tick. Some topology changes will require this */ void RequestRebuildChildren() { bRebuildChildrenRequested = true; } /** * Used for rebuilding this nodes children */ void RebuildChildren(); /** * Mark this and all children as having been rebuilt. */ void MarkChildrenAsRebuilt(); /** * For derived windows to be able to add their nodes to the child array */ void AddChildNode(TSharedPtr InNode); /** * Destroys all child nodes under this node */ void RemoveAllChildNodes(); /** * Clears cached read address data */ void ClearCachedReadAddresses(bool bRecursive = true); /** * Interface function to get at the derived FObjectPropertyNode class */ virtual FObjectPropertyNode* AsObjectNode() { return nullptr; } virtual const FObjectPropertyNode* AsObjectNode() const { return nullptr; } /** * Interface function to get at the derived FComplexPropertyNode class */ virtual FComplexPropertyNode* AsComplexNode() { return nullptr; } virtual const FComplexPropertyNode* AsComplexNode() const { return nullptr; } /** * Interface function to get at the derived FCategoryPropertyNode class */ virtual FCategoryPropertyNode* AsCategoryNode() { return nullptr; } virtual const FCategoryPropertyNode* AsCategoryNode() const { return nullptr; } /** * Interface function to get at the derived FItemPropertyNode class */ virtual FItemPropertyNode* AsItemPropertyNode() { return nullptr; } virtual const FItemPropertyNode* AsItemPropertyNode() const { return nullptr; } /** * Follows the chain of items upwards until it finds the complex property that houses this item. */ FComplexPropertyNode* FindComplexParent(); const FComplexPropertyNode* FindComplexParent() const; /** * Follows the chain of items upwards until it finds the object property that houses this item. */ FObjectPropertyNode* FindObjectItemParent(); const FObjectPropertyNode* FindObjectItemParent() const; /** * Follows the chain of items upwards until it finds the structure property that houses this item. */ FStructurePropertyNode* FindStructureItemParent(); const FStructurePropertyNode* FindStructureItemParent() const; /** * Follows the top-most object window that contains this property window item. */ FObjectPropertyNode* FindRootObjectItemParent(); /** * Used to see if any data has been destroyed from under the property tree. Should only be called during Tick */ virtual EPropertyDataValidationResult EnsureDataIsValid(); ////////////////////////////////////////////////////////////////////////// // Text /** * @param OutText The property formatted in a string * @param bAllowAlternateDisplayValue Allow the function to potentially use an alternate form more suitable for display in the UI * @param PortFlags Determines how the property's value is accessed. Defaults to PPF_PropertyWindow * @return true if the value was retrieved successfully */ FPropertyAccess::Result GetPropertyValueString(FString& OutString, const bool bAllowAlternateDisplayValue, EPropertyPortFlags PortFlags = PPF_PropertyWindow) const; /** * @param OutText The property formatted in text * @param bAllowAlternateDisplayValue Allow the function to potentially use an alternate form more suitable for display in the UI * @return true if the value was retrieved successfully */ FPropertyAccess::Result GetPropertyValueText(FText& OutText, const bool bAllowAlternateDisplayValue) const; ////////////////////////////////////////////////////////////////////////// //Flags bool HasNodeFlags(const EPropertyNodeFlags::Type InTestFlags) const { return (PropertyNodeFlags & InTestFlags) != 0; } /** * Sets the flags used by the window and the root node * @param InFlags - flags to turn on or off * @param InOnOff - whether to toggle the bits on or off */ void SetNodeFlags(const EPropertyNodeFlags::Type InFlags, const bool InOnOff); /** * Finds a child of this property node * * @param InPropertyName The name of the property to find * @param bRecurse true if we should recurse into children's children and so on. */ TSharedPtr FindChildPropertyNode(const FName InPropertyName, bool bRecurse = false); /** * Returns the parent node in the hierarchy */ FPropertyNode* GetParentNode() { return ParentNodeWeakPtr.Pin().Get(); } const FPropertyNode* GetParentNode() const { return ParentNodeWeakPtr.Pin().Get(); } TSharedPtr GetParentNodeSharedPtr() { return ParentNodeWeakPtr.Pin(); } /** * Returns the Property this Node represents */ FProperty* GetProperty() { return Property.Get(); } const FProperty* GetProperty() const { return Property.Get(); } /** * Accessor functions for internals */ int32 GetArrayOffset() const { return ArrayOffset; } int32 GetArrayIndex() const { return ArrayIndex; } /** * Return number of children that survived being filtered */ int32 GetNumChildNodes() const { return ChildNodes.Num(); } /** * Returns the matching Child node */ TSharedPtr& GetChildNode(const int32 ChildIndex) { check(ChildNodes[ChildIndex].IsValid()); return ChildNodes[ChildIndex]; } /** * Returns the matching Child node */ const TSharedPtr& GetChildNode(const int32 ChildIndex) const { check(ChildNodes[ChildIndex].IsValid()); return ChildNodes[ChildIndex]; } /** * Returns the Child node whose ArrayIndex matches the supplied ChildIndex */ bool GetChildNode(const int32 ChildArrayIndex, TSharedPtr& OutChildNode); /** * Returns the Child node whose ArrayIndex matches the supplied ChildIndex */ bool GetChildNode(const int32 ChildArrayIndex, TSharedPtr& OutChildNode) const; /** * Returns whether this window's property is read only or has the CPF_EditConst flag. */ bool IsPropertyConst() const; /** @return whether this window's property is constant (can't be edited by the user). If bIncludeEditCondition is false, the property is considered editable regardless of the EditCondition result. */ bool IsEditConst(const bool bIncludeEditCondition = true) const; /** * Returns whether this window's property should not be serialized (determined by the CPF_SkipSerialization flag). */ bool ShouldSkipSerialization() const; /** * Gets the full name of this node * @param PathPlusIndex - return value with full path of node * @param bWithArrayIndex - If True, adds an array index (where appropriate) * @param StopParent - Stop at this parent (if any). Does NOT include it in the path * @param bIgnoreCategories - Skip over categories */ virtual bool GetQualifiedName(FString& PathPlusIndex, const bool bWithArrayIndex, const FPropertyNode* StopParent = nullptr, bool bIgnoreCategories = false) const; // The bArrayPropertiesCanDifferInSize flag is an override for array properties which want to display // e.g. the "Clear" and "Empty" buttons, even though the array properties may differ in the number of elements. bool GetReadAddress( bool InRequiresSingleSelection, FReadAddressList& OutAddresses, bool bComparePropertyContents = true, bool bObjectForceCompare = false, bool bArrayPropertiesCanDifferInSize = false) const; /** * fills in the OutAddresses array with the addresses of all of the available objects. * @param OutAddresses Storage array for all of the objects' addresses. */ bool GetReadAddress(FReadAddressList& OutAddresses) const; /** * Fills in the OutValueAddress with the address of the value of all the available objects. * If multiple items are selected, this will return a null address unless they are all the same value. * @param OutValueAddress The address of the item */ FPropertyAccess::Result GetSingleReadAddress(uint8*& OutValueAddress) const; /** * Fills in the OutObject with the address of the object of all the available objects. * If multiple items are selected, this will return a null address unless they are all the same value. * @param OutObject The address of the Object */ FPropertyAccess::Result GetSingleObject(UObject*& OutObject) const; /** * Fills in the OutContainer with the address of the container (struct or UObject instance) that owns the property this node represents. * @param OutContainer The address of the container instance */ FPropertyAccess::Result GetSingleEditStack(FPropertyNodeEditStack& OutStack) const; /** * Gets read addresses without accessing cached data. Is less efficient but gets the must up to date data */ virtual bool GetReadAddressUncached(const FPropertyNode& InNode, bool InRequiresSingleSelection, FReadAddressListData* OutAddresses, bool bComparePropertyContents = true, bool bObjectForceCompare = false, bool bArrayPropertiesCanDifferInSize = false) const; virtual bool GetReadAddressUncached(const FPropertyNode& InNode, FReadAddressListData& OutAddresses) const; /** * Calculates the memory address for the data associated with this item's property. This is typically the value of a FProperty or a UObject address. * * @param StartAddress the location to use as the starting point for the calculation; typically the address of the object that contains this property. * @param bIsSparseData True if StartAddress is pointing to a sidecar structure containing sparse class data, false otherwise * * @return a pointer to a FProperty value or UObject. (For dynamic arrays, you'd cast this value to an FArray*) */ virtual uint8* GetValueBaseAddress(uint8* StartAddress, bool bIsSparseData, bool bIsStruct = false) const; /** * Calculates the memory address for the data associated with this item's value. For most properties, identical to GetValueBaseAddress. For items corresponding * to dynamic array elements, the pointer returned will be the location for that element's data. * * @param StartAddress the location to use as the starting point for the calculation; typically the address of the object that contains this property. * @param bIsSparseData True if StartAddress is pointing to a sidecar structure containing sparse class data, false otherwise * * @return a pointer to a FProperty value or UObject. (For dynamic arrays, you'd cast this value to whatever type is the Inner for the dynamic array) */ virtual uint8* GetValueAddress(uint8* StartAddress, bool bIsSparseData, bool bIsStruct = false) const; /** * Caclulates the memory address for the starting point of the structure that contains the property this node uses. * This will often be Obj but may also point to a sidecar data structure. */ uint8* GetStartAddressFromObject(const UObject* Obj) const; /** * Calculates the memory address for the data associated with this item's property. This is typically the value of a FProperty or a UObject address. * * @param Obj The object that contains this property; used as the starting point for the calculation * * @return a pointer to a FProperty value or UObject. (For dynamic arrays, you'd cast this value to an FArray*) */ uint8* GetValueBaseAddressFromObject(const UObject* Obj) const; /** * Calculates the memory address for the data associated with this item's value. For most properties, identical to GetValueBaseAddress. For items corresponding * to dynamic array elements, the pointer returned will be the location for that element's data. * * @param Obj The object that contains this property; used as the starting point for the calculation * * @return a pointer to a FProperty value or UObject. (For dynamic arrays, you'd cast this value to whatever type is the Inner for the dynamic array) */ uint8* GetValueAddressFromObject(const UObject* Obj) const; /** * Sets the display name override to use instead of the display name */ virtual void SetDisplayNameOverride(const FText& InDisplayNameOverride) {} /** * @return true if the property is mark as a favorite */ virtual void SetFavorite(bool FavoriteValue) {} /** * @return true if the property is mark as a favorite */ virtual bool IsFavorite() const { return false; } /** * @return The formatted display name for the property in this node */ virtual FText GetDisplayName() const { return FText::GetEmpty(); } /** * Sets the tooltip override to use instead of the property tooltip */ virtual void SetToolTipOverride(const FText& InToolTipOverride) {} /** * @return The tooltip for the property in this node */ virtual FText GetToolTipText() const { return FText::GetEmpty(); } /** * If there is a property, sees if it matches. Otherwise sees if the entire parent structure matches */ bool GetDiffersFromDefault(); /** * @return The label for displaying a reset to default value */ FText GetResetToDefaultLabel(); /** * @return If this property node is associated with a property that can be reordered within an array */ bool IsReorderable(); /**Walks up the hierarchy and return true if any parent node is a favorite*/ bool IsChildOfFavorite() const; void NotifyPreChange(FProperty* PropertyAboutToChange, FNotifyHook* InNotifyHook); void NotifyPreChange(FProperty* PropertyAboutToChange, FNotifyHook* InNotifyHook, const TSet& AffectedInstances); void NotifyPreChange(FProperty* PropertyAboutToChange, FNotifyHook* InNotifyHook, TSet&& AffectedInstances); void NotifyPostChange(FPropertyChangedEvent& InPropertyChangedEvent, FNotifyHook* InNotifyHook); DECLARE_EVENT(FPropertyNode, FPropertyChildrenRebuiltEvent); FDelegateHandle SetOnRebuildChildren(const FSimpleDelegate& InOnRebuildChildren); FPropertyChildrenRebuiltEvent& OnRebuildChildren() { return OnRebuildChildrenEvent; } /** * Propagates the property change to all instances of an archetype * * @param ModifiedObject Object which property has been modified * @param NewValue New value of the property * @param PreviousValue Value of the property before the modification */ void PropagatePropertyChange(UObject* ModifiedObject, const TCHAR* NewValue, const FString& PreviousValue); /** * Propagates the property change of a container property to all instances of an archetype * * @param ModifiedObject Object which property has been modified * @param OriginalContainerAddr Original address holding the container value before the modification * @param ChangeType In which way was the container modified * @param Index Index of the modified item */ void PropagateContainerPropertyChange(UObject* ModifiedObject, const void* OriginalContainerAddr, EPropertyArrayChangeType::Type ChangeType, int32 Index, int32 SwapIndex = INDEX_NONE); /** * Helper function to centralize logic for duplcating an array entry and ensuring that instanced object references are correctly handled */ static void DuplicateArrayEntry(FProperty* NodeProperty, FScriptArrayHelper& ArrayHelper, int32 Index); /** * Gather the list of all instances that will be affected by a container property change * * @param ModifiedObject Object which property has been modified * @param OriginalContainerAddr Original address holding the container value before the modification * @param ChangeType In which way is the container modified * @param OutAffectedInstances Instances affected by the property change */ void GatherInstancesAffectedByContainerPropertyChange(UObject* ModifiedObject, const void* OriginalContainerAddr, EPropertyArrayChangeType::Type ChangeType, TArray& OutAffectedInstances); /** * Propagates the property change of a container property to the provided archetype instances * * @param ModifiedObject Object which property has been modified * @param OriginalContainerAddr Original address holding the container value before the modification * @param AffectedInstances Instances affected by the property change * @param ChangeType In which way was the container modified * @param Index Index of the modified item */ void PropagateContainerPropertyChange(UObject* ModifiedObject, const void* OriginalContainerAddr, const TArray& AffectedInstances, EPropertyArrayChangeType::Type ChangeType, int32 Index, int32 SwapIndex = INDEX_NONE); /** Broadcasts when a property value changes */ DECLARE_EVENT(FPropertyNode, FPropertyValueChangedEvent); /** Broadcasts when a property value changes, but additionally includes the property changed event.*/ DECLARE_MULTICAST_DELEGATE_OneParam(FPropertyValueChangedWithData, const FPropertyChangedEvent&) FPropertyValueChangedEvent& OnPropertyValueChanged() { return PropertyValueChangedEvent; } FPropertyValueChangedWithData& OnPropertyValueChangedWithData() { return PropertyValueChangedDelegate; } /** Broadcasts when a child of this property changes */ FPropertyValueChangedEvent& OnChildPropertyValueChanged() { return ChildPropertyValueChangedEvent; } FPropertyValueChangedWithData& OnChildPropertyValueChangedWithData() { return ChildPropertyValueChangedDelegate; } /** Broadcasts when a property value changes */ DECLARE_EVENT(FPropertyNode, FPropertyValuePreChangeEvent); FPropertyValuePreChangeEvent& OnPropertyValuePreChange() { return PropertyValuePreChangeEvent; } /** Broadcasts when a child of this property changes */ FPropertyValuePreChangeEvent& OnChildPropertyValuePreChange() { return ChildPropertyValuePreChangeEvent; } /** Broadcasts when this property is reset to default */ DECLARE_EVENT(FPropertyNode, FPropertyResetToDefaultEvent); FPropertyResetToDefaultEvent& OnPropertyResetToDefault() { return PropertyResetToDefaultEvent; } /** * Marks window's seem due to filtering flags * @param InFilterStrings - List of strings that must be in the property name in order to display * Empty InFilterStrings means display as normal * All strings must match in order to be accepted by the filter * @param bParentAllowsVisible - is NOT true for an expander that is NOT expanded */ void FilterNodes(const TArray& InFilterStrings, const bool bParentSeenDueToFiltering = false); /** * Marks windows as visible based on the filter strings or standard visibility * * @param bParentAllowsVisible - is NOT true for an expander that is NOT expanded */ void ProcessSeenFlags(const bool bParentAllowsVisible); /** * Marks windows as visible based their favorites status */ void ProcessSeenFlagsForFavorites(); /** * @return true if this node should be visible in a tree */ bool IsVisible() const { return HasNodeFlags(EPropertyNodeFlags::IsBeingFiltered) == 0 || HasNodeFlags(EPropertyNodeFlags::IsSeen) || HasNodeFlags(EPropertyNodeFlags::IsSeenDueToChildFiltering); }; static TSharedRef< FPropertyPath > CreatePropertyPath(const TSharedRef< FPropertyNode >& PropertyNode) { TArray< FPropertyInfo > Properties; FPropertyNode* CurrentNode = &PropertyNode.Get(); if (CurrentNode != nullptr && CurrentNode->AsCategoryNode() != nullptr) { TSharedRef< FPropertyPath > NewPath = MakeShared(); return NewPath; } while (CurrentNode != nullptr) { if (CurrentNode->AsItemPropertyNode() != nullptr) { FPropertyInfo NewPropInfo; NewPropInfo.Property = CurrentNode->GetProperty(); if (!NewPropInfo.Property.IsValid()) { return MakeShared(); } NewPropInfo.ArrayIndex = CurrentNode->GetArrayIndex(); Properties.Add(NewPropInfo); } CurrentNode = CurrentNode->GetParentNode(); } TSharedRef< FPropertyPath > NewPath = MakeShared(); for (int PropertyIndex = Properties.Num() - 1; PropertyIndex >= 0; --PropertyIndex) { NewPath->AddProperty(Properties[PropertyIndex]); } return NewPath; } static TSharedPtr< FPropertyNode > FindPropertyNodeByPath(const TSharedPtr< FPropertyPath > Path, const TSharedRef< FPropertyNode >& StartingNode) { if (!Path.IsValid() || Path->GetNumProperties() == 0) { return StartingNode; } bool FailedToFindProperty = false; TSharedPtr< FPropertyNode > PropertyNode = StartingNode; for (int PropertyIndex = 0; PropertyIndex < Path->GetNumProperties() && !FailedToFindProperty; PropertyIndex++) { FailedToFindProperty = true; const FPropertyInfo& PropInfo = Path->GetPropertyInfo(PropertyIndex); TArray< TSharedRef< FPropertyNode > > ChildrenStack; ChildrenStack.Push(PropertyNode.ToSharedRef()); while (ChildrenStack.Num() > 0) { const TSharedRef< FPropertyNode > CurrentNode = ChildrenStack.Pop(); for (int32 ChildIndex = 0; ChildIndex < CurrentNode->GetNumChildNodes(); ++ChildIndex) { const TSharedPtr< FPropertyNode > ChildNode = CurrentNode->GetChildNode(ChildIndex); if (ChildNode->AsItemPropertyNode() == nullptr) { ChildrenStack.Add(ChildNode.ToSharedRef()); } else if (ChildNode.IsValid() && ChildNode->GetProperty() == PropInfo.Property.Get() && ChildNode->GetArrayIndex() == PropInfo.ArrayIndex) { PropertyNode = ChildNode; FailedToFindProperty = false; break; } } } } if (FailedToFindProperty) { PropertyNode = nullptr; } return PropertyNode; } static TArray< FPropertyInfo > GetPossibleExtensionsForPath(const TSharedPtr< FPropertyPath > Path, const TSharedRef< FPropertyNode >& StartingNode) { TArray< FPropertyInfo > PossibleExtensions; TSharedPtr< FPropertyNode > PropertyNode = FindPropertyNodeByPath(Path, StartingNode); if (!PropertyNode.IsValid()) { return PossibleExtensions; } for (int32 ChildIndex = 0; ChildIndex < PropertyNode->GetNumChildNodes(); ++ChildIndex) { TSharedPtr< FPropertyNode > CurrentNode = PropertyNode->GetChildNode(ChildIndex); if (CurrentNode.IsValid() && CurrentNode->AsItemPropertyNode() != nullptr) { FPropertyInfo NewPropInfo; NewPropInfo.Property = CurrentNode->GetProperty(); NewPropInfo.ArrayIndex = CurrentNode->GetArrayIndex(); bool AlreadyExists = false; for (auto ExtensionIter = PossibleExtensions.CreateConstIterator(); ExtensionIter; ++ExtensionIter) { if (*ExtensionIter == NewPropInfo) { AlreadyExists = true; break; } } if (!AlreadyExists) { PossibleExtensions.Add(NewPropInfo); } } } return PossibleExtensions; } static UObject* GetArchetype(const UObject* Object); static void RegisterArchetypePolicy(PropertyEditorPolicy::IArchetypePolicy* ArchetypePolicy); static void UnregisterArchetypePolicy(PropertyEditorPolicy::IArchetypePolicy* ArchetypePolicy); static void RegisterEditConstPolicy(PropertyEditorPolicy::IEditConstPolicy* EditConstPolicy); static void UnregisterEditConstPolicy(PropertyEditorPolicy::IEditConstPolicy* EditConstPolicy); static bool IsPropertyEditConst(const FEditPropertyChain& PropertyChain, UObject* Object); static bool IsPropertyEditConst(const FProperty* Property, UObject* Object); /** * Adds a restriction to the possible values for this property. * @param Restriction The restriction being added to this property. */ virtual void AddRestriction(TSharedRef Restriction); /** * Tests if a value is hidden for this property * @param Value The value to test for being hidden. * @return True if this value is hidden. */ bool IsHidden(const FString& Value) const { return IsHidden(Value, nullptr); } /** * Tests if a value is disabled for this property * @param Value The value to test for being disabled. * @return True if this value is disabled. */ bool IsDisabled(const FString& Value) const { return IsDisabled(Value, nullptr); } bool IsRestricted(const FString& Value) const { return IsHidden(Value) || IsDisabled(Value); } /** * Tests if a value is hidden for this property. * @param Value The value to test for being hidden. * @param OutReasons If hidden, the reasons why. * @return True if this value is hidden. */ virtual bool IsHidden(const FString& Value, TArray* OutReasons) const; /** * Tests if a value is disabled for this property. * @param Value The value to test for being disabled. * @param OutReasons If disabled, the reasons why. * @return True if this value is disabled. */ virtual bool IsDisabled(const FString& Value, TArray* OutReasons) const; /** * Tests if a value is restricted for this property. * @param Value The value to test for being restricted. * @param OutReasons If restricted, the reasons why. * @return True if this value is restricted. */ bool IsRestricted(const FString& Value, TArray& OutReasons) const; /** * Generates a consistent tooltip describing this restriction for use in the editor. * @param Value The value to test for restriction and generate the tooltip from. * @param OutTooltip The tooltip describing why this value is restricted. * @return True if this value is restricted. */ virtual bool GenerateRestrictionToolTip(const FString& Value, FText& OutTooltip)const; const TArray>& GetRestrictions() const { return Restrictions; } FPropertyChangedEvent& FixPropertiesInEvent(FPropertyChangedEvent& Event); /** Set metadata value for 'Key' to 'Value' on this property instance (as opposed to the class) */ void SetInstanceMetaData(const FName& Key, const FString& Value); /** * Get metadata value for 'Key' for this property instance (as opposed to the class) * * @return Pointer to metadata value; nullptr if Key not found */ const FString* GetInstanceMetaData(const FName& Key) const; /** * Get metadata map for this property instance (as opposed to the class) * * @return Map ptr containing metadata pairs */ const TMap* GetInstanceMetaDataMap() const; bool ParentOrSelfHasMetaData(const FName& MetaDataKey) const; /** * Gets the property we should use to read meta-data */ FProperty* GetMetaDataProperty(); /** * Invalidates the cached state of this node in all children; */ void InvalidateCachedState(); static void SetupKeyValueNodePair( TSharedPtr& KeyNode, TSharedPtr& ValueNode ) { check( KeyNode.IsValid() && ValueNode.IsValid() ); check( !KeyNode->PropertyKeyNode.IsValid() && !ValueNode->PropertyKeyNode.IsValid() ); ValueNode->PropertyKeyNode = KeyNode; } TSharedPtr& GetPropertyKeyNode() { return PropertyKeyNode; } const TSharedPtr& GetPropertyKeyNode() const { return PropertyKeyNode; } /** * Gets the default value of the property as string. */ FString GetDefaultValueAsString(bool bUseDisplayName = true); /** * Broadcasts reset to default property changes */ void BroadcastPropertyResetToDefault(); /** @return Whether this property should have an edit condition toggle. */ bool SupportsEditConditionToggle() const; /** Toggle the current state of the edit condition if this SupportsEditConditionToggle() */ void ToggleEditConditionState(); /** @return Whether the property has a condition which must be met before allowing editing of it's value */ bool HasEditCondition() const; /** @return Whether the condition has been met to allow editing of this property's value */ bool IsEditConditionMet() const; /** @return Whether this property derives its visibility from its edit condition */ bool IsOnlyVisibleWhenEditConditionMet() const; /** * Helper to fetch a list of child property nodes that are expanded */ void GetExpandedChildPropertyPaths(TSet& OutExpandedChildPropertyPaths) const; /** * Helper to set the expansion state of a list of child property nodes */ void SetExpandedChildPropertyNodes(const TSet& InNodesToExpand); /** * Helper to fetch a PropertyPath */ const FString& GetPropertyPath() const { return PropertyPath; } /** Marks this property node as ignoring CPF_InstancedReference */ void SetIgnoreInstancedReference(); /** Queries whether the node would like to ignore CPF_InstancedReference semantics */ bool IsIgnoringInstancedReference() const; /** Return true if DestroyTree() has been called on this node. */ bool IsDestroyed() const; /** * Sets bIsDestroyed on all nodes within the hierarchy */ void MarkDestroyedRecursive(); TSharedRef BuildPropertyChain( FProperty* PropertyAboutToChange ) const; protected: TSharedRef BuildPropertyChain( FProperty* PropertyAboutToChange, const TSet& InAffectedArchetypeInstances ) const; TSharedRef BuildPropertyChain( FProperty* PropertyAboutToChange, TSet&& InAffectedArchetypeInstances ) const; void NotifyPreChangeInternal(TSharedRef PropertyChain, FProperty* PropertyAboutToChange, FNotifyHook* InNotifyHook); /** * Destroys all node within the hierarchy */ void DestroyTree(const bool bInDestroySelf=true); /** * Interface function for Custom Setup of Node (prior to node flags being set) */ virtual void InitBeforeNodeFlags() {}; /** * Interface function for Custom expansion Flags. Default is objects and categories which always expand */ virtual void InitExpansionFlags() { SetNodeFlags(EPropertyNodeFlags::CanBeExpanded, true); }; /** * Interface function for Creating Child Nodes */ virtual void InitChildNodes() = 0; /** * Does the string compares to ensure this Name is acceptable to the filter that is passed in */ bool IsFilterAcceptable(const TArray& InAcceptableNames, const TArray& InFilterStrings); /** * Make sure that parent nodes are expanded */ void ExpandParent( bool bInRecursive ); /** @return The property stored at this node, to be passed to Pre/PostEditChange. */ FProperty* GetStoredProperty() { return nullptr; } bool GetDiffersFromDefault(const uint8* PropertyValueAddress, const uint8* PropertyDefaultAddress, const uint8* DefaultPropertyValueBaseAddress, const FProperty* InProperty, const UObject* TopLevelObject) const; bool GetDiffersFromDefaultForObject( FPropertyItemValueDataTrackerSlate& ValueTracker, FProperty* InProperty ); enum class EValueAsStringMode { None, UseDisplayName, ForDiff, }; FString GetDefaultValueAsString(const uint8* PropertyDefaultAddress, const FProperty* InProperty, EValueAsStringMode Mode, const UObject* TopLevelObject) const; FString GetDefaultValueAsStringForObject( FPropertyItemValueDataTrackerSlate& ValueTracker, UObject* InObject, FProperty* InProperty, EValueAsStringMode Mode); /** * Helper function to obtain the display name for an enum property * @param InEnum The enum whose metadata to pull from * @param DisplayName The name of the enum value to adjust * * @return true if the DisplayName has been changed */ bool AdjustEnumPropDisplayName(UEnum* InEnum, FString& DisplayName) const; /** * Helper function for derived members to be able to * broadcast property changed notifications */ void BroadcastPropertyChangedDelegates(); /** * Helper function for derived members to be able to * broadcast property changed notifications including property changed event data */ void BroadcastPropertyChangedDelegates(const FPropertyChangedEvent& Event); /** * Helper function for derived members to be able to * broadcast property pre-change notifications */ void BroadcastPropertyPreChangeDelegates(); /** * Gets a value tracker for the default of this property in the passed in object * * @param Object The object to get the value for * @param ObjIndex The index of the object in the parent property node's object array (for caching) */ TSharedPtr< FPropertyItemValueDataTrackerSlate > GetValueTracker( UObject* Object, uint32 ObjIndex ); /** * Updates and caches the current edit const state of this property */ void UpdateEditConstState(); /** * Checks to see if the supplied property of a child node requires validation * @param InChildProp The property of the child node * @return True if the property requires validation, false otherwise */ static bool DoesChildPropertyRequireValidation(FProperty* InChildProp); protected: static FEditConditionParser EditConditionParser; /** * The node that is the parent of this node or nullptr for the root */ TWeakPtr ParentNodeWeakPtr; /** The property node, if any, that serves as the key value for this node */ TSharedPtr PropertyKeyNode; /** Cached read addresses for this property node */ mutable FReadAddressListData CachedReadAddresses; /** List of per object default value trackers associated with this property node */ TArray< TSharedPtr > ObjectDefaultValueTrackers; /** List of all child nodes this node is responsible for */ TArray< TSharedPtr > ChildNodes; /** Called when this node's children are rebuilt */ FPropertyChildrenRebuiltEvent OnRebuildChildrenEvent; /** Called when this node's property value is about to change (called during NotifyPreChange) */ FPropertyValuePreChangeEvent PropertyValuePreChangeEvent; /** Called when a child's property value is about to change */ FPropertyValuePreChangeEvent ChildPropertyValuePreChangeEvent; /** Called when this node's property value has changed (called during NotifyPostChange) */ FPropertyValueChangedEvent PropertyValueChangedEvent; /** Called when this node's property value has changed with the property changed event data as payload (called during NotifyPostChange) */ FPropertyValueChangedWithData PropertyValueChangedDelegate; /** Called when a child's property value has changed */ FPropertyValueChangedEvent ChildPropertyValueChangedEvent; /** Called when a child's property value has changed with the property changed event data as payload */ FPropertyValueChangedWithData ChildPropertyValueChangedDelegate; /** Called when the property is reset to default */ FPropertyResetToDefaultEvent PropertyResetToDefaultEvent; /** The property being displayed/edited. */ TWeakFieldPtr Property; /** Offset to the property data within either a fixed array or a dynamic array */ int32 ArrayOffset; /** The index of the property if it is inside an array, set, or map (internally, we'll use set/map helpers that store element indices in an array) */ int32 ArrayIndex; /** Safety Value representing Depth in the property tree used to stop diabolical topology cases * -1 = No limit on children * 0 = No more children are allowed. Do not process child nodes * >0 = A limit has been set by the property and will tick down for successive children */ int32 MaxChildDepthAllowed; /** * Used for flags to determine if the node is seen (if not seen and never been created, don't create it on display) */ EPropertyNodeFlags::Type PropertyNodeFlags; /** If true, children of this node will be rebuilt next tick. */ bool bRebuildChildrenRequested; /** Set to true when RebuildChildren is called on the node */ bool bChildrenRebuilt; /** Set to true when we want to ignore CPF_InstancedReference */ bool bIgnoreInstancedReference; /** If true, DestroyTree() has been called on the node. */ bool bIsDestroyed = false; /** An array of restrictions limiting this property's potential values in property editors.*/ TArray> Restrictions; /** Optional reference to a tree node that is displaying this property */ TWeakPtr TreeNode; /** * Stores metadata for this instance of the property (in contrast * to regular metadata, which is stored per-class) */ TMap InstanceMetaData; /** * The property path for this property */ FString PropertyPath; /** Edit condition expression used to determine if this property editor can modify its property */ TSharedPtr EditConditionExpression; TSharedPtr EditConditionContext; /** * Cached state of flags that are expensive to update * These update when values are changed in the details panel */ mutable bool bIsEditConst; // Includes EditCondition state mutable bool bIsEditConstWithoutCondition; // Ignores EditCondition state mutable bool bUpdateEditConstState; mutable int32 UpdateEditConstStateEpoch; mutable bool bDiffersFromDefault; mutable bool bUpdateDiffersFromDefault; mutable int32 UpdateDiffersFromDefaultEpoch; }; class FComplexPropertyNode : public FPropertyNode { public: enum EPropertyType { EPT_Object, EPT_StandaloneStructure, }; FComplexPropertyNode() : FPropertyNode() {} virtual ~FComplexPropertyNode() {} virtual FComplexPropertyNode* AsComplexNode() override { return this; } virtual const FComplexPropertyNode* AsComplexNode() const override { return this; } virtual FStructurePropertyNode* AsStructureNode() { return nullptr; } virtual const FStructurePropertyNode* AsStructureNode() const { return nullptr; } virtual void SetDisplayNameOverride(const FText& InDisplayNameOverride) override; virtual FText GetDisplayName() const override; virtual UStruct* GetBaseStructure() = 0; virtual const UStruct* GetBaseStructure() const = 0; // Returns the base struct as well as any sidecar data structs virtual TArray GetAllStructures() = 0; virtual TArray GetAllStructures() const = 0; virtual int32 GetInstancesNum() const = 0; virtual uint8* GetMemoryOfInstance(int32 Index) const = 0; /** * Returns a pointer to the stored value of InProperty on InParentNode's Index'th instance. */ virtual uint8* GetValuePtrOfInstance(int32 Index, const FProperty* InProperty, const FPropertyNode* InParentNode) const = 0; virtual TWeakObjectPtr GetInstanceAsUObject(int32 Index) const = 0; virtual EPropertyType GetPropertyType() const = 0; virtual void Disconnect() = 0; /** Generates a single child from the provided property name. Any existing children are destroyed */ virtual TSharedPtr GenerateSingleChild(FName ChildPropertyName) = 0; private: /** Display name to use instead of the fully qualified name */ FText DisplayNameOverride; };