Files
UnrealEngine/Engine/Plugins/PCG/Source/PCGEditor/Private/PCGEditor.cpp
2025-05-18 13:04:45 +08:00

3478 lines
104 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PCGEditor.h"
#include "PCGComponent.h"
#include "PCGEdge.h"
#include "PCGEditorModule.h"
#include "PCGGraph.h"
#include "PCGGraphFactory.h"
#include "PCGPin.h"
#include "PCGSubsystem.h"
#include "Editor/IPCGEditorModule.h"
#include "Elements/PCGReroute.h"
#include "Helpers/PCGSubgraphHelpers.h"
#include "Rendering/SlateRenderer.h"
#include "Tests/Determinism/PCGDeterminismNativeTests.h"
#include "Tests/Determinism/PCGDeterminismTestBlueprintBase.h"
#include "Tests/Determinism/PCGDeterminismTestsCommon.h"
#include "PCGEditorCommands.h"
#include "PCGEditorGraph.h"
#include "PCGEditorGraphSchema.h"
#include "PCGEditorGraphSchemaActions.h"
#include "PCGEditorMenuContext.h"
#include "PCGEditorSettings.h"
#include "PCGEditorStyle.h"
#include "PCGEditorUtils.h"
#include "Nodes/PCGEditorGraphNode.h"
#include "Nodes/PCGEditorGraphNodeInput.h"
#include "Nodes/PCGEditorGraphNodeOutput.h"
#include "Nodes/PCGEditorGraphNodeReroute.h"
#include "Widgets/SPCGEditorGraphActionWidget.h"
#include "Widgets/SPCGEditorGraphAttributeListView.h"
#include "Widgets/SPCGEditorGraphDebugObjectTree.h"
#include "Widgets/SPCGEditorGraphDetailsView.h"
#include "Widgets/SPCGEditorGraphDeterminism.h"
#include "Widgets/SPCGEditorGraphFind.h"
#include "Widgets/SPCGEditorGraphLogView.h"
#include "Widgets/SPCGEditorGraphNodePalette.h"
#include "Widgets/SPCGEditorGraphParamsView.h"
#include "Widgets/SPCGEditorGraphProfilingView.h"
#include "Widgets/SPCGEditorNodeSource.h"
#include "Widgets/AssetEditorViewport/SPCGEditorViewport.h"
#include "AssetToolsModule.h"
#include "EdGraphUtilities.h"
#include "EditorAssetLibrary.h"
#include "GraphEditorActions.h"
#include "IAssetTools.h"
#include "LevelEditor.h"
#include "ScopedTransaction.h"
#include "SGraphEditorActionMenu.h"
#include "ShaderCore.h"
#include "SNodePanel.h"
#include "SourceCodeNavigation.h"
#include "ToolMenu.h"
#include "ToolMenuEntry.h"
#include "ToolMenus.h"
#include "ToolMenuSection.h"
#include "UnrealEdGlobals.h"
#include "Algo/AnyOf.h"
#include "Editor/UnrealEdEngine.h"
#include "Fonts/FontMeasure.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/GenericCommands.h"
#include "Framework/Notifications/NotificationManager.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Misc/ITransaction.h"
#include "Misc/MessageDialog.h"
#include "Misc/TransactionObjectEvent.h"
#include "Preferences/UnrealEdOptions.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Notifications/SNotificationList.h"
#define LOCTEXT_NAMESPACE "PCGGraphEditor"
namespace FPCGEditor_private
{
const FName GraphEditorID = FName(TEXT("GraphEditor"));
const FName PropertyDetailsID[] = {
FName(TEXT("PropertyDetails")),
FName(TEXT("PropertyDetails2")),
FName(TEXT("PropertyDetails3")),
FName(TEXT("PropertyDetails4")) };
const FName PaletteID = FName(TEXT("Palette"));
const FName DebugObjectID = FName(TEXT("DebugObject"));
const FName AttributesID[] = {
FName(TEXT("Attributes")),
FName(TEXT("Attributes2")),
FName(TEXT("Attributes3")),
FName(TEXT("Attributes4")) };
const FName FindID = FName(TEXT("Find"));
const FName DeterminismID = FName(TEXT("Determinism"));
const FName ProfilingID = FName(TEXT("Profiling"));
const FName LogID = FName(TEXT("Log"));
const FName HLSLSourceID = FName(TEXT("HLSLSource"));
const FName UserParamsID = FName(TEXT("UserParams"));
const FName ViewportID[] = {
FName(TEXT("Viewport")),
FName(TEXT("Viewport2")),
FName(TEXT("Viewport3")),
FName(TEXT("Viewport4")) };
const FText UserParamsTabName = LOCTEXT("UserParamsTab", "Graph Parameters");
}
UPCGEditorGraph* FPCGEditor::GetPCGEditorGraph(UPCGGraph* InGraph)
{
if (!InGraph)
{
return nullptr;
}
if (!InGraph->PCGEditorGraph)
{
InGraph->PCGEditorGraph = NewObject<UPCGEditorGraph>(InGraph, UPCGEditorGraph::StaticClass(), NAME_None, RF_Transactional | RF_Transient);
InGraph->PCGEditorGraph->Schema = UPCGEditorGraphSchema::StaticClass();
InGraph->PCGEditorGraph->InitFromNodeGraph(InGraph);
}
return InGraph->PCGEditorGraph;
}
UPCGEditorGraph* FPCGEditor::GetPCGEditorGraph(const UPCGNode* InNode)
{
UPCGGraph* PCGGraph = InNode ? Cast<UPCGGraph>(InNode->GetOuter()) : nullptr;
return GetPCGEditorGraph(PCGGraph);
}
UPCGEditorGraph* FPCGEditor::GetPCGEditorGraph(const UPCGSettings* InSettings)
{
UPCGNode* PCGNode = InSettings ? Cast<UPCGNode>(InSettings->GetOuter()) : nullptr;
return GetPCGEditorGraph(PCGNode);
}
void FPCGEditor::Initialize(const EToolkitMode::Type InMode, const TSharedPtr<class IToolkitHost>& InToolkitHost, UPCGGraph* InPCGGraph)
{
PCGGraphBeingEdited = InPCGGraph;
// Initializes the UPCGEditorGraph if needed
GetPCGEditorGraph(InPCGGraph);
PCGGraphBeingEdited->PCGEditorGraph->SetEditor(SharedThis(this));
PCGEditorGraph = PCGGraphBeingEdited->PCGEditorGraph;
for (int PropertyDetailsIndex = 0; PropertyDetailsIndex < 4; ++PropertyDetailsIndex)
{
TSharedRef<SPCGEditorGraphDetailsView> PropertyDetailsWidget = SNew(SPCGEditorGraphDetailsView);
PropertyDetailsWidget->SetEditor(SharedThis(this));
PropertyDetailsWidget->SetObject(PCGGraphBeingEdited);
PropertyDetailsWidgets.Add(PropertyDetailsWidget);
}
GraphEditorWidget = CreateGraphEditorWidget();
PaletteWidget = CreatePaletteWidget();
DebugObjectTreeWidget = CreateDebugObjectTreeWidget();
FindWidget = CreateFindWidget();
for (int AttributesIndex = 0; AttributesIndex < 4; ++AttributesIndex)
{
const int ViewportEditorPanel = static_cast<int>(EPCGEditorPanel::Viewport1) + AttributesIndex;
AttributesWidgets.Add(CreateAttributesWidget());
AttributesWidgets[AttributesIndex]->SetViewportWidget(CreateViewportWidget(), static_cast<EPCGEditorPanel>(ViewportEditorPanel));
}
DeterminismWidget = CreateDeterminismWidget();
ProfilingWidget = CreateProfilingWidget();
LogWidget = CreateLogWidget();
NodeSourceWidget = CreateNodeSourceWidget();
UserParamsWidget = CreateGraphParamsWidget();
BindCommands();
RegisterToolbar();
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = GetDefaultLayout();
const FName PCGGraphEditorAppName = FName(TEXT("PCGEditorApp"));
InitAssetEditor(InMode, InToolkitHost, PCGGraphEditorAppName, StandaloneDefaultLayout, /*bCreateDefaultStandaloneMenu=*/ true, /*bCreateDefaultToolbar=*/ true, InPCGGraph);
PCGGraphBeingEdited->OnGraphChangedDelegate.AddRaw(this, &FPCGEditor::OnGraphChanged);
PCGGraphBeingEdited->OnNodeSourceCompiledDelegate.AddRaw(this, &FPCGEditor::OnNodeSourceCompiled);
// Hook to map change / delete actor to refresh debug object selection list, to help prevent it going stale.
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
LevelEditor.OnMapChanged().AddRaw(this, &FPCGEditor::OnMapChanged);
if (GEngine)
{
GEngine->OnLevelActorDeleted().AddRaw(this, &FPCGEditor::OnLevelActorDeleted);
}
// Hook to PIE start/end to keep callbacks up to date.
FEditorDelegates::PostPIEStarted.AddRaw(this, &FPCGEditor::OnPostPIEStarted);
FEditorDelegates::EndPIE.AddRaw(this, &FPCGEditor::OnEndPIE);
if (GEditor)
{
RegisterDelegatesForWorld(GEditor->GetEditorWorldContext().World());
// In case the editor is opened while in PIE, we should try setting up callbacks for the PIE world.
RegisterDelegatesForWorld(GEditor->PlayWorld.Get());
}
// Clear inspection flag on all nodes.
for (UEdGraphNode* EdGraphNode : PCGEditorGraph->Nodes)
{
if (UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(EdGraphNode))
{
PCGEditorGraphNode->SetInspected(false);
}
}
}
UPCGEditorGraph* FPCGEditor::GetPCGEditorGraph()
{
return PCGEditorGraph;
}
void FPCGEditor::SetStackBeingInspectedFromAnotherEditor(const FPCGStack& FullStack)
{
if (DebugObjectTreeWidget)
{
DebugObjectTreeWidget->SetDebugObjectFromStackFromAnotherEditor(FullStack);
}
}
void FPCGEditor::SetStackBeingInspected(const FPCGStack& FullStack)
{
if (FullStack == StackBeingInspected)
{
// No-op if we're already inspecting this stack.
return;
}
UPCGComponent* LastComponent = LastValidPCGComponentBeingInspected.Get();
UPCGComponent* NewComponent = const_cast<UPCGComponent*>(FullStack.GetRootComponent());
if (NewComponent && NewComponent != LastComponent)
{
if (LastComponent && LastComponent->GetExecutionState().GetInspection().IsInspecting())
{
LastComponent->GetExecutionState().GetInspection().DisableInspection();
}
LastValidPCGComponentBeingInspected = NewComponent;
}
if (PCGGraphBeingEdited)
{
if (PCGGraphBeingEdited->IsInspecting())
{
PCGGraphBeingEdited->DisableInspection();
}
PCGGraphBeingEdited->EnableInspection(StackBeingInspected);
}
PCGComponentBeingInspected = NewComponent;
StackBeingInspected = FullStack;
OnInspectedStackChangedDelegate.Broadcast(StackBeingInspected);
UpdateAfterInspectedStackChanged();
}
void FPCGEditor::OnComponentGenerated(UPCGComponent* InComponent)
{
if (DebugObjectTreeWidget)
{
DebugObjectTreeWidget->RequestRefresh();
}
if(InComponent == GetPCGComponentBeingInspected())
{
TRACE_CPUPROFILER_EVENT_SCOPE(FPCGEditor::SetStackBeingInspected::BroadcastStackBeingInspected);
OnInspectedStackChangedDelegate.Broadcast(StackBeingInspected);
}
}
bool FPCGEditor::OnValidateNodeTitle(const FText& NewName, UEdGraphNode* GraphNode, FText& OutErrorMessage)
{
if (UPCGEditorGraphNode* PCGGraphNode = Cast<UPCGEditorGraphNode>(GraphNode))
{
return PCGGraphNode->OnValidateNodeTitle(NewName, OutErrorMessage);
}
else if (GraphNode && GraphNode->IsA<UEdGraphNode_Comment>())
{
return true;
}
return false;
}
void FPCGEditor::UpdateAfterInspectedStackChanged()
{
UPCGComponent* Component = PCGComponentBeingInspected.Get();
if (Component)
{
// Implementation note: if we're inspecting and have not pre-run the graph, then it probably makes sense to enable inspection by default.
// TODO This could be selected with a cvar though.
const bool bHasBeenGeneratedThisSession = Component->bGenerated && Component->WasGeneratedThisSession();
const bool bWasInspecting = Component->GetExecutionState().GetInspection().IsInspecting();
const bool bNeedsInspection = Algo::AnyOf(AttributesWidgets, [](const TSharedPtr<SPCGEditorGraphAttributeListView>& ALV) { return ALV->GetNodeBeingInspected() != nullptr; });
if (!bHasBeenGeneratedThisSession || (bNeedsInspection && !bWasInspecting))
{
Component->GetExecutionState().GetInspection().EnableInspection();
UpdateDebugAfterComponentSelection(Component, Component, true);
}
}
check(PCGEditorGraph);
for (UEdGraphNode* Node : PCGEditorGraph->Nodes)
{
if (UPCGEditorGraphNodeBase* PCGNode = Cast<UPCGEditorGraphNodeBase>(Node))
{
// Update now that component has changed. Will fire OnNodeChanged if necessary.
EPCGChangeType ChangeType = PCGNode->UpdateErrorsAndWarnings();
ChangeType |= PCGNode->UpdateStructuralVisualization(Component, &StackBeingInspected);
ChangeType |= PCGNode->UpdateGPUVisualization(Component, &StackBeingInspected);
if (ChangeType != EPCGChangeType::None)
{
PCGNode->ReconstructNode();
}
}
}
}
void FPCGEditor::ClearStackBeingInspected()
{
if (GetStackBeingInspected())
{
SetStackBeingInspected(FPCGStack());
}
}
void FPCGEditor::UpdateDebugAfterComponentSelection(UPCGComponent* InOldComponent, UPCGComponent* InNewComponent, bool bInNewComponentStartedInspecting)
{
if (!ensure(PCGGraphBeingEdited))
{
return;
}
auto RefreshComponent = [](UPCGComponent* Component)
{
if (!ensure(Component))
{
return;
}
// GenerateAtRuntime components should be refreshed through the runtime gen scheduler.
if (Component->IsManagedByRuntimeGenSystem())
{
if (UPCGSubsystem* Subsystem = GetSubsystem())
{
// We don't want to do a full cleanup if we're setting the debug object, since full cleanup destroys the component, which is the debug object itself!
Subsystem->RefreshRuntimeGenComponent(Component);
}
}
else
{
Component->GenerateLocal(/*bForce=*/true);
}
};
// If individual component debugging is disabled, just generate the new component if required.
if (!PCGGraphBeingEdited->DebugFlagAppliesToIndividualComponents())
{
if (InNewComponent && bInNewComponentStartedInspecting)
{
RefreshComponent(InNewComponent);
}
return;
}
// Trigger necessary generation(s) for per-component debugging.
if (!InOldComponent)
{
if (InNewComponent && bInNewComponentStartedInspecting)
{
// Transition from 'null' to 'any component not already inspecting' - generate to create debug/inspection info.
// If we have null selected, all components are displaying debug. Go to Original component so that all refresh.
RefreshComponent(InNewComponent->GetOriginalComponent());
}
}
else
{
const bool bDebugFlagSetOnAnyNode = Algo::AnyOf(PCGGraphBeingEdited->GetNodes(), [](const UPCGNode* InNode)
{
return InNode && InNode->GetSettings() && InNode->GetSettings()->bDebug;
});
// Regenerate to clear debug info if switching components, or if changing from a component to null.
if (InNewComponent != InOldComponent && (InNewComponent || bDebugFlagSetOnAnyNode))
{
// Use original component - debug can be displayed both by the local component and parent local components.
RefreshComponent(InOldComponent->GetOriginalComponent());
}
// Debug new component if it wasn't already
if (InNewComponent && bInNewComponentStartedInspecting)
{
// Use original component - debug can be displayed both by the local component and parent local components.
RefreshComponent(InNewComponent->GetOriginalComponent());
}
}
}
const FPCGStack* FPCGEditor::GetStackBeingInspected() const
{
return StackBeingInspected.GetStackFrames().IsEmpty() ? nullptr : &StackBeingInspected;
}
void FPCGEditor::SetSourceEditorTargetObject(UObject* InObject)
{
NodeSourceWidget->SetTextProviderObject(InObject);
}
void FPCGEditor::JumpToNode(const UEdGraphNode* InNode)
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->JumpToNode(InNode);
}
}
UPCGEditorGraphNodeBase* FPCGEditor::GetEditorNode(const UPCGNode* InNode)
{
if (!ensure(PCGEditorGraph) || !InNode)
{
return nullptr;
}
for (UEdGraphNode* EdGraphNode : PCGEditorGraph->Nodes)
{
if (UPCGEditorGraphNodeBase* PCGEdGraphNode = Cast<UPCGEditorGraphNodeBase>(EdGraphNode))
{
if (PCGEdGraphNode->GetPCGNode() == InNode)
{
return PCGEdGraphNode;
}
}
}
return nullptr;
}
void FPCGEditor::JumpToNode(const UPCGNode* InNode)
{
if (const UPCGEditorGraphNodeBase* EditorNode = GetEditorNode(InNode))
{
JumpToNode(EditorNode);
}
}
FName FPCGEditor::GetPanelID(const EPCGEditorPanel Panel) const
{
switch (Panel)
{
case EPCGEditorPanel::Attributes1:
return FPCGEditor_private::AttributesID[0];
case EPCGEditorPanel::Attributes2:
return FPCGEditor_private::AttributesID[1];
case EPCGEditorPanel::Attributes3:
return FPCGEditor_private::AttributesID[2];
case EPCGEditorPanel::Attributes4:
return FPCGEditor_private::AttributesID[3];
case EPCGEditorPanel::DebugObjectTree:
return FPCGEditor_private::DebugObjectID;
case EPCGEditorPanel::Determinism:
return FPCGEditor_private::DeterminismID;
case EPCGEditorPanel::Find:
return FPCGEditor_private::FindID;
case EPCGEditorPanel::GraphEditor:
return FPCGEditor_private::GraphEditorID;
case EPCGEditorPanel::Log:
return FPCGEditor_private::LogID;
case EPCGEditorPanel::NodePalette:
return FPCGEditor_private::PaletteID;
case EPCGEditorPanel::NodeSource:
return FPCGEditor_private::HLSLSourceID;
case EPCGEditorPanel::Profiling:
return FPCGEditor_private::ProfilingID;
case EPCGEditorPanel::PropertyDetails1:
return FPCGEditor_private::PropertyDetailsID[0];
case EPCGEditorPanel::PropertyDetails2:
return FPCGEditor_private::PropertyDetailsID[1];
case EPCGEditorPanel::PropertyDetails3:
return FPCGEditor_private::PropertyDetailsID[2];
case EPCGEditorPanel::PropertyDetails4:
return FPCGEditor_private::PropertyDetailsID[3];
case EPCGEditorPanel::UserParams:
return FPCGEditor_private::UserParamsID;
case EPCGEditorPanel::Viewport1:
return FPCGEditor_private::ViewportID[0];
case EPCGEditorPanel::Viewport2:
return FPCGEditor_private::ViewportID[1];
case EPCGEditorPanel::Viewport3:
return FPCGEditor_private::ViewportID[2];
case EPCGEditorPanel::Viewport4:
return FPCGEditor_private::ViewportID[3];
default:
return NAME_None;
}
}
void FPCGEditor::BringFocusToPanel(const EPCGEditorPanel Panel) const
{
const FName PanelID = GetPanelID(Panel);
if (PanelID != NAME_None)
{
const TSharedPtr<SDockTab> Tab = TabManager->TryInvokeTab(PanelID);
Tab->DrawAttention(); // Bring the panel to focus and flash the tab
}
}
void FPCGEditor::CloseGraphPanel(const EPCGEditorPanel Panel) const
{
const FName PanelID = GetPanelID(Panel);
if (PanelID != NAME_None)
{
const TSharedPtr<SDockTab> Tab = TabManager->FindExistingLiveTab(PanelID);
Tab->RequestCloseTab();
}
}
bool FPCGEditor::IsPanelCurrentlyOpen(const EPCGEditorPanel Panel) const
{
return TabManager.IsValid() && TabManager->FindExistingLiveTab(GetPanelID(Panel));
}
bool FPCGEditor::IsPanelCurrentlyForeground(const EPCGEditorPanel Panel) const
{
const TSharedPtr<SDockTab> DockTab = TabManager.IsValid() ? TabManager->FindExistingLiveTab(GetPanelID(Panel)) : nullptr;
return DockTab.IsValid() && DockTab->IsForeground();
}
void FPCGEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_PCGEditor", "PCG Editor"));
TSharedRef<FWorkspaceItem> DetailsGroup = WorkspaceMenuCategory->AddGroup(LOCTEXT("WorkspaceMenu_PCGEditor_Details", "Details"));
TSharedRef<FWorkspaceItem> AttributesGroup = WorkspaceMenuCategory->AddGroup(LOCTEXT("WorkspaceMenu_PCGEditor_Attributes", "Attributes"));
TSharedRef<FWorkspaceItem> ViewportGroup = WorkspaceMenuCategory->AddGroup(LOCTEXT("WorkspaceMenu_PCGEditor_Viewport", "Data Viewport"));
const TSharedRef<FWorkspaceItem>& WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
//TODO: Add Icons
InTabManager->RegisterTabSpawner(FPCGEditor_private::GraphEditorID, FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_GraphEditor))
.SetDisplayName(LOCTEXT("GraphTab", "Graph"))
.SetGroup(WorkspaceMenuCategoryRef);
InTabManager->RegisterTabSpawner(FPCGEditor_private::PropertyDetailsID[0], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_PropertyDetails, 0))
.SetDisplayName(LOCTEXT("DetailsTab1", "Details 1"))
.SetGroup(DetailsGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::PropertyDetailsID[1], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_PropertyDetails, 1))
.SetDisplayName(LOCTEXT("DetailsTab2", "Details 2"))
.SetGroup(DetailsGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::PropertyDetailsID[2], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_PropertyDetails, 2))
.SetDisplayName(LOCTEXT("DetailsTab3", "Details 3"))
.SetGroup(DetailsGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::PropertyDetailsID[3], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_PropertyDetails, 3))
.SetDisplayName(LOCTEXT("DetailsTab4", "Details 4"))
.SetGroup(DetailsGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::PaletteID, FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Palette))
.SetDisplayName(LOCTEXT("PaletteTab", "Palette"))
.SetGroup(WorkspaceMenuCategoryRef);
InTabManager->RegisterTabSpawner(FPCGEditor_private::DebugObjectID, FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_DebugObjectTree))
.SetDisplayName(LOCTEXT("DebugTab", "Debug Object Tree"))
.SetGroup(WorkspaceMenuCategoryRef);
InTabManager->RegisterTabSpawner(FPCGEditor_private::AttributesID[0], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Attributes, 0))
.SetDisplayName(LOCTEXT("AttributesTab1", "Attributes 1"))
.SetGroup(AttributesGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::AttributesID[1], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Attributes, 1))
.SetDisplayName(LOCTEXT("AttributesTab2", "Attributes 2"))
.SetGroup(AttributesGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::AttributesID[2], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Attributes, 2))
.SetDisplayName(LOCTEXT("AttributesTab3", "Attributes 3"))
.SetGroup(AttributesGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::AttributesID[3], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Attributes, 3))
.SetDisplayName(LOCTEXT("AttributesTab4", "Attributes 4"))
.SetGroup(AttributesGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::FindID, FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Find))
.SetDisplayName(LOCTEXT("FindTab", "Find"))
.SetGroup(WorkspaceMenuCategoryRef);
InTabManager->RegisterTabSpawner(FPCGEditor_private::DeterminismID, FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Determinism))
.SetDisplayName(LOCTEXT("DeterminismTab", "Determinism"))
.SetGroup(WorkspaceMenuCategoryRef);
InTabManager->RegisterTabSpawner(FPCGEditor_private::ProfilingID, FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Profiling))
.SetDisplayName(LOCTEXT("ProfilingTab", "Profiling"))
.SetGroup(WorkspaceMenuCategoryRef);
InTabManager->RegisterTabSpawner(FPCGEditor_private::LogID, FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Log))
.SetDisplayName(LOCTEXT("LogCaptureTab", "Log Capture"))
.SetGroup(WorkspaceMenuCategoryRef);
InTabManager->RegisterTabSpawner(FPCGEditor_private::HLSLSourceID, FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_NodeSource))
.SetDisplayName(LOCTEXT("NodeSourceTab", "HLSL Source"))
.SetGroup(WorkspaceMenuCategoryRef);
InTabManager->RegisterTabSpawner(FPCGEditor_private::UserParamsID, FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_UserParams))
.SetDisplayName(FPCGEditor_private::UserParamsTabName)
.SetGroup(WorkspaceMenuCategoryRef);
InTabManager->RegisterTabSpawner(FPCGEditor_private::ViewportID[0], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Viewport, 0))
.SetDisplayName(LOCTEXT("ViewportTab1", "Data Viewport 1"))
.SetGroup(ViewportGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::ViewportID[1], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Viewport, 1))
.SetDisplayName(LOCTEXT("ViewportTab2", "Data Viewport 2"))
.SetGroup(ViewportGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::ViewportID[2], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Viewport, 2))
.SetDisplayName(LOCTEXT("ViewportTab3", "Data Viewport 3"))
.SetGroup(ViewportGroup);
InTabManager->RegisterTabSpawner(FPCGEditor_private::ViewportID[3], FOnSpawnTab::CreateSP(this, &FPCGEditor::SpawnTab_Viewport, 3))
.SetDisplayName(LOCTEXT("ViewportTab4", "Data Viewport 4"))
.SetGroup(ViewportGroup);
}
void FPCGEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
InTabManager->UnregisterTabSpawner(FPCGEditor_private::GraphEditorID);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::PropertyDetailsID[0]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::PropertyDetailsID[1]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::PropertyDetailsID[2]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::PropertyDetailsID[3]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::PaletteID);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::DebugObjectID);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::AttributesID[0]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::AttributesID[1]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::AttributesID[2]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::AttributesID[3]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::FindID);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::DeterminismID);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::ProfilingID);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::LogID);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::HLSLSourceID);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::UserParamsID);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::ViewportID[0]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::ViewportID[1]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::ViewportID[2]);
InTabManager->UnregisterTabSpawner(FPCGEditor_private::ViewportID[3]);
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
}
void FPCGEditor::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObject(PCGGraphBeingEdited);
for (TSharedPtr<SPCGEditorGraphAttributeListView> ALV : AttributesWidgets)
{
if (ALV)
{
ALV->AddReferencedObjects(Collector);
}
}
}
bool FPCGEditor::MatchesContext(const FTransactionContext& InContext, const TArray<TPair<UObject*, FTransactionObjectEvent>>& TransactionObjectContexts) const
{
if (InContext.Context == FPCGEditorCommon::ContextIdentifier)
{
return true;
}
// This is done to catch transaction blocks made outside PCG editor code were we need to trigger PostUndo for our context, i.e. UPCGEditorGraphSchema::TryCreateConnection
for (const TPair<UObject*, FTransactionObjectEvent>& TransactionObjectContext : TransactionObjectContexts)
{
const UObject* Object = TransactionObjectContext.Key;
while (Object != nullptr)
{
if (Object == PCGGraphBeingEdited)
{
return true;
}
Object = Object->GetOuter();
}
}
return false;
}
void FPCGEditor::PostUndo(bool bSuccess)
{
if (bSuccess)
{
if (PCGGraphBeingEdited)
{
// Deepest change type to catch all types of change (like redoing adding a grid size node or etc).
PCGGraphBeingEdited->NotifyGraphChanged(EPCGChangeType::Structural | EPCGChangeType::GenerationGrid);
}
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->ClearSelectionSet();
GraphEditorWidget->NotifyGraphChanged();
FSlateApplication::Get().DismissAllMenus();
}
}
}
FName FPCGEditor::GetToolkitFName() const
{
return FName(TEXT("PCGEditor"));
}
FText FPCGEditor::GetBaseToolkitName() const
{
return LOCTEXT("AppLabel", "PCG Editor");
}
FLinearColor FPCGEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor::White;
}
FString FPCGEditor::GetWorldCentricTabPrefix() const
{
return LOCTEXT("WorldCentricTabPrefix", "PCG ").ToString();
}
void FPCGEditor::RegisterToolbar() const
{
UToolMenus* ToolMenus = UToolMenus::Get();
FName ParentName;
const FName ToolbarName = GetToolMenuToolbarName(ParentName);
if (!ToolMenus->IsMenuRegistered(ToolbarName))
{
UToolMenu* ToolBar = ToolMenus->RegisterMenu(ToolbarName, ParentName, EMultiBoxType::ToolBar);
const FPCGEditorCommands& PCGEditorCommands = FPCGEditorCommands::Get();
FToolMenuSection& Section = ToolBar->AddSection("PCGToolbar", TAttribute<FText>());
Section.AddEntry(FToolMenuEntry::InitToolBarButton(
PCGEditorCommands.Find,
TAttribute<FText>(),
TAttribute<FText>(),
FSlateIcon(FPCGEditorStyle::Get().GetStyleSetName(), "PCG.Command.Find")));
Section.AddEntry(FToolMenuEntry::InitToolBarButton(
FPCGEditorCommands::Get().PauseAutoRegeneration,
TAttribute<FText>(),
TAttribute<FText>(),
FSlateIcon(FPCGEditorStyle::Get().GetStyleSetName(), "PCG.Command.PauseRegen")));
Section.AddEntry(FToolMenuEntry::InitToolBarButton(
FPCGEditorCommands::Get().ForceGraphRegeneration,
TAttribute<FText>(),
TAttribute<FText>(),
TAttribute<FSlateIcon>::CreateLambda([]()
{
static const FSlateIcon ForceRegen = FSlateIcon(FPCGEditorStyle::Get().GetStyleSetName(), "PCG.Command.ForceRegen");
static const FSlateIcon ForceRegenClearCache = FSlateIcon(FPCGEditorStyle::Get().GetStyleSetName(), "PCG.Command.ForceRegenClearCache");
FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys();
return ModifierKeys.IsControlDown() ? ForceRegenClearCache : ForceRegen;
})));
Section.AddEntry(FToolMenuEntry::InitToolBarButton(
FPCGEditorCommands::Get().CancelExecution,
TAttribute<FText>(),
TAttribute<FText>(),
FSlateIcon(FPCGEditorStyle::Get().GetStyleSetName(), "PCG.Command.StopRegen")));
Section.AddEntry(FToolMenuEntry::InitToolBarButton(
FPCGEditorCommands::Get().OpenDebugObjectTreeTab,
TAttribute<FText>(),
TAttribute<FText>(),
FSlateIcon(FPCGEditorStyle::Get().GetStyleSetName(), "PCG.Command.OpenDebugTreeTab")));
Section.AddSeparator(NAME_None);
Section.AddEntry(FToolMenuEntry::InitToolBarButton(
PCGEditorCommands.ToggleGraphParams,
TAttribute<FText>(),
TAttribute<FText>(),
FSlateIcon(FPCGEditorStyle::Get().GetStyleSetName(), "PCG.Command.OpenGraphParams")));
Section.AddEntry(FToolMenuEntry::InitToolBarButton(
PCGEditorCommands.EditGraphSettings,
TAttribute<FText>(),
TAttribute<FText>(),
FSlateIcon(FPCGEditorStyle::Get().GetStyleSetName(), "PCG.Command.GraphSettings")));
}
}
void FPCGEditor::BindCommands()
{
const FPCGEditorCommands& PCGEditorCommands = FPCGEditorCommands::Get();
ToolkitCommands->MapAction(
PCGEditorCommands.Find,
FExecuteAction::CreateSP(this, &FPCGEditor::OnFind));
ToolkitCommands->MapAction(
PCGEditorCommands.ShowSelectedDetails,
FExecuteAction::CreateSP(this, &FPCGEditor::OpenDetailsView));
ToolkitCommands->MapAction(
PCGEditorCommands.PauseAutoRegeneration,
FExecuteAction::CreateSP(this, &FPCGEditor::OnPauseAutomaticRegeneration_Clicked),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FPCGEditor::IsAutomaticRegenerationPaused));
ToolkitCommands->MapAction(
PCGEditorCommands.ForceGraphRegeneration,
FExecuteAction::CreateSP(this, &FPCGEditor::OnForceGraphRegeneration_Clicked));
ToolkitCommands->MapAction(
PCGEditorCommands.CancelExecution,
FExecuteAction::CreateSP(this, &FPCGEditor::OnCancelExecution_Clicked),
FCanExecuteAction::CreateSP(this, &FPCGEditor::IsCurrentlyGenerating));
// Left on UI as a disabled button if debug object tree tab already open. This is a deliberate
// hint for 5.4 to help direct users to use the tree.
ToolkitCommands->MapAction(
PCGEditorCommands.OpenDebugObjectTreeTab,
FExecuteAction::CreateSP(this, &FPCGEditor::OnOpenDebugObjectTreeTab_Clicked),
FCanExecuteAction::CreateSP(this, &FPCGEditor::IsDebugObjectTreeTabClosed));
ToolkitCommands->MapAction(
PCGEditorCommands.RunDeterminismGraphTest,
FExecuteAction::CreateSP(this, &FPCGEditor::OnDeterminismGraphTest),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanRunDeterminismGraphTest));
ToolkitCommands->MapAction(
PCGEditorCommands.ToggleGraphParams,
FExecuteAction::CreateSP(this, &FPCGEditor::OnToggleGraphParamsPanel),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FPCGEditor::IsToggleGraphParamsToggled));
ToolkitCommands->MapAction(
PCGEditorCommands.EditGraphSettings,
FExecuteAction::CreateSP(this, &FPCGEditor::OnEditGraphSettings),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FPCGEditor::IsEditGraphSettingsToggled));
GraphEditorCommands->MapAction(
PCGEditorCommands.CollapseNodes,
FExecuteAction::CreateSP(this, &FPCGEditor::OnCollapseNodesInSubgraph),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanCollapseNodesInSubgraph));
GraphEditorCommands->MapAction(
PCGEditorCommands.ExportNodes,
FExecuteAction::CreateSP(this, &FPCGEditor::OnExportNodes),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanExportNodes));
GraphEditorCommands->MapAction(
PCGEditorCommands.ConvertToStandaloneNodes,
FExecuteAction::CreateSP(this, &FPCGEditor::OnConvertToStandaloneNodes),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanConvertToStandaloneNodes));
GraphEditorCommands->MapAction(
PCGEditorCommands.ToggleInspect,
FExecuteAction::CreateSP(this, &FPCGEditor::OnToggleInspected),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanToggleInspected),
FGetActionCheckState::CreateSP(this, &FPCGEditor::GetInspectedCheckState));
GraphEditorCommands->MapAction(
PCGEditorCommands.RunDeterminismNodeTest,
FExecuteAction::CreateSP(this, &FPCGEditor::OnDeterminismNodeTest),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanRunDeterminismNodeTest));
GraphEditorCommands->MapAction(
PCGEditorCommands.ToggleEnabled,
FExecuteAction::CreateSP(this, &FPCGEditor::OnToggleEnabled),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanToggleEnabled),
FGetActionCheckState::CreateSP(this, &FPCGEditor::GetEnabledCheckState));
GraphEditorCommands->MapAction(
PCGEditorCommands.ToggleDebug,
FExecuteAction::CreateSP(this, &FPCGEditor::OnToggleDebug),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanToggleDebug),
FGetActionCheckState::CreateSP(this, &FPCGEditor::GetDebugCheckState));
GraphEditorCommands->MapAction(
PCGEditorCommands.DebugOnlySelected,
FExecuteAction::CreateSP(this, &FPCGEditor::OnDebugOnlySelected),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanToggleDebug));
GraphEditorCommands->MapAction(
PCGEditorCommands.DisableDebugOnAllNodes,
FExecuteAction::CreateSP(this, &FPCGEditor::OnDisableDebugOnAllNodes),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanToggleDebug));
GraphEditorCommands->MapAction(
PCGEditorCommands.AddSourcePin,
FExecuteAction::CreateSP(this, &FPCGEditor::OnAddDynamicInputPin),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanAddDynamicInputPin));
GraphEditorCommands->MapAction(
PCGEditorCommands.RenameNode,
FExecuteAction::CreateSP(this, &FPCGEditor::OnRenameNode),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanRenameNode));
GraphEditorCommands->MapAction(
PCGEditorCommands.SelectNamedRerouteUsages,
FExecuteAction::CreateSP(this, &FPCGEditor::OnSelectNamedRerouteUsages),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanSelectNamedRerouteUsages));
GraphEditorCommands->MapAction(
PCGEditorCommands.SelectNamedRerouteDeclaration,
FExecuteAction::CreateSP(this, &FPCGEditor::OnSelectNamedRerouteDeclaration),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanSelectNamedRerouteDeclaration));
GraphEditorCommands->MapAction(
PCGEditorCommands.JumpToSource,
FExecuteAction::CreateSP(this, &FPCGEditor::OnJumpToSource));
}
void FPCGEditor::OnFind()
{
if (TabManager.IsValid() && FindWidget.IsValid())
{
TabManager->TryInvokeTab(FPCGEditor_private::FindID);
FindWidget->FocusForUse();
}
}
void FPCGEditor::OpenDetailsView()
{
if (TabManager.IsValid())
{
auto InvokeFirstUnlockedTab = [this](bool bVisibleOnly) -> bool
{
for (int DetailsViewIndex = 0; DetailsViewIndex < PropertyDetailsWidgets.Num(); ++DetailsViewIndex)
{
TSharedPtr<SPCGEditorGraphDetailsView> DetailsView = PropertyDetailsWidgets[DetailsViewIndex];
if (DetailsView.IsValid() && !DetailsView->IsLocked())
{
if (!bVisibleOnly || TabManager->FindExistingLiveTab(FPCGEditor_private::PropertyDetailsID[DetailsViewIndex]))
{
TabManager->TryInvokeTab(FPCGEditor_private::PropertyDetailsID[DetailsViewIndex]);
return true;
}
}
}
return false;
};
if (InvokeFirstUnlockedTab(true) || InvokeFirstUnlockedTab(false))
{
return;
}
// Default to first if they are all locked
if (PropertyDetailsWidgets[0].IsValid())
{
TabManager->TryInvokeTab(FPCGEditor_private::PropertyDetailsID[0]);
}
}
}
void FPCGEditor::OnDetailsViewTabClosed(TSharedRef<SDockTab> DockTab, int Index)
{
if (!PropertyDetailsWidgets.IsValidIndex(Index))
{
return;
}
TSharedPtr<SPCGEditorGraphDetailsView> DetailsView = PropertyDetailsWidgets[Index];
if (DetailsView.IsValid() && DetailsView->IsLocked())
{
DetailsView->SetIsLocked(false);
}
}
void FPCGEditor::OnAttributeListViewTabClosed(TSharedRef<SDockTab> DockTab, int Index)
{
if (!AttributesWidgets.IsValidIndex(Index))
{
return;
}
TSharedPtr<SPCGEditorGraphAttributeListView> AttributeListView = AttributesWidgets[Index];
if (AttributeListView.IsValid())
{
if (AttributeListView->IsLocked())
{
AttributeListView->SetIsLocked(false);
}
UPCGEditorGraphNodeBase* NodeInspected = AttributeListView->GetNodeBeingInspected();
AttributeListView->SetNodeBeingInspected(nullptr);
if (NodeInspected)
{
bool bIsStillInspectedOnVisibleTabs = false;
for (int OtherTabIndex = 0; OtherTabIndex < AttributesWidgets.Num(); ++OtherTabIndex)
{
TSharedPtr<SPCGEditorGraphAttributeListView> ALV = AttributesWidgets[OtherTabIndex];
if (ALV.IsValid() && ALV->GetNodeBeingInspected() == NodeInspected && TabManager->FindExistingLiveTab(FPCGEditor_private::AttributesID[OtherTabIndex]))
{
bIsStillInspectedOnVisibleTabs = true;
break;
}
}
if (!bIsStillInspectedOnVisibleTabs)
{
NodeInspected->SetInspected(false);
for (TSharedPtr<SPCGEditorGraphAttributeListView> ALV : AttributesWidgets)
{
if (ALV.IsValid() && ALV->GetNodeBeingInspected() == NodeInspected)
{
ALV->SetNodeBeingInspected(nullptr);
}
}
}
}
}
}
void FPCGEditor::OnViewportViewTabClosed(TSharedRef<SDockTab> DockTab, int Index)
{
AttributesWidgets[Index]->ResetViewport();
}
void FPCGEditor::OnPauseAutomaticRegeneration_Clicked()
{
if (!PCGGraphBeingEdited)
{
return;
}
PCGGraphBeingEdited->ToggleUserPausedNotificationsForEditor();
}
bool FPCGEditor::IsAutomaticRegenerationPaused() const
{
return PCGGraphBeingEdited && PCGGraphBeingEdited->NotificationsForEditorArePausedByUser();
}
void FPCGEditor::OnForceGraphRegeneration_Clicked()
{
if (PCGGraphBeingEdited)
{
EPCGChangeType ChangeType = EPCGChangeType::Structural;
FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys();
if (ModifierKeys.IsControlDown())
{
if (UPCGSubsystem* Subsystem = GetSubsystem())
{
Subsystem->FlushCache();
}
ChangeType |= EPCGChangeType::GenerationGrid;
ChangeType |= EPCGChangeType::ShaderSource;
}
PCGGraphBeingEdited->ForceNotificationForEditor(ChangeType);
}
}
void FPCGEditor::OnCancelExecution_Clicked()
{
UPCGSubsystem* Subsystem = GetSubsystem();
if (PCGEditorGraph && Subsystem)
{
Subsystem->CancelGeneration(PCGEditorGraph->GetPCGGraph());
}
}
bool FPCGEditor::IsCurrentlyGenerating() const
{
UPCGSubsystem* Subsystem = GetSubsystem();
if (PCGGraphBeingEdited && Subsystem)
{
return Subsystem->IsGraphCurrentlyExecuting(PCGGraphBeingEdited);
}
return false;
}
bool FPCGEditor::IsDebugObjectTreeTabClosed() const
{
return !TabManager.IsValid() || !TabManager->FindExistingLiveTab(FPCGEditor_private::DebugObjectID).IsValid();
}
void FPCGEditor::OnOpenDebugObjectTreeTab_Clicked()
{
TabManager->TryInvokeTab(FPCGEditor_private::DebugObjectID);
}
bool FPCGEditor::CanRunDeterminismNodeTest() const
{
check(GraphEditorWidget.IsValid());
for (const UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
if (Cast<const UPCGEditorGraphNodeBase>(Object) && !Cast<const UPCGEditorGraphNodeInput>(Object) && !Cast<const UPCGEditorGraphNodeOutput>(Object))
{
return true;
}
}
return false;
}
void FPCGEditor::OnDeterminismNodeTest() const
{
check(GraphEditorWidget.IsValid());
if (!DeterminismWidget.IsValid() || !DeterminismWidget->WidgetIsConstructed())
{
return;
}
TMap<FName, FTestColumnInfo> TestsConducted;
DeterminismWidget->ClearItems();
DeterminismWidget->BuildBaseColumns();
int64 TestIndex = 0;
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
check(Object);
// Gets an appropriate width for each new column
auto GetSlateTextWidth = [](const FText& Text) -> float
{
check(FSlateApplication::Get().GetRenderer());
const TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
// TODO: Verify the below property for this part of the UI
FSlateFontInfo FontInfo(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"));
constexpr float Padding = 30.f;
return Padding + FontMeasure->Measure(Text, FontInfo).X;
};
if (!Object->IsA<UPCGEditorGraphNodeInput>() && !Object->IsA<UPCGEditorGraphNodeOutput>())
{
if (const UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<const UPCGEditorGraphNodeBase>(Object))
{
const UPCGNode* PCGNode = PCGEditorGraphNode->GetPCGNode();
check(PCGNode && PCGNode->GetSettings());
TSharedPtr<FDeterminismTestResult> NodeResult = MakeShared<FDeterminismTestResult>();
NodeResult->Index = TestIndex++;
NodeResult->TestResultTitle = FName(*PCGNode->GetNodeTitle(EPCGNodeTitleType::ListView).ToString());
NodeResult->TestResultName = PCGNode->GetName();
NodeResult->Seed = PCGNode->GetSettings()->GetSeed();
if (PCGNode->GetSettings()->DeterminismSettings.bNativeTests)
{
// If the settings has a native test suite
if (TFunction<bool()> NativeTestSuite = PCGDeterminismTests::FNativeTestRegistry::GetNativeTestFunction(PCGNode->GetSettings()))
{
FName NodeName(PCGNode->GetName());
bool bSuccess = NativeTestSuite();
NodeResult->TestResults.Emplace(NodeName, bSuccess ? EDeterminismLevel::Basic : EDeterminismLevel::NoDeterminism);
NodeResult->AdditionalDetails.Emplace(FString(TEXT("Native test conducted for - ")) + NodeName.ToString());
NodeResult->bFlagRaised = !bSuccess;
FText ColumnText = NSLOCTEXT("PCGDeterminism", "NativeTest", "Native Test");
TestsConducted.FindOrAdd(NodeName, {NodeName, ColumnText, GetSlateTextWidth(ColumnText), HAlign_Center});
}
else // There is no native test suite, so run the basic tests
{
PCGDeterminismTests::FNodeTestInfo BasicTestInfo = PCGDeterminismTests::Defaults::DeterminismBasicTestInfo;
PCGDeterminismTests::RunDeterminismTest(PCGNode, *NodeResult, BasicTestInfo);
TestsConducted.FindOrAdd(BasicTestInfo.TestName, {BasicTestInfo.TestName, BasicTestInfo.TestLabel, BasicTestInfo.TestLabelWidth, HAlign_Center});
PCGDeterminismTests::FNodeTestInfo OrderIndependenceTestInfo = PCGDeterminismTests::Defaults::DeterminismOrderIndependenceInfo;
PCGDeterminismTests::RunDeterminismTest(PCGNode, *NodeResult, OrderIndependenceTestInfo);
TestsConducted.FindOrAdd(OrderIndependenceTestInfo.TestName, {OrderIndependenceTestInfo.TestName, OrderIndependenceTestInfo.TestLabel, OrderIndependenceTestInfo.TestLabelWidth, HAlign_Center});
}
}
// Custom tests
if (PCGNode->GetSettings()->DeterminismSettings.bUseBlueprintDeterminismTest)
{
TSubclassOf<UPCGDeterminismTestBlueprintBase> Blueprint = PCGNode->GetSettings()->DeterminismSettings.DeterminismTestBlueprint;
Blueprint.GetDefaultObject()->ExecuteTest(PCGNode, *NodeResult);
FName BlueprintName(Blueprint->GetName());
FText ColumnText = FText::FromString(Blueprint->GetName());
TestsConducted.FindOrAdd(BlueprintName, {BlueprintName, ColumnText, GetSlateTextWidth(ColumnText), HAlign_Center});
}
DeterminismWidget->AddItem(NodeResult);
}
}
}
for (const TTuple<FName, FTestColumnInfo>& Test : TestsConducted)
{
DeterminismWidget->AddColumn(Test.Value);
}
DeterminismWidget->AddDetailsColumn();
DeterminismWidget->RefreshItems();
// Give focus to the Determinism Output Tab
if (TabManager.IsValid())
{
TabManager->TryInvokeTab(FPCGEditor_private::DeterminismID);
}
}
bool FPCGEditor::CanRunDeterminismGraphTest() const
{
return PCGEditorGraph && PCGComponentBeingInspected.IsValid();
}
void FPCGEditor::OnDeterminismGraphTest() const
{
check(GraphEditorWidget.IsValid());
if (!DeterminismWidget.IsValid() || !DeterminismWidget->WidgetIsConstructed() || !PCGGraphBeingEdited || !PCGComponentBeingInspected.IsValid())
{
return;
}
if (PCGComponentBeingInspected->GetGraph() != PCGGraphBeingEdited)
{
// TODO: Should we alert the user more directly or disable this altogether?
UE_LOG(LogPCGEditor, Warning, TEXT("Running Determinism on a PCG Component with different/no attached PCG Graph"));
}
DeterminismWidget->ClearItems();
DeterminismWidget->BuildBaseColumns();
FTestColumnInfo ColumnInfo({PCGDeterminismTests::Defaults::GraphResultName, NSLOCTEXT("PCGDeterminism", "Result", "Result"), 120.f, HAlign_Center});
DeterminismWidget->AddColumn(ColumnInfo);
TSharedPtr<FDeterminismTestResult> TestResult = MakeShared<FDeterminismTestResult>();
TestResult->Index = 0;
TestResult->TestResultTitle = TEXT("Full Graph Test");
TestResult->TestResultName = PCGGraphBeingEdited->GetName();
TestResult->Seed = PCGComponentBeingInspected->Seed;
PCGDeterminismTests::RunDeterminismTest(PCGGraphBeingEdited, PCGComponentBeingInspected.Get(), *TestResult);
DeterminismWidget->AddItem(TestResult);
DeterminismWidget->AddDetailsColumn();
DeterminismWidget->RefreshItems();
// Give focus to the Determinism Output Tab
if (TabManager.IsValid())
{
TabManager->TryInvokeTab(FPCGEditor_private::DeterminismID);
}
}
void FPCGEditor::OnEditGraphSettings()
{
check(GraphEditorWidget)
// Clear any selected nodes.
GraphEditorWidget->ClearSelectionSet();
for (TSharedPtr<SPCGEditorGraphDetailsView> PropertyDetailsWidget : PropertyDetailsWidgets)
{
PropertyDetailsWidget->SetObject(PCGGraphBeingEdited);
}
OpenDetailsView();
}
bool FPCGEditor::IsEditGraphSettingsToggled() const
{
if (!TabManager.IsValid())
{
return false;
}
for (int32 WidgetIndex = 0; WidgetIndex < PropertyDetailsWidgets.Num(); ++WidgetIndex)
{
const TArray<TWeakObjectPtr<UObject>>& SelectedObjects = PropertyDetailsWidgets[WidgetIndex]->GetSelectedObjects();
// The only object selected should be the graph. If there is no details view panel open, leave it disabled.
if (SelectedObjects.Num() == 1 && SelectedObjects[0] == PCGGraphBeingEdited.Get())
{
if (const TSharedPtr<SDockTab>& Tab = TabManager->FindExistingLiveTab(FPCGEditor_private::PropertyDetailsID[WidgetIndex]))
{
if (Tab.IsValid() && Tab->IsForeground())
{
return true;
}
}
}
}
return false;
}
void FPCGEditor::OnToggleGraphParamsPanel() const
{
if (IsPanelCurrentlyForeground(EPCGEditorPanel::UserParams))
{
CloseGraphPanel(EPCGEditorPanel::UserParams);
}
else
{
BringFocusToPanel(EPCGEditorPanel::UserParams);
}
}
bool FPCGEditor::IsToggleGraphParamsToggled() const
{
return IsPanelCurrentlyOpen(EPCGEditorPanel::UserParams);
}
bool FPCGEditor::CanCollapseNodesInSubgraph() const
{
bool HasPCGNode = false;
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
check(Object);
// Exclude input and output nodes from the subgraph.
if (Object->IsA<UPCGEditorGraphNodeInput>() || Object->IsA<UPCGEditorGraphNodeOutput>())
{
continue;
}
if (Object->IsA<UPCGEditorGraphNodeBase>())
{
if (HasPCGNode)
{
return true;
}
HasPCGNode = true;
}
}
return false;
}
void FPCGEditor::OnAddDynamicInputPin()
{
check(GraphEditorWidget.IsValid());
const FGraphPanelSelectionSet SelectedNodes = GraphEditorWidget->GetSelectedNodes();
if (!ensure(SelectedNodes.Num() == 1))
{
UE_LOG(LogPCGEditor, Warning, TEXT("Attempting to add new input pin to multiple nodes."));
return;
}
UPCGEditorGraphNodeBase* Node = CastChecked<UPCGEditorGraphNodeBase>(*SelectedNodes.CreateConstIterator());
Node->OnUserAddDynamicInputPin();
}
bool FPCGEditor::CanAddDynamicInputPin() const
{
check(GraphEditorWidget.IsValid());
const FGraphPanelSelectionSet SelectedNodes = GraphEditorWidget->GetSelectedNodes();
if (SelectedNodes.Num() != 1)
{
return false;
}
const UPCGEditorGraphNodeBase* Node = Cast<const UPCGEditorGraphNodeBase>(*SelectedNodes.CreateConstIterator());
return (Node && Node->CanUserAddRemoveDynamicInputPins());
}
void FPCGEditor::OnRenameNode()
{
check(GraphEditorWidget.IsValid());
const FGraphPanelSelectionSet SelectedNodes = GraphEditorWidget->GetSelectedNodes();
if (!ensure(SelectedNodes.Num() == 1))
{
UE_LOG(LogPCGEditor, Warning, TEXT("Attempting to rename multiple nodes."));
return;
}
const UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(*SelectedNodes.CreateConstIterator());
if (SelectedNode != nullptr && SelectedNode->GetCanRenameNode())
{
GraphEditorWidget->IsNodeTitleVisible(SelectedNode, true);
}
}
bool FPCGEditor::CanRenameNode() const
{
check(GraphEditorWidget.IsValid());
const FGraphPanelSelectionSet SelectedNodes = GraphEditorWidget->GetSelectedNodes();
// You cannot enter renaming mode on multiple nodes at once, since they will not all enter synchronously.
// Simultaneous editing of multiple InlineEditableTextBlocks may not even be possible with default behavior.
if (SelectedNodes.Num() != 1)
{
return false;
}
UObject* SelectedObject = *SelectedNodes.CreateConstIterator();
if (const UPCGEditorGraphNode* SelectedNode = Cast<UPCGEditorGraphNode>(SelectedObject))
{
return SelectedNode->GetCanRenameNode();
}
else if (SelectedObject && SelectedObject->IsA<UEdGraphNode_Comment>())
{
return true;
}
else
{
return false;
}
}
bool FPCGEditor::InternalValidationOnAction()
{
if (!GraphEditorWidget.IsValid() || PCGEditorGraph == nullptr)
{
UE_LOG(LogPCGEditor, Error, TEXT("GraphEditorWidget or PCGEditorGraph is null, aborting"));
return false;
}
UPCGGraph* PCGGraph = PCGEditorGraph->GetPCGGraph();
if (PCGGraph == nullptr)
{
UE_LOG(LogPCGEditor, Error, TEXT("PCGGraph is null, aborting"));
return false;
}
return true;
}
void FPCGEditor::OnSelectNamedRerouteUsages()
{
if (!InternalValidationOnAction())
{
return;
}
const FGraphPanelSelectionSet SelectedNodes = GraphEditorWidget->GetSelectedNodes();
if (SelectedNodes.Num() != 1)
{
return;
}
const UPCGEditorGraphNodeNamedRerouteDeclaration* DeclarationNode = nullptr;
for (const UObject* Object : SelectedNodes)
{
DeclarationNode = Cast<UPCGEditorGraphNodeNamedRerouteDeclaration>(Object);
}
if (!DeclarationNode || !DeclarationNode->GetPCGNode())
{
return;
}
GraphEditorWidget->ClearSelectionSet();
// Some assumptions below - that only usages are connected to the invisible pin.
if (const UPCGPin* InvisiblePin = DeclarationNode->GetPCGNode()->GetOutputPin(PCGNamedRerouteConstants::InvisiblePinLabel))
{
for (const UPCGEdge* Edge : InvisiblePin->Edges)
{
if (const UPCGNode* Usage = Edge->OutputPin->Node)
{
GraphEditorWidget->SetNodeSelection(GetEditorNode(Usage), true);
}
}
}
GraphEditorWidget->ZoomToFit(true);
}
bool FPCGEditor::CanSelectNamedRerouteUsages() const
{
if (!GraphEditorWidget || GraphEditorWidget->GetSelectedNodes().Num() != 1)
{
return false;
}
if (auto It = GraphEditorWidget->GetSelectedNodes().CreateConstIterator(); It)
{
const UObject* Object = *It;
return Object && Object->IsA<UPCGEditorGraphNodeNamedRerouteDeclaration>();
}
return false;
}
void FPCGEditor::OnSelectNamedRerouteDeclaration()
{
if (!InternalValidationOnAction())
{
return;
}
const FGraphPanelSelectionSet SelectedNodes = GraphEditorWidget->GetSelectedNodes();
if (SelectedNodes.Num() != 1)
{
return;
}
for (const UObject* Object : SelectedNodes)
{
const UPCGEditorGraphNodeNamedRerouteUsage* UsageNode = Cast<UPCGEditorGraphNodeNamedRerouteUsage>(Object);
if (!UsageNode)
{
continue;
}
GraphEditorWidget->ClearSelectionSet();
if (!UsageNode->GetPCGNode())
{
continue;
}
// Find the declaration node that matches the settings in the Usage node.
if (UPCGNamedRerouteUsageSettings* UsageSettings = Cast<UPCGNamedRerouteUsageSettings>(UsageNode->GetPCGNode()->GetSettings()))
{
if (UsageSettings->Declaration && UsageSettings->Declaration->GetOuter() && UsageSettings->Declaration->GetOuter()->IsA<UPCGNode>())
{
JumpToNode(Cast<UPCGNode>(UsageSettings->Declaration->GetOuter()));
break;
}
}
}
}
bool FPCGEditor::CanSelectNamedRerouteDeclaration() const
{
if (!GraphEditorWidget || GraphEditorWidget->GetSelectedNodes().Num() != 1)
{
return false;
}
if (auto It = GraphEditorWidget->GetSelectedNodes().CreateConstIterator(); It)
{
const UObject* Object = *It;
return Object && Object->IsA<UPCGEditorGraphNodeNamedRerouteUsage>();
}
return false;
}
void FPCGEditor::OnJumpToSource()
{
if (!InternalValidationOnAction())
{
return;
}
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
const UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<const UPCGEditorGraphNodeBase>(Object);
const UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
const UPCGSettings* Settings = PCGNode ? PCGNode->GetSettings() : nullptr;
if (Settings)
{
JumpToDefinition(Settings->GetClass());
}
}
}
FReply FPCGEditor::OnSpawnNodeByShortcut(FInputChord InChord, const FVector2D& InPosition, UPCGEditorGraph* InGraph)
{
return OnSpawnNodeByShortcut(InChord, UE::Slate::CastToVector2f(InPosition), InGraph);
}
FReply FPCGEditor::OnSpawnNodeByShortcut(FInputChord InChord, const FVector2f& InPosition, UPCGEditorGraph* InGraph)
{
const TSharedPtr<FEdGraphSchemaAction> Action = FPCGEditorSpawnNodeCommands::Get().GetGraphActionByChord(InChord);
if (Action.IsValid())
{
TArray<UEdGraphPin*> DummyPins;
Action->PerformAction(InGraph, DummyPins, UE::Slate::FDeprecateVector2DParameter(InPosition));
return FReply::Handled();
}
else
{
return FReply::Unhandled();
}
}
FActionMenuContent FPCGEditor::OnCreateActionMenuContent(UEdGraph* InGraph, const FVector2f& Location, const TArray<UEdGraphPin*>& InDraggedPins, bool bAutoExpand, SGraphEditor::FActionMenuClosed OnMenuClosed)
{
TSharedRef<SGraphEditorActionMenu> Menu = SNew(SGraphEditorActionMenu)
.GraphObj(InGraph)
.NewNodePosition(Location)
.DraggedFromPins(InDraggedPins)
.AutoExpandActionMenu(bAutoExpand)
.OnClosedCallback(OnMenuClosed)
.OnCreateWidgetForAction(SGraphActionMenu::FOnCreateWidgetForAction::CreateLambda(
[](const FCreateWidgetForActionData* const Data)
{
return SNew(SPCGGraphActionWidget, Data);
}));
return FActionMenuContent(Menu, Menu->GetFilterTextBox());
}
void FPCGEditor::OnCollapseNodesInSubgraph()
{
if (!InternalValidationOnAction())
{
return;
}
UPCGGraph* PCGGraph = PCGEditorGraph->GetPCGGraph();
check(PCGGraph);
// Gather all nodes that will be included in the subgraph, and the extra nodes
TArray<UPCGNode*> NodesToCollapse;
TArray<UObject*> ExtraNodesToCollapse;
check(GraphEditorWidget);
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
check(Object);
// Exclude input and output nodes from the subgraph.
if (Object->IsA<UPCGEditorGraphNodeInput>() || Object->IsA<UPCGEditorGraphNodeOutput>())
{
continue;
}
if (UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object))
{
UPCGNode* PCGNode = PCGEditorGraphNode->GetPCGNode();
check(PCGNode);
NodesToCollapse.Add(PCGNode);
}
else if (UEdGraphNode* GraphNode = Cast<UEdGraphNode>(Object))
{
ExtraNodesToCollapse.Add(GraphNode);
}
}
// If we have at most 1 node to collapse, just exit
if (NodesToCollapse.Num() <= 1)
{
UE_LOG(LogPCGEditor, Warning, TEXT("There were less than 2 PCG nodes selected, abort"));
return;
}
// Create a new subgraph, by creating a new PCGGraph asset.
TObjectPtr<UPCGGraph> NewPCGGraph = nullptr;
IAssetTools& AssetTools = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
TObjectPtr<UPCGGraphFactory> Factory = NewObject<UPCGGraphFactory>();
Factory->bSkipTemplateSelection = true;
FString NewPackageName;
FString NewAssetName;
PCGEditorUtils::GetParentPackagePathAndUniqueName(PCGGraph, LOCTEXT("NewPCGSubgraphAsset", "NewPCGSubgraph").ToString(), NewPackageName, NewAssetName);
NewPCGGraph = Cast<UPCGGraph>(AssetTools.CreateAssetWithDialog(NewAssetName, NewPackageName, PCGGraph->GetClass(), Factory, "PCGEditor_CollapseInSubgraph"));
if (NewPCGGraph == nullptr)
{
UE_LOG(LogPCGEditor, Warning, TEXT("Subgraph asset creation was aborted or failed, abort."));
return;
}
{
FScopedTransaction Transaction(LOCTEXT("PCGCollapseInSubgraphMessage", "[PCG] Collapse into Subgraph"));
FText OutFailReason;
NewPCGGraph = FPCGSubgraphHelpers::CollapseIntoSubgraphWithReason(PCGGraph, NodesToCollapse, ExtraNodesToCollapse, OutFailReason, NewPCGGraph);
if (NewPCGGraph == nullptr)
{
FMessageDialog::Open(EAppMsgType::Ok, OutFailReason, LOCTEXT("PCGCollapseInSubgraphFailed", "PCG Subgraph Collapse Failed"));
Transaction.Cancel();
return;
}
// Force a refresh
PCGEditorGraph->ReconstructGraph();
}
if (NewPCGGraph)
{
// Save the new asset
UEditorAssetLibrary::SaveLoadedAsset(NewPCGGraph);
// Notify the widget
GraphEditorWidget->NotifyGraphChanged();
}
}
bool FPCGEditor::CanExportNodes() const
{
for (const UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
check(Object);
// Exclude input and output nodes from the subgraph.
if (Object->IsA<UPCGEditorGraphNodeInput>() || Object->IsA<UPCGEditorGraphNodeOutput>())
{
continue;
}
// Also exclude reroute nodes
if (Object->IsA<UPCGEditorGraphNodeReroute>() || Object->IsA<UPCGEditorGraphNodeNamedRerouteBase>())
{
continue;
}
if (Object->IsA<UPCGEditorGraphNodeBase>())
{
return true;
}
}
return false;
}
void FPCGEditor::OnExportNodes()
{
if (!GraphEditorWidget.IsValid() || PCGEditorGraph == nullptr)
{
UE_LOG(LogPCGEditor, Error, TEXT("GraphEditorWidget or PCGEditorGraph is null, aborting"));
return;
}
if (PCGGraphBeingEdited == nullptr)
{
UE_LOG(LogPCGEditor, Error, TEXT("Editor has no graph loaded, aborting"));
return;
}
IAssetTools& AssetTools = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
check(Object);
// Exclude input and output nodes from the subgraph.
if (Object->IsA<UPCGEditorGraphNodeInput>() || Object->IsA<UPCGEditorGraphNodeOutput>())
{
continue;
}
UPCGSettings* Settings = nullptr;
if (UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object))
{
UPCGNode* PCGNode = PCGEditorGraphNode->GetPCGNode();
check(PCGNode);
Settings = PCGNode->GetSettings();
}
if (!Settings)
{
continue;
}
// Create new settings asset
FString NewPackageName;
FString NewAssetName;
PCGEditorUtils::GetParentPackagePathAndUniqueName(PCGGraphBeingEdited, LOCTEXT("NewPCGSettingsAsset", "NewPCGSettings").ToString(), NewPackageName, NewAssetName);
UObject* NewSettings = AssetTools.DuplicateAssetWithDialogAndTitle(NewAssetName, NewPackageName, Settings, NSLOCTEXT("PCGEditor_ExportNodes", "PCGEditor_ExportNodesTitle", "Export Settings As..."));
if (NewSettings == nullptr)
{
UE_LOG(LogPCGEditor, Warning, TEXT("Settings asset creation was aborted or failed, abort."));
return;
}
}
}
void FPCGEditor::OnConvertToStandaloneNodes()
{
const FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGEditorConvertToStandaloneMessage", "PCG Editor: Converting instanced nodes to standalone"), nullptr);
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
check(Object);
// Exclude input and output nodes from the subgraph.
if (Object->IsA<UPCGEditorGraphNodeInput>() || Object->IsA<UPCGEditorGraphNodeOutput>())
{
continue;
}
if (UPCGEditorGraphNodeBase* Node = Cast<UPCGEditorGraphNodeBase>(Object))
{
if (UPCGNode* PCGNode = Node->GetPCGNode())
{
if (PCGNode->IsInstance())
{
PCGNode->Modify();
UPCGSettings* SourceSettings = PCGNode->GetSettings();
check(SourceSettings);
UPCGSettings* SettingsCopy = DuplicateObject(SourceSettings, PCGNode);
SettingsCopy->SetFlags(RF_Transactional);
PCGNode->SetSettingsInterface(SettingsCopy);
}
}
Node->ReconstructNode();
}
}
// Notify the widget
if (GraphEditorWidget)
{
GraphEditorWidget->NotifyGraphChanged();
}
}
bool FPCGEditor::CanConvertToStandaloneNodes() const
{
for (const UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
check(Object);
// Exclude input and output nodes from the subgraph.
if (Object->IsA<UPCGEditorGraphNodeInput>() || Object->IsA<UPCGEditorGraphNodeOutput>())
{
continue;
}
if (const UPCGEditorGraphNodeBase* Node = Cast<const UPCGEditorGraphNodeBase>(Object))
{
if (const UPCGNode* PCGNode = Node->GetPCGNode())
{
if (PCGNode->IsInstance())
{
return true;
}
}
}
}
return false;
}
void FPCGEditor::OnToggleInspected()
{
if (!GraphEditorWidget.IsValid())
{
return;
}
UEdGraphNode* GraphNode = GraphEditorWidget->GetSingleSelectedNode();
UPCGEditorGraphNodeBase* PCGGraphNodeBase = Cast<UPCGEditorGraphNodeBase>(GraphNode);
const UPCGNode* PCGNode = PCGGraphNodeBase ? PCGGraphNodeBase->GetPCGNode() : nullptr;
const UPCGSettingsInterface* PCGSettingsInterface = PCGNode ? PCGNode->GetSettingsInterface() : nullptr;
if (PCGSettingsInterface && !PCGSettingsInterface->CanBeDebugged())
{
return;
}
TArray<UPCGEditorGraphNodeBase*, TInlineAllocator<4>> InspectedNodesBefore;
for (TSharedPtr<SPCGEditorGraphAttributeListView> AttributeListView : AttributesWidgets)
{
InspectedNodesBefore.Add(AttributeListView->GetNodeBeingInspected());
}
bool bIsInspecting = false;
// If the selected node was previously inspected, stop inspecting it, and unselect it from the attribute list views
if (InspectedNodesBefore.Contains(PCGGraphNodeBase))
{
PCGGraphNodeBase->SetInspected(false);
for (TSharedPtr<SPCGEditorGraphAttributeListView> AttributeListView : AttributesWidgets)
{
if (AttributeListView->GetNodeBeingInspected() == PCGGraphNodeBase)
{
AttributeListView->SetNodeBeingInspected(nullptr);
}
}
}
else
{
TArray<UPCGEditorGraphNodeBase*, TInlineAllocator<4>> InspectedNodesAfter;
for (TSharedPtr<SPCGEditorGraphAttributeListView> AttributeListView : AttributesWidgets)
{
if (!AttributeListView->IsLocked())
{
AttributeListView->SetNodeBeingInspected(PCGGraphNodeBase);
}
InspectedNodesAfter.Add(AttributeListView->GetNodeBeingInspected());
}
if (InspectedNodesAfter.Contains(PCGGraphNodeBase))
{
PCGGraphNodeBase->SetInspected(true);
bIsInspecting = true;
}
for (UPCGEditorGraphNodeBase* BeforeNode : InspectedNodesBefore)
{
if (!InspectedNodesAfter.Contains(BeforeNode) && BeforeNode)
{
BeforeNode->SetInspected(false);
}
}
}
if (bIsInspecting)
{
// Summon the first attribute list view that is inspecting this node
auto InvokeFirstTab = [this, PCGGraphNodeBase](bool bVisibleOnly) -> bool
{
for (int AttributeListViewIndex = 0; AttributeListViewIndex < AttributesWidgets.Num(); ++AttributeListViewIndex)
{
TSharedPtr<SPCGEditorGraphAttributeListView> AttributeListView = AttributesWidgets[AttributeListViewIndex];
if (AttributeListView->GetNodeBeingInspected() == PCGGraphNodeBase)
{
if (!bVisibleOnly || TabManager->FindExistingLiveTab(FPCGEditor_private::AttributesID[AttributeListViewIndex]))
{
GetTabManager()->TryInvokeTab(FPCGEditor_private::AttributesID[AttributeListViewIndex]);
return true;
}
}
}
return false;
};
const bool bTabSummoned = (InvokeFirstTab(true) || InvokeFirstTab(false));
// Default to first if they are all locked
if (!bTabSummoned)
{
GetTabManager()->TryInvokeTab(FPCGEditor_private::AttributesID[0]);
}
DebugObjectTreeWidget->SetNodeBeingInspected(PCGNode);
}
else
{
DebugObjectTreeWidget->SetNodeBeingInspected(nullptr);
}
// Turn on "inspecting" on graph if we now have at least one inspected node and had none before
UpdateAfterInspectedStackChanged();
}
bool FPCGEditor::CanToggleInspected() const
{
if (!GraphEditorWidget.IsValid())
{
return false;
}
const FGraphPanelSelectionSet& SelectedNodes = GraphEditorWidget->GetSelectedNodes();
if (SelectedNodes.Num() != 1)
{
// Can only inspect one node.
return false;
}
for (const UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
const UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object);
if (!PCGEditorGraphNode)
{
return false;
}
const UPCGSettingsInterface* PCGSettingsInterface = PCGEditorGraphNode->GetPCGNode() ? PCGEditorGraphNode->GetPCGNode()->GetSettingsInterface() : nullptr;
if (PCGSettingsInterface && PCGSettingsInterface->CanBeDebugged())
{
return true;
}
}
return false;
}
ECheckBoxState FPCGEditor::GetInspectedCheckState() const
{
if (GraphEditorWidget.IsValid())
{
const FGraphPanelSelectionSet& SelectedNodes = GraphEditorWidget->GetSelectedNodes();
if (SelectedNodes.IsEmpty())
{
return ECheckBoxState::Unchecked;
}
bool bAllEnabled = true;
bool bAnyEnabled = false;
for (UObject* Object : SelectedNodes)
{
const UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object);
if (!PCGEditorGraphNode)
{
continue;
}
bAllEnabled &= PCGEditorGraphNode->GetInspected();
bAnyEnabled |= PCGEditorGraphNode->GetInspected();
}
if (bAllEnabled)
{
return ECheckBoxState::Checked;
}
else if (bAnyEnabled)
{
return ECheckBoxState::Undetermined;
}
}
return ECheckBoxState::Unchecked;
}
void FPCGEditor::OnToggleEnabled()
{
const ECheckBoxState CheckState = GetEnabledCheckState();
const bool bNewCheckState = !(CheckState != ECheckBoxState::Unchecked);
// To prevent the changes on the editor node from being in the transaction, we delay reconstruction.
TArray<FPCGDeferNodeReconstructScope> DeferredEditorNodes;
if (GraphEditorWidget.IsValid())
{
FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGEditorToggleEnableTransactionMessage", "PCG Editor: Toggle Enable Nodes"), nullptr);
UPCGGraph* PCGGraph = PCGEditorGraph ? PCGEditorGraph->GetPCGGraph() : nullptr;
if (!ensure(PCGGraph))
{
return;
}
PCGGraph->DisableNotificationsForEditor();
bool bChanged = false;
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object);
UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
UPCGSettingsInterface* PCGSettingsInterface = PCGNode ? PCGNode->GetSettingsInterface() : nullptr;
if (!PCGSettingsInterface || !PCGSettingsInterface->CanBeDisabled())
{
continue;
}
if (PCGSettingsInterface->bEnabled != bNewCheckState)
{
DeferredEditorNodes.Emplace(PCGEditorGraphNode);
PCGSettingsInterface->Modify();
PCGSettingsInterface->SetEnabled(bNewCheckState);
bChanged = true;
}
}
PCGGraph->EnableNotificationsForEditor();
if (bChanged)
{
GraphEditorWidget->NotifyGraphChanged();
}
else
{
Transaction.Cancel();
}
}
}
bool FPCGEditor::CanToggleEnabled() const
{
if (!GraphEditorWidget.IsValid())
{
return false;
}
for (const UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
const UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object);
const UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
if (!PCGNode)
{
continue;
}
if (PCGNode->GetSettingsInterface() && PCGNode->GetSettingsInterface()->CanBeDisabled())
{
return true;
}
}
// Could not toggle enabled on anything in selection.
return false;
}
ECheckBoxState FPCGEditor::GetEnabledCheckState() const
{
if (GraphEditorWidget.IsValid())
{
bool bAllEnabled = true;
bool bAnyEnabled = false;
for (const UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
const UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object);
const UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
const UPCGSettingsInterface* PCGSettingsInterface = PCGNode ? PCGNode->GetSettingsInterface() : nullptr;
if (!PCGSettingsInterface || !PCGSettingsInterface->CanBeDisabled())
{
continue;
}
bAllEnabled &= PCGSettingsInterface->bEnabled;
bAnyEnabled |= PCGSettingsInterface->bEnabled;
}
if (bAllEnabled)
{
return ECheckBoxState::Checked;
}
else if (bAnyEnabled)
{
return ECheckBoxState::Undetermined;
}
}
return ECheckBoxState::Unchecked;
}
void FPCGEditor::OnToggleDebug()
{
const ECheckBoxState CheckState = GetDebugCheckState();
const bool bNewCheckState = !(CheckState != ECheckBoxState::Unchecked);
if (GraphEditorWidget.IsValid())
{
FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGEditorToggleDebugTransactionMessage", "PCG Editor: Toggle Debug Nodes"), nullptr);
bool bChanged = false;
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object);
UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
UPCGSettingsInterface* PCGSettingsInterface = PCGNode ? PCGNode->GetSettingsInterface() : nullptr;
if (!PCGSettingsInterface || !PCGSettingsInterface->CanBeDebugged())
{
continue;
}
if (PCGSettingsInterface->bDebug != bNewCheckState)
{
PCGSettingsInterface->Modify(/*bAlwaysMarkDirty=*/false);
PCGSettingsInterface->bDebug = bNewCheckState;
PCGNode->OnNodeChangedDelegate.Broadcast(PCGNode, EPCGChangeType::Settings);
bChanged = true;
}
}
if (!bChanged)
{
Transaction.Cancel();
}
}
}
bool FPCGEditor::CanToggleDebug() const
{
if (!GraphEditorWidget.IsValid())
{
return false;
}
for (const UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
const UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object);
const UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
if (PCGNode && PCGNode->GetSettingsInterface()->CanBeDebugged())
{
return true;
}
}
// Could not toggle debug on anything in selection.
return false;
}
void FPCGEditor::OnDebugOnlySelected()
{
if (GraphEditorWidget.IsValid() && PCGEditorGraph)
{
bool bChanged = false;
const FGraphPanelSelectionSet& SelectedNodes = GraphEditorWidget->GetSelectedNodes();
FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGEditorDebugOnlySelectedTransactionMessage", "PCG Editor: Debug only selected nodes"), nullptr);
bool bAnyNonSelectedNodesDebugged = false;
bool bAllSelectedNodesDebugged = true;
// Initial pass - inspect state of selected and non-selected nodes.
for (const UEdGraphNode* Node : PCGEditorGraph->Nodes)
{
const UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Node);
const UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
const UPCGSettingsInterface* PCGSettingsInterface = PCGNode ? PCGNode->GetSettingsInterface() : nullptr;
if (!PCGSettingsInterface)
{
continue;
}
if (SelectedNodes.Contains(PCGEditorGraphNode))
{
bAllSelectedNodesDebugged &= PCGSettingsInterface->bDebug;
}
else
{
bAnyNonSelectedNodesDebugged |= PCGSettingsInterface->bDebug;
}
}
// The selected nodes should be debugged if any non-selected nodes are being debugged, or if the selected
// nodes are partially being debugged.
const bool bTargetDebugState = bAnyNonSelectedNodesDebugged || !bAllSelectedNodesDebugged;
for (UEdGraphNode* Node : PCGEditorGraph->Nodes)
{
UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Node);
UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
UPCGSettingsInterface* PCGSettingsInterface = PCGNode ? PCGNode->GetSettingsInterface() : nullptr;
if (!PCGSettingsInterface || !PCGSettingsInterface->CanBeDebugged())
{
continue;
}
// Selected set to target state, non-selected should not be debugged.
const bool bShouldBeDebug = SelectedNodes.Contains(PCGEditorGraphNode) ? bTargetDebugState : false;
if (PCGSettingsInterface->bDebug != bShouldBeDebug)
{
PCGSettingsInterface->Modify(/*bAlwaysMarkDirty=*/false);
PCGSettingsInterface->bDebug = bShouldBeDebug;
PCGNode->OnNodeChangedDelegate.Broadcast(PCGNode, EPCGChangeType::Settings);
bChanged = true;
}
}
if (!bChanged)
{
Transaction.Cancel();
}
}
}
void FPCGEditor::OnDisableDebugOnAllNodes()
{
if (GraphEditorWidget.IsValid() && PCGEditorGraph)
{
bool bChanged = false;
FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGEditorDisableDebugAllNodesTransactionMessage", "PCG Editor: Disable debug on all nodes"), nullptr);
for (UEdGraphNode* Node : PCGEditorGraph->Nodes)
{
UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Node);
UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
UPCGSettingsInterface* PCGSettingsInterface = PCGNode ? PCGNode->GetSettingsInterface() : nullptr;
if (!PCGSettingsInterface)
{
continue;
}
if (PCGSettingsInterface->bDebug)
{
PCGSettingsInterface->Modify(/*bAlwaysMarkDirty=*/false);
PCGSettingsInterface->bDebug = false;
PCGNode->OnNodeChangedDelegate.Broadcast(PCGNode, EPCGChangeType::Settings);
bChanged = true;
}
}
if (!bChanged)
{
Transaction.Cancel();
}
}
}
ECheckBoxState FPCGEditor::GetDebugCheckState() const
{
if (GraphEditorWidget.IsValid())
{
bool bAllDebug = true;
bool bAnyDebug = false;
for (const UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
const UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object);
const UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
const UPCGSettingsInterface* PCGSettingsInterface = PCGNode ? PCGNode->GetSettingsInterface() : nullptr;
if (!PCGSettingsInterface || !PCGSettingsInterface->CanBeDebugged())
{
continue;
}
bAllDebug &= PCGSettingsInterface->bDebug;
bAnyDebug |= PCGSettingsInterface->bDebug;
}
if (bAllDebug)
{
return ECheckBoxState::Checked;
}
else if (bAnyDebug)
{
return ECheckBoxState::Undetermined;
}
}
return ECheckBoxState::Unchecked;
}
void FPCGEditor::SelectAllNodes()
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->SelectAllNodes();
}
}
bool FPCGEditor::CanSelectAllNodes() const
{
return GraphEditorWidget.IsValid();
}
void FPCGEditor::DeleteSelectedNodes()
{
if (GraphEditorWidget.IsValid())
{
UPCGGraph* PCGGraph = PCGEditorGraph->GetPCGGraph();
check(PCGEditorGraph && PCGGraph);
// DeleteSelectedNodes is called directly from UI command
PCGGraph->PrimeGraphCompilationCache();
bool bChanged = false;
{
const FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGEditorDeleteTransactionMessage", "PCG Editor: Delete"), nullptr);
PCGEditorGraph->Modify();
TArray<UPCGNode*> NodesToRemove;
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
if (UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Object))
{
if (PCGEditorGraphNode->CanUserDeleteNode())
{
UPCGNode* PCGNode = PCGEditorGraphNode->GetPCGNode();
check(PCGNode);
NodesToRemove.Add(PCGNode);
PCGEditorGraphNode->DestroyNode();
bChanged = true;
}
}
else if (UEdGraphNode* GraphNode = Cast<UEdGraphNode>(Object))
{
if (GraphNode->CanUserDeleteNode())
{
GraphNode->DestroyNode();
bChanged = true;
}
}
}
if (bChanged)
{
// Need to modify the pcg graph so comments are also caught.
PCGGraph->Modify();
PCGGraph->RemoveNodes(NodesToRemove);
}
}
if (bChanged)
{
GraphEditorWidget->ClearSelectionSet();
GraphEditorWidget->NotifyGraphChanged();
}
}
}
bool FPCGEditor::CanDeleteSelectedNodes() const
{
if (GraphEditorWidget.IsValid())
{
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
UEdGraphNode* GraphNode = CastChecked<UEdGraphNode>(Object);
if (GraphNode->CanUserDeleteNode())
{
return true;
}
}
}
return false;
}
void FPCGEditor::CopySelectedNodes()
{
if (GraphEditorWidget.IsValid())
{
const FGraphPanelSelectionSet SelectedNodes = GraphEditorWidget->GetSelectedNodes();
//TODO: evaluate creating a clipboard object instead of ownership hack
for (UObject* SelectedNode : SelectedNodes)
{
UEdGraphNode* GraphNode = CastChecked<UEdGraphNode>(SelectedNode);
GraphNode->PrepareForCopying();
}
FString ExportedText;
FEdGraphUtilities::ExportNodesToText(SelectedNodes, ExportedText);
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
for (UObject* SelectedNode : SelectedNodes)
{
if (UPCGEditorGraphNodeBase* PCGGraphNode = Cast<UPCGEditorGraphNodeBase>(SelectedNode))
{
PCGGraphNode->PostCopy();
}
}
}
}
bool FPCGEditor::CanCopySelectedNodes() const
{
if (GraphEditorWidget.IsValid())
{
for (UObject* Object : GraphEditorWidget->GetSelectedNodes())
{
if (UEdGraphNode* GraphNode = CastChecked<UEdGraphNode>(Object))
{
if (GraphNode->CanDuplicateNode())
{
return true;
}
}
}
}
return false;
}
void FPCGEditor::CutSelectedNodes()
{
CopySelectedNodes();
DeleteSelectedNodes();
}
bool FPCGEditor::CanCutSelectedNodes() const
{
return CanCopySelectedNodes() && CanDeleteSelectedNodes();
}
void FPCGEditor::PasteNodes()
{
if (GraphEditorWidget.IsValid())
{
PasteNodesHere(GraphEditorWidget->GetPasteLocation2f());
}
}
void FPCGEditor::PasteNodesHere(const FVector2D& Location)
{
if (!GraphEditorWidget.IsValid() || !PCGEditorGraph)
{
return;
}
const FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGEditorPasteTransactionMessage", "PCG Editor: Paste"), nullptr);
PCGEditorGraph->Modify();
// Clear the selection set (newly pasted stuff will be selected)
GraphEditorWidget->ClearSelectionSet();
// Grab the text to paste from the clipboard.
FString TextToImport;
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
// Import the nodes
TSet<UEdGraphNode*> PastedNodes;
FEdGraphUtilities::ImportNodesFromText(PCGEditorGraph, TextToImport, /*out*/ PastedNodes);
//Average position of nodes so we can move them while still maintaining relative distances to each other
FVector2D AvgNodePosition(0.0f, 0.0f);
// Number of nodes used to calculate AvgNodePosition
int32 AvgCount = 0;
for (UEdGraphNode* PastedNode : PastedNodes)
{
if (PastedNode)
{
AvgNodePosition.X += PastedNode->NodePosX;
AvgNodePosition.Y += PastedNode->NodePosY;
++AvgCount;
}
}
if (AvgCount > 0)
{
float InvNumNodes = 1.0f / float(AvgCount);
AvgNodePosition.X *= InvNumNodes;
AvgNodePosition.Y *= InvNumNodes;
}
TArray<UPCGNode*> NodesToPaste;
for (UEdGraphNode* PastedNode : PastedNodes)
{
GraphEditorWidget->SetNodeSelection(PastedNode, true);
PastedNode->NodePosX = (PastedNode->NodePosX - AvgNodePosition.X) + Location.X;
PastedNode->NodePosY = (PastedNode->NodePosY - AvgNodePosition.Y) + Location.Y;
PastedNode->SnapToGrid(SNodePanel::GetSnapGridSize());
PastedNode->CreateNewGuid();
UPCGEditorGraphNodeBase* PastedPCGGraphNode = Cast<UPCGEditorGraphNodeBase>(PastedNode);
if (UPCGNode* PastedPCGNode = PastedPCGGraphNode ? PastedPCGGraphNode->GetPCGNode() : nullptr)
{
NodesToPaste.Add(PastedPCGNode);
}
}
// Need to modify the pcg graph so comments are also caught.
PCGGraphBeingEdited->Modify();
PCGGraphBeingEdited->AddNodes(NodesToPaste);
for (UEdGraphNode* PastedNode : PastedNodes)
{
UPCGEditorGraphNodeBase* PastedPCGGraphNode = Cast<UPCGEditorGraphNodeBase>(PastedNode);
if (UPCGNode* PastedPCGNode = PastedPCGGraphNode ? PastedPCGGraphNode->GetPCGNode() : nullptr)
{
PastedPCGGraphNode->RebuildAfterPaste();
}
}
for (UEdGraphNode* PastedNode : PastedNodes)
{
UPCGEditorGraphNodeBase* PastedPCGGraphNode = Cast<UPCGEditorGraphNodeBase>(PastedNode);
if (UPCGNode* PastedPCGNode = PastedPCGGraphNode ? PastedPCGGraphNode->GetPCGNode() : nullptr)
{
PastedPCGGraphNode->PostPaste();
if (UPCGSettings* Settings = PastedPCGNode->GetSettings())
{
Settings->PostPaste();
}
}
}
GraphEditorWidget->NotifyGraphChanged();
}
bool FPCGEditor::CanPasteNodes() const
{
FString ClipboardContent;
FPlatformApplicationMisc::ClipboardPaste(ClipboardContent);
return FEdGraphUtilities::CanImportNodesFromText(PCGEditorGraph, ClipboardContent);
}
void FPCGEditor::DuplicateNodes()
{
CopySelectedNodes();
PasteNodes();
}
bool FPCGEditor::CanDuplicateNodes() const
{
return CanCopySelectedNodes();
}
void FPCGEditor::OnAlignTop()
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->OnAlignTop();
}
}
void FPCGEditor::OnAlignMiddle()
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->OnAlignMiddle();
}
}
void FPCGEditor::OnAlignBottom()
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->OnAlignBottom();
}
}
void FPCGEditor::OnAlignLeft()
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->OnAlignLeft();
}
}
void FPCGEditor::OnAlignCenter()
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->OnAlignCenter();
}
}
void FPCGEditor::OnAlignRight()
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->OnAlignRight();
}
}
void FPCGEditor::OnStraightenConnections()
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->OnStraightenConnections();
}
}
void FPCGEditor::OnDistributeNodesH()
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->OnDistributeNodesH();
}
}
void FPCGEditor::OnDistributeNodesV()
{
if (GraphEditorWidget.IsValid())
{
GraphEditorWidget->OnDistributeNodesV();
}
}
void FPCGEditor::OnCreateComment()
{
if (PCGEditorGraph)
{
FPCGEditorGraphSchemaAction_NewComment CommentAction;
TSharedPtr<SGraphEditor> GraphEditorPtr = SGraphEditor::FindGraphEditorForGraph(PCGEditorGraph);
FVector2f Location = FVector2f::ZeroVector;
if (GraphEditorPtr)
{
Location = GraphEditorPtr->GetPasteLocation2f();
}
CommentAction.PerformAction(PCGEditorGraph, nullptr, Location);
}
}
TSharedRef<SGraphEditor> FPCGEditor::CreateGraphEditorWidget()
{
GraphEditorCommands = MakeShareable(new FUICommandList);
// Editing commands
GraphEditorCommands->MapAction(FGenericCommands::Get().SelectAll,
FExecuteAction::CreateSP(this, &FPCGEditor::SelectAllNodes),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanSelectAllNodes));
GraphEditorCommands->MapAction(FGenericCommands::Get().Delete,
FExecuteAction::CreateSP(this, &FPCGEditor::DeleteSelectedNodes),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanDeleteSelectedNodes));
GraphEditorCommands->MapAction(FGenericCommands::Get().Copy,
FExecuteAction::CreateSP(this, &FPCGEditor::CopySelectedNodes),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanCopySelectedNodes));
GraphEditorCommands->MapAction(FGenericCommands::Get().Cut,
FExecuteAction::CreateSP(this, &FPCGEditor::CutSelectedNodes),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanCutSelectedNodes));
GraphEditorCommands->MapAction(FGenericCommands::Get().Paste,
FExecuteAction::CreateSP(this, &FPCGEditor::PasteNodes),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanPasteNodes));
GraphEditorCommands->MapAction(FGenericCommands::Get().Duplicate,
FExecuteAction::CreateSP(this, &FPCGEditor::DuplicateNodes),
FCanExecuteAction::CreateSP(this, &FPCGEditor::CanDuplicateNodes));
// Alignment Commands
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesTop,
FExecuteAction::CreateSP(this, &FPCGEditor::OnAlignTop)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesMiddle,
FExecuteAction::CreateSP(this, &FPCGEditor::OnAlignMiddle)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesBottom,
FExecuteAction::CreateSP(this, &FPCGEditor::OnAlignBottom)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesLeft,
FExecuteAction::CreateSP(this, &FPCGEditor::OnAlignLeft)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesCenter,
FExecuteAction::CreateSP(this, &FPCGEditor::OnAlignCenter)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesRight,
FExecuteAction::CreateSP(this, &FPCGEditor::OnAlignRight)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().StraightenConnections,
FExecuteAction::CreateSP(this, &FPCGEditor::OnStraightenConnections)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().CreateComment,
FExecuteAction::CreateSP(this, &FPCGEditor::OnCreateComment)
);
// Distribution Commands
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().DistributeNodesHorizontally,
FExecuteAction::CreateSP(this, &FPCGEditor::OnDistributeNodesH)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().DistributeNodesVertically,
FExecuteAction::CreateSP(this, &FPCGEditor::OnDistributeNodesV)
);
FGraphAppearanceInfo AppearanceInfo;
AppearanceInfo.CornerText = LOCTEXT("PCGGraphEditorCornerText", "PCG Graph");
SGraphEditor::FGraphEditorEvents InEvents;
InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &FPCGEditor::OnSelectedNodesChanged);
InEvents.OnVerifyTextCommit = FOnNodeVerifyTextCommit::CreateSP(this, &FPCGEditor::OnValidateNodeTitle);
InEvents.OnTextCommitted = FOnNodeTextCommitted::CreateSP(this, &FPCGEditor::OnNodeTitleCommitted);
InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FPCGEditor::OnNodeDoubleClicked);
InEvents.OnSpawnNodeByShortcutAtLocation = SGraphEditor::FOnSpawnNodeByShortcutAtLocation::CreateSP(this, &FPCGEditor::OnSpawnNodeByShortcut, PCGEditorGraph);
InEvents.OnCreateActionMenuAtLocation = SGraphEditor::FOnCreateActionMenuAtLocation::CreateSP(this, &FPCGEditor::OnCreateActionMenuContent);
return SNew(SGraphEditor)
.AdditionalCommands(GraphEditorCommands)
.IsEditable(true)
.Appearance(AppearanceInfo)
.GraphToEdit(PCGEditorGraph)
.GraphEvents(InEvents)
.ShowGraphStateOverlay(false);
}
void FPCGEditor::OnClose()
{
if (PCGEditorGraph)
{
PCGEditorGraph->OnClose();
}
if (FLevelEditorModule* LevelEditor = FModuleManager::GetModulePtr<FLevelEditorModule>(TEXT("LevelEditor")))
{
LevelEditor->OnMapChanged().RemoveAll(this);
}
if (GEngine)
{
GEngine->OnLevelActorDeleted().RemoveAll(this);
}
FEditorDelegates::PostPIEStarted.RemoveAll(this);
FEditorDelegates::EndPIE.RemoveAll(this);
FAssetEditorToolkit::OnClose();
if (PCGComponentBeingInspected.IsValid())
{
if (PCGComponentBeingInspected->GetExecutionState().GetInspection().IsInspecting())
{
PCGComponentBeingInspected->GetExecutionState().GetInspection().DisableInspection();
}
}
if (LastValidPCGComponentBeingInspected.IsValid())
{
if (LastValidPCGComponentBeingInspected->GetExecutionState().GetInspection().IsInspecting())
{
LastValidPCGComponentBeingInspected->GetExecutionState().GetInspection().DisableInspection();
}
}
if (PCGGraphBeingEdited)
{
PCGGraphBeingEdited->OnGraphChangedDelegate.RemoveAll(this);
PCGGraphBeingEdited->OnNodeSourceCompiledDelegate.RemoveAll(this);
if (PCGGraphBeingEdited->IsInspecting())
{
PCGGraphBeingEdited->DisableInspection();
}
if (PCGGraphBeingEdited->NotificationsForEditorArePausedByUser())
{
PCGGraphBeingEdited->ToggleUserPausedNotificationsForEditor();
}
}
if (GEditor)
{
UnregisterDelegatesForWorld(GEditor->GetEditorWorldContext().World());
UnregisterDelegatesForWorld(GEditor->PlayWorld.Get());
}
}
void FPCGEditor::InitToolMenuContext(FToolMenuContext& MenuContext)
{
FAssetEditorToolkit::InitToolMenuContext(MenuContext);
UPCGEditorMenuContext* Context = NewObject<UPCGEditorMenuContext>();
Context->PCGEditor = SharedThis(this);
MenuContext.AddObject(Context);
}
TSharedRef<SPCGEditorGraphNodePalette> FPCGEditor::CreatePaletteWidget()
{
return SNew(SPCGEditorGraphNodePalette, SharedThis(this));
}
TSharedRef<SPCGEditorGraphDebugObjectTree> FPCGEditor::CreateDebugObjectTreeWidget()
{
return SNew(SPCGEditorGraphDebugObjectTree, SharedThis(this));
}
TSharedRef<SPCGEditorGraphFind> FPCGEditor::CreateFindWidget()
{
return SNew(SPCGEditorGraphFind, SharedThis(this));
}
TSharedRef<SPCGEditorGraphAttributeListView> FPCGEditor::CreateAttributesWidget()
{
return SNew(SPCGEditorGraphAttributeListView, SharedThis(this));
}
TSharedRef<SPCGEditorGraphDeterminismListView> FPCGEditor::CreateDeterminismWidget()
{
return SNew(SPCGEditorGraphDeterminismListView, SharedThis(this));
}
TSharedRef<SPCGEditorGraphProfilingView> FPCGEditor::CreateProfilingWidget()
{
return SNew(SPCGEditorGraphProfilingView, SharedThis(this));
}
TSharedRef<SPCGEditorGraphLogView> FPCGEditor::CreateLogWidget()
{
return SNew(SPCGEditorGraphLogView, SharedThis(this));
}
TSharedRef<SPCGEditorNodeSource> FPCGEditor::CreateNodeSourceWidget()
{
return SNew(SPCGEditorNodeSource);
}
TSharedRef<SPCGEditorGraphUserParametersView> FPCGEditor::CreateGraphParamsWidget()
{
return SNew(SPCGEditorGraphUserParametersView, SharedThis(this));
}
TSharedRef<SPCGEditorViewport> FPCGEditor::CreateViewportWidget()
{
return SNew(SPCGEditorViewport);
}
void FPCGEditor::OnSelectedNodesChanged(const TSet<UObject*>& NewSelection)
{
TArray<TWeakObjectPtr<UObject>> SelectedObjects;
if (NewSelection.Num() == 0)
{
SelectedObjects.Add(PCGGraphBeingEdited);
}
else
{
for (UObject* Object : NewSelection)
{
if (UEdGraphNode* GraphNode = Cast<UEdGraphNode>(Object))
{
SelectedObjects.Add(GraphNode);
}
}
}
for (TSharedPtr<SPCGEditorGraphDetailsView> PropertyDetailsWidget : PropertyDetailsWidgets)
{
PropertyDetailsWidget->SetObjects(SelectedObjects, /*bForceRefresh=*/true);
}
// Give a single selected node with valid settings to the source editor, or give it null so it can clear the UI.
UPCGEditorGraphNode* SelectedNode = (NewSelection.Num() == 1) ? Cast<UPCGEditorGraphNode>(*NewSelection.CreateConstIterator()) : nullptr;
UPCGNode* PCGNode = SelectedNode ? SelectedNode->GetPCGNode() : nullptr;
SetSourceEditorTargetObject(PCGNode ? PCGNode->GetSettings() : nullptr);
}
void FPCGEditor::OnNodeTitleCommitted(const FText& NewText, ETextCommit::Type CommitInfo, UEdGraphNode* NodeBeingChanged)
{
check(PCGGraphBeingEdited);
if (NodeBeingChanged)
{
if (CommitInfo == ETextCommit::OnEnter || CommitInfo == ETextCommit::OnUserMovedFocus)
{
FText ErrorText;
if (OnValidateNodeTitle(NewText, NodeBeingChanged, ErrorText))
{
const FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGEditorRenameNode", "PCG Editor: Rename Node"), nullptr);
// Implementation detail: In UPCGEditorGraphNode we only set the title under certain conditions, so it calls Modify() itself.
// However, UEdGraphNode does not call Modify() on its own, so we should still call it in this case.
if (!NodeBeingChanged->IsA<UPCGEditorGraphNode>())
{
NodeBeingChanged->Modify();
// Modify the graph as well, as non-pcg editor nodes (like the comment nodes) are serialized in UPCGGraph::ExtraEditorNodes.
PCGGraphBeingEdited->Modify();
}
NodeBeingChanged->OnRenameNode(NewText.ToString());
}
else
{
UE_LOG(LogPCGEditor, Warning, TEXT("%s"), *FText::Format(LOCTEXT("UnableToRenameNode", "Unable to rename node {0}. Reason: {1}"), NodeBeingChanged->GetNodeTitle(ENodeTitleType::FullTitle), std::move(ErrorText)).ToString());
}
}
if (const UPCGEditorGraphNodeBase* PCGEditorNode = Cast<UPCGEditorGraphNodeBase>(NodeBeingChanged))
{
PCGEditorNode->OnNodeChangedDelegate.ExecuteIfBound();
}
}
}
void FPCGEditor::OnNodeDoubleClicked(UEdGraphNode* Node)
{
if (Node != nullptr)
{
UObject* Object = Node->GetJumpTargetForDoubleClick();
// "Normal" node
if (const UPCGSettings* PCGSettings = Cast<UPCGSettings>(Object))
{
// Functions may require the GraphEditorWidget's node selection, so set it manually to be safe.
GraphEditorWidget->SetNodeSelection(Node, /*bSelect=*/true);
switch (GetDefault<UPCGEditorSettings>()->NodeDoubleClickAction)
{
case EPCGEditorDoubleClickAction::ToggleInspectNode:
if (CanToggleInspected())
{
OnToggleInspected();
}
break;
case EPCGEditorDoubleClickAction::ToggleDebugNode:
if (CanToggleDebug())
{
OnToggleDebug();
}
break;
case EPCGEditorDoubleClickAction::JumpToSourceFile:
JumpToDefinition(PCGSettings->GetClass());
break;
case EPCGEditorDoubleClickAction::DoNothing: // fall-through
default:
break;
}
}
else // Special options with non-UPCGSettings based targets.
{
const UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<const UPCGEditorGraphNodeBase>(Node);
const UPCGNode* PCGNode = PCGEditorGraphNode ? PCGEditorGraphNode->GetPCGNode() : nullptr;
FPCGStack StackToInspect;
// If we're inspecting, we'll try to find a match in the stacks for subgraphs instead of relying on the static/template subgraph
if (GetStackBeingInspected() && DebugObjectTreeWidget)
{
if (DebugObjectTreeWidget->GetFirstStackFromSelection(PCGNode, /*Graph=*/nullptr, StackToInspect))
{
Object = const_cast<UPCGGraph*>(StackToInspect.GetGraphForCurrentFrame());
}
}
if (Object)
{
// Open other editor...
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Object);
IAssetEditorInstance* EditorInstance = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(Object, /*bFocusIfOpen*/true);
FPCGEditor* OtherPCGEditor = static_cast<FPCGEditor*>(EditorInstance);
if (OtherPCGEditor && !StackToInspect.GetStackFrames().IsEmpty())
{
OtherPCGEditor->SetStackBeingInspectedFromAnotherEditor(StackToInspect);
}
}
}
}
}
void FPCGEditor::JumpToDefinition(const UClass* Class) const
{
if (ensure(GUnrealEd) && GUnrealEd->GetUnrealEdOptions()->IsCPPAllowed())
{
FSourceCodeNavigation::NavigateToClass(Class);
}
}
void FPCGEditor::OnComponentUnregistered(UPCGComponent* Component)
{
// Refresh the debug object tree to avoid stale entries from components that have been unregistered.
if (!Component || Component->GetGraph() == PCGGraphBeingEdited)
{
DebugObjectTreeWidget->RequestRefresh();
}
if (IPCGEditorModule* PCGEditorModule = IPCGEditorModule::Get())
{
PCGEditorModule->GetNodeVisualLogsMutable().ClearLogs(Component);
}
}
void FPCGEditor::OnComponentGenerationDone(UPCGSubsystem* Subsystem, UPCGComponent* Component, EPCGGenerationStatus Status)
{
// We want to refresh if the component that is done generating has generated the current graph being edited,
// or if it is the root of the current stack being inspected (for subgraphs to also be refreshed).
// If we don't have a component, we refresh nonetheless.
bool bShouldRefresh = !Component || StackBeingInspected.GetRootComponent() == Component || Component->GetGraph() == PCGGraphBeingEdited;
// Additionally, if we are not inspecting but the component that's done executing contains this graph, then we should also update.
IPCGEditorModule* PCGEditorModule = IPCGEditorModule::Get();
if (!bShouldRefresh && PCGComponentBeingInspected.IsNull() && PCGGraphBeingEdited && PCGEditorModule)
{
TArray<FPCGStackSharedPtr> ExecutedStacksContainingThisGraph = PCGEditorModule->GetExecutedStacksPtrs(Component, PCGGraphBeingEdited);
bShouldRefresh |= !ExecutedStacksContainingThisGraph.IsEmpty();
}
if (!bShouldRefresh)
{
return;
}
OnComponentGenerated(Component);
const bool CacheDebuggingEnabled = Subsystem && Subsystem->IsGraphCacheDebuggingEnabled();
// Refresh nodes to report any errors/warnings, and to display culling state after execution.
check(PCGEditorGraph);
for (UEdGraphNode* Node : PCGEditorGraph->Nodes)
{
if (UPCGEditorGraphNodeBase* PCGEditorGraphNode = Cast<UPCGEditorGraphNodeBase>(Node))
{
// If we are debugging the graph cache then we need to refresh the cache count displayed in the title after every generation.
EPCGChangeType ChangeType = CacheDebuggingEnabled ? EPCGChangeType::Cosmetic : EPCGChangeType::None;
ChangeType |= PCGEditorGraphNode->UpdateErrorsAndWarnings();
ChangeType |= PCGEditorGraphNode->UpdateStructuralVisualization(PCGComponentBeingInspected.Get(), &StackBeingInspected);
ChangeType |= PCGEditorGraphNode->UpdateGPUVisualization(PCGComponentBeingInspected.Get(), &StackBeingInspected);
if (ChangeType != EPCGChangeType::None)
{
PCGEditorGraphNode->ReconstructNode();
}
}
}
}
UPCGSubsystem* FPCGEditor::GetSubsystem()
{
UWorld* World = (GEditor ? (GEditor->PlayWorld ? GEditor->PlayWorld.Get() : GEditor->GetEditorWorldContext().World()) : nullptr);
return UPCGSubsystem::GetInstance(World);
}
void FPCGEditor::RegisterDelegatesForWorld(UWorld* World)
{
UnregisterDelegatesForWorld(World);
if (UPCGSubsystem* Subsystem = UPCGSubsystem::GetInstance(World))
{
Subsystem->OnPCGComponentUnregistered.AddRaw(this, &FPCGEditor::OnComponentUnregistered);
Subsystem->OnPCGComponentGenerationDone.AddRaw(this, &FPCGEditor::OnComponentGenerationDone);
}
}
void FPCGEditor::UnregisterDelegatesForWorld(UWorld* World)
{
if (UPCGSubsystem* Subsystem = UPCGSubsystem::GetInstance(World))
{
Subsystem->OnPCGComponentUnregistered.RemoveAll(this);
Subsystem->OnPCGComponentGenerationDone.RemoveAll(this);
}
}
void FPCGEditor::OnGraphChanged(UPCGGraphInterface* InGraph, EPCGChangeType ChangeType)
{
if (!!(ChangeType & EPCGChangeType::ShaderSource))
{
// Flush the shader file cache in case we are editing engine or data interface shaders.
// We could make the user do this manually, but that makes iterating on data interfaces really painful.
FlushShaderFileCache();
}
if (!!(ChangeType & EPCGChangeType::GraphCustomization))
{
if (PaletteWidget)
{
PaletteWidget->RequestRefresh();
}
}
if (!!(ChangeType & EPCGChangeType::Edge))
{
for (TSharedPtr<SPCGEditorGraphDetailsView> Widget : PropertyDetailsWidgets)
{
if (Widget.IsValid() && Widget->GetVisibility() == EVisibility::Visible)
{
const TSharedPtr<IDetailsView> DetailsViewPtr = Widget->GetDetailsView();
DetailsViewPtr->ForceRefresh();
}
}
}
}
void FPCGEditor::OnNodeSourceCompiled(const UPCGNode* InNode, const FPCGCompilerDiagnostics& InDiagnostics)
{
check(NodeSourceWidget);
const UPCGSettings* Settings = InNode ? InNode->GetSettings() : nullptr;
if (Settings && NodeSourceWidget->GetTextProviderObject() == Settings)
{
NodeSourceWidget->OnDiagnosticsUpdated(InDiagnostics);
}
}
void FPCGEditor::OnMapChanged(UWorld* InWorld, EMapChangeType InMapChangedType)
{
if (InMapChangedType != EMapChangeType::SaveMap)
{
RefreshViewsOnLevelChange();
// Subsystem has been torn down and rebuilt.
if (GEditor)
{
RegisterDelegatesForWorld(GEditor->GetEditorWorldContext().World());
RegisterDelegatesForWorld(GEditor->PlayWorld.Get());
}
}
}
void FPCGEditor::OnPostPIEStarted(bool bIsSimulating)
{
RegisterDelegatesForWorld(GEditor ? GEditor->PlayWorld.Get() : nullptr);
}
void FPCGEditor::OnEndPIE(bool bIsSimulating)
{
UnregisterDelegatesForWorld(GEditor ? GEditor->PlayWorld.Get() : nullptr);
}
void FPCGEditor::OnLevelActorDeleted(AActor* InActor)
{
// Forward call as this makes an effort to retain the selection if the selected component has not been deleted.
if (DebugObjectTreeWidget.IsValid())
{
DebugObjectTreeWidget->RequestRefresh();
}
}
void FPCGEditor::RefreshViewsOnLevelChange()
{
if (DebugObjectTreeWidget.IsValid())
{
DebugObjectTreeWidget->RequestRefresh();
}
for (TSharedPtr<SPCGEditorGraphAttributeListView>& AttributeWidget : AttributesWidgets)
{
if (AttributeWidget.IsValid())
{
AttributeWidget->RequestRefresh();
}
}
}
TSharedRef<FTabManager::FLayout> FPCGEditor::GetDefaultLayout()
{
return FTabManager::NewLayout("Standalone_PCGGraphEditor_DefaultLayout_v1.0")
->AddArea // Main PCG Graph Editor Area
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
->Split // Top Section - Graph, Data Viewport, HLSL Source Editor, and Details View
(
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
->SetSizeCoefficient(0.65f)
->Split // Graph Palette
(
FTabManager::NewStack()
->AddTab(FPCGEditor_private::PaletteID, ETabState::SidebarTab, ESidebarLocation::Left, /*SidebarSizeCoefficient=*/0.13f)
)
->Split // Data Viewport/HLSL Source Editor
(
FTabManager::NewStack()
->SetSizeCoefficient(0.2f)
->AddTab(FPCGEditor_private::ViewportID[0], ETabState::OpenedTab)
->AddTab(FPCGEditor_private::HLSLSourceID, ETabState::OpenedTab)
->SetForegroundTab(FPCGEditor_private::ViewportID[0])
)
->Split // Node Graph
(
FTabManager::NewStack()
->SetSizeCoefficient(0.6f)
->AddTab(FPCGEditor_private::GraphEditorID, ETabState::OpenedTab)
->SetHideTabWell(true)
)
->Split // Details View
(
FTabManager::NewStack()
->SetSizeCoefficient(0.2f)
->AddTab(FPCGEditor_private::PropertyDetailsID[0], ETabState::OpenedTab)
)
)
->Split // Bottom Section - Debug/Params and ALV
(
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
->SetSizeCoefficient(0.35f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.2f)
->AddTab(FPCGEditor_private::DebugObjectID, ETabState::OpenedTab)
->AddTab(FPCGEditor_private::UserParamsID, ETabState::OpenedTab)
->SetForegroundTab(FPCGEditor_private::DebugObjectID)
)
->Split // ALV, Profiling, Find, Determinism
(
FTabManager::NewStack()
->SetSizeCoefficient(0.8f)
->SetHideTabWell(false)
->AddTab(FPCGEditor_private::AttributesID[0], ETabState::OpenedTab)
->AddTab(FPCGEditor_private::ProfilingID, ETabState::OpenedTab)
->AddTab(FPCGEditor_private::FindID, ETabState::OpenedTab)
->AddTab(FPCGEditor_private::DeterminismID, ETabState::ClosedTab)
->SetForegroundTab(FPCGEditor_private::AttributesID[0])
)
)
);
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_GraphEditor(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("PCGGraphTitle", "Graph"))
.TabColorScale(GetTabColorScale())
[
GraphEditorWidget.ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_PropertyDetails(const FSpawnTabArgs& Args, int PropertyDetailsIndex)
{
TAttribute<FText> Label = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateRaw(this, &FPCGEditor::GetDetailsTabLabel, PropertyDetailsIndex));
TSharedPtr<SPCGEditorGraphDetailsView> DetailsView = PropertyDetailsWidgets[PropertyDetailsIndex];
return SNew(SDockTab)
.Label(Label)
.OnTabClosed_Raw(this, &FPCGEditor::OnDetailsViewTabClosed, PropertyDetailsIndex)
.TabColorScale(GetTabColorScale())
[
DetailsView.ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_Palette(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("PCGPaletteTitle", "Palette"))
.TabColorScale(GetTabColorScale())
[
PaletteWidget.ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_DebugObjectTree(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("PCGDebugObjectTitle", "Debug Object"))
.TabColorScale(GetTabColorScale())
[
DebugObjectTreeWidget.ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_Attributes(const FSpawnTabArgs& Args, int AttributesIndex)
{
TAttribute<FText> Label = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateRaw(this, &FPCGEditor::GetAttributesTabLabel, AttributesIndex));
return SNew(SDockTab)
.Label(Label)
.OnTabClosed_Raw(this, &FPCGEditor::OnAttributeListViewTabClosed, AttributesIndex)
.TabColorScale(GetTabColorScale())
[
AttributesWidgets[AttributesIndex].ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_Find(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("PCGFindTitle", "Find"))
.TabColorScale(GetTabColorScale())
[
FindWidget.ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_Determinism(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("PCGDeterminismTitle", "Determinism"))
.TabColorScale(GetTabColorScale())
[
DeterminismWidget.ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_Profiling(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("PCGProfilingTitle", "Profiling"))
.TabColorScale(GetTabColorScale())
[
ProfilingWidget.ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_Log(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("PCGLogTitle", "Log Capture"))
.TabColorScale(GetTabColorScale())
[
LogWidget.ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_NodeSource(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("PCGHLSLSourceTitle", "HLSL Source"))
.TabColorScale(GetTabColorScale())
[
NodeSourceWidget.ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_UserParams(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(FPCGEditor_private::UserParamsTabName)
.TabColorScale(GetTabColorScale())
[
UserParamsWidget.ToSharedRef()
];
}
TSharedRef<SDockTab> FPCGEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args, int ViewportIndex)
{
TAttribute<FText> Label = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateRaw(this, &FPCGEditor::GetViewportTabLabel, ViewportIndex));
AttributesWidgets[ViewportIndex]->RequestViewportRefresh();
return SNew(SDockTab)
.Label(Label)
.OnTabClosed_Raw(this, &FPCGEditor::OnViewportViewTabClosed, ViewportIndex)
.TabColorScale(GetTabColorScale())
[
AttributesWidgets[ViewportIndex]->GetViewportWidget().ToSharedRef()
];
}
FText FPCGEditor::GetDetailsTabLabel(int DetailsIndex)
{
if (DetailsIndex == 0)
{
return LOCTEXT("PCGDetailsTitle", "Details");
}
else
{
return FText::Format(LOCTEXT("PCGDetailsTitle_Multi", "Details {0}"), DetailsIndex + 1);
}
}
FText FPCGEditor::GetDetailsViewObjectName(int DetailsIndex)
{
return LOCTEXT("PCGDetailsName", "This is a node name placeholder");
}
FText FPCGEditor::GetAttributesTabLabel(int AttributesIndex)
{
if (AttributesIndex == 0)
{
return LOCTEXT("PCGAttributesTitle", "Attributes");
}
else
{
return FText::Format(LOCTEXT("PCGAttributesTitle_Multi", "Attributes {0}"), AttributesIndex + 1);
}
}
FText FPCGEditor::GetViewportTabLabel(int ViewportIndex)
{
if (ViewportIndex == 0)
{
return LOCTEXT("PCGViewportTitle", "Data Viewport");
}
else
{
return FText::Format(LOCTEXT("PCGViewportTitle_Multi", "Data Viewport {0}"), ViewportIndex + 1);
}
}
#undef LOCTEXT_NAMESPACE