// Copyright Epic Games, Inc. All Rights Reserved. #include "DetailPropertyRow.h" #include "CategoryPropertyNode.h" #include "CustomChildBuilder.h" #include "DetailCategoryGroupNode.h" #include "DetailItemNode.h" #include "DetailWidgetRow.h" #include "ItemPropertyNode.h" #include "ObjectPropertyNode.h" #include "ObjectPropertyNode.h" #include "PropertyCustomizationHelpers.h" #include "PropertyEditorHelpers.h" #include "StructurePropertyNode.h" #include "Modules/ModuleManager.h" #include "Widgets/Layout/SSpacer.h" #define LOCTEXT_NAMESPACE "DetailPropertyRow" FDetailPropertyRow::FDetailPropertyRow(TSharedPtr InPropertyNode, TSharedRef InParentCategory, TSharedPtr InExternalRootNode) : CustomIsEnabledAttrib( true ) , PropertyNode( InPropertyNode ) , ParentCategory( InParentCategory ) , ExternalRootNode( InExternalRootNode ) , bShowPropertyButtons( true ) , bShowCustomPropertyChildren( true ) , bForceAutoExpansion( false ) , bCachedCustomTypeInterface(false) { PropertyHandle = InParentCategory->GetParentLayoutImpl().GetPropertyHandle(PropertyNode); if (PropertyNode.IsValid()) { TSharedRef PropertyNodeRef = PropertyNode.ToSharedRef(); const TSharedRef Utilities = InParentCategory->GetParentLayoutImpl().GetPropertyUtilities(); if (PropertyNode->AsCategoryNode() == nullptr) { MakePropertyEditor(PropertyNodeRef, Utilities, PropertyEditor); } if (PropertyNode->AsObjectNode() && ExternalRootNode.IsValid()) { // We are showing an entirely different object inline. Generate a layout for it now. if (IDetailsViewPrivate* DetailsView = InParentCategory->GetDetailsView()) { ExternalObjectLayout = MakeShared(); DetailsView->UpdateSinglePropertyMap(InExternalRootNode, *ExternalObjectLayout, true); } } if (PropertyNode->GetPropertyKeyNode().IsValid()) { TSharedPtr FoundPropertyCustomisation = GetPropertyCustomization(PropertyNode->GetPropertyKeyNode().ToSharedRef(), ParentCategory.Pin().ToSharedRef()); bool bInlineRow = FoundPropertyCustomisation != nullptr ? FoundPropertyCustomisation->ShouldInlineKey() : false; static FName InlineKeyMeta("ForceInlineRow"); bInlineRow |= InPropertyNode->GetParentNode()->GetProperty()->HasMetaData(InlineKeyMeta); // Only create the property editor if it's not a struct or if it requires to be inlined (and has customization) if (!NeedsKeyNode(PropertyNodeRef, InParentCategory) || (bInlineRow && FoundPropertyCustomisation != nullptr)) { CachedKeyCustomTypeInterface = FoundPropertyCustomisation; MakePropertyEditor(PropertyNode->GetPropertyKeyNode().ToSharedRef(), Utilities, PropertyKeyEditor); } } } } bool FDetailPropertyRow::NeedsKeyNode(TSharedRef InPropertyNode, TSharedRef InParentCategory) { FStructProperty* KeyStructProp = CastField(InPropertyNode->GetPropertyKeyNode()->GetProperty()); return KeyStructProp != nullptr; } IDetailPropertyRow& FDetailPropertyRow::DisplayName( const FText& InDisplayName ) { if (PropertyNode.IsValid()) { PropertyNode->SetDisplayNameOverride( InDisplayName ); } return *this; } IDetailPropertyRow& FDetailPropertyRow::ToolTip( const FText& InToolTip ) { if (PropertyNode.IsValid()) { PropertyNode->SetToolTipOverride( InToolTip ); } return *this; } IDetailPropertyRow& FDetailPropertyRow::ShowPropertyButtons( bool bInShowPropertyButtons ) { bShowPropertyButtons = bInShowPropertyButtons; return *this; } IDetailPropertyRow& FDetailPropertyRow::EditCondition( TAttribute EditConditionValue, FOnBooleanValueChanged OnEditConditionValueChanged ) { CustomEditConditionValue = EditConditionValue; CustomEditConditionValueChanged = OnEditConditionValueChanged; return *this; } IDetailPropertyRow& FDetailPropertyRow::IsEnabled( TAttribute InIsEnabled ) { CustomIsEnabledAttrib = InIsEnabled; return *this; } IDetailPropertyRow& FDetailPropertyRow::ShouldAutoExpand(bool bInForceExpansion) { bForceAutoExpansion = bInForceExpansion; return *this; } IDetailPropertyRow& FDetailPropertyRow::Visibility( TAttribute Visibility ) { PropertyVisibility = Visibility; return *this; } IDetailPropertyRow& FDetailPropertyRow::OverrideResetToDefault(const FResetToDefaultOverride& ResetToDefault) { CustomResetToDefault = ResetToDefault; return *this; } IDetailPropertyRow& FDetailPropertyRow::DragDropHandler(TSharedPtr InDragDropHandler) { CustomDragDropHandler = InDragDropHandler; return *this; } void FDetailPropertyRow::GetDefaultWidgets( TSharedPtr& OutNameWidget, TSharedPtr& OutValueWidget, bool bAddWidgetDecoration ) { FDetailWidgetRow Row; GetDefaultWidgets(OutNameWidget, OutValueWidget, Row, bAddWidgetDecoration); } void FDetailPropertyRow::GetDefaultWidgets( TSharedPtr& OutNameWidget, TSharedPtr& OutValueWidget, FDetailWidgetRow& Row, bool bAddWidgetDecoration ) { TSharedPtr CustomTypeRow; TSharedPtr& CustomTypeInterface = GetTypeInterface(); if ( CustomTypeInterface.IsValid() ) { CustomTypeRow = MakeShareable(new FDetailWidgetRow); CustomTypeInterface->CustomizeHeader(PropertyHandle.ToSharedRef(), *CustomTypeRow, *this); } SetWidgetRowProperties(Row); MakeNameOrKeyWidget(Row, CustomTypeRow); MakeValueWidget(Row, CustomTypeRow, bAddWidgetDecoration); OutNameWidget = Row.NameWidget.Widget; OutValueWidget = Row.ValueWidget.Widget; } bool FDetailPropertyRow::HasColumns() const { // Regular properties always have columns return !CustomPropertyWidget.IsValid() || CustomPropertyWidget->HasColumns(); } bool FDetailPropertyRow::ShowOnlyChildren() const { return bForceShowOnlyChildren || (PropertyTypeLayoutBuilder.IsValid() && CustomPropertyWidget.IsValid() && !CustomPropertyWidget->HasAnyContent()); } bool FDetailPropertyRow::RequiresTick() const { return PropertyVisibility.IsBound() || (PropertyEditor.IsValid() && PropertyEditor->IsOnlyVisibleWhenEditConditionMet()); } FDetailWidgetRow& FDetailPropertyRow::CustomWidget( bool bShowChildren ) { bShowCustomPropertyChildren = bShowChildren; CustomPropertyWidget = MakeShareable( new FDetailWidgetRow ); return *CustomPropertyWidget; } FDetailWidgetDecl* FDetailPropertyRow::CustomNameWidget() { return CustomPropertyWidget.IsValid() ? &CustomPropertyWidget->NameContent() : nullptr; } FDetailWidgetDecl* FDetailPropertyRow::CustomValueWidget() { return CustomPropertyWidget.IsValid() ? &CustomPropertyWidget->ValueContent() : nullptr; } TSharedPtr FDetailPropertyRow::GetThumbnailPool() const { TSharedPtr ParentCategoryPinned = ParentCategory.Pin(); return ParentCategoryPinned.IsValid() ? ParentCategoryPinned->GetParentLayout().GetThumbnailPool() : NULL; } TSharedPtr FDetailPropertyRow::GetPropertyUtilities() const { TSharedPtr ParentCategoryPinned = ParentCategory.Pin(); if (ParentCategoryPinned.IsValid() && ParentCategoryPinned->IsParentLayoutValid()) { return ParentCategoryPinned->GetParentLayout().GetPropertyUtilities(); } return nullptr; } FDetailWidgetRow FDetailPropertyRow::GetWidgetRow() { if( HasColumns() ) { FDetailWidgetRow Row; SetWidgetRowProperties(Row); MakeNameOrKeyWidget( Row, CustomPropertyWidget ); MakeValueWidget( Row, CustomPropertyWidget ); return Row; } else { return *CustomPropertyWidget; } } static bool IsHeaderRowRequired(const TSharedPtr& PropertyHandle) { TSharedPtr ParentHandle = PropertyHandle->GetParentHandle(); while (ParentHandle.IsValid()) { if (ParentHandle->AsMap().IsValid()) { return true; } ParentHandle = ParentHandle->GetParentHandle(); } return false; } static void FixEmptyHeaderRowInContainers(const TSharedPtr& PropertyHandle, const TSharedPtr& HeaderRow) { if (IsHeaderRowRequired(PropertyHandle)) { if (!HeaderRow->HasAnyContent()) { if (!HeaderRow->HasNameContent()) { HeaderRow->NameContent() [ PropertyHandle->CreatePropertyNameWidget() ]; } if (!HeaderRow->HasValueContent()) { HeaderRow->ValueContent() [ PropertyHandle->CreatePropertyValueWidget(false) ]; } } } } void FDetailPropertyRow::OnItemNodeInitialized( TSharedRef InParentCategory, const TAttribute& InIsParentEnabled, TSharedPtr InParentGroup) { IsParentEnabled = InIsParentEnabled; TSharedPtr& CustomTypeInterface = GetTypeInterface(); // Don't customize the user already customized if (!CustomPropertyWidget.IsValid() && CustomTypeInterface.IsValid()) { CustomPropertyWidget = MakeShareable(new FDetailWidgetRow); CustomTypeInterface->CustomizeHeader(PropertyHandle.ToSharedRef(), *CustomPropertyWidget, *this); FixEmptyHeaderRowInContainers(PropertyHandle, CustomPropertyWidget); // set initial value of enabled attribute to settings from struct customization if (CustomPropertyWidget->IsEnabledAttr.IsSet()) { CustomIsEnabledAttrib = CustomPropertyWidget->IsEnabledAttr; } // set initial value of auto-expand from struct customization if (CustomPropertyWidget->ForceAutoExpansion.IsSet()) { bForceAutoExpansion = CustomPropertyWidget->ForceAutoExpansion.GetValue(); } } if( bShowCustomPropertyChildren && CustomTypeInterface.IsValid() ) { PropertyTypeLayoutBuilder = MakeShareable(new FCustomChildrenBuilder(InParentCategory, InParentGroup)); /** Does this row pass its custom reset behavior to its children? */ if (CustomResetToDefault.IsSet() && CustomResetToDefault->PropagatesToChildren()) { PropertyTypeLayoutBuilder->OverrideResetChildrenToDefault(CustomResetToDefault.GetValue()); } CustomTypeInterface->CustomizeChildren(PropertyHandle.ToSharedRef(), *PropertyTypeLayoutBuilder, *this); } } void FDetailPropertyRow::OnGenerateChildren( FDetailNodeList& OutChildren ) { if (PropertyNode->AsCategoryNode() && PropertyNode->GetParentNode() && !PropertyNode->GetParentNode()->AsObjectNode()) { // This is a sub-category. Populate from SubCategory builder TSharedRef ParentCategoryRef = ParentCategory.Pin().ToSharedRef(); FDetailLayoutBuilderImpl& LayoutBuilder = ParentCategoryRef->GetParentLayoutImpl(); TSharedPtr MyCategory = LayoutBuilder.GetSubCategoryImpl(PropertyNode->AsCategoryNode()->GetCategoryName()); if(MyCategory.IsValid()) { MyCategory->GenerateLayout(); // Ignore the header of the category by just getting the categories children directly. We are the header in this case. // Also ignore visibility here as we dont have a filter yet and the children will be filtered later anyway const bool bIgnoreVisibility = true; const bool bIgnoreAdvancedDropdown = true; MyCategory->GetGeneratedChildren(OutChildren, bIgnoreVisibility, bIgnoreAdvancedDropdown); } else { // Fall back to the default if we can't find the category implementation GenerateChildrenForPropertyNode(PropertyNode, OutChildren); } } else if (PropertyNode->AsCategoryNode() || PropertyNode->GetProperty() || ExternalObjectLayout.IsValid()) { GenerateChildrenForPropertyNode( PropertyNode, OutChildren ); } } void FDetailPropertyRow::GenerateChildrenForPropertyNode( TSharedPtr& RootPropertyNode, FDetailNodeList& OutChildren ) { // Children should be disabled if we are disabled TAttribute ParentEnabledState = TAttribute::CreateSP(this, &FDetailPropertyRow::GetEnabledState); if( PropertyTypeLayoutBuilder.IsValid() && bShowCustomPropertyChildren ) { const TArray< FDetailLayoutCustomization >& ChildRows = PropertyTypeLayoutBuilder->GetChildCustomizations(); for( int32 ChildIndex = 0; ChildIndex < ChildRows.Num(); ++ChildIndex ) { TSharedRef ChildNodeItem = MakeShareable( new FDetailItemNode( ChildRows[ChildIndex], ParentCategory.Pin().ToSharedRef(), ParentEnabledState ) ); ChildNodeItem->Initialize(); OutChildren.Add( ChildNodeItem ); } } else if (ExternalObjectLayout.IsValid() && ExternalObjectLayout->DetailLayout->HasDetails()) { OutChildren.Append(ExternalObjectLayout->DetailLayout->GetAllRootTreeNodes()); } else if ((bShowCustomPropertyChildren || !CustomPropertyWidget.IsValid()) && RootPropertyNode->GetNumChildNodes() > 0) { TSharedRef ParentCategoryRef = ParentCategory.Pin().ToSharedRef(); IDetailLayoutBuilder& LayoutBuilder = ParentCategoryRef->GetParentLayout(); FProperty* ParentProperty = RootPropertyNode->GetProperty(); const bool bStructProperty = ParentProperty && ParentProperty->IsA(); const bool bMapProperty = ParentProperty && ParentProperty->IsA(); const bool bSetProperty = ParentProperty && ParentProperty->IsA(); for( int32 ChildIndex = 0; ChildIndex < RootPropertyNode->GetNumChildNodes(); ++ChildIndex ) { TSharedPtr ChildNode = RootPropertyNode->GetChildNode(ChildIndex); if (!LayoutBuilder.IsPropertyPathAllowed(ChildNode->GetPropertyPath())) { ChildNode->SetNodeFlags( EPropertyNodeFlags::RequiresValidation, false); ChildNode->SetNodeFlags( EPropertyNodeFlags::IsBeingFiltered | EPropertyNodeFlags::SkipChildValidation, true); continue; } if( ChildNode.IsValid() && ChildNode->HasNodeFlags( EPropertyNodeFlags::IsCustomized ) == 0 ) { if( ChildNode->AsObjectNode() ) { // Skip over object nodes and generate their children. Object nodes are not visible GenerateChildrenForPropertyNode( ChildNode, OutChildren ); } // Only struct children can have custom visibility that is different from their parent. else if ( !bStructProperty || LayoutBuilder.IsPropertyVisible(FPropertyAndParent(ChildNode.ToSharedRef())) ) { TArray> PropNodes; bool bHasKeyNode = false; // Create and initialize the child first FDetailLayoutCustomization Customization; Customization.PropertyRow = MakeShareable(new FDetailPropertyRow(ChildNode, ParentCategoryRef)); if (CustomResetToDefault.IsSet() && CustomResetToDefault->PropagatesToChildren()) { Customization.PropertyRow->OverrideResetToDefault(CustomResetToDefault.GetValue()); } TSharedRef ChildNodeItem = MakeShareable(new FDetailItemNode(Customization, ParentCategoryRef, ParentEnabledState)); ChildNodeItem->Initialize(); if ( ChildNode->GetPropertyKeyNode().IsValid() ) { // If the child has a key property, only create a second node for the key if the child did not already create a property // editor for it if ( !Customization.PropertyRow->PropertyKeyEditor.IsValid() ) { FDetailLayoutCustomization KeyCustom; KeyCustom.PropertyRow = MakeShareable(new FDetailPropertyRow(ChildNode->GetPropertyKeyNode(), ParentCategoryRef)); TSharedRef KeyNodeItem = MakeShareable(new FDetailItemNode(KeyCustom, ParentCategoryRef, ParentEnabledState)); KeyNodeItem->Initialize(); PropNodes.Add(KeyNodeItem); bHasKeyNode = true; } } // Add the child node PropNodes.Add(ChildNodeItem); // For set properties, set the name override to match the index if (bSetProperty) { ChildNode->SetDisplayNameOverride(FText::AsNumber(ChildIndex)); } if (bMapProperty && bHasKeyNode) { // Group the key/value nodes for map properties static FText KeyValueGroupNameFormat = LOCTEXT("KeyValueGroupName", "Element {0}"); FText KeyValueGroupName = FText::Format(KeyValueGroupNameFormat, ChildIndex); TSharedRef KeyValueGroupNode = MakeShared(FName(*KeyValueGroupName.ToString()), ParentCategoryRef); KeyValueGroupNode->SetChildren(PropNodes); KeyValueGroupNode->SetShowBorder(false); KeyValueGroupNode->SetHasSplitter(true); OutChildren.Add(KeyValueGroupNode); } else { OutChildren.Append(PropNodes); } } } } } } FName FDetailPropertyRow::GetRowName() const { if (HasExternalProperty()) { if (GetCustomExpansionId() != NAME_None) { return GetCustomExpansionId(); } else if (FProperty* ExternalRootProperty = ExternalRootNode->GetProperty()) { return ExternalRootProperty->GetFName(); } } if (GetPropertyNode()) { if (FProperty* Property = GetPropertyNode()->GetProperty()) { return Property->GetFName(); } } return NAME_None; } TSharedRef FDetailPropertyRow::MakePropertyEditor(const TSharedRef& InPropertyNode, const TSharedRef& PropertyUtilities, TSharedPtr& InEditor ) { if( !InEditor.IsValid() ) { InEditor = FPropertyEditor::Create( InPropertyNode, PropertyUtilities ); } return InEditor.ToSharedRef(); } TSharedPtr FDetailPropertyRow::GetPropertyCustomization(const TSharedRef& InPropertyNode, const TSharedRef& InParentCategory) { TSharedPtr CustomInterface; if (!PropertyEditorHelpers::IsStaticArray(*InPropertyNode)) { FProperty* Property = InPropertyNode->GetProperty(); TSharedPtr PropHandle = InParentCategory->GetParentLayoutImpl().GetPropertyHandle(InPropertyNode); static FName NAME_PropertyEditor("PropertyEditor"); FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked(NAME_PropertyEditor); FPropertyTypeLayoutCallback LayoutCallback = PropertyEditorModule.GetPropertyTypeCustomization(Property, *PropHandle, InParentCategory->GetCustomPropertyTypeLayoutMap() ); if (LayoutCallback.IsValid()) { if (PropHandle->IsValidHandle()) { CustomInterface = LayoutCallback.GetCustomizationInstance(); } } } return CustomInterface; } void FDetailPropertyRow::MakeExternalPropertyRowCustomization(TSharedPtr StructData, FName PropertyName, TSharedRef ParentCategory, FDetailLayoutCustomization& OutCustomization, const FAddPropertyParams& Parameters) { TSharedRef RootPropertyNode = MakeShared(); //SET RootPropertyNode->SetStructure(StructData); FPropertyNodeInitParams InitParams; InitParams.ParentNode = nullptr; InitParams.Property = nullptr; InitParams.ArrayOffset = 0; InitParams.ArrayIndex = INDEX_NONE; InitParams.bForceHiddenPropertyVisibility = Parameters.ShouldForcePropertyVisible() || FPropertySettings::Get().ShowHiddenProperties(); InitParams.bCreateCategoryNodes = false; InitParams.bAllowChildren = false; Parameters.OverrideAllowChildren(InitParams.bAllowChildren); Parameters.OverrideCreateCategoryNodes(InitParams.bCreateCategoryNodes); RootPropertyNode->InitNode(InitParams); ParentCategory->GetParentLayoutImpl().AddExternalRootPropertyNode(RootPropertyNode); if (PropertyName != NAME_None) { RootPropertyNode->RebuildChildren(); for (int32 ChildIdx = 0; ChildIdx < RootPropertyNode->GetNumChildNodes(); ++ChildIdx) { TSharedPtr< FPropertyNode > PropertyNode = RootPropertyNode->GetChildNode(ChildIdx); if (FProperty* Property = PropertyNode->GetProperty()) { if (Property->GetFName() == PropertyName) { OutCustomization.PropertyRow = MakeShareable(new FDetailPropertyRow(PropertyNode, ParentCategory, RootPropertyNode)); OutCustomization.PropertyRow->SetCustomExpansionId(Parameters.GetUniqueId()); break; } } } } else { static const FName PropertyEditorModuleName("PropertyEditor"); FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked(PropertyEditorModuleName); // Make a "fake" struct property to represent the entire struct FStructProperty* StructProperty = PropertyEditorModule.RegisterStructOnScopeProperty(StructData.ToSharedRef()); // Generate a node for the struct TSharedPtr ItemNode = MakeShared(); FPropertyNodeInitParams ItemNodeInitParams; ItemNodeInitParams.ParentNode = RootPropertyNode; ItemNodeInitParams.Property = StructProperty; ItemNodeInitParams.ArrayOffset = 0; ItemNodeInitParams.ArrayIndex = INDEX_NONE; ItemNodeInitParams.bAllowChildren = true; ItemNodeInitParams.bForceHiddenPropertyVisibility = Parameters.ShouldForcePropertyVisible() || FPropertySettings::Get().ShowHiddenProperties(); ItemNodeInitParams.bCreateCategoryNodes = false; ItemNode->InitNode(ItemNodeInitParams); RootPropertyNode->AddChildNode(ItemNode); OutCustomization.PropertyRow = MakeShareable(new FDetailPropertyRow(ItemNode, ParentCategory, RootPropertyNode)); OutCustomization.PropertyRow->SetCustomExpansionId(Parameters.GetUniqueId()); } } void FDetailPropertyRow::MakeExternalPropertyRowCustomization(const TArray& InObjects, FName PropertyName, TSharedRef ParentCategory, struct FDetailLayoutCustomization& OutCustomization, const FAddPropertyParams& Parameters) { TSharedRef RootPropertyNode = MakeShared(); for (UObject* Object : InObjects) { RootPropertyNode->AddObject(Object); } FPropertyNodeInitParams InitParams; InitParams.ParentNode = nullptr; InitParams.Property = nullptr; InitParams.ArrayOffset = 0; InitParams.ArrayIndex = INDEX_NONE; InitParams.bAllowChildren = false; InitParams.bForceHiddenPropertyVisibility = Parameters.ShouldForcePropertyVisible() || FPropertySettings::Get().ShowHiddenProperties(); InitParams.bCreateCategoryNodes = PropertyName == NAME_None; Parameters.OverrideAllowChildren(InitParams.bAllowChildren); Parameters.OverrideCreateCategoryNodes(InitParams.bCreateCategoryNodes); RootPropertyNode->InitNode(InitParams); ParentCategory->GetParentLayoutImpl().AddExternalRootPropertyNode(RootPropertyNode); if (PropertyName != NAME_None) { TSharedPtr PropertyNode = RootPropertyNode->GenerateSingleChild(PropertyName); if(PropertyNode.IsValid()) { RootPropertyNode->AddChildNode(PropertyNode); PropertyNode->RebuildChildren(); OutCustomization.PropertyRow = MakeShared(PropertyNode, ParentCategory, RootPropertyNode); OutCustomization.PropertyRow->SetCustomExpansionId(Parameters.GetUniqueId()); } } else { OutCustomization.PropertyRow = MakeShared(RootPropertyNode, ParentCategory, RootPropertyNode); OutCustomization.PropertyRow->SetCustomExpansionId(Parameters.GetUniqueId()); } } EVisibility FDetailPropertyRow::GetPropertyVisibility() const { if (PropertyEditor.IsValid() && PropertyEditor->IsOnlyVisibleWhenEditConditionMet() && !PropertyEditor->IsEditConditionMet()) { return EVisibility::Collapsed; } return PropertyVisibility.Get(); } bool FDetailPropertyRow::HasEditCondition() const { return (PropertyEditor.IsValid() && PropertyEditor->HasEditCondition()) || CustomEditConditionValue.IsSet(); } bool FDetailPropertyRow::GetEnabledState() const { bool Result = IsParentEnabled.Get(true); Result = Result && CustomIsEnabledAttrib.Get(true); if (HasEditCondition()) { if (CustomEditConditionValue.IsSet()) { Result = Result && CustomEditConditionValue.Get(); } else if (PropertyEditor.IsValid()) { Result = Result && PropertyEditor->IsEditConditionMet(); } } return Result; } TSharedPtr& FDetailPropertyRow::GetTypeInterface() { if (!bCachedCustomTypeInterface) { if (PropertyNode.IsValid() && ParentCategory.IsValid()) { CachedCustomTypeInterface = GetPropertyCustomization(PropertyNode.ToSharedRef(), ParentCategory.Pin().ToSharedRef()); } bCachedCustomTypeInterface = true; } return CachedCustomTypeInterface; } bool FDetailPropertyRow::GetForceAutoExpansion() const { return bForceAutoExpansion; } static void TogglePropertyEditorEditCondition(bool bValue, TWeakPtr PropertyEditorWeak) { TSharedPtr PropertyEditorPtr = PropertyEditorWeak.Pin(); if (PropertyEditorPtr.IsValid() && PropertyEditorPtr->IsEditConditionMet() != bValue) { PropertyEditorPtr->ToggleEditConditionState(); } } static void ExecuteCustomEditConditionToggle(bool bValue, FOnBooleanValueChanged CustomEditConditionToggle, TWeakPtr PropertyEditorWeak) { CustomEditConditionToggle.ExecuteIfBound(bValue); TSharedPtr PropertyEditorPtr = PropertyEditorWeak.Pin(); if (PropertyEditorPtr.IsValid()) { PropertyEditorPtr->GetPropertyNode()->InvalidateCachedState(); } } void FDetailPropertyRow::SetWidgetRowProperties(FDetailWidgetRow& Row) const { // set edit condition handlers - use customized if provided TAttribute EditConditionValue = CustomEditConditionValue; if (!EditConditionValue.IsSet() && PropertyEditor.IsValid()) { EditConditionValue = TAttribute(PropertyEditor.ToSharedRef(), &FPropertyEditor::IsEditConditionMet); } FOnBooleanValueChanged OnEditConditionValueChanged; if (CustomEditConditionValueChanged.IsBound()) { TWeakPtr PropertyEditorWeak = PropertyEditor; OnEditConditionValueChanged = FOnBooleanValueChanged::CreateStatic(&ExecuteCustomEditConditionToggle, CustomEditConditionValueChanged, PropertyEditorWeak); } else if (PropertyEditor.IsValid() && PropertyEditor->SupportsEditConditionToggle()) { TWeakPtr PropertyEditorWeak = PropertyEditor; OnEditConditionValueChanged = FOnBooleanValueChanged::CreateStatic(&TogglePropertyEditorEditCondition, PropertyEditorWeak); } Row.EditCondition(EditConditionValue, OnEditConditionValueChanged); Row.IsEnabled(CustomIsEnabledAttrib); Row.CustomResetToDefault = CustomResetToDefault; Row.CustomDragDropHandler = CustomDragDropHandler; Row.PropertyHandles.Add(GetPropertyHandle()); // set custom actions and reset to default if (CustomPropertyWidget.IsValid()) { Row.CopyMenuAction = CustomPropertyWidget->CopyMenuAction; Row.PasteMenuAction = CustomPropertyWidget->PasteMenuAction; Row.CustomMenuItems = CustomPropertyWidget->CustomMenuItems; if (CustomPropertyWidget->CustomResetToDefault.IsSet()) { ensureMsgf(!CustomResetToDefault.IsSet(), TEXT("Duplicate reset to default handlers set on both FDetailPropertyRow and CustomWidget()!")); Row.CustomResetToDefault = CustomPropertyWidget->CustomResetToDefault; } } } void FDetailPropertyRow::MakeNameOrKeyWidget( FDetailWidgetRow& Row, const TSharedPtr InCustomRow ) const { EVerticalAlignment VerticalAlignment = VAlign_Center; EHorizontalAlignment HorizontalAlignment = HAlign_Fill; // We will only use key widgets for non-struct keys const bool bHasKeyNode = PropertyKeyEditor.IsValid() && !PropertyHandle->HasMetaData(TEXT("ReadOnlyKeys")); if (!bHasKeyNode && InCustomRow.IsValid()) { VerticalAlignment = InCustomRow->NameWidget.VerticalAlignment; HorizontalAlignment = InCustomRow->NameWidget.HorizontalAlignment; } TAttribute IsEnabledAttrib = TAttribute::CreateSP( this, &FDetailPropertyRow::GetEnabledState ); TSharedRef NameHorizontalBox = SNew(SHorizontalBox) .Clipping(EWidgetClipping::OnDemand); TSharedPtr NameWidget = SNullWidget::NullWidget; // Key nodes take precedence over custom rows if (bHasKeyNode) { // Does this key have a custom type, use it if (CachedKeyCustomTypeInterface) { // Create a widget that will properly represent the key const TSharedPtr CustomTypeWidget = MakeShared(); CachedKeyCustomTypeInterface->CustomizeHeader(PropertyKeyEditor->GetPropertyHandle(), *CustomTypeWidget, const_cast(*this)); NameWidget = CustomTypeWidget->ValueWidget.Widget; } else { const TSharedRef PropertyUtilities = ParentCategory.Pin()->GetParentLayoutImpl().GetPropertyUtilities(); NameWidget = SNew(SPropertyValueWidget, PropertyKeyEditor, PropertyUtilities) .IsEnabled(IsEnabledAttrib) .ShowPropertyButtons(false); } } else if (InCustomRow.IsValid()) { NameWidget = SNew( SBox ) .IsEnabled( IsEnabledAttrib ) [ InCustomRow->NameWidget.Widget ]; } else if (PropertyEditor.IsValid()) { NameWidget = SNew( SPropertyNameWidget, PropertyEditor ) .IsEnabled( IsEnabledAttrib ); } SHorizontalBox::FSlot* SlotPointer = nullptr; NameHorizontalBox->AddSlot() .Expose(SlotPointer) [ NameWidget.ToSharedRef() ]; if (bHasKeyNode) { SlotPointer->SetPadding(FMargin(0.0f, 0.0f, 2.0f, 0.0f)); } else if (InCustomRow.IsValid()) { // Allow custom name slots to fill all of the area. Eg., the user adds a SHorizontalBox with left and right align slots. SlotPointer->SetFillWidth(1.0f); } else { SlotPointer->SetAutoWidth(); } Row.NameContent() .HAlign( HorizontalAlignment ) .VAlign( VerticalAlignment ) [ NameHorizontalBox ]; } void FDetailPropertyRow::MakeValueWidget( FDetailWidgetRow& Row, const TSharedPtr InCustomRow, bool bAddWidgetDecoration ) const { EVerticalAlignment VerticalAlignment = VAlign_Center; EHorizontalAlignment HorizontalAlignment = HAlign_Left; TOptional MinWidth; TOptional MaxWidth; TAttribute IsEnabledAttrib = TAttribute::CreateSP( this, &FDetailPropertyRow::GetEnabledState ); TSharedRef ValueWidget = SNew( SHorizontalBox ) .IsEnabled( IsEnabledAttrib ); if (InCustomRow.IsValid()) { VerticalAlignment = InCustomRow->ValueWidget.VerticalAlignment; HorizontalAlignment = InCustomRow->ValueWidget.HorizontalAlignment; MinWidth = InCustomRow->ValueWidget.MinWidth; MaxWidth = InCustomRow->ValueWidget.MaxWidth; ValueWidget->AddSlot() [ InCustomRow->ValueWidget.Widget ]; Row .ExtensionContent() [ InCustomRow->ExtensionWidget.Widget ]; } else if (PropertyEditor.IsValid()) { TSharedPtr PropertyValue; ValueWidget->AddSlot() [ SAssignNew( PropertyValue, SPropertyValueWidget, PropertyEditor, GetPropertyUtilities() ) .ShowPropertyButtons( false ) // We handle this ourselves ]; MinWidth = PropertyValue->GetMinDesiredWidth(); MaxWidth = PropertyValue->GetMaxDesiredWidth(); } if (bAddWidgetDecoration && PropertyEditor.IsValid()) { if (bShowPropertyButtons) { TArray< TSharedRef > RequiredButtons; PropertyEditorHelpers::MakeRequiredPropertyButtons( PropertyEditor.ToSharedRef(), /*OUT*/RequiredButtons ); for( int32 ButtonIndex = 0; ButtonIndex < RequiredButtons.Num(); ++ButtonIndex ) { ValueWidget->AddSlot() .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .Padding(4.0f, 1.0f, 0.0f, 1.0f) [ RequiredButtons[ButtonIndex] ]; } } // Don't add config hierarchy to container children, can't edit child properties at the hiearchy's per file level TSharedPtr ParentHandle = PropertyHandle->GetParentHandle(); bool bIsChildProperty = ParentHandle && (ParentHandle->AsArray() || ParentHandle->AsMap() || ParentHandle->AsSet()); if (!bIsChildProperty && PropertyHandle->HasMetaData(TEXT("ConfigHierarchyEditable"))) { ValueWidget->AddSlot() .AutoWidth() .VAlign(VAlign_Center) .HAlign(HAlign_Left) .Padding(4.0f, 0.0f, 4.0f, 0.0f) [ PropertyCustomizationHelpers::MakeEditConfigHierarchyButton(FSimpleDelegate::CreateSP(PropertyEditor.ToSharedRef(), &FPropertyEditor::EditConfigHierarchy)) ]; } } Row.ValueContent() .HAlign( HorizontalAlignment ) .VAlign( VerticalAlignment ) .MinDesiredWidth( MinWidth ) .MaxDesiredWidth( MaxWidth ) [ ValueWidget ]; } #undef LOCTEXT_NAMESPACE