// Copyright Epic Games, Inc. All Rights Reserved. #include "StructViewerNode.h" #include "AssetRegistry/AssetData.h" #include "StructUtils/UserDefinedStruct.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "Misc/AssertionMacros.h" #include "Misc/ScopedSlowTask.h" #include "PropertyHandle.h" #include "Templates/Casts.h" #include "UObject/Class.h" #include "UObject/UObjectGlobals.h" #include "UObject/WeakObjectPtr.h" #define LOCTEXT_NAMESPACE "StructViewer" FStructViewerNodeData::FStructViewerNodeData() : StructName(TEXT("None")) , StructDisplayName(LOCTEXT("None", "None")) { } FStructViewerNodeData::FStructViewerNodeData(const UScriptStruct* InStruct) : StructName(InStruct->GetName()) , StructDisplayName(InStruct->GetDisplayNameText()) , StructPath(*InStruct->GetPathName()) , Struct(InStruct) { if (const UScriptStruct* ParentStruct = Cast(InStruct->GetSuperStruct())) { ParentStructPath = *ParentStruct->GetPathName(); } } FStructViewerNodeData::FStructViewerNodeData(const FAssetData& InStructAsset) : StructName(InStructAsset.AssetName.ToString()) , StructPath(InStructAsset.GetSoftObjectPath()) { // Attempt to find the struct asset in the case where it's already been loaded Struct = FindObject(nullptr, *StructPath.ToString()); // Cache the resolved display name if available, or synthesize one if the struct asset is unloaded StructDisplayName = Struct.IsValid() ? Struct->GetDisplayNameText() : FText::AsCultureInvariant(FName::NameToDisplayString(StructName, false)); } const UScriptStruct* FStructViewerNodeData::GetStruct() const { return Struct.Get(); } const UUserDefinedStruct* FStructViewerNodeData::GetStructAsset() const { return Cast(Struct.Get()); } bool FStructViewerNodeData::LoadStruct() const { if (Struct.IsValid()) { return true; } // Attempt to load the struct if (!StructPath.IsNull()) { FScopedSlowTask SlowTask(0.0f, LOCTEXT("LoadingStruct", "Loading Struct...")); SlowTask.MakeDialogDelayed(1.0f); Struct = LoadObject(nullptr, *StructPath.ToString()); } // Re-cache the resolved display name as it may be different than the one we synthesized for an unloaded struct asset if (Struct.IsValid()) { StructDisplayName = Struct->GetDisplayNameText(); return true; } return false; } void FStructViewerNodeData::AddChild(const TSharedRef& InChild) { InChild->ParentNode = AsShared(); ChildNodes.Add(InChild); } void FStructViewerNodeData::AddUniqueChild(const TSharedRef& InChild) { const bool bAlreadyAChild = ChildNodes.ContainsByPredicate([&InChild](const TSharedPtr& InExistingChild) -> bool { return InExistingChild->GetStructPath() == InChild->GetStructPath(); }); if (!bAlreadyAChild) { AddChild(InChild); } } bool FStructViewerNodeData::RemoveChild(const FSoftObjectPath& InStructPath) { for (auto ChildIt = ChildNodes.CreateIterator(); ChildIt; ++ChildIt) { if ((*ChildIt)->GetStructPath() == InStructPath) { ChildIt.RemoveCurrent(); return true; } } return false; } FStructViewerNode::FStructViewerNode() : NodeData(MakeShared()) , bPassedFilter(true) { } FStructViewerNode::FStructViewerNode(const TSharedRef& InData, const TSharedPtr& InPropertyHandle, const bool InPassedFilter) : NodeData(InData) , PropertyHandle(InPropertyHandle) , bPassedFilter(InPassedFilter) { } FText FStructViewerNode::GetStructDisplayName(const EStructViewerNameTypeToDisplay InNameType) const { switch (InNameType) { case EStructViewerNameTypeToDisplay::StructName: return FText::AsCultureInvariant(GetStructName()); case EStructViewerNameTypeToDisplay::DisplayName: return GetStructDisplayName(); case EStructViewerNameTypeToDisplay::Dynamic: { const FText BasicName = FText::AsCultureInvariant(GetStructName()); const FText DisplayName = GetStructDisplayName(); const FString SynthesizedDisplayName = FName::NameToDisplayString(BasicName.ToString(), false); // Only show both names if we have a display name set that is different from the basic name and not synthesized from it return (DisplayName.IsEmpty() || DisplayName.ToString().Equals(BasicName.ToString()) || DisplayName.ToString().Equals(SynthesizedDisplayName)) ? BasicName : FText::Format(LOCTEXT("StructDynamicDisplayNameFmt", "{0} ({1})"), BasicName, DisplayName); } default: break; } ensureMsgf(false, TEXT("FStructViewerNode::GetStructName called with invalid name type.")); return GetStructDisplayName(); } void FStructViewerNode::AddChild(const TSharedRef& InChild) { InChild->ParentNode = AsShared(); ChildNodes.Add(InChild); } void FStructViewerNode::AddUniqueChild(const TSharedRef& InChild) { const bool bAlreadyAChild = ChildNodes.ContainsByPredicate([&InChild](const TSharedPtr& InExistingChild) -> bool { return InExistingChild->GetStructPath() == InChild->GetStructPath(); }); if (!bAlreadyAChild) { AddChild(InChild); } } void FStructViewerNode::SortChildrenRecursive() { SortChildren(); for (const TSharedPtr& ChildNode : ChildNodes) { ChildNode->SortChildrenRecursive(); } } void FStructViewerNode::SortChildren() { SortNodes(ChildNodes, PropertyHandle); } bool FStructViewerNode::SortPredicate(const TSharedPtr& InA, const TSharedPtr& InB) { check(InA.IsValid()); check(InB.IsValid()); const FString& AString = InA->GetStructName(); const FString& BString = InB->GetStructName(); return AString < BString; } void FStructViewerNode::SortNodes(TArray>& Nodes, const TSharedPtr& PropertyHandle) { if (PropertyHandle.IsValid()) { static const FName SortFunctionMetaName = "SortFunction"; const FString& FunctionName = PropertyHandle->GetMetaData(SortFunctionMetaName); if (!FunctionName.IsEmpty()) { const UFunction* Function = nullptr; // Fully-qualified function name if (UE::String::FindFirstChar(FunctionName, TEXT('.')) != INDEX_NONE) { Function = FindObject(nullptr, *FunctionName, true); } else { TArray Containers; PropertyHandle->GetOuterObjects(Containers); for (const UObject* Container : Containers) { Function = Container ? Container->FindFunction(FName(FunctionName)) : nullptr; if (Function) { break; } } } if (ensureMsgf(Function && Function->HasAnyFunctionFlags(EFunctionFlags::FUNC_Static), TEXT("Invalid SortFunction: %s"), *FunctionName)) { DECLARE_DELEGATE_RetVal_TwoParams(bool, FStructViewerUserCompare, const UScriptStruct*, const UScriptStruct*); UObject* Container = Function->GetOuterUClass()->GetDefaultObject(); FStructViewerUserCompare CompareFunc = FStructViewerUserCompare::CreateUFunction(Container, Function->GetFName()); Nodes.Sort([&CompareFunc](const TSharedPtr& InA, const TSharedPtr& InB) { check(InA.IsValid() && InB.IsValid()); const UScriptStruct* AStruct = InA->GetStruct(); const UScriptStruct* BStruct = InB->GetStruct(); if (AStruct && BStruct) { return CompareFunc.Execute(InA->GetStruct(), InB->GetStruct()); } return SortPredicate(InA, InB); }); return; } } } Nodes.Sort(&SortPredicate); } bool FStructViewerNode::IsRestricted() const { return PropertyHandle && PropertyHandle->IsRestricted(GetStructName()); } #undef LOCTEXT_NAMESPACE