// Copyright Epic Games, Inc. All Rights Reserved. #include "Widgets/PropertyViewer/SPropertyViewerImpl.h" #include "AdvancedWidgetsModule.h" #include "Misc/IFilter.h" #include "Misc/TextFilter.h" #include "Framework/PropertyViewer/IFieldExpander.h" #include "Framework/PropertyViewer/IFieldIterator.h" #include "Framework/PropertyViewer/INotifyHook.h" #include "Framework/PropertyViewer/PropertyPath.h" #include "Framework/PropertyViewer/PropertyValueFactory.h" #include "Framework/Views/TreeFilterHandler.h" #include "Styling/SlateIconFinder.h" #include "Widgets/Input/SSpinBox.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/PropertyViewer/SFieldName.h" #include "Widgets/Text/SInlineEditableTextBlock.h" #include "Widgets/Views/SHeaderRow.h" #include "Widgets/Views/STreeView.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SNullWidget.h" #include "UObject/UnrealType.h" #if WITH_EDITOR #include "Editor.h" #include "Editor/EditorEngine.h" #endif //WITH_EDITOR #define LOCTEXT_NAMESPACE "SPropertyViewerImpl" namespace UE::PropertyViewer::Private { /** * Column name */ static FName ColumnName_Field = "Field"; static FName ColumnName_PropertyValue = "FieldValue"; static FName ColumnName_FieldPostWidget = "FieldPostWidget"; /** * FContainer */ FContainer::FContainer(SPropertyViewer::FHandle InIdentifier, TOptional InDisplayName, const UStruct* ClassToDisplay) : Identifier(InIdentifier) , Container(ClassToDisplay) , DisplayName(InDisplayName) { } FContainer::FContainer(SPropertyViewer::FHandle InIdentifier, TOptional InDisplayName, UObject* InstanceToDisplay) : Identifier(InIdentifier) , Container(InstanceToDisplay->GetClass()) , ObjectInstance(InstanceToDisplay) , DisplayName(InDisplayName) , bIsObject(true) { } FContainer::FContainer(SPropertyViewer::FHandle InIdentifier, TOptional InDisplayName, const UScriptStruct* Struct, void* Data) : Identifier(InIdentifier) , Container(Struct) , StructInstance(Data) , DisplayName(InDisplayName) { } bool InternalIsValid(const UStruct* Container) { if (const UClass* Class = Cast(Container)) { return !Class->HasAnyClassFlags(EClassFlags::CLASS_NewerVersionExists); } else if (const UScriptStruct* ScriptStruct = Cast(Container)) { return (ScriptStruct->StructFlags & (EStructFlags::STRUCT_Trashed)) == 0; } return false; } bool FContainer::IsValid() const { const UStruct * ContainerPtr = Container.Get(); if (const UClass* Class = Cast(ContainerPtr)) { return !Class->HasAnyClassFlags(EClassFlags::CLASS_NewerVersionExists) && (!bIsObject || (ObjectInstance.Get() && ObjectInstance.Get()->GetClass() == Class)); } else if (const UScriptStruct* ScriptStruct = Cast(ContainerPtr)) { return (ScriptStruct->StructFlags & (EStructFlags::STRUCT_Trashed)) == 0; } return false; } /** * FTreeNode */ TSharedRef FTreeNode::MakeContainer(const TSharedPtr& InContainer, TOptional InDisplayName) { TSharedRef Result = MakeShared(); Result->Container = InContainer; Result->OverrideDisplayName = InDisplayName; return Result; } TSharedRef FTreeNode::MakeField(TSharedPtr InParent, const FProperty* Property, TOptional InDisplayName) { check(Property); TSharedRef Result = MakeShared(); Result->FieldOwner = Property->GetOwnerStruct(); Result->PropertyName = Property->GetFName(); Result->bIsStructProperty = CastField(Property) != nullptr; Result->bIsObjectProperty = CastField(Property) != nullptr; Result->OverrideDisplayName = InDisplayName; Result->ParentNode = InParent; InParent->ChildNodes.Add(Result); return Result; } TSharedRef FTreeNode::MakeField(TSharedPtr InParent, const UFunction* Function, TOptional InDisplayName) { check(Function); TSharedRef Result = MakeShared(); Result->FieldOwner = Function->GetOwnerClass(); Result->FunctionName = Function->GetFName(); Result->OverrideDisplayName = InDisplayName; Result->ParentNode = InParent; InParent->ChildNodes.Add(Result); return Result; } FFieldVariant FTreeNode::GetField() const { if (const UStruct* FieldOwnerPtr = FieldOwner.Get()) { if (!PropertyName.IsNone()) { return FFieldVariant(FieldOwnerPtr->FindPropertyByName(PropertyName)); } else if (!FunctionName.IsNone()) { return FFieldVariant(CastChecked(FieldOwnerPtr)->FindFunctionByName(FunctionName)); } } return FFieldVariant(); } FPropertyPath FTreeNode::GetPropertyPath() const { const FTreeNode* CurrentNode = this; FPropertyPath::FPropertyArray Properties; while (CurrentNode) { const UStruct* CurrentFieldOwner = CurrentNode->FieldOwner.Get(); if (CurrentFieldOwner && !CurrentNode->PropertyName.IsNone()) { FProperty* Property = CurrentFieldOwner->FindPropertyByName(CurrentNode->PropertyName); if (Property) { Properties.Insert(Property, 0); } else { return FPropertyPath(); } } TSharedPtr ContainerPin = CurrentNode->Container.Pin(); if (ContainerPin) { if (ContainerPin->IsObjectInstance()) { if (UObject* ObjectInstance = ContainerPin->GetObjectInstance()) { return FPropertyPath(ObjectInstance, MoveTemp(Properties)); } } else if (ContainerPin->IsScriptStructInstance()) { if (const UStruct* ContainerStruct = ContainerPin->GetStruct()) { return FPropertyPath(CastChecked(ContainerStruct), ContainerPin->GetScriptStructInstance(), MoveTemp(Properties)); } } else if (const UClass* ContainerClass = Cast(ContainerPin->GetStruct())) { return FPropertyPath(ContainerClass->GetDefaultObject(), MoveTemp(Properties)); } return FPropertyPath(); } if (TSharedPtr ParentNodePin = CurrentNode->ParentNode.Pin()) { // The property path are temporary for function if (ParentNodePin->GetField().Get()) { return FPropertyPath(); } CurrentNode = ParentNodePin.Get(); } else if (ContainerPin == nullptr) { return FPropertyPath(); } } return FPropertyPath(); } TArray FTreeNode::GetFieldPath() const { TArray Fields; const FTreeNode* CurrentNode = this; while (CurrentNode) { if (FFieldVariant Field = CurrentNode->GetField()) { Fields.Insert(Field, 0); } CurrentNode = CurrentNode->ParentNode.Pin().Get(); } return Fields; } TSharedPtr FTreeNode::GetOwnerContainer() const { TSharedPtr Result; const FTreeNode* CurrentNode = this; while(CurrentNode) { if (TSharedPtr ContainerPin = CurrentNode->Container.Pin()) { return ContainerPin; } if (TSharedPtr ParentNodePin = CurrentNode->ParentNode.Pin()) { CurrentNode = ParentNodePin.Get(); } else { ensureMsgf(false, TEXT("The tree is not owned by a container")); break; } } return TSharedPtr(); } void FTreeNode::GetFilterStrings(TArray& OutStrings) const { const UStruct* CurrentFieldOwner = FieldOwner.Get(); if (CurrentFieldOwner && !PropertyName.IsNone()) { FProperty* PropertyPtr = CurrentFieldOwner->FindPropertyByName(PropertyName); if (PropertyPtr) { OutStrings.Add(PropertyPtr->GetName()); #if WITH_EDITORONLY_DATA OutStrings.Add(PropertyPtr->GetDisplayNameText().ToString()); #endif } } if (CurrentFieldOwner && !FunctionName.IsNone()) { const UFunction* FunctionPtr = CastChecked(CurrentFieldOwner)->FindFunctionByName(FunctionName); if (FunctionPtr) { OutStrings.Add(FunctionPtr->GetName()); #if WITH_EDITORONLY_DATA OutStrings.Add(FunctionPtr->GetDisplayNameText().ToString()); #endif } } if (const TSharedPtr ContainerPtr = Container.Pin()) { if (const UStruct* StructPtr = ContainerPtr->GetStruct()) { OutStrings.Add(StructPtr->GetName()); #if WITH_EDITORONLY_DATA OutStrings.Add(StructPtr->GetDisplayNameText().ToString()); #endif } } if (GetOverrideDisplayName().IsSet()) { OutStrings.Add(GetOverrideDisplayName().GetValue().ToString()); } } TArray FTreeNode::BuildChildNodes(IFieldIterator& FieldIterator, IFieldExpander& FieldExpander, bool bSortChildNode) { TArray TickReferences; BuildChildNodesRecursive(FieldIterator, FieldExpander, bSortChildNode, 2, TickReferences); return TickReferences; } void FTreeNode::BuildChildNodesRecursive(IFieldIterator& FieldIterator, IFieldExpander& FieldExpander, bool bSortChildNode, int32 RecursiveCount, TArray& OutTickReference) { if (RecursiveCount <= 0) { return; } --RecursiveCount; ChildNodes.Reset(); const UStruct* ChildStructType = nullptr; const UStruct* CurrentFieldOwner = nullptr; FName CurrentFieldName = NAME_None; if (bIsStructProperty || bIsObjectProperty) { check(!PropertyName.IsNone()); CurrentFieldName = PropertyName; CurrentFieldOwner = FieldOwner.Get(); FProperty* PropertyPtr = CurrentFieldOwner ? CurrentFieldOwner->FindPropertyByName(PropertyName) : nullptr; if (const FStructProperty* StructProperty = CastField(PropertyPtr)) { if (FieldExpander.CanExpandScriptStruct(StructProperty)) { ChildStructType = StructProperty->Struct; } } else if (const FObjectPropertyBase* ObjectProperty = CastField(PropertyPtr)) { UObject* Instance = nullptr; if (TSharedPtr OwnerContainer = GetOwnerContainer()) { if (OwnerContainer->IsInstance()) { const FPropertyPath PropertyPath = GetPropertyPath(); if (const void* ContainerPtr = PropertyPath.GetContainerPtr()) { Instance = ObjectProperty->GetObjectPropertyValue_InContainer(ContainerPtr); } } } TOptional ClassToExpand = FieldExpander.CanExpandObject(ObjectProperty, Instance); if (ClassToExpand.IsSet()) { if (Instance) { OutTickReference.Emplace(Instance, AsShared()); } ChildStructType = ClassToExpand.GetValue(); } } } else if (!FunctionName.IsNone()) { CurrentFieldOwner = FieldOwner.Get(); CurrentFieldName = FunctionName; UFunction* FunctionPtr = CurrentFieldOwner ? CastChecked(CurrentFieldOwner)->FindFunctionByName(FunctionName) : nullptr; if (FunctionPtr) { TOptional StructToExpand = FieldExpander.GetExpandedFunction(FunctionPtr); if (StructToExpand.IsSet()) { ChildStructType = StructToExpand.GetValue(); } } } else if (const TSharedPtr ContainerPin = Container.Pin()) { ChildStructType = ContainerPin->GetStruct(); CurrentFieldName = FName(ContainerPin->GetDisplayName().Get(FText::GetEmpty()).ToString()); } if (ChildStructType) { for (const FFieldVariant& FieldIt : FieldIterator.GetFields(ChildStructType, CurrentFieldName, CurrentFieldOwner)) { if (const FProperty* PropertyIt = FieldIt.Get()) { TSharedPtr Node = MakeField(AsShared(), PropertyIt, TOptional()); Node->BuildChildNodesRecursive(FieldIterator, FieldExpander, bSortChildNode, RecursiveCount, OutTickReference); } if (const UFunction* FunctionIt = FieldIt.Get()) { TSharedPtr Node = MakeField(AsShared(), FunctionIt, TOptional()); Node->BuildChildNodesRecursive(FieldIterator, FieldExpander, bSortChildNode, RecursiveCount, OutTickReference); } } if (bSortChildNode) { ChildNodes.Sort(Sort); } } bChildGenerated = true; } bool FTreeNode::Sort(const TSharedPtr& NodeA, const TSharedPtr& NodeB) { bool bIsContainerA = NodeA->IsContainer(); bool bIsContainerB = NodeB->IsContainer(); bool bIsObjectPropertyA = NodeA->bIsObjectProperty; bool bIsObjectPropertyB = NodeB->bIsObjectProperty; bool bIsFunctionA = !NodeA->FunctionName.IsNone(); bool bIsFunctionB = !NodeB->FunctionName.IsNone(); const FName NodeStrA = bIsContainerA ? NodeA->GetContainer()->GetStruct()->GetFName() : NodeA->GetField().GetFName(); const FName NodeStrB = bIsContainerB ? NodeB->GetContainer()->GetStruct()->GetFName() : NodeB->GetField().GetFName(); if (bIsFunctionA && bIsFunctionB) { return NodeStrA.LexicalLess(NodeStrB); } if (bIsObjectPropertyA && bIsObjectPropertyB) { return NodeStrA.LexicalLess(NodeStrB); } if (bIsFunctionA) { return true; } if (bIsFunctionB) { return false; } if (bIsObjectPropertyA) { return true; } if (bIsObjectPropertyB) { return false; } return NodeStrA.LexicalLess(NodeStrB); }; /** * FPropertyViewerImpl */ FPropertyViewerImpl::FPropertyViewerImpl(const SPropertyViewer::FArguments& InArgs) { FieldIterator = InArgs._FieldIterator; FieldExpander = InArgs._FieldExpander; if (FieldIterator == nullptr) { FieldIterator = new FFieldIterator_BlueprintVisible(); bOwnFieldIterator = true; } if (FieldExpander == nullptr) { FFieldExpander_Default* DefaultFieldExpander = new FFieldExpander_Default(); DefaultFieldExpander->SetExpandObject(FFieldExpander_Default::EObjectExpandFlag::None); DefaultFieldExpander->SetExpandScriptStruct(true); FieldExpander = DefaultFieldExpander; bOwnFieldExpander = true; } NotifyHook = InArgs._NotifyHook; OnGetPreSlot = InArgs._OnGetPreSlot; OnGetPostSlot = InArgs._OnGetPostSlot; OnContextMenuOpening = InArgs._OnContextMenuOpening; OnSelectionChanged = InArgs._OnSelectionChanged; OnDoubleClicked = InArgs._OnDoubleClicked; OnDragDetected = InArgs._OnDragDetected; OnGenerateContainer = InArgs._OnGenerateContainer; PropertyVisibility = InArgs._PropertyVisibility; bSanitizeName = InArgs._bSanitizeName; bShowFieldIcon = InArgs._bShowFieldIcon; bSortChildNode = InArgs._bSortChildNode; #if WITH_EDITOR if (GEditor) { GEditor->OnBlueprintCompiled().AddRaw(this, &FPropertyViewerImpl::HandleBlueprintCompiled); FCoreUObjectDelegates::OnObjectsReplaced.AddRaw(this, &FPropertyViewerImpl::HandleReplaceViewedObjects); } #endif } FPropertyViewerImpl::~FPropertyViewerImpl() { if (bOwnFieldIterator) { delete FieldIterator; } if (bOwnFieldExpander) { delete FieldExpander; } #if WITH_EDITOR if (GEditor) { GEditor->OnBlueprintCompiled().RemoveAll(this); FCoreUObjectDelegates::OnObjectsReplaced.RemoveAll(this); } #endif } TSharedRef FPropertyViewerImpl::Construct(const SPropertyViewer::FArguments& InArgs) { SearchFilter = MakeShared(FTextFilter::FItemToStringArray::CreateSP(this, &FPropertyViewerImpl::HandleGetFilterStrings)); FilterHandler = MakeShared(); FilterHandler->SetFilter(SearchFilter.Get()); FilterHandler->SetRootItems(&TreeSource, &FilteredTreeSource); FilterHandler->SetGetChildrenDelegate(FTreeFilter::FOnGetChildren::CreateRaw(this, &FPropertyViewerImpl::HandleGetChildren)); OverrideIconColorSettings = InArgs._OverrideIconColorSettings; TSharedPtr SearchBox; if (InArgs._bShowSearchBox || InArgs._SearchBoxPreSlot.Widget != SNullWidget::NullWidget || InArgs._SearchBoxPostSlot.Widget != SNullWidget::NullWidget) { SearchBox = SNew(SHorizontalBox); if (InArgs._SearchBoxPreSlot.Widget != SNullWidget::NullWidget) { SearchBox->AddSlot() .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .Padding(0, 0, 4, 0) [ InArgs._SearchBoxPreSlot.Widget ]; } if (InArgs._bShowSearchBox) { SearchBox->AddSlot() .FillWidth(1.f) .VAlign(VAlign_Center) [ CreateSearch() ]; if (InArgs._bFocusSearchBox) { SearchBox->RegisterActiveTimer( 0.f, FWidgetActiveTimerDelegate::CreateSPLambda(this, [this](double InCurrentTime, float InDeltaTime) { if (SearchBoxWidget.IsValid()) { FWidgetPath WidgetToFocusPath; FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchBoxWidget.ToSharedRef(), WidgetToFocusPath); if(WidgetToFocusPath.IsValid()) { FSlateApplication::Get().SetKeyboardFocus(WidgetToFocusPath, EFocusCause::SetDirectly); WidgetToFocusPath.GetWindow()->SetWidgetToFocusOnActivate(SearchBoxWidget); } return EActiveTimerReturnType::Stop; } return EActiveTimerReturnType::Continue; })); } } else { SearchBox->AddSlot() .FillWidth(1.f) [ SNullWidget::NullWidget ]; } if (InArgs._SearchBoxPostSlot.Widget != SNullWidget::NullWidget) { SearchBox->AddSlot() .AutoWidth() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .Padding(4, 0, 0, 0) [ InArgs._SearchBoxPostSlot.Widget ]; } } TSharedRef ConstructedTree = SNew(SBorder) .BorderImage(FAppStyle::Get().GetBrush("Brushes.Recessed")) .Padding(0) [ CreateTree(OnGetPreSlot.IsBound(), PropertyVisibility != SPropertyViewer::EPropertyVisibility::Hidden, OnGetPostSlot.IsBound(), InArgs._SelectionMode) ]; if (SearchBox) { return SNew(SVerticalBox) + SVerticalBox::Slot() .Padding(4) .AutoHeight() [ SearchBox.ToSharedRef() ] + SVerticalBox::Slot() .FillHeight(1.0f) [ ConstructedTree ]; } else { return ConstructedTree; } } void FPropertyViewerImpl::Tick() { auto RemoveChild = [](const TSharedPtr>>& InTreeWidget, const TSharedPtr& InNodePin) { if (TSharedPtr ParentNode = InNodePin->GetParentNode()) { ParentNode->RemoveChild(); } InTreeWidget->RequestListRefresh(); }; // If the object instance is not the same as the previous frame. Rebuild the tree. for (int32 Index = NodeReferences.Num() - 1; Index >= 0; --Index) { UObject* PreviousPtr = NodeReferences[Index].Previous.Get(); TSharedPtr NodePin = NodeReferences[Index].Node.Pin(); if (PreviousPtr && NodePin) { UObject* Instance = nullptr; if (FObjectPropertyBase* ObjectProperty = CastField(NodePin->GetField().Get())) { const FPropertyPath PropertyPath = NodePin->GetPropertyPath(); if (const void* ContainerPtr = PropertyPath.GetContainerPtr()) { Instance = ObjectProperty->GetObjectPropertyValue_InContainer(ContainerPtr); } } if (Instance != PreviousPtr) { RemoveChild(TreeWidget, NodePin); NodeReferences.RemoveAtSwap(Index); } } else { if (NodePin) { RemoveChild(TreeWidget, NodePin); } NodeReferences.RemoveAtSwap(Index); } } } void FPropertyViewerImpl::AddContainer(SPropertyViewer::FHandle Identifier, TOptional DisplayName, const UStruct* Struct) { TSharedPtr NewContainer = MakeShared(Identifier, DisplayName, Struct); Containers.Add(NewContainer); AddContainerInternal(Identifier, NewContainer); } void FPropertyViewerImpl::AddContainerInstance(SPropertyViewer::FHandle Identifier, TOptional DisplayName, UObject* Object) { TSharedPtr NewContainer = MakeShared(Identifier, DisplayName, Object); Containers.Add(NewContainer); AddContainerInternal(Identifier, NewContainer); } void FPropertyViewerImpl::AddContainerInstance(SPropertyViewer::FHandle Identifier, TOptional DisplayName, const UScriptStruct* Struct, void* Data) { TSharedPtr NewContainer = MakeShared(Identifier, DisplayName, Struct, Data); Containers.Add(NewContainer); AddContainerInternal(Identifier, NewContainer); } void FPropertyViewerImpl::AddContainerInternal(SPropertyViewer::FHandle Identifier, TSharedPtr& NewContainer) { TSharedPtr NewNode = FTreeNode::MakeContainer(NewContainer, NewContainer->GetDisplayName()); NodeReferences.Append(NewNode->BuildChildNodes(*FieldIterator, *FieldExpander, bSortChildNode)); TreeSource.Add(NewNode); if (TreeWidget) { TreeWidget->SetItemExpansion(NewNode, true); } if (FilterHandler) { FilterHandler->RefreshAndFilterTree(); } } void FPropertyViewerImpl::Remove(SPropertyViewer::FHandle Identifier) { bool bRemoved = false; for (int32 Index = 0; Index < TreeSource.Num(); ++Index) { if (TSharedPtr Container = TreeSource[Index]->GetContainer()) { if (Container->GetIdentifier() == Identifier) { TreeSource.RemoveAt(Index); bRemoved = true; break; } } } for (int32 Index = 0; Index < Containers.Num(); ++Index) { if (Containers[Index]->GetIdentifier() == Identifier) { Containers.RemoveAt(Index); break; } } if (bRemoved) { if (FilterHandler) { FilterHandler->RefreshAndFilterTree(); } else { TreeWidget->RequestTreeRefresh(); } } } void FPropertyViewerImpl::RemoveAll() { bool bRemoved = TreeSource.Num() > 0 || Containers.Num() > 0; TreeSource.Reset(); Containers.Reset(); if (bRemoved) { if (FilterHandler) { FilterHandler->RefreshAndFilterTree(); } else { TreeWidget->RequestTreeRefresh(); } } } TSharedRef FPropertyViewerImpl::CreateSearch() { return SAssignNew(SearchBoxWidget, SSearchBox) .HintText(LOCTEXT("SearchHintText", "Search")) .OnTextChanged(this, &FPropertyViewerImpl::HandleSearchChanged); } TArray FPropertyViewerImpl::GetSelectedItems() const { TArray Result; TArray> SelectedItem = TreeWidget->GetSelectedItems(); for (TSharedPtr Node : SelectedItem) { if (TSharedPtr Container = Node->GetContainer()) { int32 FoundIndex = Result.IndexOfByPredicate([Container](const SPropertyViewer::FSelectedItem& Other) { return Other.Handle == Container->GetIdentifier(); }); if (FoundIndex == INDEX_NONE) { SPropertyViewer::FSelectedItem Item; Item.Handle = Container->GetIdentifier(); FoundIndex = Result.Add(Item); } Result[FoundIndex].bIsContainerSelected = true; } else { if (TSharedPtr OwnerContainer = Node->GetOwnerContainer()) { int32 FoundIndex = Result.IndexOfByPredicate([OwnerContainer](const SPropertyViewer::FSelectedItem& Other){ return Other.Handle == OwnerContainer->GetIdentifier(); }); if (FoundIndex == INDEX_NONE) { SPropertyViewer::FSelectedItem Item; Item.Handle = OwnerContainer->GetIdentifier(); FoundIndex = Result.Add(Item); } Result[FoundIndex].Fields.Add(Node->GetFieldPath()); } } } return Result; } void FPropertyViewerImpl::SetRawFilterText(const FText& InFilterText) { SetRawFilterTextInternal(InFilterText); } FText FPropertyViewerImpl::SetRawFilterTextInternal(const FText& InFilterText) { const bool bNewFilteringEnabled = !InFilterText.IsEmpty(); FilterHandler->SetIsEnabled(bNewFilteringEnabled); SearchFilter->SetRawFilterText(InFilterText); FilterHandler->RefreshAndFilterTree(); for (const TSharedPtr& Node : FilteredTreeSource) { SetHighlightTextRecursive(Node, InFilterText); } return SearchFilter->GetFilterErrorText(); } TSharedPtr FPropertyViewerImpl::FindExistingChild(const TSharedPtr& Node, TArrayView FieldPath) const { if (FieldPath.Num() == 0) { return TSharedPtr(); } for (const TSharedPtr& Child : Node->ChildNodes) { if (Child->GetField() == FieldPath[0]) { if (FieldPath.Num() == 1) { return Child; } else { return FindExistingChild(Child, FieldPath.RightChop(1)); } } } return TSharedPtr(); } void FPropertyViewerImpl::SetSelection(SPropertyViewer::FHandle Identifier, TArrayView FieldPath) { TreeWidget->ClearSelection(); for (const TSharedPtr& Node : TreeSource) { if (TSharedPtr Container = Node->GetContainer()) { if (Container->GetIdentifier() == Identifier) { if (TSharedPtr FoundNode = FindExistingChild(Node, FieldPath)) { TreeWidget->SetItemSelection(FoundNode, true); break; } } } } } TSharedRef FPropertyViewerImpl::CreateTree(bool bHasPreWidget, bool bShowPropertyValue, bool bHasPostWidget, ESelectionMode::Type SelectionMode) { TSharedPtr HeaderRowWidget; if (bShowPropertyValue || bHasPostWidget) { bUseRows = true; HeaderRowWidget = SNew(SHeaderRow) .Visibility(EVisibility::Collapsed); HeaderRowWidget->AddColumn( SHeaderRow::FColumn::FArguments() .ColumnId(ColumnName_Field) .DefaultLabel(LOCTEXT("FieldName", "Field Name")) .FillWidth(0.75f) ); if (bShowPropertyValue) { HeaderRowWidget->AddColumn( SHeaderRow::FColumn::FArguments() .ColumnId(ColumnName_PropertyValue) .FillSized(100) .DefaultLabel(LOCTEXT("PropertyValue", "Field Value")) .FillWidth(0.25f) ); } if (bHasPostWidget) { HeaderRowWidget->AddColumn( SHeaderRow::FColumn::FArguments() .ColumnId(ColumnName_FieldPostWidget) .DefaultLabel(FText()) ); } } if (FilterHandler) { SAssignNew(TreeWidget, STreeView>) .TreeItemsSource(&FilteredTreeSource) .SelectionMode(SelectionMode) .OnGetChildren(FilterHandler.ToSharedRef(), &FTreeFilter::OnGetFilteredChildren) .OnGenerateRow(this, &FPropertyViewerImpl::HandleGenerateRow) .OnSelectionChanged(this, &FPropertyViewerImpl::HandleSelectionChanged) .OnContextMenuOpening(this, &FPropertyViewerImpl::HandleContextMenuOpening) .OnMouseButtonDoubleClick(this, &FPropertyViewerImpl::HandleDoubleClick) .HeaderRow(HeaderRowWidget); FilterHandler->SetTreeView(TreeWidget.Get()); } else { SAssignNew(TreeWidget, STreeView>) .TreeItemsSource(&TreeSource) .SelectionMode(SelectionMode) .OnGetChildren(this, &FPropertyViewerImpl::HandleGetChildren) .OnGenerateRow(this, &FPropertyViewerImpl::HandleGenerateRow) .OnSelectionChanged(this, &FPropertyViewerImpl::HandleSelectionChanged) .OnContextMenuOpening(this, &FPropertyViewerImpl::HandleContextMenuOpening) .OnMouseButtonDoubleClick(this, &FPropertyViewerImpl::HandleDoubleClick) .HeaderRow(HeaderRowWidget); } return TreeWidget.ToSharedRef(); } void FPropertyViewerImpl::SetHighlightTextRecursive(const TSharedPtr& OwnerNode, const FText& HighlightText) { if (TSharedPtr PropertyNameWidget = OwnerNode->PropertyWidget.Pin()) { PropertyNameWidget->SetHighlightText(HighlightText); } if (OwnerNode->bChildGenerated) { for (const TSharedPtr& Node : OwnerNode->ChildNodes) { SetHighlightTextRecursive(Node, HighlightText); } } } void FPropertyViewerImpl::HandleGetFilterStrings(TSharedPtr Item, TArray& OutStrings) { Item->GetFilterStrings(OutStrings); } TSharedRef FPropertyViewerImpl::HandleGenerateRow(TSharedPtr Item, const TSharedRef& OwnerTable) { FText HighlightText = SearchFilter ? SearchFilter->GetRawFilterText() : FText::GetEmpty(); TSharedPtr PreWidget; if (OnGetPreSlot.IsBound()) { TSharedPtr OwnerContainer = Item->GetOwnerContainer(); PreWidget = OnGetPreSlot.Execute(OwnerContainer.IsValid() ? OwnerContainer->GetIdentifier() : SPropertyViewer::FHandle(), Item->GetFieldPath()); } TSharedPtr ItemWidget; if (TSharedPtr ContainerPin = Item->GetContainer()) { if (OnGenerateContainer.IsBound()) { ItemWidget = OnGenerateContainer.Execute(ContainerPin->GetIdentifier(), Item->GetOverrideDisplayName()); } else if (ContainerPin->IsValid()) { if (const UClass* Class = Cast(ContainerPin->GetStruct())) { ItemWidget = SNew(SFieldName, Class) .bShowIcon(true) .bSanitizeName(bSanitizeName) .OverrideDisplayName(Item->GetOverrideDisplayName()) .OverrideIconColorSettings(OverrideIconColorSettings); } if (const UScriptStruct* Struct = Cast(ContainerPin->GetStruct())) { ItemWidget = SNew(SFieldName, Struct) .bShowIcon(true) .bSanitizeName(bSanitizeName) .OverrideDisplayName(Item->GetOverrideDisplayName()) .OverrideIconColorSettings(OverrideIconColorSettings); } } } else if (TSharedPtr OwnerContainerPin = Item->GetOwnerContainer()) { if (OwnerContainerPin->IsValid()) { if (FFieldVariant FieldVariant = Item->GetField()) { if (FProperty* Property = FieldVariant.Get()) { TSharedPtr FieldName = SNew(SFieldName, Property) .bShowIcon(bShowFieldIcon) .bSanitizeName(bSanitizeName) .OverrideDisplayName(Item->GetOverrideDisplayName()) .OverrideIconColorSettings(OverrideIconColorSettings) .HighlightText(HighlightText); Item->PropertyWidget = FieldName; ItemWidget = FieldName; } else if (UFunction* Function = FieldVariant.Get()) { TSharedPtr FieldName = SNew(SFieldName, Function) .bShowIcon(bShowFieldIcon) .bSanitizeName(bSanitizeName) .OverrideDisplayName(Item->GetOverrideDisplayName()) .OverrideIconColorSettings(OverrideIconColorSettings) .HighlightText(HighlightText); Item->PropertyWidget = FieldName; ItemWidget = FieldName; } } } } struct SMultiRowType : public SMultiColumnTableRow> { void Construct(const FArguments& Args, const TSharedRef PropertyViewer, const TSharedRef& OwnerTableView, TSharedRef InItem, TSharedRef InFieldWidget) { PropertyViewOwner = PropertyViewer; Item = InItem; FieldWidget = InFieldWidget; SMultiColumnTableRow>::Construct(Args, OwnerTableView); } virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override { if (ColumnName == ColumnName_Field) { TSharedRef Stack = SNew(SHorizontalBox); TSharedPtr PropertyViewOwnerPin = PropertyViewOwner.Pin(); TSharedPtr PreWidget; if (PropertyViewOwnerPin && PropertyViewOwnerPin->OnGetPreSlot.IsBound()) { if (TSharedPtr ItemPin = Item.Pin()) { TSharedPtr OwnerContainer = ItemPin->GetOwnerContainer(); PreWidget = PropertyViewOwnerPin->OnGetPreSlot.Execute(OwnerContainer.IsValid() ? OwnerContainer->GetIdentifier() : SPropertyViewer::FHandle(), ItemPin->GetFieldPath()); } } Stack->AddSlot() .AutoWidth() [ SNew(SExpanderArrow, SharedThis(this)) .IndentAmount(16) .ShouldDrawWires(true) ]; if (PreWidget) { Stack->AddSlot() .AutoWidth() [ PreWidget.ToSharedRef() ]; } Stack->AddSlot() .AutoWidth() .Padding(2.0f, 0.0f) .VAlign(VAlign_Center) [ FieldWidget.ToSharedRef() ]; return Stack; } if (ColumnName == ColumnName_PropertyValue) { TSharedPtr ItemPin = Item.Pin(); TSharedPtr PropertyViewOwnerPin = PropertyViewOwner.Pin(); if (ItemPin && PropertyViewOwnerPin) { if (ItemPin->IsField()) { FFieldVariant Field = ItemPin->GetField(); if (!Field.IsUObject()) { bool bCanEditContainer = false; if (const TSharedPtr OwnerContainer = ItemPin->GetOwnerContainer()) { bCanEditContainer = OwnerContainer->CanEdit(); } FPropertyValueFactory::FGenerateArgs Args; Args.Path = ItemPin->GetPropertyPath(); Args.NotifyHook = PropertyViewOwnerPin->NotifyHook; Args.bCanEditValue = bCanEditContainer && PropertyViewOwnerPin->PropertyVisibility == SPropertyViewer::EPropertyVisibility::Editable && Args.Path.GetLastProperty() != nullptr && !Args.Path.GetLastProperty()->HasAllPropertyFlags(CPF_BlueprintReadOnly); FAdvancedWidgetsModule& Module = FAdvancedWidgetsModule::GetModule(); TSharedPtr ValueWidget = Module.GetPropertyValueFactory().Generate(Args); if (!ValueWidget) { ValueWidget = Module.GetPropertyValueFactory().GenerateDefault(Args); } if (ValueWidget) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(EVerticalAlignment::VAlign_Center) [ ValueWidget.ToSharedRef() ] + SHorizontalBox::Slot() .FillWidth(1.f) [ SNullWidget::NullWidget ]; } } } } } if (ColumnName == ColumnName_FieldPostWidget) { TSharedPtr ItemPin = Item.Pin(); TSharedPtr PropertyViewOwnerPin = PropertyViewOwner.Pin(); if (ItemPin && PropertyViewOwnerPin) { TSharedPtr OwnerContainer = ItemPin->GetOwnerContainer(); TSharedPtr PostWidget = PropertyViewOwnerPin->OnGetPostSlot.Execute(OwnerContainer.IsValid() ? OwnerContainer->GetIdentifier() : SPropertyViewer::FHandle(), ItemPin->GetFieldPath()); if (PostWidget) { return PostWidget.ToSharedRef(); } } } return SNullWidget::NullWidget; } private: TWeakPtr Item; TSharedPtr FieldWidget; TWeakPtr PropertyViewOwner; }; TSharedRef FieldWidget = ItemWidget ? ItemWidget.ToSharedRef() : SNullWidget::NullWidget; if (bUseRows) { return SNew(SMultiRowType, AsShared(), OwnerTable, Item.ToSharedRef(), FieldWidget) .OnDragDetected(this, &FPropertyViewerImpl::HandleDragDetected, Item) .Padding(0.0f); } using SSimpleRowType = STableRow>; if (PreWidget) { return SNew(SSimpleRowType, OwnerTable) .Padding(0.0f) .Content() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ PreWidget.ToSharedRef() ] + SHorizontalBox::Slot() .FillWidth(1.f) [ FieldWidget ] ]; } return SNew(SSimpleRowType, OwnerTable) .Padding(0.0f) .Content() [ FieldWidget ]; } void FPropertyViewerImpl::HandleGetChildren(TSharedPtr InParent, TArray>& OutChildren) { if (!InParent->bChildGenerated) { // Do not build when filtering (only search in what it's already been built) if (FilterHandler == nullptr || !FilterHandler->GetIsEnabled()) { NodeReferences.Append(InParent->BuildChildNodes(*FieldIterator, *FieldExpander, bSortChildNode)); } } OutChildren = InParent->ChildNodes; } TSharedPtr FPropertyViewerImpl::HandleContextMenuOpening() { if (OnContextMenuOpening.IsBound()) { TArray> Items = TreeWidget->GetSelectedItems(); if (Items.Num() == 1 && Items[0].IsValid()) { TSharedPtr OwnerContainer = Items[0]->GetOwnerContainer(); return OnContextMenuOpening.Execute(OwnerContainer.IsValid() ? OwnerContainer->GetIdentifier() : SPropertyViewer::FHandle(), Items[0]->GetFieldPath()); } } return TSharedPtr(); } void FPropertyViewerImpl::HandleSelectionChanged(TSharedPtr Item, ESelectInfo::Type SelectionType) { if (OnSelectionChanged.IsBound()) { if (Item.IsValid()) { TSharedPtr OwnerContainer = Item->GetOwnerContainer(); OnSelectionChanged.Execute(OwnerContainer.IsValid() ? OwnerContainer->GetIdentifier() : SPropertyViewer::FHandle(), Item->GetFieldPath(), SelectionType); } else { OnSelectionChanged.Execute(SPropertyViewer::FHandle(), TArray(), SelectionType); } } } void FPropertyViewerImpl::HandleDoubleClick(TSharedPtr Item) { if (OnDoubleClicked.IsBound()) { if (Item.IsValid()) { TSharedPtr OwnerContainer = Item->GetOwnerContainer(); OnDoubleClicked.Execute(OwnerContainer.IsValid() ? OwnerContainer->GetIdentifier() : SPropertyViewer::FHandle(), Item->GetFieldPath()); } else { OnDoubleClicked.Execute(SPropertyViewer::FHandle(), TArray()); } } } FReply FPropertyViewerImpl::HandleDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, const TSharedPtr Item) { if (OnDragDetected.IsBound()) { if (Item.IsValid()) { TSharedPtr OwnerContainer = Item->GetOwnerContainer(); return OnDragDetected.Execute(MyGeometry, MouseEvent, OwnerContainer.IsValid() ? OwnerContainer->GetIdentifier() : SPropertyViewer::FHandle(), Item->GetFieldPath()); } else { return OnDragDetected.Execute(MyGeometry, MouseEvent, SPropertyViewer::FHandle(), TArray()); } } return FReply::Unhandled(); } void FPropertyViewerImpl::HandleSearchChanged(const FText& InFilterText) { if (SearchBoxWidget) { SearchBoxWidget->SetError(SetRawFilterTextInternal(InFilterText)); } } #if WITH_EDITOR void FPropertyViewerImpl::HandleBlueprintCompiled() { bool bRemoved = false; for (int32 Index = TreeSource.Num()-1; Index >= 0 ; --Index) { if (TSharedPtr Container = TreeSource[Index]->GetContainer()) { if (!Container->IsValid()) { TreeSource.RemoveAt(Index); bRemoved = true; } } } for (int32 Index = Containers.Num()-1; Index >= 0; --Index) { if (!Containers[Index]->IsValid()) { Containers.RemoveAt(Index); } } if (bRemoved) { TreeWidget->RebuildList(); if (FilterHandler) { FilterHandler->RefreshAndFilterTree(); } } } void FPropertyViewerImpl::HandleReplaceViewedObjects(const TMap& OldToNewObjectMap) { TArray KeyArray; OldToNewObjectMap.GenerateKeyArray(KeyArray); TArray> ItemsToReplace; for (TSharedPtr Container : Containers) { if (Container->GetStruct()) { if (KeyArray.Contains(Container->GetStruct())) { ItemsToReplace.Add(Container); } } } for (TSharedPtr Container : ItemsToReplace) { Remove(Container->GetIdentifier()); Containers.Add(Container); AddContainerInternal(Container->GetIdentifier(), Container); } } #endif //WITH_EDITOR } //namespace #undef LOCTEXT_NAMESPACE