Files
UnrealEngine/Engine/Source/Editor/StructViewer/Private/SStructViewer.cpp
2025-05-18 13:04:45 +08:00

2053 lines
69 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SStructViewer.h"
#include "StructViewerNode.h"
#include "StructViewerFilter.h"
#include "StructViewerProjectSettings.h"
#include "Misc/PackageName.h"
#include "Misc/ScopedSlowTask.h"
#include "Modules/ModuleManager.h"
#include "UObject/UObjectIterator.h"
#include "Misc/TextFilterExpressionEvaluator.h"
#include "Editor.h"
#include "Styling/AppStyle.h"
#include "Styling/SlateIconFinder.h"
#include "SlateOptMacros.h"
#include "EditorWidgetsModule.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/SOverlay.h"
#include "Widgets/SToolTip.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Layout/SSeparator.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Layout/SScrollBorder.h"
#include "Framework/Docking/TabManager.h"
#include "SListViewSelectorDropdownMenu.h"
#include "AssetRegistry/ARFilter.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "ContentBrowserDataDragDropOp.h"
#include "Editor/UnrealEdEngine.h"
#include "StructUtils/UserDefinedStruct.h"
#include "EditorDirectories.h"
#include "Dialogs/Dialogs.h"
#include "IDocumentation.h"
#include "PropertyHandle.h"
#include "Logging/LogMacros.h"
#include "SourceCodeNavigation.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Subsystems/AssetEditorSubsystem.h"
#define LOCTEXT_NAMESPACE "SStructViewer"
DEFINE_LOG_CATEGORY_STATIC(LogEditorStructViewer, Log, All);
EStructFilterReturn FStructViewerFilterFuncs::IfInChildOfStructsSet(TSet<const UScriptStruct*>& InSet, const UScriptStruct* InStruct)
{
check(InStruct);
if (InSet.Num())
{
// If a struct is a child of any structs on this list, it will be allowed onto the list, unless it also appears on a disallowed list.
for (const UScriptStruct* CurStruct : InSet)
{
if (InStruct->IsChildOf(CurStruct))
{
return EStructFilterReturn::Passed;
}
}
return EStructFilterReturn::Failed;
}
// Since there are none on this list, return that there is no items.
return EStructFilterReturn::NoItems;
}
EStructFilterReturn FStructViewerFilterFuncs::IfMatchesAllInChildOfStructsSet(TSet<const UScriptStruct*>& InSet, const UScriptStruct* InStruct)
{
check(InStruct);
if (InSet.Num())
{
// If a struct is a child of any structs on this list, it will be allowed onto the list, unless it also appears on a disallowed list.
for (const UScriptStruct* CurStruct : InSet)
{
if (!InStruct->IsChildOf(CurStruct))
{
// Since it doesn't match one, it fails.
return EStructFilterReturn::Failed;
}
}
// It matches all of them, so it passes.
return EStructFilterReturn::Passed;
}
// Since there are none on this list, return that there is no items.
return EStructFilterReturn::NoItems;
}
EStructFilterReturn FStructViewerFilterFuncs::IfInStructsSet(TSet<const UScriptStruct*>& InSet, const UScriptStruct* InStruct)
{
check(InStruct);
if (InSet.Num())
{
return InSet.Contains(InStruct)
? EStructFilterReturn::Passed
: EStructFilterReturn::Failed;
}
// Since there are none on this list, return that there is no items.
return EStructFilterReturn::NoItems;
}
class FStructHierarchy
{
public:
FStructHierarchy();
~FStructHierarchy();
/** Get the singleton instance, creating it if required */
static FStructHierarchy& Get();
/** Get the singleton instance, or null if it doesn't exist */
static FStructHierarchy* GetPtr();
/** Destroy the singleton instance */
static void DestroyInstance();
/** Used to inform any registered Struct Viewers to refresh. */
DECLARE_MULTICAST_DELEGATE(FPopulateStructViewer);
FPopulateStructViewer& GetPopulateStructViewerDelegate()
{
return PopulateStructViewerDelegate;
}
/** Get the root node of the tree */
TSharedRef<FStructViewerNodeData> GetStructRootNode() const
{
return StructRootNode.ToSharedRef();
}
/**
* Finds the node, recursively going deeper into the hierarchy.
* @param InRootNode The node to start the search from.
* @param InStructPath The path name of the struct to find the node for.
* @return The node.
*/
TSharedPtr<FStructViewerNodeData> FindNodeByStructPath(const TSharedRef<FStructViewerNodeData>& InRootNode, const FSoftObjectPath& InStructPath);
TSharedPtr<FStructViewerNodeData> FindNodeByStructPath(const FSoftObjectPath& InStructPath)
{
return FindNodeByStructPath(GetStructRootNode(), InStructPath);
}
/** Update the struct hierarchy if it is pending a refresh */
void UpdateStructHierarchy();
private:
/** Dirty the struct hierarchy so it will be rebuilt on the next call to UpdateStructHierarchy */
void DirtyStructHierarchy();
/** Populates the struct hierarchy tree, pulling in all the loaded and unloaded structs. */
void PopulateStructHierarchy();
/**
* Recursively searches through the hierarchy to find and remove the asset. Used when deleting assets.
*
* @param InRootNode The node to start the search from.
* @param InStructPath The path name of the struct to delete
*
* @return Returns true if the struct was found and deleted successfully.
*/
bool FindAndRemoveNodeByStructPath(const TSharedRef<FStructViewerNodeData>& InRootNode, const FSoftObjectPath& InStructPath);
bool FindAndRemoveNodeByStructPath(const FSoftObjectPath& InStructPath)
{
return FindAndRemoveNodeByStructPath(GetStructRootNode(), InStructPath);
}
/** Callback registered to the Asset Registry to be notified when an asset is added. */
void AddAsset(const FAssetData& InAddedAssetData);
/** Callback registered to the Asset Registry to be notified when an asset is removed. */
void RemoveAsset(const FAssetData& InRemovedAssetData);
/** Called when reload has finished */
void OnReloadComplete(EReloadCompleteReason Reason);
/** Called when modules are loaded or unloaded */
void OnModulesChanged(FName ModuleThatChanged, EModuleChangeReason ReasonForChange);
private:
/** The struct hierarchy singleton that manages the unfiltered struct tree for the Struct Viewer. */
static TUniquePtr<FStructHierarchy> Singleton;
/** True if the struct hierarchy should be refreshed */
bool bRefreshStructHierarchy = false;
/** Used to inform any registered Struct Viewers to refresh. */
FPopulateStructViewer PopulateStructViewerDelegate;
/** The dummy struct data node that is used as a root point for the Struct Viewer. */
TSharedPtr<FStructViewerNodeData> StructRootNode;
};
TUniquePtr<FStructHierarchy> FStructHierarchy::Singleton;
namespace StructViewer
{
namespace Helpers
{
/**
* Checks if the struct is allowed under the init options of the struct viewer currently building it's tree/list.
* @param InInitOptions The struct viewer's options, holds the AllowedStructs and DisallowedStructs.
* @param InStruct The struct to test against.
*/
bool IsStructAllowed(const FStructViewerInitializationOptions& InInitOptions, const TWeakObjectPtr<const UScriptStruct> InStruct)
{
if (InInitOptions.StructFilter.IsValid())
{
return InInitOptions.StructFilter->IsStructAllowed(InInitOptions, InStruct.Get(), MakeShared<FStructViewerFilterFuncs>());
}
return true;
}
/**
* Checks if the unloaded struct is allowed under the init options of the struct viewer currently building it's tree/list.
* @param InInitOptions The struct viewer's options, holds the AllowedStructs and DisallowedStructs.
* @param InStructPath The path name to test against.
*/
bool IsStructAllowed_UnloadedStruct(const FStructViewerInitializationOptions& InInitOptions, const FSoftObjectPath& InStructPath)
{
if (InInitOptions.StructFilter.IsValid())
{
return InInitOptions.StructFilter->IsUnloadedStructAllowed(InInitOptions, InStructPath, MakeShared<FStructViewerFilterFuncs>());
}
return true;
}
/**
* Checks if the TestString passes the filter.
* @param InTestString The string to test against the filter.
* @param InTextFilter Compiled text filter to apply.
* @return true if it passes the filter.
*/
bool PassesFilter(const FString& InTestString, const FTextFilterExpressionEvaluator& InTextFilter)
{
return InTextFilter.TestTextFilter(FBasicStringFilterExpressionContext(InTestString));
}
/**
* Recursive function to build a tree, filtering out nodes based on the InitOptions and filter search terms.
* @param InInitOptions The struct viewer's options, holds the AllowedStructs and DisallowedStructs.
* @param InOutRootNode The node that this function will add the children of to the tree.
* @param InOriginalRootNode The original root node holding the data we produce a filtered node for.
* @param InTextFilter Compiled text filter to apply.
* @param bInShowUnloadedStructs Filter option to not remove unloaded structs due to struct filter options.
* @param InAllowedDeveloperType Filter option for dealing with developer folders.
* @param bInInternalStructs Filter option for showing internal structs.
* @param InternalStructs The structs that have been marked as Internal Only.
* @param InternalPaths The paths that have been marked Internal Only.
* @return Returns true if the child passed the filter.
*/
bool AddChildren_Tree(
const FStructViewerInitializationOptions& InInitOptions,
const TSharedRef<FStructViewerNode>& InOutRootNode,
const TSharedRef<FStructViewerNodeData>& InOriginalRootNode,
const FTextFilterExpressionEvaluator& InTextFilter,
const bool bInShowUnloadedStructs,
const EStructViewerDeveloperType InAllowedDeveloperType,
const bool bInInternalStructs,
const TArray<const UScriptStruct*>& InternalStructs,
const TArray<FDirectoryPath>& InternalPaths
)
{
static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir());
static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir());
const UScriptStruct* OriginalRootNodeStruct = InOriginalRootNode->GetStruct();
// Determine if we allow any developer folder classes, if so determine if this struct is in one of the allowed developer folders.
const FString GeneratedStructPathString = InOriginalRootNode->GetStructPath().ToString();
bool bPassesDeveloperFilter = true;
if (InAllowedDeveloperType == EStructViewerDeveloperType::SVDT_None)
{
bPassesDeveloperFilter = !GeneratedStructPathString.StartsWith(DeveloperPathWithSlash);
}
else if (InAllowedDeveloperType == EStructViewerDeveloperType::SVDT_CurrentUser)
{
if (GeneratedStructPathString.StartsWith(DeveloperPathWithSlash))
{
bPassesDeveloperFilter = GeneratedStructPathString.StartsWith(UserDeveloperPathWithSlash);
}
}
// The INI files declare structs and folders that are considered internal only. Does this struct match any of those patterns?
// INI path: /Script/StructViewer.StructViewerProjectSettings
bool bPassesInternalFilter = true;
if (!bInInternalStructs && InternalPaths.Num() > 0)
{
for (const FDirectoryPath& InternalPath : InternalPaths)
{
if (GeneratedStructPathString.StartsWith(InternalPath.Path))
{
bPassesInternalFilter = false;
break;
}
}
}
if (!bInInternalStructs && InternalStructs.Num() > 0 && bPassesInternalFilter && OriginalRootNodeStruct)
{
for (const UScriptStruct* InternalStruct : InternalStructs)
{
if (OriginalRootNodeStruct->IsChildOf(InternalStruct))
{
bPassesInternalFilter = false;
break;
}
}
}
// There are few options for filtering an unloaded struct, if it matches with this filter, it passes.
bool bReturnPassesFilter = false;
if (OriginalRootNodeStruct)
{
bReturnPassesFilter = bPassesDeveloperFilter && bPassesInternalFilter && IsStructAllowed(InInitOptions, OriginalRootNodeStruct) &&
(PassesFilter(InOriginalRootNode->GetStructName(), InTextFilter) || PassesFilter(InOriginalRootNode->GetStructDisplayName().ToString(), InTextFilter));
}
else
{
if (bInShowUnloadedStructs)
{
bReturnPassesFilter = bPassesDeveloperFilter && bPassesInternalFilter && IsStructAllowed_UnloadedStruct(InInitOptions, InOutRootNode->GetStructPath()) &&
(PassesFilter(InOriginalRootNode->GetStructName(), InTextFilter) || PassesFilter(InOriginalRootNode->GetStructDisplayName().ToString(), InTextFilter));
}
}
InOutRootNode->PassedFilter(bReturnPassesFilter);
for (const TSharedPtr<FStructViewerNodeData>& ChildNode : InOriginalRootNode->GetChildNodes())
{
TSharedRef<FStructViewerNode> NewNode = MakeShared<FStructViewerNode>(ChildNode.ToSharedRef(), InInitOptions.PropertyHandle, /*bPassedFilter*/false); // Whether we pass the filter is calculated in the recursive call
const bool bChildrenPassesFilter = AddChildren_Tree(InInitOptions, NewNode, ChildNode.ToSharedRef(), InTextFilter, bInShowUnloadedStructs, InAllowedDeveloperType, bInInternalStructs, InternalStructs, InternalPaths);
bReturnPassesFilter |= bChildrenPassesFilter;
if (bChildrenPassesFilter)
{
InOutRootNode->AddChild(NewNode);
}
}
return bReturnPassesFilter;
}
/**
* Builds the struct tree.
* @param InInitOptions The struct viewer's options, holds the AllowedStructs and DisallowedStructs.
* @param InOutRootNode The node to root the tree to.
* @param InTextFilter Compiled text filter to apply.
* @param bInShowUnloadedStructs Filter option to not remove unloaded structs due to struct filter options.
* @param InAllowedDeveloperType Filter option for dealing with developer folders.
* @param bInInternalStructs Filter option for showing internal structs.
* @param InternalStructs The structs that have been marked as Internal Only.
* @param InternalPaths The paths that have been marked Internal Only.
* @return A fully built tree.
*/
void GetStructTree(
const FStructViewerInitializationOptions& InInitOptions,
TSharedPtr<FStructViewerNode>& InOutRootNode,
const FTextFilterExpressionEvaluator& InTextFilter,
const bool bInShowUnloadedStructs,
const EStructViewerDeveloperType InAllowedDeveloperType = EStructViewerDeveloperType::SVDT_All,
const bool bInInternalStructs = true,
const TArray<const UScriptStruct*>& InternalStructs = TArray<const UScriptStruct*>(),
const TArray<FDirectoryPath>& InternalPaths = TArray<FDirectoryPath>()
)
{
const TSharedRef<FStructViewerNodeData> StructRootNode = FStructHierarchy::Get().GetStructRootNode();
// Make a dummy root node
InOutRootNode = MakeShared<FStructViewerNode>();
AddChildren_Tree(InInitOptions, InOutRootNode.ToSharedRef(), StructRootNode, InTextFilter, bInShowUnloadedStructs, InAllowedDeveloperType, bInInternalStructs, InternalStructs, InternalPaths);
}
/**
* Recursive function to build the list, filtering out nodes based on the InitOptions and filter search terms.
* @param InInitOptions The struct viewer's options, holds the AllowedStructs and DisallowedStructs.
* @param InOutNodeList The list to add all the nodes to.
* @param InOriginalRootNode The original root node holding the data we produce a filtered node for.
* @param InTextFilter Compiled text filter to apply.
* @param bInShowUnloadedStructs Filter option to not remove unloaded structs due to struct filter options.
* @param InAllowedDeveloperType Filter option for dealing with developer folders.
* @param bInInternalStructs Filter option for showing internal structs.
* @param InternalStructs The structs that have been marked as Internal Only.
* @param InternalPaths The paths that have been marked Internal Only.
* @return Returns true if the child passed the filter.
*/
void AddChildren_List(
const FStructViewerInitializationOptions& InInitOptions,
TArray<TSharedPtr<FStructViewerNode>>& InOutNodeList,
const TSharedRef<FStructViewerNodeData>& InOriginalRootNode,
const FTextFilterExpressionEvaluator& InTextFilter,
const bool bInShowUnloadedStructs,
const EStructViewerDeveloperType InAllowedDeveloperType,
const bool bInInternalStructs,
const TArray<const UScriptStruct*>& InternalStructs,
const TArray<FDirectoryPath>& InternalPaths
)
{
static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir());
static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir());
const UScriptStruct* OriginalRootNodeStruct = InOriginalRootNode->GetStruct();
// Determine if we allow any developer folder structs, if so determine if this struct is in one of the allowed developer folders.
FString GeneratedStructPathString = InOriginalRootNode->GetStructPath().ToString();
bool bPassesDeveloperFilter = true;
if (InAllowedDeveloperType == EStructViewerDeveloperType::SVDT_None)
{
bPassesDeveloperFilter = !GeneratedStructPathString.StartsWith(DeveloperPathWithSlash);
}
else if (InAllowedDeveloperType == EStructViewerDeveloperType::SVDT_CurrentUser)
{
if (GeneratedStructPathString.StartsWith(DeveloperPathWithSlash))
{
bPassesDeveloperFilter = GeneratedStructPathString.StartsWith(UserDeveloperPathWithSlash);
}
}
// The INI files declare structs and folders that are considered internal only. Does this struct match any of those patterns?
// INI path: /Script/StructViewer.StructViewerProjectSettings
bool bPassesInternalFilter = true;
if (!bInInternalStructs && InternalPaths.Num() > 0)
{
for (const FDirectoryPath& InternalPath : InternalPaths)
{
if (GeneratedStructPathString.StartsWith(InternalPath.Path))
{
bPassesInternalFilter = false;
break;
}
}
}
if (!bInInternalStructs && InternalStructs.Num() > 0 && bPassesInternalFilter && OriginalRootNodeStruct)
{
for (const UScriptStruct* InternalStruct : InternalStructs)
{
if (OriginalRootNodeStruct->IsChildOf(InternalStruct))
{
bPassesInternalFilter = false;
break;
}
}
}
// There are few options for filtering an unloaded struct, if it matches with this filter, it passes.
bool bPassedFilter = false;
if (OriginalRootNodeStruct)
{
bPassedFilter = bPassesDeveloperFilter && bPassesInternalFilter && IsStructAllowed(InInitOptions, OriginalRootNodeStruct) &&
(PassesFilter(InOriginalRootNode->GetStructName(), InTextFilter) || PassesFilter(InOriginalRootNode->GetStructDisplayName().ToString(), InTextFilter));
}
else
{
if (bInShowUnloadedStructs)
{
bPassedFilter = bPassesDeveloperFilter && bPassesInternalFilter && IsStructAllowed_UnloadedStruct(InInitOptions, InOriginalRootNode->GetStructPath()) &&
(PassesFilter(InOriginalRootNode->GetStructName(), InTextFilter) || PassesFilter(InOriginalRootNode->GetStructDisplayName().ToString(), InTextFilter));
}
}
if (bPassedFilter)
{
InOutNodeList.Add(MakeShared<FStructViewerNode>(InOriginalRootNode, InInitOptions.PropertyHandle, bPassedFilter));
}
for (const TSharedPtr<FStructViewerNodeData>& ChildNode : InOriginalRootNode->GetChildNodes())
{
AddChildren_List(InInitOptions, InOutNodeList, ChildNode.ToSharedRef(), InTextFilter, bInShowUnloadedStructs, InAllowedDeveloperType, bInInternalStructs, InternalStructs, InternalPaths);
}
}
/**
* Builds the struct list.
* @param InInitOptions The struct viewer's options, holds the AllowedStructs and DisallowedStructs.
* @param InOutNodeList The list to add all the nodes to.
* @param InTextFilter Compiled text filter to apply.
* @param bInShowUnloadedStructs Filter option to not remove unloaded structs due to struct filter options.
* @param InAllowedDeveloperType Filter option for dealing with developer folders.
* @param bInInternalStructs Filter option for showing internal structs.
* @param InternalStructs The structs that have been marked as Internal Only.
* @param InternalPaths The paths that have been marked Internal Only.
* @return A fully built list.
*/
void GetStructList(
const FStructViewerInitializationOptions& InInitOptions,
TArray<TSharedPtr<FStructViewerNode>>& InOutNodeList,
const FTextFilterExpressionEvaluator& InTextFilter,
const bool bInShowUnloadedStructs,
const EStructViewerDeveloperType InAllowedDeveloperType = EStructViewerDeveloperType::SVDT_All,
const bool bInInternalStructs = true,
const TArray<const UScriptStruct*>& InternalStructs = TArray<const UScriptStruct*>(),
const TArray<FDirectoryPath>& InternalPaths = TArray<FDirectoryPath>()
)
{
const TSharedRef<FStructViewerNodeData> StructRootNode = FStructHierarchy::Get().GetStructRootNode();
// Always skip the dummy root, only adding its children
for (const TSharedPtr<FStructViewerNodeData>& ChildNode : StructRootNode->GetChildNodes())
{
AddChildren_List(InInitOptions, InOutNodeList, ChildNode.ToSharedRef(), InTextFilter, bInShowUnloadedStructs, InAllowedDeveloperType, bInInternalStructs, InternalStructs, InternalPaths);
}
}
/**
* Opens an asset editor for a user defined struct.
*/
void OpenAssetEditor(const UUserDefinedStruct* InStruct)
{
if (InStruct)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(const_cast<UUserDefinedStruct*>(InStruct));
}
}
/**
* Opens a struct source file.
*/
void OpenStructInIDE(const UScriptStruct* InStruct)
{
FSourceCodeNavigation::NavigateToStruct(InStruct);
}
/**
* Copy struct FTopLevelAssetPath to clipboard.
*/
void CopyStructPathToClipboard(const UScriptStruct* InStruct)
{
if (InStruct)
{
FPlatformApplicationMisc::ClipboardCopy(*InStruct->GetStructPathName().ToString());
}
}
/**
* Finds the struct in the content browser.
*/
void FindInContentBrowser(const UScriptStruct* InStruct)
{
if (InStruct)
{
TArray<UObject*> Objects;
Objects.Add(const_cast<UScriptStruct*>(InStruct));
GEditor->SyncBrowserToObjects(Objects);
}
}
TSharedRef<SWidget> CreateMenu(const UScriptStruct* InStruct)
{
// Empty list of commands.
TSharedPtr<FUICommandList> Commands;
const bool bShouldCloseWindowAfterMenuSelection = true; // Set the menu to automatically close when the user commits to a choice
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, Commands);
{
if (const UUserDefinedStruct* UserDefinedStruct = Cast<UUserDefinedStruct>(InStruct))
{
MenuBuilder.AddMenuEntry(
LOCTEXT("StructViewerMenuEditStructAsset", "Edit Struct..."),
LOCTEXT("StructViewerMenuEditStructAsset_Tooltip", "Open the struct in the asset editor."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateStatic(&StructViewer::Helpers::OpenAssetEditor, UserDefinedStruct))
);
MenuBuilder.AddMenuEntry(
LOCTEXT("StructViewerMenuFindContent", "Find in Content Browser..."),
LOCTEXT("StructViewerMenuFindContent_Tooltip", "Find in Content Browser"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateStatic(&StructViewer::Helpers::FindInContentBrowser, InStruct))
);
}
else
{
MenuBuilder.AddMenuEntry(
LOCTEXT("StructViewerMenuOpenSourceCode", "Open Source Code..."),
LOCTEXT("StructViewerMenuOpenSourceCode_Tooltip", "Open the source file for this struct in the IDE."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateStatic(&StructViewer::Helpers::OpenStructInIDE, InStruct))
);
}
MenuBuilder.AddMenuEntry(
LOCTEXT("StructViewerMenuCopyStructPath", "Copy Struct Path"),
LOCTEXT("StructViewerMenuCopyStructPath_Tooltip", "Copies the path for this struct (e.g '/Script/Module.StructName') to the clipboard."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateStatic(&StructViewer::Helpers::CopyStructPathToClipboard, InStruct))
);
}
return MenuBuilder.MakeWidget();
}
} // namespace Helpers
} // namespace StructViewer
/** Delegate used with the Struct Viewer in 'struct picking' mode. You'll bind a delegate when the struct viewer widget is created, which will be fired off when the selected struct is double clicked */
DECLARE_DELEGATE_OneParam(FOnStructItemDoubleClickDelegate, TSharedPtr<FStructViewerNode>);
/** The item used for visualizing the struct in the tree. */
class SStructItem : public STableRow<TSharedPtr<FString>>
{
public:
SLATE_BEGIN_ARGS(SStructItem)
: _StructDisplayName()
, _bIsInStructViewer(true)
, _bDynamicStructLoading(true)
, _HighlightText()
, _TextColor(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f))
{}
/** The struct name this item contains. */
SLATE_ARGUMENT(FText, StructDisplayName)
/** true if this item is in a Struct Viewer (as opposed to a Struct Picker) */
SLATE_ARGUMENT(bool, bIsInStructViewer)
/** true if this item should allow dynamic struct loading */
SLATE_ARGUMENT(bool, bDynamicStructLoading)
/** The text this item should highlight, if any. */
SLATE_ARGUMENT(FText, HighlightText)
/** The color text this item will use. */
SLATE_ARGUMENT(FSlateColor, TextColor)
/** The node this item is associated with. */
SLATE_ARGUMENT(TSharedPtr<FStructViewerNode>, AssociatedNode)
/** the delegate for handling double clicks outside of the SStructItem */
SLATE_ARGUMENT(FOnStructItemDoubleClickDelegate, OnStructItemDoubleClicked)
/** On Struct Picked callback. */
SLATE_EVENT(FOnDragDetected, OnDragDetected)
SLATE_END_ARGS()
/**
* Construct the widget
*
* @param InArgs A declaration from which to construct the widget
*/
void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
StructDisplayName = InArgs._StructDisplayName;
bIsInStructViewer = InArgs._bIsInStructViewer;
bDynamicStructLoading = InArgs._bDynamicStructLoading;
AssociatedNode = InArgs._AssociatedNode;
OnDoubleClicked = InArgs._OnStructItemDoubleClicked;
auto BuildToolTip = [this]() -> TSharedPtr<SToolTip>
{
TSharedPtr<SToolTip> ToolTip;
TSharedPtr<IPropertyHandle> PropertyHandle = AssociatedNode->GetPropertyHandle();
if (PropertyHandle && AssociatedNode->IsRestricted())
{
FText RestrictionToolTip;
PropertyHandle->GenerateRestrictionToolTip(*AssociatedNode->GetStructName(), RestrictionToolTip);
ToolTip = SNew(SToolTip).Text(RestrictionToolTip);
}
else if (!AssociatedNode->GetStructPath().IsNull())
{
const UScriptStruct* Struct = AssociatedNode->GetStruct();
if (Struct != nullptr && Struct->GetBoolMetaDataHierarchical("ShowTooltip"))
{
const FText ToolTipText = FText::Format(LOCTEXT("ToolTipFormat", "{0}\n\n{1}"),
AssociatedNode->GetStruct()->GetToolTipText(),
FText::FromString(AssociatedNode->GetStructPath().ToString())
);
ToolTip = SNew(SToolTip).Text(ToolTipText);
}
else
{
ToolTip = SNew(SToolTip).Text(FText::FromString(AssociatedNode->GetStructPath().ToString()));
}
}
return ToolTip;
};
const bool bIsRestricted = AssociatedNode->IsRestricted();
this->ChildSlot
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SExpanderArrow, SharedThis(this))
]
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(0.0f, 0.0f, 6.0f, 0.0f)
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(this, &SStructItem::GetIconImage)
.Visibility(this, &SStructItem::GetIconVisibility)
]
+SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(0.0f, 3.0f, 6.0f, 3.0f)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(StructDisplayName)
.HighlightText(InArgs._HighlightText)
.ColorAndOpacity(this, &SStructItem::GetTextColor)
.ToolTip(BuildToolTip())
.IsEnabled(!bIsRestricted)
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 6.0f, 0.0f)
[
SNew(SComboButton)
.ContentPadding(FMargin(2.0f))
.Visibility(this, &SStructItem::ShowOptions)
.OnGetMenuContent(this, &SStructItem::GenerateDropDown)
]
];
TextColor = InArgs._TextColor;
UE_LOG(LogEditorStructViewer, VeryVerbose, TEXT("STRUCT [%s]"), *StructDisplayName.ToString());
STableRow<TSharedPtr<FString>>::ConstructInternal(
STableRow::FArguments()
.ShowSelection(true)
.OnDragDetected(InArgs._OnDragDetected),
InOwnerTableView
);
}
private:
FReply OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
{
// If in a Struct Viewer and it has not been loaded, load the struct when double-left clicking.
if (bIsInStructViewer)
{
if (bDynamicStructLoading && InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
AssociatedNode->LoadStruct();
}
// If there is a struct asset, open its asset editor; otherwise try to open the struct header
if (const UUserDefinedStruct* UserDefinedStruct = AssociatedNode->GetStructAsset())
{
StructViewer::Helpers::OpenAssetEditor(UserDefinedStruct);
}
else if (const UScriptStruct* Struct = AssociatedNode->GetStruct())
{
StructViewer::Helpers::OpenStructInIDE(Struct);
}
}
else
{
OnDoubleClicked.ExecuteIfBound(AssociatedNode);
}
return FReply::Handled();
}
EVisibility ShowOptions() const
{
// If it's in viewer mode, show the options combo button.
return (bIsInStructViewer && AssociatedNode->GetStruct())
? EVisibility::Visible
: EVisibility::Collapsed;
}
/**
* Generates the drop down menu for the item.
*
* @return The drop down menu widget.
*/
TSharedRef<SWidget> GenerateDropDown()
{
if (const UScriptStruct* Struct = AssociatedNode->GetStruct())
{
return StructViewer::Helpers::CreateMenu(Struct);
}
return SNullWidget::NullWidget;
}
/** Returns the text color for the item based on if it is selected or not. */
FSlateColor GetTextColor() const
{
const TSharedPtr<ITypedTableView<TSharedPtr<FString>>> OwnerWidget = OwnerTablePtr.Pin();
const TSharedPtr<FString>* MyItem = OwnerWidget->Private_ItemFromWidget(this);
const bool bIsSelected = OwnerWidget->Private_IsItemSelected(*MyItem);
if (bIsSelected)
{
return FSlateColor::UseForeground();
}
return TextColor;
}
const FSlateBrush* GetIconImage() const
{
if (!AssociatedNode)
{
return nullptr;
}
return FSlateIconFinder::FindIconBrushForClass(AssociatedNode->GetStruct(), "ClassIcon.Object");
}
EVisibility GetIconVisibility() const
{
const UScriptStruct* Struct = AssociatedNode ? AssociatedNode->GetStruct() : nullptr;
return Struct && Struct->GetBoolMetaDataHierarchical("ShowIcon") && GetIconImage()
? EVisibility::Visible
: EVisibility::Collapsed;
}
private:
/** The struct name for which this item is associated with. */
FText StructDisplayName;
/** true if in a Struct Viewer (as opposed to a Struct Picker). */
bool bIsInStructViewer;
/** true if dynamic struct loading is permitted. */
bool bDynamicStructLoading;
/** The text color for this item. */
FSlateColor TextColor;
/** The Struct Viewer Node this item is associated with. */
TSharedPtr<FStructViewerNode> AssociatedNode;
/** the on Double Clicked delegate */
FOnStructItemDoubleClickDelegate OnDoubleClicked;
};
FStructHierarchy::FStructHierarchy()
{
// Register with the Asset Registry to be informed when it is done loading up files.
FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
AssetRegistryModule.Get().OnFilesLoaded().AddRaw(this, &FStructHierarchy::DirtyStructHierarchy);
AssetRegistryModule.Get().OnAssetAdded().AddRaw(this, &FStructHierarchy::AddAsset);
AssetRegistryModule.Get().OnAssetRemoved().AddRaw(this, &FStructHierarchy::RemoveAsset);
// Register to have Populate called when doing a Reload.
FCoreUObjectDelegates::ReloadCompleteDelegate.AddRaw(this, &FStructHierarchy::OnReloadComplete);
FModuleManager::Get().OnModulesChanged().AddRaw(this, &FStructHierarchy::OnModulesChanged);
PopulateStructHierarchy();
}
FStructHierarchy::~FStructHierarchy()
{
// Unregister with the Asset Registry to be informed when it is done loading up files.
if (FModuleManager::Get().IsModuleLoaded(TEXT("AssetRegistry")))
{
IAssetRegistry* AssetRegistry = FModuleManager::GetModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).TryGet();
if (AssetRegistry)
{
AssetRegistry->OnFilesLoaded().RemoveAll(this);
AssetRegistry->OnAssetAdded().RemoveAll(this);
AssetRegistry->OnAssetRemoved().RemoveAll(this);
}
// Unregister to have Populate called when doing a Reload.
FCoreUObjectDelegates::ReloadCompleteDelegate.RemoveAll(this);
}
FModuleManager::Get().OnModulesChanged().RemoveAll(this);
}
FStructHierarchy& FStructHierarchy::Get()
{
if (!Singleton)
{
Singleton = MakeUnique<FStructHierarchy>();
Singleton->PopulateStructHierarchy();
}
return *Singleton;
}
FStructHierarchy* FStructHierarchy::GetPtr()
{
return Singleton.Get();
}
void FStructHierarchy::DestroyInstance()
{
Singleton.Reset();
}
void FStructHierarchy::UpdateStructHierarchy()
{
if (bRefreshStructHierarchy)
{
bRefreshStructHierarchy = false;
PopulateStructHierarchy();
}
}
void FStructHierarchy::DirtyStructHierarchy()
{
bRefreshStructHierarchy = true;
}
void FStructHierarchy::PopulateStructHierarchy()
{
FScopedSlowTask SlowTask(0.0f, LOCTEXT("RebuildingStructHierarchy", "Rebuilding Struct Hierarchy"));
SlowTask.MakeDialog();
// Make a dummy root node
StructRootNode = MakeShared<FStructViewerNodeData>();
// Add the tree of native structs
{
TMap<const UScriptStruct*, TSharedPtr<FStructViewerNodeData>> DataNodes;
DataNodes.Add(nullptr, StructRootNode);
TSet<const UScriptStruct*> Visited;
UPackage* TransientPackage = GetTransientPackage();
// Go through all of the structs and see if they should be added to the list.
for (TObjectIterator<UScriptStruct> StructIt; StructIt; ++StructIt)
{
const UScriptStruct* CurrentStruct = *StructIt;
if (Visited.Contains(CurrentStruct) || CurrentStruct->GetOutermost() == TransientPackage)
{
// Skip transient structs as they are dead leftovers from user struct editing
continue;
}
while (CurrentStruct)
{
const UScriptStruct* SuperStruct = Cast<UScriptStruct>(CurrentStruct->GetSuperStruct());
TSharedPtr<FStructViewerNodeData>& ParentEntryRef = DataNodes.FindOrAdd(SuperStruct);
if (!ParentEntryRef.IsValid())
{
check(SuperStruct); // The null entry should have been created above
ParentEntryRef = MakeShared<FStructViewerNodeData>(SuperStruct);
}
// Need to have a pointer in-case the ref moves to avoid an extra look up in the map
TSharedPtr<FStructViewerNodeData> ParentEntry = ParentEntryRef;
TSharedPtr<FStructViewerNodeData>& MyEntryRef = DataNodes.FindOrAdd(CurrentStruct);
if (!MyEntryRef.IsValid())
{
MyEntryRef = MakeShared<FStructViewerNodeData>(CurrentStruct);
}
// Need to re-acquire the reference in the struct as it may have been invalidated from a re-size
if (!Visited.Contains(CurrentStruct))
{
ParentEntry->AddChild(MyEntryRef.ToSharedRef());
Visited.Add(CurrentStruct);
}
CurrentStruct = SuperStruct;
}
}
}
// Add any struct assets directly under the root (since they don't support inheritance)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
FARFilter Filter;
Filter.ClassPaths.Add(UUserDefinedStruct::StaticClass()->GetClassPathName());
Filter.bRecursiveClasses = true;
TArray<FAssetData> UserDefinedStructsList;
AssetRegistryModule.Get().GetAssets(Filter, UserDefinedStructsList);
for (const FAssetData& UserDefinedStructData : UserDefinedStructsList)
{
if (!UserDefinedStructData.IsAssetLoaded())
{
// If the asset is loaded it was added by the object iterator
StructRootNode->AddChild(MakeShared<FStructViewerNodeData>(UserDefinedStructData));
}
}
}
// All viewers must refresh.
PopulateStructViewerDelegate.Broadcast();
}
TSharedPtr<FStructViewerNodeData> FStructHierarchy::FindNodeByStructPath(const TSharedRef<FStructViewerNodeData>& InRootNode, const FSoftObjectPath& InStructPath)
{
// Check if the current node is the struct path that is being searched for
if (InRootNode->GetStructPath() == InStructPath)
{
return InRootNode;
}
// Search the children recursively, one of them might have the node
for (const TSharedPtr<FStructViewerNodeData>& ChildNode : InRootNode->GetChildNodes())
{
// Check the child, then check the return to see if it is valid. If it is valid, end the recursion.
TSharedPtr<FStructViewerNodeData> ReturnNode = FindNodeByStructPath(ChildNode.ToSharedRef(), InStructPath);
if (ReturnNode)
{
return ReturnNode;
}
}
return nullptr;
}
bool FStructHierarchy::FindAndRemoveNodeByStructPath(const TSharedRef<FStructViewerNodeData>& InRootNode, const FSoftObjectPath& InStructPath)
{
// Check if the current node contains a child of struct path that is being searched for
if (InRootNode->RemoveChild(InStructPath))
{
return true;
}
// Search the children recursively, one of them might have the node
for (const TSharedPtr<FStructViewerNodeData>& ChildNode : InRootNode->GetChildNodes())
{
// Check the child, then check the return to see if it is valid. If it is valid, end the recursion.
if (FindAndRemoveNodeByStructPath(ChildNode.ToSharedRef(), InStructPath))
{
return true;
}
}
return false;
}
void FStructHierarchy::AddAsset(const FAssetData& InAddedAssetData)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
if (!AssetRegistryModule.Get().IsLoadingAssets())
{
// Only handle structs
UClass* AssetClass = InAddedAssetData.GetClass();
if (AssetClass && AssetClass->IsChildOf(UScriptStruct::StaticClass()))
{
// Make sure that the node does not already exist. There is a bit of double adding going on at times and this prevents it.
if (!FindNodeByStructPath(InAddedAssetData.GetSoftObjectPath()))
{
// User defined structs are always root level structs
StructRootNode->AddChild(MakeShared<FStructViewerNodeData>(InAddedAssetData));
// All Viewers must repopulate.
PopulateStructViewerDelegate.Broadcast();
}
}
}
}
void FStructHierarchy::RemoveAsset(const FAssetData& InRemovedAssetData)
{
if (FindAndRemoveNodeByStructPath(InRemovedAssetData.GetSoftObjectPath()))
{
// All viewers must refresh.
PopulateStructViewerDelegate.Broadcast();
}
}
void FStructHierarchy::OnReloadComplete(EReloadCompleteReason Reason)
{
DirtyStructHierarchy();
}
void FStructHierarchy::OnModulesChanged(FName ModuleThatChanged, EModuleChangeReason ReasonForChange)
{
if (ReasonForChange == EModuleChangeReason::ModuleLoaded || ReasonForChange == EModuleChangeReason::ModuleUnloaded)
{
DirtyStructHierarchy();
}
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SStructViewer::Construct(const FArguments& InArgs, const FStructViewerInitializationOptions& InInitOptions)
{
bNeedsRefresh = true;
NumStructs = 0;
bCanShowInternalStructs = true;
// Listen for when view settings are changed
UStructViewerSettings::OnSettingChanged().AddSP(this, &SStructViewer::HandleSettingChanged);
InitOptions = InInitOptions;
OnStructPicked = InArgs._OnStructPickedDelegate;
TextFilterPtr = MakeShareable(new FTextFilterExpressionEvaluator(ETextFilterExpressionEvaluatorMode::BasicString));
bSaveExpansionStates = true;
bPendingSetExpansionStates = false;
bEnableStructDynamicLoading = InInitOptions.bEnableStructDynamicLoading;
EVisibility HeaderVisibility = (this->InitOptions.Mode == EStructViewerMode::StructBrowsing) ? EVisibility::Visible : EVisibility::Collapsed;
// Set these values to the user specified settings.
bShowUnloadedStructs = InitOptions.bShowUnloadedStructs;
bool bHasTitle = InitOptions.ViewerTitleString.IsEmpty() == false;
// If set to default, decide what display mode to use.
if (InitOptions.DisplayMode == EStructViewerDisplayMode::DefaultView)
{
// By default the Browser uses the tree view, the Picker the list. The option is available to users to force to another display mode when creating the Struct Browser/Picker.
if (InitOptions.Mode == EStructViewerMode::StructBrowsing)
{
InitOptions.DisplayMode = EStructViewerDisplayMode::TreeView;
}
else
{
InitOptions.DisplayMode = EStructViewerDisplayMode::ListView;
}
}
// Create the asset discovery indicator
FEditorWidgetsModule& EditorWidgetsModule = FModuleManager::LoadModuleChecked<FEditorWidgetsModule>("EditorWidgets");
TSharedRef<SWidget> AssetDiscoveryIndicator = EditorWidgetsModule.CreateAssetDiscoveryIndicator(EAssetDiscoveryIndicatorScaleMode::Scale_Vertical);
FOnContextMenuOpening OnContextMenuOpening;
if (InitOptions.Mode == EStructViewerMode::StructBrowsing)
{
OnContextMenuOpening = FOnContextMenuOpening::CreateSP(this, &SStructViewer::BuildMenuWidget);
}
SAssignNew(StructList, SListView<TSharedPtr<FStructViewerNode>>)
.SelectionMode(ESelectionMode::Single)
.ListItemsSource(&RootTreeItems)
// Generates the actual widget for a tree item
.OnGenerateRow(this, &SStructViewer::OnGenerateRowForStructViewer)
// Generates the right click menu.
.OnContextMenuOpening(OnContextMenuOpening)
// Find out when the user selects something in the tree
.OnSelectionChanged(this, &SStructViewer::OnStructViewerSelectionChanged)
.HeaderRow
(
SNew(SHeaderRow)
.Visibility(EVisibility::Collapsed)
+SHeaderRow::Column(TEXT("Struct"))
.DefaultLabel(NSLOCTEXT("StructViewer", "Struct", "Struct"))
);
SAssignNew(StructTree, STreeView<TSharedPtr<FStructViewerNode>>)
.SelectionMode(ESelectionMode::Single)
.TreeItemsSource(&RootTreeItems)
// Called to child items for any given parent item
.OnGetChildren(this, &SStructViewer::OnGetChildrenForStructViewerTree)
// Called to handle recursively expanding/collapsing items
.OnSetExpansionRecursive(this, &SStructViewer::SetAllExpansionStates_Helper)
// Generates the actual widget for a tree item
.OnGenerateRow(this, &SStructViewer::OnGenerateRowForStructViewer)
// Generates the right click menu.
.OnContextMenuOpening(OnContextMenuOpening)
// Find out when the user selects something in the tree
.OnSelectionChanged(this, &SStructViewer::OnStructViewerSelectionChanged)
// Called when the expansion state of an item changes
.OnExpansionChanged(this, &SStructViewer::OnStructViewerExpansionChanged)
.HeaderRow
(
SNew(SHeaderRow)
.Visibility(EVisibility::Collapsed)
+ SHeaderRow::Column(TEXT("Struct"))
.DefaultLabel(NSLOCTEXT("StructViewer", "Struct", "Struct"))
);
TSharedRef<STreeView<TSharedPtr<FStructViewerNode>>> StructTreeView = StructTree.ToSharedRef();
TSharedRef<SListView<TSharedPtr<FStructViewerNode>>> StructListView = StructList.ToSharedRef();
// Holds the bulk of the struct viewer's sub-widgets, to be added to the widget after construction
TSharedPtr<SWidget> StructViewerContent =
SNew(SBox)
.MaxDesiredHeight(800.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush(InitOptions.bShowBackgroundBorder ? "ToolPanel.GroupBorder" : "NoBorder"))
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Visibility(bHasTitle ? EVisibility::Visible : EVisibility::Collapsed)
.ColorAndOpacity(FAppStyle::GetColor("MultiboxHookColor"))
.Text(InitOptions.ViewerTitleString)
]
]
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.Padding(2.0f, 2.0f)
[
SAssignNew(SearchBox, SSearchBox)
.OnTextChanged(this, &SStructViewer::OnFilterTextChanged)
.OnTextCommitted(this, &SStructViewer::OnFilterTextCommitted)
]
]
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SSeparator)
.Visibility(HeaderVisibility)
]
+SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SOverlay)
+SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SScrollBorder, StructTreeView)
.Visibility(InitOptions.DisplayMode == EStructViewerDisplayMode::TreeView ? EVisibility::Visible : EVisibility::Collapsed)
[
StructTreeView
]
]
+SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SScrollBorder, StructListView)
.Visibility(InitOptions.DisplayMode == EStructViewerDisplayMode::ListView ? EVisibility::Visible : EVisibility::Collapsed)
[
StructListView
]
]
]
+SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Bottom)
.Padding(FMargin(24, 0, 24, 0))
[
// Asset discovery indicator
AssetDiscoveryIndicator
]
]
// Bottom panel
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
// Asset count
+SHorizontalBox::Slot()
.FillWidth(1.f)
.VAlign(VAlign_Center)
.Padding(8, 0)
[
SNew(STextBlock)
.Text(this, &SStructViewer::GetStructCountText)
]
// View mode combo button
+SHorizontalBox::Slot()
.AutoWidth()
[
SAssignNew(ViewOptionsComboButton, SComboButton)
.ContentPadding(0)
.ForegroundColor(this, &SStructViewer::GetViewButtonForegroundColor)
.ButtonStyle(FAppStyle::Get(), "ToggleButton") // Use the tool bar item style for this button
.OnGetMenuContent(this, &SStructViewer::GetViewButtonContent)
.ButtonContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SImage).Image(FAppStyle::GetBrush("GenericViewButton"))
]
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(2, 0, 0, 0)
.VAlign(VAlign_Center)
[
SNew(STextBlock).Text(LOCTEXT("ViewButton", "View Options"))
]
]
]
]
]
];
if (ViewOptionsComboButton.IsValid())
{
ViewOptionsComboButton->SetVisibility(InitOptions.bAllowViewOptions ? EVisibility::Visible : EVisibility::Collapsed);
}
// When using a struct picker in list-view mode, the widget will auto-focus the search box
// and allow the up and down arrow keys to navigate and enter to pick without using the mouse ever
if (InitOptions.Mode == EStructViewerMode::StructPicker && InitOptions.DisplayMode == EStructViewerDisplayMode::ListView)
{
this->ChildSlot
[
SNew(SListViewSelectorDropdownMenu<TSharedPtr<FStructViewerNode>>, SearchBox, StructList)
[
StructViewerContent.ToSharedRef()
]
];
}
else
{
this->ChildSlot
[
StructViewerContent.ToSharedRef()
];
}
// Ensure the struct hierarchy exists, and and watch for changes
FStructHierarchy::Get().GetPopulateStructViewerDelegate().AddSP(this, &SStructViewer::Refresh);
// Request delayed setting of focus to the search box
bPendingFocusNextFrame = true;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedRef<SWidget> SStructViewer::GetContent()
{
return SharedThis(this);
}
SStructViewer::~SStructViewer()
{
// No longer watching for changes
if (FStructHierarchy* StructHierarchy = FStructHierarchy::GetPtr())
{
StructHierarchy->GetPopulateStructViewerDelegate().RemoveAll(this);
}
// Remove the listener for when view settings are changed
UStructViewerSettings::OnSettingChanged().RemoveAll(this);
}
void SStructViewer::ClearSelection()
{
StructTree->ClearSelection();
}
void SStructViewer::OnGetChildrenForStructViewerTree(TSharedPtr<FStructViewerNode> InParent, TArray<TSharedPtr<FStructViewerNode>>& OutChildren)
{
// Simply return the children, it's already setup.
OutChildren = InParent->GetChildNodes();
}
void SStructViewer::OnStructViewerSelectionChanged(TSharedPtr<FStructViewerNode> Item, ESelectInfo::Type SelectInfo)
{
// Do not act on selection change when it is for navigation
if (SelectInfo == ESelectInfo::OnNavigation && InitOptions.DisplayMode == EStructViewerDisplayMode::ListView)
{
return;
}
// Sometimes the item is not valid anymore due to filtering.
if (Item.IsValid() == false || Item->IsRestricted())
{
return;
}
if (InitOptions.Mode != EStructViewerMode::StructBrowsing)
{
// Attempt to ensure the struct is loaded
Item->LoadStruct();
// Check if the item passed the filter, parent items might be displayed but filtered out and thus not desired to be selected.
if (Item->PassedFilter())
{
OnStructPicked.ExecuteIfBound(Item->GetStruct());
}
else
{
OnStructPicked.ExecuteIfBound(nullptr);
}
}
}
void SStructViewer::OnStructViewerExpansionChanged(TSharedPtr<FStructViewerNode> Item, bool bExpanded)
{
// Sometimes the item is not valid anymore due to filtering.
if (!Item.IsValid() || Item->IsRestricted())
{
return;
}
ExpansionStateMap.Add(Item->GetStructPath(), bExpanded);
}
TSharedPtr<SWidget> SStructViewer::BuildMenuWidget()
{
TArray<TSharedPtr<FStructViewerNode>> SelectedList;
// Based upon which mode the viewer is in, pull the selected item.
if (InitOptions.DisplayMode == EStructViewerDisplayMode::TreeView)
{
SelectedList = StructTree->GetSelectedItems();
}
else
{
SelectedList = StructList->GetSelectedItems();
}
// If there is no selected item, return a null widget.
if (SelectedList.Num() == 0)
{
return SNullWidget::NullWidget;
}
// Get the struct
const UScriptStruct* RightClickStruct = SelectedList[0]->GetStruct();
if (bEnableStructDynamicLoading && !RightClickStruct)
{
SelectedList[0]->LoadStruct();
RightClickStruct = SelectedList[0]->GetStruct();
// Populate the tree/list so any changes to previously unloaded structs will be reflected.
Refresh();
}
return StructViewer::Helpers::CreateMenu(RightClickStruct);
}
TSharedRef<ITableRow> SStructViewer::OnGenerateRowForStructViewer(TSharedPtr<FStructViewerNode> Item, const TSharedRef<STableViewBase>& OwnerTable)
{
// If the item was accepted by the filter, leave it bright, otherwise dim it.
float AlphaValue = Item->PassedFilter() ? 1.0f : 0.5f;
TSharedRef<SStructItem> ReturnRow = SNew(SStructItem, OwnerTable)
.StructDisplayName(Item->GetStructDisplayName(InitOptions.NameTypeToDisplay))
.HighlightText(SearchBox->GetText())
.TextColor(FLinearColor(1.0f, 1.0f, 1.0f, AlphaValue))
.AssociatedNode(Item)
.bIsInStructViewer(InitOptions.Mode == EStructViewerMode::StructBrowsing)
.bDynamicStructLoading(bEnableStructDynamicLoading)
.OnDragDetected(this, &SStructViewer::OnDragDetected)
.OnStructItemDoubleClicked(FOnStructItemDoubleClickDelegate::CreateSP(this, &SStructViewer::ToggleExpansionState_Helper));
// Expand the item if needed.
if (!bPendingSetExpansionStates)
{
bool* bIsExpanded = ExpansionStateMap.Find(Item->GetStructPath());
if (bIsExpanded && *bIsExpanded)
{
bPendingSetExpansionStates = true;
}
}
return ReturnRow;
}
const TArray<TSharedPtr<FStructViewerNode>> SStructViewer::GetSelectedItems() const
{
if (InitOptions.DisplayMode == EStructViewerDisplayMode::ListView)
{
return StructList->GetSelectedItems();
}
return StructTree->GetSelectedItems();
}
const int SStructViewer::GetNumItems() const
{
return NumStructs;
}
FSlateColor SStructViewer::GetViewButtonForegroundColor() const
{
static const FName InvertedForegroundName("InvertedForeground");
static const FName DefaultForegroundName("DefaultForeground");
return ViewOptionsComboButton->IsHovered() ? FAppStyle::GetSlateColor(InvertedForegroundName) : FAppStyle::GetSlateColor(DefaultForegroundName);
}
TSharedRef<SWidget> SStructViewer::GetViewButtonContent()
{
// Get all menu extenders for this context menu from the content browser module
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr, nullptr, /*bCloseSelfOnly=*/ true);
MenuBuilder.AddMenuEntry(LOCTEXT("ExpandAll", "Expand All"), LOCTEXT("ExpandAll_Tooltip", "Expands the entire tree"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SStructViewer::SetAllExpansionStates, bool(true))), NAME_None, EUserInterfaceActionType::Button);
MenuBuilder.AddMenuEntry(LOCTEXT("CollapseAll", "Collapse All"), LOCTEXT("CollapseAll_Tooltip", "Collapses the entire tree"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SStructViewer::SetAllExpansionStates, bool(false))), NAME_None, EUserInterfaceActionType::Button);
MenuBuilder.BeginSection("Filters", LOCTEXT("StructViewerFiltersHeading", "Struct Filters"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ShowInternalStructsOption", "Show Internal Structs"),
LOCTEXT("ShowInternalStructsOptionToolTip", "Shows internal-use only structs in the view."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SStructViewer::ToggleShowInternalStructs),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SStructViewer::IsShowingInternalStructs)
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("DeveloperViewType", LOCTEXT("DeveloperViewTypeHeading", "Developer Folder Filter"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("NoneDeveloperViewOption", "None"),
LOCTEXT("NoneDeveloperViewOptionToolTip", "Filter structs to show no structs in developer folders."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SStructViewer::SetCurrentDeveloperViewType, EStructViewerDeveloperType::SVDT_None),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SStructViewer::IsCurrentDeveloperViewType, EStructViewerDeveloperType::SVDT_None)
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("CurrentUserDeveloperViewOption", "Current Developer"),
LOCTEXT("CurrentUserDeveloperViewOptionToolTip", "Filter structs to allow structs in the current user's development folder."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SStructViewer::SetCurrentDeveloperViewType, EStructViewerDeveloperType::SVDT_CurrentUser),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SStructViewer::IsCurrentDeveloperViewType, EStructViewerDeveloperType::SVDT_CurrentUser)
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("AllUsersDeveloperViewOption", "All Developers"),
LOCTEXT("AllUsersDeveloperViewOptionToolTip", "Filter structs to allow structs in all users' development folders."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SStructViewer::SetCurrentDeveloperViewType, EStructViewerDeveloperType::SVDT_All),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SStructViewer::IsCurrentDeveloperViewType, EStructViewerDeveloperType::SVDT_All)
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
void SStructViewer::SetCurrentDeveloperViewType(EStructViewerDeveloperType NewType)
{
if (ensure((int)NewType < (int)EStructViewerDeveloperType::SVDT_Max) && NewType != GetDefault<UStructViewerSettings>()->DeveloperFolderType)
{
GetMutableDefault<UStructViewerSettings>()->DeveloperFolderType = NewType;
GetMutableDefault<UStructViewerSettings>()->PostEditChange();
}
}
EStructViewerDeveloperType SStructViewer::GetCurrentDeveloperViewType() const
{
if (!InitOptions.bAllowViewOptions)
{
return EStructViewerDeveloperType::SVDT_All;
}
return GetDefault<UStructViewerSettings>()->DeveloperFolderType;
}
bool SStructViewer::IsCurrentDeveloperViewType(EStructViewerDeveloperType ViewType) const
{
return GetCurrentDeveloperViewType() == ViewType;
}
void SStructViewer::GetInternalOnlyStructs(TArray<TSoftObjectPtr<const UScriptStruct>>& Structs)
{
if (!InitOptions.bAllowViewOptions)
{
return;
}
Structs = GetDefault<UStructViewerProjectSettings>()->InternalOnlyStructs;
}
void SStructViewer::GetInternalOnlyPaths(TArray<FDirectoryPath>& Paths)
{
if (!InitOptions.bAllowViewOptions)
{
return;
}
Paths = GetDefault<UStructViewerProjectSettings>()->InternalOnlyPaths;
}
FText SStructViewer::GetStructCountText() const
{
const int32 NumSelectedStructs = GetSelectedItems().Num();
if (NumSelectedStructs == 0)
{
return FText::Format(LOCTEXT("StructCountLabel", "{0} {0}|plural(one=item,other=items)"), NumStructs);
}
else
{
return FText::Format(LOCTEXT("StructCountLabelPlusSelection", "{0} {0}|plural(one=item,other=items) ({1} selected)"), NumStructs, NumSelectedStructs);
}
}
void SStructViewer::ExpandRootNodes()
{
for (int32 NodeIdx = 0; NodeIdx < RootTreeItems.Num(); ++NodeIdx)
{
ExpansionStateMap.Add(RootTreeItems[NodeIdx]->GetStructPath(), true);
StructTree->SetItemExpansion(RootTreeItems[NodeIdx], true);
}
}
FReply SStructViewer::OnDragDetected(const FGeometry& Geometry, const FPointerEvent& PointerEvent)
{
if (InitOptions.Mode == EStructViewerMode::StructBrowsing)
{
const TArray<TSharedPtr<FStructViewerNode>> SelectedItems = GetSelectedItems();
if (SelectedItems.Num() > 0 && SelectedItems[0].IsValid())
{
TSharedRef<FStructViewerNode> Item = SelectedItems[0].ToSharedRef();
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
// Spawn a loaded user defined struct just like any other asset from the Content Browser.
const FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(Item->GetStructPath());
if (AssetData.IsValid())
{
return FReply::Handled().BeginDragDrop(FContentBrowserDataDragDropOp::Legacy_New(MakeArrayView(&AssetData, 1)));
}
}
}
return FReply::Unhandled();
}
void SStructViewer::OnFilterTextChanged(const FText& InFilterText)
{
// Update the compiled filter and report any syntax error information back to the user
TextFilterPtr->SetFilterText(InFilterText);
SearchBox->SetError(TextFilterPtr->GetFilterErrorText());
// Repopulate the list to show only what has not been filtered out.
Refresh();
}
void SStructViewer::OnFilterTextCommitted(const FText& InText, ETextCommit::Type CommitInfo)
{
if (CommitInfo == ETextCommit::OnEnter)
{
if (InitOptions.Mode == EStructViewerMode::StructPicker)
{
TArray<TSharedPtr<FStructViewerNode>> SelectedList = StructList->GetSelectedItems();
if (SelectedList.Num() > 0)
{
TSharedPtr<FStructViewerNode> FirstSelected = SelectedList[0];
const UScriptStruct* Struct = FirstSelected->GetStruct();
// Try and ensure the struct is loaded
if (bEnableStructDynamicLoading && !Struct)
{
FirstSelected->LoadStruct();
Struct = FirstSelected->GetStruct();
}
// Check if the item passes the filter, parent items might be displayed but filtered out and thus not desired to be selected.
if (Struct && FirstSelected->PassedFilter())
{
OnStructPicked.ExecuteIfBound(Struct);
}
}
}
}
}
void SStructViewer::SetAllExpansionStates(bool bInExpansionState)
{
// Go through all the items in the root of the tree and recursively visit their children to set every item in the tree.
for (int32 ChildIndex = 0; ChildIndex < RootTreeItems.Num(); ChildIndex++)
{
SetAllExpansionStates_Helper(RootTreeItems[ChildIndex], bInExpansionState);
}
}
void SStructViewer::SetAllExpansionStates_Helper(TSharedPtr<FStructViewerNode> InNode, bool bInExpansionState)
{
StructTree->SetItemExpansion(InNode, bInExpansionState);
// Recursively go through the children.
for (const TSharedPtr<FStructViewerNode>& ChildNode : InNode->GetChildNodes())
{
SetAllExpansionStates_Helper(ChildNode, bInExpansionState);
}
}
void SStructViewer::ToggleExpansionState_Helper(TSharedPtr<FStructViewerNode> InNode)
{
const bool bExpanded = StructTree->IsItemExpanded(InNode);
StructTree->SetItemExpansion(InNode, !bExpanded);
}
bool SStructViewer::ExpandFilteredInNodes(TSharedPtr<FStructViewerNode> InNode)
{
bool bShouldExpand = InNode->PassedFilter();
for (const TSharedPtr<FStructViewerNode>& ChildNode : InNode->GetChildNodes())
{
bShouldExpand |= ExpandFilteredInNodes(ChildNode);
}
if (bShouldExpand)
{
StructTree->SetItemExpansion(InNode, true);
}
return bShouldExpand;
}
void SStructViewer::MapExpansionStatesInTree(TSharedPtr<FStructViewerNode> InItem)
{
ExpansionStateMap.Add(InItem->GetStructPath(), StructTree->IsItemExpanded(InItem));
// Map out all the children, this will be done recursively.
for (const TSharedPtr<FStructViewerNode>& ChildItem : InItem->GetChildNodes())
{
MapExpansionStatesInTree(ChildItem);
}
}
void SStructViewer::SetExpansionStatesInTree(TSharedPtr<FStructViewerNode> InItem)
{
const bool* bIsExpanded = ExpansionStateMap.Find(InItem->GetStructPath());
if (bIsExpanded)
{
StructTree->SetItemExpansion(InItem, *bIsExpanded);
// No reason to set expansion states if the parent is not expanded, it does not seem to do anything.
if (*bIsExpanded)
{
for (const TSharedPtr<FStructViewerNode>& ChildItem : InItem->GetChildNodes())
{
SetExpansionStatesInTree(ChildItem);
}
}
}
else
{
// Default to no expansion.
StructTree->SetItemExpansion(InItem, false);
}
}
int32 SStructViewer::CountTreeItems(FStructViewerNode* Node)
{
if (Node == nullptr)
{
return 0;
}
int32 Count = 1;
for (const TSharedPtr<FStructViewerNode>& ChildNode : Node->GetChildNodes())
{
Count += CountTreeItems(ChildNode.Get());
}
return Count;
}
void SStructViewer::Populate()
{
bPendingSetExpansionStates = false;
// If showing a struct tree, we may need to save expansion states.
if (InitOptions.DisplayMode == EStructViewerDisplayMode::TreeView)
{
if (bSaveExpansionStates)
{
for (const TSharedPtr<FStructViewerNode>& ChildNode : RootTreeItems)
{
// Check if the item is actually expanded or if it's only expanded because it is root level.
bool* bIsExpanded = ExpansionStateMap.Find(ChildNode->GetStructPath());
if ((bIsExpanded && !*bIsExpanded) || !bIsExpanded)
{
StructTree->SetItemExpansion(ChildNode, false);
}
// Recursively map out the expansion state of the tree-node.
MapExpansionStatesInTree(ChildNode);
}
}
// This is set to false before the call to populate when it is not desired.
bSaveExpansionStates = true;
}
// Empty the tree out so it can be redone.
RootTreeItems.Empty();
const bool ShowingInternalStructs = IsShowingInternalStructs();
TArray<const UScriptStruct*> InternalStructs;
TArray<FDirectoryPath> InternalPaths;
// If we aren't showing the internal structs, then we need to know what structs to consider Internal Only, so let's gather them up from the settings object.
if (!ShowingInternalStructs)
{
TArray<TSoftObjectPtr<const UScriptStruct>> InternalStructNames;
GetInternalOnlyPaths(InternalPaths);
GetInternalOnlyStructs(InternalStructNames);
// Take the package names for the internal only structs and convert them into their UScriptStructs
for (const TSoftObjectPtr<const UScriptStruct>& InternalStructName : InternalStructNames)
{
const TSharedPtr<FStructViewerNodeData> StructNode = FStructHierarchy::Get().FindNodeByStructPath(InternalStructName.ToSoftObjectPath());
if (StructNode.IsValid())
{
if (const UScriptStruct* Struct = StructNode->GetStruct())
{
InternalStructs.Add(Struct);
}
}
}
}
// Based on if the list or tree is visible we create what will be displayed differently.
if (InitOptions.DisplayMode == EStructViewerDisplayMode::TreeView)
{
// The root node for the tree, will be dummy instance which we will skip.
TSharedPtr<FStructViewerNode> RootNode;
// Get the struct tree, passing in certain filter options.
StructViewer::Helpers::GetStructTree(InitOptions, RootNode, *TextFilterPtr, bShowUnloadedStructs, GetCurrentDeveloperViewType(), ShowingInternalStructs, InternalStructs, InternalPaths);
// Sort the tree
RootNode->SortChildrenRecursive();
// Check if we will restore expansion states, we will not if there is filtering happening.
const bool bRestoreExpansionState = TextFilterPtr->GetFilterType() == ETextFilterExpressionType::Empty;
// Add all the children of the dummy root.
for (const TSharedPtr<FStructViewerNode>& ChildItem : RootNode->GetChildNodes())
{
RootTreeItems.Add(ChildItem);
if (bRestoreExpansionState)
{
SetExpansionStatesInTree(ChildItem);
}
// Expand any items that pass the filter.
if (TextFilterPtr->GetFilterType() != ETextFilterExpressionType::Empty)
{
ExpandFilteredInNodes(ChildItem);
}
}
// Only display this option if the user wants it and in Picker Mode.
if (InitOptions.bShowNoneOption && InitOptions.Mode == EStructViewerMode::StructPicker)
{
// @todo - It would seem smart to add this in before the other items, since it needs to be on top. However, that causes strange issues with saving/restoring expansion states.
// This is likely not very efficient since the list can have hundreds and even thousands of items.
RootTreeItems.Insert(MakeShared<FStructViewerNode>(), 0);
}
NumStructs = 0;
for (const TSharedPtr<FStructViewerNode>& ChildNode : RootTreeItems)
{
NumStructs += CountTreeItems(ChildNode.Get());
}
// Now that new items are in the tree, we need to request a refresh.
StructTree->RequestTreeRefresh();
}
else
{
// Get the struct list, passing in certain filter options.
StructViewer::Helpers::GetStructList(InitOptions, RootTreeItems, *TextFilterPtr, bShowUnloadedStructs, GetCurrentDeveloperViewType(), ShowingInternalStructs, InternalStructs, InternalPaths);
// Sort the list alphabetically.
FStructViewerNode::SortNodes(RootTreeItems, InitOptions.PropertyHandle);
// Scroll to selected struct
for (const TSharedPtr<FStructViewerNode>& Node : RootTreeItems)
{
if (Node.IsValid())
{
if (InitOptions.SelectedStruct == Node->GetStruct())
{
StructList->RequestScrollIntoView(Node);
}
}
}
// Only display this option if the user wants it and in Picker Mode.
if (InitOptions.bShowNoneOption && InitOptions.Mode == EStructViewerMode::StructPicker)
{
TSharedRef<FStructViewerNode> NoneNode = MakeShared<FStructViewerNode>();
// @todo - It would seem smart to add this in before the other items, since it needs to be on top. However, that causes strange issues with saving/restoring expansion states.
// This is likely not very efficient since the list can have hundreds and even thousands of items.
if (StructViewer::Helpers::PassesFilter(NoneNode->GetStructName(), *TextFilterPtr) ||
StructViewer::Helpers::PassesFilter(NoneNode->GetStructDisplayName().ToString(), *TextFilterPtr))
{
RootTreeItems.Insert(MoveTemp(NoneNode), 0);
}
}
NumStructs = 0;
for (const TSharedPtr<FStructViewerNode>& ChildNode : RootTreeItems)
{
NumStructs += CountTreeItems(ChildNode.Get());
}
// Now that new items are in the list, we need to request a refresh.
StructList->RequestListRefresh();
}
}
FReply SStructViewer::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
// Forward key down to struct tree
return StructTree->OnKeyDown(MyGeometry, InKeyEvent);
}
FReply SStructViewer::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent)
{
if (RootTreeItems.Num() > 0)
{
StructTree->SetItemSelection(RootTreeItems[0], true, ESelectInfo::OnMouseClick);
StructTree->SetItemExpansion(RootTreeItems[0], true);
OnStructViewerSelectionChanged(RootTreeItems[0], ESelectInfo::OnMouseClick);
}
FSlateApplication::Get().SetKeyboardFocus(SearchBox.ToSharedRef(), EFocusCause::SetDirectly);
return FReply::Unhandled();
}
bool SStructViewer::SupportsKeyboardFocus() const
{
return true;
}
void SStructViewer::DestroyStructHierarchy()
{
FStructHierarchy::DestroyInstance();
}
void SStructViewer::Refresh()
{
bNeedsRefresh = true;
}
void SStructViewer::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
// Will populate the struct hierarchy as needed.
FStructHierarchy::Get().UpdateStructHierarchy();
// Move focus to search box
if (bPendingFocusNextFrame && SearchBox.IsValid())
{
FWidgetPath WidgetToFocusPath;
FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchBox.ToSharedRef(), WidgetToFocusPath);
FSlateApplication::Get().SetKeyboardFocus(WidgetToFocusPath, EFocusCause::SetDirectly);
bPendingFocusNextFrame = false;
}
if (bNeedsRefresh)
{
bNeedsRefresh = false;
Populate();
if (InitOptions.bExpandRootNodes)
{
ExpandRootNodes();
}
}
if (bPendingSetExpansionStates)
{
check(RootTreeItems.Num() > 0);
SetExpansionStatesInTree(RootTreeItems[0]);
bPendingSetExpansionStates = false;
}
}
bool SStructViewer::IsStructAllowed(const UScriptStruct* InStruct) const
{
return StructViewer::Helpers::IsStructAllowed(InitOptions, InStruct);
}
void SStructViewer::HandleSettingChanged(FName PropertyName)
{
if ((PropertyName == "DisplayInternalClasses") ||
(PropertyName == "DeveloperFolderType") ||
(PropertyName == NAME_None)) // @todo: Needed if PostEditChange was called manually, for now
{
Refresh();
}
}
void SStructViewer::ToggleShowInternalStructs()
{
check(IsToggleShowInternalStructsAllowed());
GetMutableDefault<UStructViewerSettings>()->DisplayInternalStructs = !GetDefault<UStructViewerSettings>()->DisplayInternalStructs;
GetMutableDefault<UStructViewerSettings>()->PostEditChange();
}
bool SStructViewer::IsToggleShowInternalStructsAllowed() const
{
return bCanShowInternalStructs;
}
bool SStructViewer::IsShowingInternalStructs() const
{
if (!InitOptions.bAllowViewOptions)
{
return true;
}
return IsToggleShowInternalStructsAllowed() && GetDefault<UStructViewerSettings>()->DisplayInternalStructs;
}
#undef LOCTEXT_NAMESPACE