Add Generic Graph Plugin for Creating Custom Graph

This commit is contained in:
Philip W 2023-11-20 21:25:36 +00:00
parent a450f68d7c
commit c682f72afa
65 changed files with 5200 additions and 0 deletions

View File

@ -0,0 +1,14 @@
Binaries
DerivedDataCache
Intermediate
Saved
Build
*.sdf
*.sln
*.suo
*.opensdf
*.opendb
*.db
/docs/sphinx-build-result
/.vs
NoCommit

View File

@ -0,0 +1,30 @@
{
"FileVersion" : 3,
"Version" : 1,
"VersionName" : "1.0",
"FriendlyName" : "Generic Graph Plugin",
"Description" : "",
"Category" : "GenericGraph",
"CreatedBy" : "jinyuliao",
"CreatedByURL" : "",
"DocsURL" : "",
"MarketplaceURL" : "",
"SupportURL" : "",
"EnabledByDefault" : true,
"CanContainContent" : true,
"IsBetaVersion" : false,
"Installed" : false,
"Modules" :
[
{
"Name" : "GenericGraphRuntime",
"Type" : "Runtime",
"LoadingPhase" : "PreDefault"
},
{
"Name" : "GenericGraphEditor",
"Type" : "Editor",
"LoadingPhase" : "Default"
}
]
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 jinyuliao
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

BIN
EndlessVendetta/Plugins/GenericGraph/Resources/Icon128.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,62 @@
using UnrealBuildTool;
public class GenericGraphEditor : ModuleRules
{
public GenericGraphEditor(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
bLegacyPublicIncludePaths = false;
ShadowVariableWarningLevel = WarningLevel.Error;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
"GenericGraphEditor/Private",
"GenericGraphEditor/Public",
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
"UnrealEd",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"GenericGraphRuntime",
"AssetTools",
"Slate",
"InputCore",
"SlateCore",
"GraphEditor",
"PropertyEditor",
"EditorStyle",
"Kismet",
"KismetWidgets",
"ApplicationCore",
"ToolMenus",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@ -0,0 +1,48 @@
#include "AssetTypeActions_GenericGraph.h"
#include "GenericGraphEditorPCH.h"
#include "GenericGraphAssetEditor/AssetEditor_GenericGraph.h"
#define LOCTEXT_NAMESPACE "AssetTypeActions_GenericGraph"
FAssetTypeActions_GenericGraph::FAssetTypeActions_GenericGraph(EAssetTypeCategories::Type InAssetCategory)
: MyAssetCategory(InAssetCategory)
{
}
FText FAssetTypeActions_GenericGraph::GetName() const
{
return LOCTEXT("FGenericGraphAssetTypeActionsName", "Generic Graph");
}
FColor FAssetTypeActions_GenericGraph::GetTypeColor() const
{
return FColor(129, 196, 115);
}
UClass* FAssetTypeActions_GenericGraph::GetSupportedClass() const
{
return UGenericGraph::StaticClass();
}
void FAssetTypeActions_GenericGraph::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor)
{
const EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;
for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt)
{
if (UGenericGraph* Graph = Cast<UGenericGraph>(*ObjIt))
{
TSharedRef<FAssetEditor_GenericGraph> NewGraphEditor(new FAssetEditor_GenericGraph());
NewGraphEditor->InitGenericGraphAssetEditor(Mode, EditWithinLevelEditor, Graph);
}
}
}
uint32 FAssetTypeActions_GenericGraph::GetCategories()
{
return MyAssetCategory;
}
//////////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,96 @@
#include "AutoLayout/AutoLayoutStrategy.h"
#include "Kismet/KismetMathLibrary.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/SEdNode_GenericGraphNode.h"
UAutoLayoutStrategy::UAutoLayoutStrategy()
{
Settings = nullptr;
MaxIteration = 50;
OptimalDistance = 150;
}
UAutoLayoutStrategy::~UAutoLayoutStrategy()
{
}
FBox2D UAutoLayoutStrategy::GetNodeBound(UEdGraphNode* EdNode)
{
int32 NodeWidth = GetNodeWidth(Cast<UEdNode_GenericGraphNode>(EdNode));
int32 NodeHeight = GetNodeHeight(Cast<UEdNode_GenericGraphNode>(EdNode));
FVector2D Min(EdNode->NodePosX, EdNode->NodePosY);
FVector2D Max(EdNode->NodePosX + NodeWidth, EdNode->NodePosY + NodeHeight);
return FBox2D(Min, Max);
}
FBox2D UAutoLayoutStrategy::GetActualBounds(UGenericGraphNode* RootNode)
{
int Level = 0;
TArray<UGenericGraphNode*> CurrLevelNodes = { RootNode };
TArray<UGenericGraphNode*> NextLevelNodes;
FBox2D Rtn = GetNodeBound(EdGraph->NodeMap[RootNode]);
while (CurrLevelNodes.Num() != 0)
{
for (int i = 0; i < CurrLevelNodes.Num(); ++i)
{
UGenericGraphNode* Node = CurrLevelNodes[i];
check(Node != nullptr);
Rtn += GetNodeBound(EdGraph->NodeMap[Node]);
for (int j = 0; j < Node->ChildrenNodes.Num(); ++j)
{
NextLevelNodes.Add(Node->ChildrenNodes[j]);
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
++Level;
}
return Rtn;
}
void UAutoLayoutStrategy::RandomLayoutOneTree(UGenericGraphNode* RootNode, const FBox2D& Bound)
{
int Level = 0;
TArray<UGenericGraphNode*> CurrLevelNodes = { RootNode };
TArray<UGenericGraphNode*> NextLevelNodes;
while (CurrLevelNodes.Num() != 0)
{
for (int i = 0; i < CurrLevelNodes.Num(); ++i)
{
UGenericGraphNode* Node = CurrLevelNodes[i];
check(Node != nullptr);
UEdNode_GenericGraphNode* EdNode_Node = EdGraph->NodeMap[Node];
EdNode_Node->NodePosX = UKismetMathLibrary::RandomFloatInRange(Bound.Min.X, Bound.Max.X);
EdNode_Node->NodePosY = UKismetMathLibrary::RandomFloatInRange(Bound.Min.Y, Bound.Max.Y);
for (int j = 0; j < Node->ChildrenNodes.Num(); ++j)
{
NextLevelNodes.Add(Node->ChildrenNodes[j]);
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
++Level;
}
}
int32 UAutoLayoutStrategy::GetNodeWidth(UEdNode_GenericGraphNode* EdNode)
{
return EdNode->SEdNode->GetCachedGeometry().GetLocalSize().X;
}
int32 UAutoLayoutStrategy::GetNodeHeight(UEdNode_GenericGraphNode* EdNode)
{
return EdNode->SEdNode->GetCachedGeometry().GetLocalSize().Y;
}

View File

@ -0,0 +1,162 @@
#include "AutoLayout/ForceDirectedLayoutStrategy.h"
static inline float CoolDown(float Temp, float CoolDownRate)
{
if (Temp < .01) return .01;
return Temp - (Temp / CoolDownRate);
}
static inline float GetAttractForce(float X, float K)
{
return (X * X) / K;
}
static inline float GetRepulseForce(float X, float k)
{
return X != 0 ? k * k / X : TNumericLimits<float>::Max();
}
UForceDirectedLayoutStrategy::UForceDirectedLayoutStrategy()
{
bRandomInit = false;
CoolDownRate = 10;
InitTemperature = 10.f;
}
UForceDirectedLayoutStrategy::~UForceDirectedLayoutStrategy()
{
}
void UForceDirectedLayoutStrategy::Layout(UEdGraph* _EdGraph)
{
EdGraph = Cast<UEdGraph_GenericGraph>(_EdGraph);
check(EdGraph != nullptr);
EdGraph->RebuildGenericGraph();
Graph = EdGraph->GetGenericGraph();
check(Graph != nullptr);
if (Settings != nullptr)
{
OptimalDistance = Settings->OptimalDistance;
MaxIteration = Settings->MaxIteration;
bRandomInit = Settings->bRandomInit;
}
FBox2D PreTreeBound(ForceInitToZero);
for (int32 i = 0; i < Graph->RootNodes.Num(); ++i)
{
PreTreeBound = LayoutOneTree(Graph->RootNodes[i], PreTreeBound);
}
}
FBox2D UForceDirectedLayoutStrategy::LayoutOneTree(UGenericGraphNode* RootNode, const FBox2D& PreTreeBound)
{
float Temp = InitTemperature;
FBox2D TreeBound = GetActualBounds(RootNode);
TreeBound.Min.X += PreTreeBound.Max.X + OptimalDistance;
TreeBound.Max.X += PreTreeBound.Max.X + OptimalDistance;
if (bRandomInit)
{
RandomLayoutOneTree(RootNode, TreeBound);
}
float RepulseForce, AttractForce, Distance;
FVector2D Diff;
TMap<UEdGraphNode*, FVector2D> NodeToDisplacement;
for (int32 i = 0; i < EdGraph->Nodes.Num(); ++i)
{
NodeToDisplacement.Add(EdGraph->Nodes[i], FVector2D(0.f, 0.f));
}
for (int32 IterrationNum = 0; IterrationNum < MaxIteration; ++IterrationNum)
{
// Calculate the repulsive forces.
for (int32 i = 0; i < EdGraph->Nodes.Num(); ++i)
{
for (int32 j = 0; j < EdGraph->Nodes.Num(); ++j)
{
if (i == j)
continue;
Diff.X = EdGraph->Nodes[i]->NodePosX - EdGraph->Nodes[j]->NodePosX;
Diff.Y = EdGraph->Nodes[i]->NodePosY - EdGraph->Nodes[j]->NodePosY;
Distance = Diff.Size();
Diff.Normalize();
RepulseForce = Distance > 2 * OptimalDistance ? 0 : GetRepulseForce(Distance, OptimalDistance);
NodeToDisplacement[EdGraph->Nodes[i]] += Diff * RepulseForce;
}
}
// Calculate the attractive forces.
int Level = 0;
TArray<UGenericGraphNode*> CurrLevelNodes = { RootNode };
TArray<UGenericGraphNode*> NextLevelNodes;
while (CurrLevelNodes.Num() != 0)
{
for (int32 i = 0; i < CurrLevelNodes.Num(); ++i)
{
UGenericGraphNode* Node = CurrLevelNodes[i];
check(Node != nullptr);
UEdNode_GenericGraphNode* EdNode_ParentNode = EdGraph->NodeMap[Node];
for (int32 j = 0; j < Node->ChildrenNodes.Num(); ++j)
{
NextLevelNodes.Add(Node->ChildrenNodes[j]);
UEdNode_GenericGraphNode* EdNode_ChildNode = EdGraph->NodeMap[Node->ChildrenNodes[j]];
Diff.X = EdNode_ChildNode->NodePosX - EdNode_ParentNode->NodePosY;
Diff.Y = EdNode_ChildNode->NodePosY - EdNode_ParentNode->NodePosY;
Distance = Diff.Size();
Diff.Normalize();
AttractForce = GetAttractForce(Distance, OptimalDistance);
NodeToDisplacement[EdNode_ParentNode] += Distance * Diff;
NodeToDisplacement[EdNode_ChildNode] -= Distance * Diff;
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
++Level;
}
for (int32 i = 0; i < EdGraph->Nodes.Num(); ++i)
{
UEdGraphNode* EdNode = EdGraph->Nodes[i];
Distance = NodeToDisplacement[EdNode].Size();
NodeToDisplacement[EdNode].Normalize();
float Minimum = Distance < Temp ? Distance : Temp;
EdNode->NodePosX += NodeToDisplacement[EdNode].X * Minimum;
EdNode->NodePosY += NodeToDisplacement[EdNode].Y * Minimum;
}
Temp = CoolDown(Temp, CoolDownRate);
}
FBox2D ActualBound = GetActualBounds(RootNode);
FVector2D Center = ActualBound.GetCenter();
FVector2D TreeCenter = TreeBound.GetCenter();
FVector2D Scale = (TreeBound.Max - TreeBound.Min) / (ActualBound.Max - ActualBound.Min);
for (int32 i = 0; i < EdGraph->Nodes.Num(); ++i)
{
UEdGraphNode* EdNode = EdGraph->Nodes[i];
EdNode->NodePosX = TreeCenter.X + Scale.X * (EdNode->NodePosX - Center.X);
EdNode->NodePosY = TreeCenter.Y + Scale.Y * (EdNode->NodePosY - Center.Y);
}
return TreeBound;
}

View File

@ -0,0 +1,241 @@
#include "AutoLayout/TreeLayoutStrategy.h"
#include "GenericGraphEditorPCH.h"
#include "GenericGraphAssetEditor/SEdNode_GenericGraphNode.h"
UTreeLayoutStrategy::UTreeLayoutStrategy()
{
}
UTreeLayoutStrategy::~UTreeLayoutStrategy()
{
}
void UTreeLayoutStrategy::Layout(UEdGraph* _EdGraph)
{
EdGraph = Cast<UEdGraph_GenericGraph>(_EdGraph);
check(EdGraph != nullptr);
EdGraph->RebuildGenericGraph();
Graph = EdGraph->GetGenericGraph();
check(Graph != nullptr);
bool bFirstPassOnly = false;
if (Settings != nullptr)
{
OptimalDistance = Settings->OptimalDistance;
MaxIteration = Settings->MaxIteration;
bFirstPassOnly = Settings->bFirstPassOnly;
}
FVector2D Anchor(0.f, 0.f);
for (int32 i = 0; i < Graph->RootNodes.Num(); ++i)
{
UGenericGraphNode* RootNode = Graph->RootNodes[i];
InitPass(RootNode, Anchor);
if (!bFirstPassOnly)
{
for (int32 j = 0; j < MaxIteration; ++j)
{
bool HasConflict = ResolveConflictPass(RootNode);
if (!HasConflict)
{
break;
}
}
}
}
for (int32 i = 0; i < Graph->RootNodes.Num(); ++i)
{
for (int32 j = 0; j < i; ++j)
{
ResolveConflict(Graph->RootNodes[j], Graph->RootNodes[i]);
}
}
}
void UTreeLayoutStrategy::InitPass(UGenericGraphNode* RootNode, const FVector2D& Anchor)
{
UEdNode_GenericGraphNode* EdNode_RootNode = EdGraph->NodeMap[RootNode];
FVector2D ChildAnchor(FVector2D(0.f, GetNodeHeight(EdNode_RootNode) + OptimalDistance + Anchor.Y));
for (int32 i = 0; i < RootNode->ChildrenNodes.Num(); ++i)
{
UGenericGraphNode* Child = RootNode->ChildrenNodes[i];
UEdNode_GenericGraphNode* EdNode_ChildNode = EdGraph->NodeMap[Child];
if (i > 0)
{
UGenericGraphNode* PreChild = RootNode->ChildrenNodes[i - 1];
UEdNode_GenericGraphNode* EdNode_PreChildNode = EdGraph->NodeMap[PreChild];
ChildAnchor.X += OptimalDistance + GetNodeWidth(EdNode_PreChildNode) / 2;
}
ChildAnchor.X += GetNodeWidth(EdNode_ChildNode) / 2;
InitPass(Child, ChildAnchor);
}
float NodeWidth = GetNodeWidth(EdNode_RootNode);
EdNode_RootNode->NodePosY = Anchor.Y;
if (RootNode->ChildrenNodes.Num() == 0)
{
EdNode_RootNode->NodePosX = Anchor.X - NodeWidth / 2;
}
else
{
UpdateParentNodePosition(RootNode);
}
}
bool UTreeLayoutStrategy::ResolveConflictPass(UGenericGraphNode* Node)
{
bool HasConflict = false;
for (int32 i = 0; i < Node->ChildrenNodes.Num(); ++i)
{
UGenericGraphNode* Child = Node->ChildrenNodes[i];
if (ResolveConflictPass(Child))
{
HasConflict = true;
}
}
for (int32 i = 0; i < Node->ParentNodes.Num(); ++i)
{
UGenericGraphNode* ParentNode = Node->ParentNodes[i];
for (int32 j = 0; j < ParentNode->ChildrenNodes.Num(); ++j)
{
UGenericGraphNode* LeftSibling = ParentNode->ChildrenNodes[j];
if (LeftSibling == Node)
break;
if (ResolveConflict(LeftSibling, Node))
{
HasConflict = true;
}
}
}
return HasConflict;
}
bool UTreeLayoutStrategy::ResolveConflict(UGenericGraphNode* LRoot, UGenericGraphNode* RRoot)
{
TArray<UEdNode_GenericGraphNode*> RightContour, LeftContour;
GetRightContour(LRoot, 0, RightContour);
GetLeftContour(RRoot, 0, LeftContour);
int32 MaxOverlapDistance = 0;
int32 Num = FMath::Min(LeftContour.Num(), RightContour.Num());
for (int32 i = 0; i < Num; ++i)
{
if (RightContour.Contains(LeftContour[i]) || LeftContour.Contains(RightContour[i]))
break;
int32 RightBound = RightContour[i]->NodePosX + GetNodeWidth(RightContour[i]);
int32 LeftBound = LeftContour[i]->NodePosX;
int32 Distance = RightBound + OptimalDistance - LeftBound;
if (Distance > MaxOverlapDistance)
{
MaxOverlapDistance = Distance;
}
}
if (MaxOverlapDistance > 0)
{
ShiftSubTree(RRoot, FVector2D(MaxOverlapDistance, 0.f));
TArray<UGenericGraphNode*> ParentNodes = RRoot->ParentNodes;
TArray<UGenericGraphNode*> NextParentNodes;
while (ParentNodes.Num() != 0)
{
for (int32 i = 0; i < ParentNodes.Num(); ++i)
{
UpdateParentNodePosition(ParentNodes[i]);
NextParentNodes.Append(ParentNodes[i]->ParentNodes);
}
ParentNodes = NextParentNodes;
NextParentNodes.Reset();
}
return true;
}
else
{
return false;
}
}
void UTreeLayoutStrategy::GetLeftContour(UGenericGraphNode* RootNode, int32 Level, TArray<UEdNode_GenericGraphNode*>& Contour)
{
UEdNode_GenericGraphNode* EdNode_Node = EdGraph->NodeMap[RootNode];
if (Level >= Contour.Num())
{
Contour.Add(EdNode_Node);
}
else if (EdNode_Node->NodePosX < Contour[Level]->NodePosX)
{
Contour[Level] = EdNode_Node;
}
for (int32 i = 0; i < RootNode->ChildrenNodes.Num(); ++i)
{
GetLeftContour(RootNode->ChildrenNodes[i], Level + 1, Contour);
}
}
void UTreeLayoutStrategy::GetRightContour(UGenericGraphNode* RootNode, int32 Level, TArray<UEdNode_GenericGraphNode*>& Contour)
{
UEdNode_GenericGraphNode* EdNode_Node = EdGraph->NodeMap[RootNode];
if (Level >= Contour.Num())
{
Contour.Add(EdNode_Node);
}
else if (EdNode_Node->NodePosX + GetNodeWidth(EdNode_Node) > Contour[Level]->NodePosX + GetNodeWidth(Contour[Level]))
{
Contour[Level] = EdNode_Node;
}
for (int32 i = 0; i < RootNode->ChildrenNodes.Num(); ++i)
{
GetRightContour(RootNode->ChildrenNodes[i], Level + 1, Contour);
}
}
void UTreeLayoutStrategy::ShiftSubTree(UGenericGraphNode* RootNode, const FVector2D& Offset)
{
UEdNode_GenericGraphNode* EdNode_Node = EdGraph->NodeMap[RootNode];
EdNode_Node->NodePosX += Offset.X;
EdNode_Node->NodePosY += Offset.Y;
for (int32 i = 0; i < RootNode->ChildrenNodes.Num(); ++i)
{
UGenericGraphNode* Child = RootNode->ChildrenNodes[i];
if (Child->ParentNodes[0] == RootNode)
{
ShiftSubTree(RootNode->ChildrenNodes[i], Offset);
}
}
}
void UTreeLayoutStrategy::UpdateParentNodePosition(UGenericGraphNode* ParentNode)
{
UEdNode_GenericGraphNode* EdNode_ParentNode = EdGraph->NodeMap[ParentNode];
if (ParentNode->ChildrenNodes.Num() % 2 == 0)
{
UEdNode_GenericGraphNode* FirstChild = EdGraph->NodeMap[ParentNode->ChildrenNodes[0]];
UEdNode_GenericGraphNode* LastChild = EdGraph->NodeMap[ParentNode->ChildrenNodes.Last()];
float LeftBound = FirstChild->NodePosX;
float RightBound = LastChild->NodePosX + GetNodeWidth(LastChild);
EdNode_ParentNode->NodePosX = (LeftBound + RightBound) / 2 - GetNodeWidth(EdNode_ParentNode) / 2;
}
else
{
UEdNode_GenericGraphNode* MidChild = EdGraph->NodeMap[ParentNode->ChildrenNodes[ParentNode->ChildrenNodes.Num() / 2]];
EdNode_ParentNode->NodePosX = MidChild->NodePosX + GetNodeWidth(MidChild) / 2 - GetNodeWidth(EdNode_ParentNode) / 2;
}
}

View File

@ -0,0 +1,46 @@
#include "GenericGraphAssetEditor/AssetEditorToolbar_GenericGraph.h"
#include "GenericGraphAssetEditor/AssetEditor_GenericGraph.h"
#include "GenericGraphAssetEditor/EditorCommands_GenericGraph.h"
#include "GenericGraphAssetEditor/GenericGraphEditorStyle.h"
#define LOCTEXT_NAMESPACE "AssetEditorToolbar_GenericGraph"
void FAssetEditorToolbar_GenericGraph::AddGenericGraphToolbar(TSharedPtr<FExtender> Extender)
{
check(GenericGraphEditor.IsValid());
TSharedPtr<FAssetEditor_GenericGraph> GenericGraphEditorPtr = GenericGraphEditor.Pin();
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension("Asset", EExtensionHook::After, GenericGraphEditorPtr->GetToolkitCommands(), FToolBarExtensionDelegate::CreateSP( this, &FAssetEditorToolbar_GenericGraph::FillGenericGraphToolbar ));
GenericGraphEditorPtr->AddToolbarExtender(ToolbarExtender);
}
void FAssetEditorToolbar_GenericGraph::FillGenericGraphToolbar(FToolBarBuilder& ToolbarBuilder)
{
check(GenericGraphEditor.IsValid());
TSharedPtr<FAssetEditor_GenericGraph> GenericGraphEditorPtr = GenericGraphEditor.Pin();
ToolbarBuilder.BeginSection("Generic Graph");
{
ToolbarBuilder.AddToolBarButton(FEditorCommands_GenericGraph::Get().GraphSettings,
NAME_None,
LOCTEXT("GraphSettings_Label", "Graph Settings"),
LOCTEXT("GraphSettings_ToolTip", "Show the Graph Settings"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.GameSettings"));
}
ToolbarBuilder.EndSection();
ToolbarBuilder.BeginSection("Util");
{
ToolbarBuilder.AddToolBarButton(FEditorCommands_GenericGraph::Get().AutoArrange,
NAME_None,
LOCTEXT("AutoArrange_Label", "Auto Arrange"),
LOCTEXT("AutoArrange_ToolTip", "Auto arrange nodes, not perfect, but still handy"),
FSlateIcon(FGenericGraphEditorStyle::GetStyleSetName(), "GenericGraphEditor.AutoArrange"));
}
ToolbarBuilder.EndSection();
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,819 @@
#include "GenericGraphAssetEditor/AssetEditor_GenericGraph.h"
#include "GenericGraphEditorPCH.h"
#include "GenericGraphAssetEditor/AssetEditorToolbar_GenericGraph.h"
#include "GenericGraphAssetEditor/AssetGraphSchema_GenericGraph.h"
#include "GenericGraphAssetEditor/EditorCommands_GenericGraph.h"
#include "GenericGraphAssetEditor/EdGraph_GenericGraph.h"
#include "AssetToolsModule.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Framework/Commands/GenericCommands.h"
#include "GraphEditorActions.h"
#include "IDetailsView.h"
#include "PropertyEditorModule.h"
#include "Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "EdGraphUtilities.h"
#include "GenericGraphAssetEditor/EdGraph_GenericGraph.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphEdge.h"
#include "AutoLayout/TreeLayoutStrategy.h"
#include "AutoLayout/ForceDirectedLayoutStrategy.h"
#define LOCTEXT_NAMESPACE "AssetEditor_GenericGraph"
const FName GenericGraphEditorAppName = FName(TEXT("GenericGraphEditorApp"));
struct FGenericGraphAssetEditorTabs
{
// Tab identifiers
static const FName GenericGraphPropertyID;
static const FName ViewportID;
static const FName GenericGraphEditorSettingsID;
};
//////////////////////////////////////////////////////////////////////////
const FName FGenericGraphAssetEditorTabs::GenericGraphPropertyID(TEXT("GenericGraphProperty"));
const FName FGenericGraphAssetEditorTabs::ViewportID(TEXT("Viewport"));
const FName FGenericGraphAssetEditorTabs::GenericGraphEditorSettingsID(TEXT("GenericGraphEditorSettings"));
//////////////////////////////////////////////////////////////////////////
FAssetEditor_GenericGraph::FAssetEditor_GenericGraph()
{
EditingGraph = nullptr;
GenricGraphEditorSettings = NewObject<UGenericGraphEditorSettings>(UGenericGraphEditorSettings::StaticClass());
#if ENGINE_MAJOR_VERSION < 5
OnPackageSavedDelegateHandle = UPackage::PackageSavedEvent.AddRaw(this, &FAssetEditor_GenericGraph::OnPackageSaved);
#else // #if ENGINE_MAJOR_VERSION < 5
OnPackageSavedDelegateHandle = UPackage::PackageSavedWithContextEvent.AddRaw(this, &FAssetEditor_GenericGraph::OnPackageSavedWithContext);
#endif // #else // #if ENGINE_MAJOR_VERSION < 5
}
FAssetEditor_GenericGraph::~FAssetEditor_GenericGraph()
{
#if ENGINE_MAJOR_VERSION < 5
UPackage::PackageSavedEvent.Remove(OnPackageSavedDelegateHandle);
#else // #if ENGINE_MAJOR_VERSION < 5
UPackage::PackageSavedWithContextEvent.Remove(OnPackageSavedDelegateHandle);
#endif // #else // #if ENGINE_MAJOR_VERSION < 5
}
void FAssetEditor_GenericGraph::InitGenericGraphAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, UGenericGraph* Graph)
{
EditingGraph = Graph;
CreateEdGraph();
FGenericCommands::Register();
FGraphEditorCommands::Register();
FEditorCommands_GenericGraph::Register();
if (!ToolbarBuilder.IsValid())
{
ToolbarBuilder = MakeShareable(new FAssetEditorToolbar_GenericGraph(SharedThis(this)));
}
BindCommands();
CreateInternalWidgets();
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarBuilder->AddGenericGraphToolbar(ToolbarExtender);
// Layout
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_GenericGraphEditor_Layout_v1")
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
#if ENGINE_MAJOR_VERSION < 5
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.1f)
->AddTab(GetToolbarTabId(), ETabState::OpenedTab)->SetHideTabWell(true)
)
#endif // #if ENGINE_MAJOR_VERSION < 5
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)->SetSizeCoefficient(0.9f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.65f)
->AddTab(FGenericGraphAssetEditorTabs::ViewportID, ETabState::OpenedTab)->SetHideTabWell(true)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.7f)
->AddTab(FGenericGraphAssetEditorTabs::GenericGraphPropertyID, ETabState::OpenedTab)->SetHideTabWell(true)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.3f)
->AddTab(FGenericGraphAssetEditorTabs::GenericGraphEditorSettingsID, ETabState::OpenedTab)
)
)
)
);
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = true;
FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, GenericGraphEditorAppName, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, EditingGraph, false);
RegenerateMenusAndToolbars();
}
void FAssetEditor_GenericGraph::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_GenericGraphEditor", "Generic Graph Editor"));
auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
InTabManager->RegisterTabSpawner(FGenericGraphAssetEditorTabs::ViewportID, FOnSpawnTab::CreateSP(this, &FAssetEditor_GenericGraph::SpawnTab_Viewport))
.SetDisplayName(LOCTEXT("GraphCanvasTab", "Viewport"))
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.EventGraph_16x"));
InTabManager->RegisterTabSpawner(FGenericGraphAssetEditorTabs::GenericGraphPropertyID, FOnSpawnTab::CreateSP(this, &FAssetEditor_GenericGraph::SpawnTab_Details))
.SetDisplayName(LOCTEXT("DetailsTab", "Property"))
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
InTabManager->RegisterTabSpawner(FGenericGraphAssetEditorTabs::GenericGraphEditorSettingsID, FOnSpawnTab::CreateSP(this, &FAssetEditor_GenericGraph::SpawnTab_EditorSettings))
.SetDisplayName(LOCTEXT("EditorSettingsTab", "Generic Graph Editor Setttings"))
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
}
void FAssetEditor_GenericGraph::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
InTabManager->UnregisterTabSpawner(FGenericGraphAssetEditorTabs::ViewportID);
InTabManager->UnregisterTabSpawner(FGenericGraphAssetEditorTabs::GenericGraphPropertyID);
InTabManager->UnregisterTabSpawner(FGenericGraphAssetEditorTabs::GenericGraphEditorSettingsID);
}
FName FAssetEditor_GenericGraph::GetToolkitFName() const
{
return FName("FGenericGraphEditor");
}
FText FAssetEditor_GenericGraph::GetBaseToolkitName() const
{
return LOCTEXT("GenericGraphEditorAppLabel", "Generic Graph Editor");
}
FText FAssetEditor_GenericGraph::GetToolkitName() const
{
const bool bDirtyState = EditingGraph->GetOutermost()->IsDirty();
FFormatNamedArguments Args;
Args.Add(TEXT("GenericGraphName"), FText::FromString(EditingGraph->GetName()));
Args.Add(TEXT("DirtyState"), bDirtyState ? FText::FromString(TEXT("*")) : FText::GetEmpty());
return FText::Format(LOCTEXT("GenericGraphEditorToolkitName", "{GenericGraphName}{DirtyState}"), Args);
}
FText FAssetEditor_GenericGraph::GetToolkitToolTipText() const
{
return FAssetEditorToolkit::GetToolTipTextForObject(EditingGraph);
}
FLinearColor FAssetEditor_GenericGraph::GetWorldCentricTabColorScale() const
{
return FLinearColor::White;
}
FString FAssetEditor_GenericGraph::GetWorldCentricTabPrefix() const
{
return TEXT("GenericGraphEditor");
}
FString FAssetEditor_GenericGraph::GetDocumentationLink() const
{
return TEXT("");
}
void FAssetEditor_GenericGraph::SaveAsset_Execute()
{
if (EditingGraph != nullptr)
{
RebuildGenericGraph();
}
FAssetEditorToolkit::SaveAsset_Execute();
}
void FAssetEditor_GenericGraph::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObject(EditingGraph);
Collector.AddReferencedObject(EditingGraph->EdGraph);
}
UGenericGraphEditorSettings* FAssetEditor_GenericGraph::GetSettings() const
{
return GenricGraphEditorSettings;
}
TSharedRef<SDockTab> FAssetEditor_GenericGraph::SpawnTab_Viewport(const FSpawnTabArgs& Args)
{
check(Args.GetTabId() == FGenericGraphAssetEditorTabs::ViewportID);
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("ViewportTab_Title", "Viewport"));
if (ViewportWidget.IsValid())
{
SpawnedTab->SetContent(ViewportWidget.ToSharedRef());
}
return SpawnedTab;
}
TSharedRef<SDockTab> FAssetEditor_GenericGraph::SpawnTab_Details(const FSpawnTabArgs& Args)
{
check(Args.GetTabId() == FGenericGraphAssetEditorTabs::GenericGraphPropertyID);
return SNew(SDockTab)
#if ENGINE_MAJOR_VERSION < 5
.Icon(FAppStyle::GetBrush("LevelEditor.Tabs.Details"))
#endif // #if ENGINE_MAJOR_VERSION < 5
.Label(LOCTEXT("Details_Title", "Property"))
[
PropertyWidget.ToSharedRef()
];
}
TSharedRef<SDockTab> FAssetEditor_GenericGraph::SpawnTab_EditorSettings(const FSpawnTabArgs& Args)
{
check(Args.GetTabId() == FGenericGraphAssetEditorTabs::GenericGraphEditorSettingsID);
return SNew(SDockTab)
#if ENGINE_MAJOR_VERSION < 5
.Icon(FAppStyle::GetBrush("LevelEditor.Tabs.Details"))
#endif // #if ENGINE_MAJOR_VERSION < 5
.Label(LOCTEXT("EditorSettings_Title", "Generic Graph Editor Setttings"))
[
EditorSettingsWidget.ToSharedRef()
];
}
void FAssetEditor_GenericGraph::CreateInternalWidgets()
{
ViewportWidget = CreateViewportWidget();
FDetailsViewArgs Args;
Args.bHideSelectionTip = true;
Args.NotifyHook = this;
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyWidget = PropertyModule.CreateDetailView(Args);
PropertyWidget->SetObject(EditingGraph);
PropertyWidget->OnFinishedChangingProperties().AddSP(this, &FAssetEditor_GenericGraph::OnFinishedChangingProperties);
EditorSettingsWidget = PropertyModule.CreateDetailView(Args);
EditorSettingsWidget->SetObject(GenricGraphEditorSettings);
}
TSharedRef<SGraphEditor> FAssetEditor_GenericGraph::CreateViewportWidget()
{
FGraphAppearanceInfo AppearanceInfo;
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_GenericGraph", "Generic Graph");
CreateCommandList();
SGraphEditor::FGraphEditorEvents InEvents;
InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &FAssetEditor_GenericGraph::OnSelectedNodesChanged);
InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FAssetEditor_GenericGraph::OnNodeDoubleClicked);
return SNew(SGraphEditor)
.AdditionalCommands(GraphEditorCommands)
.IsEditable(true)
.Appearance(AppearanceInfo)
.GraphToEdit(EditingGraph->EdGraph)
.GraphEvents(InEvents)
.AutoExpandActionMenu(true)
.ShowGraphStateOverlay(false);
}
void FAssetEditor_GenericGraph::BindCommands()
{
ToolkitCommands->MapAction(FEditorCommands_GenericGraph::Get().GraphSettings,
FExecuteAction::CreateSP(this, &FAssetEditor_GenericGraph::GraphSettings),
FCanExecuteAction::CreateSP(this, &FAssetEditor_GenericGraph::CanGraphSettings)
);
ToolkitCommands->MapAction(FEditorCommands_GenericGraph::Get().AutoArrange,
FExecuteAction::CreateSP(this, &FAssetEditor_GenericGraph::AutoArrange),
FCanExecuteAction::CreateSP(this, &FAssetEditor_GenericGraph::CanAutoArrange)
);
}
void FAssetEditor_GenericGraph::CreateEdGraph()
{
if (EditingGraph->EdGraph == nullptr)
{
EditingGraph->EdGraph = CastChecked<UEdGraph_GenericGraph>(FBlueprintEditorUtils::CreateNewGraph(EditingGraph, NAME_None, UEdGraph_GenericGraph::StaticClass(), UAssetGraphSchema_GenericGraph::StaticClass()));
EditingGraph->EdGraph->bAllowDeletion = false;
// Give the schema a chance to fill out any required nodes (like the results node)
const UEdGraphSchema* Schema = EditingGraph->EdGraph->GetSchema();
Schema->CreateDefaultNodesForGraph(*EditingGraph->EdGraph);
}
}
void FAssetEditor_GenericGraph::CreateCommandList()
{
if (GraphEditorCommands.IsValid())
{
return;
}
GraphEditorCommands = MakeShareable(new FUICommandList);
// Can't use CreateSP here because derived editor are already implementing TSharedFromThis<FAssetEditorToolkit>
// however it should be safe, since commands are being used only within this editor
// if it ever crashes, this function will have to go away and be reimplemented in each derived class
GraphEditorCommands->MapAction(FEditorCommands_GenericGraph::Get().GraphSettings,
FExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::GraphSettings),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::CanGraphSettings));
GraphEditorCommands->MapAction(FEditorCommands_GenericGraph::Get().AutoArrange,
FExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::AutoArrange),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::CanAutoArrange));
GraphEditorCommands->MapAction(FGenericCommands::Get().SelectAll,
FExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::SelectAllNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::CanSelectAllNodes)
);
GraphEditorCommands->MapAction(FGenericCommands::Get().Delete,
FExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::DeleteSelectedNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::CanDeleteNodes)
);
GraphEditorCommands->MapAction(FGenericCommands::Get().Copy,
FExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::CopySelectedNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::CanCopyNodes)
);
GraphEditorCommands->MapAction(FGenericCommands::Get().Cut,
FExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::CutSelectedNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::CanCutNodes)
);
GraphEditorCommands->MapAction(FGenericCommands::Get().Paste,
FExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::PasteNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::CanPasteNodes)
);
GraphEditorCommands->MapAction(FGenericCommands::Get().Duplicate,
FExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::DuplicateNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_GenericGraph::CanDuplicateNodes)
);
GraphEditorCommands->MapAction(FGenericCommands::Get().Rename,
FExecuteAction::CreateSP(this, &FAssetEditor_GenericGraph::OnRenameNode),
FCanExecuteAction::CreateSP(this, &FAssetEditor_GenericGraph::CanRenameNodes)
);
}
TSharedPtr<SGraphEditor> FAssetEditor_GenericGraph::GetCurrGraphEditor() const
{
return ViewportWidget;
}
FGraphPanelSelectionSet FAssetEditor_GenericGraph::GetSelectedNodes() const
{
FGraphPanelSelectionSet CurrentSelection;
TSharedPtr<SGraphEditor> FocusedGraphEd = GetCurrGraphEditor();
if (FocusedGraphEd.IsValid())
{
CurrentSelection = FocusedGraphEd->GetSelectedNodes();
}
return CurrentSelection;
}
void FAssetEditor_GenericGraph::RebuildGenericGraph()
{
if (EditingGraph == nullptr)
{
LOG_WARNING(TEXT("FGenericGraphAssetEditor::RebuildGenericGraph EditingGraph is nullptr"));
return;
}
UEdGraph_GenericGraph* EdGraph = Cast<UEdGraph_GenericGraph>(EditingGraph->EdGraph);
check(EdGraph != nullptr);
EdGraph->RebuildGenericGraph();
}
void FAssetEditor_GenericGraph::SelectAllNodes()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
if (CurrentGraphEditor.IsValid())
{
CurrentGraphEditor->SelectAllNodes();
}
}
bool FAssetEditor_GenericGraph::CanSelectAllNodes()
{
return true;
}
void FAssetEditor_GenericGraph::DeleteSelectedNodes()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
if (!CurrentGraphEditor.IsValid())
{
return;
}
const FScopedTransaction Transaction(FGenericCommands::Get().Delete->GetDescription());
CurrentGraphEditor->GetCurrentGraph()->Modify();
const FGraphPanelSelectionSet SelectedNodes = CurrentGraphEditor->GetSelectedNodes();
CurrentGraphEditor->ClearSelectionSet();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UEdGraphNode* EdNode = Cast<UEdGraphNode>(*NodeIt);
if (EdNode == nullptr || !EdNode->CanUserDeleteNode())
continue;;
if (UEdNode_GenericGraphNode* EdNode_Node = Cast<UEdNode_GenericGraphNode>(EdNode))
{
EdNode_Node->Modify();
const UEdGraphSchema* Schema = EdNode_Node->GetSchema();
if (Schema != nullptr)
{
Schema->BreakNodeLinks(*EdNode_Node);
}
EdNode_Node->DestroyNode();
}
else
{
EdNode->Modify();
EdNode->DestroyNode();
}
}
}
bool FAssetEditor_GenericGraph::CanDeleteNodes()
{
// If any of the nodes can be deleted then we should allow deleting
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
if (Node != nullptr && Node->CanUserDeleteNode())
{
return true;
}
}
return false;
}
void FAssetEditor_GenericGraph::DeleteSelectedDuplicatableNodes()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
if (!CurrentGraphEditor.IsValid())
{
return;
}
const FGraphPanelSelectionSet OldSelectedNodes = CurrentGraphEditor->GetSelectedNodes();
CurrentGraphEditor->ClearSelectionSet();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
if (Node && Node->CanDuplicateNode())
{
CurrentGraphEditor->SetNodeSelection(Node, true);
}
}
// Delete the duplicatable nodes
DeleteSelectedNodes();
CurrentGraphEditor->ClearSelectionSet();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter)
{
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter))
{
CurrentGraphEditor->SetNodeSelection(Node, true);
}
}
}
void FAssetEditor_GenericGraph::CutSelectedNodes()
{
CopySelectedNodes();
DeleteSelectedDuplicatableNodes();
}
bool FAssetEditor_GenericGraph::CanCutNodes()
{
return CanCopyNodes() && CanDeleteNodes();
}
void FAssetEditor_GenericGraph::CopySelectedNodes()
{
// Export the selected nodes and place the text on the clipboard
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
FString ExportedText;
for (FGraphPanelSelectionSet::TIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
if (Node == nullptr)
{
SelectedIter.RemoveCurrent();
continue;
}
if (UEdNode_GenericGraphEdge* EdNode_Edge = Cast<UEdNode_GenericGraphEdge>(*SelectedIter))
{
UEdNode_GenericGraphNode* StartNode = EdNode_Edge->GetStartNode();
UEdNode_GenericGraphNode* EndNode = EdNode_Edge->GetEndNode();
if (!SelectedNodes.Contains(StartNode) || !SelectedNodes.Contains(EndNode))
{
SelectedIter.RemoveCurrent();
continue;
}
}
Node->PrepareForCopying();
}
FEdGraphUtilities::ExportNodesToText(SelectedNodes, ExportedText);
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
}
bool FAssetEditor_GenericGraph::CanCopyNodes()
{
// If any of the nodes can be duplicated then we should allow copying
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
if (Node && Node->CanDuplicateNode())
{
return true;
}
}
return false;
}
void FAssetEditor_GenericGraph::PasteNodes()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
if (CurrentGraphEditor.IsValid())
{
PasteNodesHere(CurrentGraphEditor->GetPasteLocation());
}
}
void FAssetEditor_GenericGraph::PasteNodesHere(const FVector2D& Location)
{
// Find the graph editor with focus
TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
if (!CurrentGraphEditor.IsValid())
{
return;
}
// Select the newly pasted stuff
UEdGraph* EdGraph = CurrentGraphEditor->GetCurrentGraph();
{
const FScopedTransaction Transaction(FGenericCommands::Get().Paste->GetDescription());
EdGraph->Modify();
// Clear the selection set (newly pasted stuff will be selected)
CurrentGraphEditor->ClearSelectionSet();
// Grab the text to paste from the clipboard.
FString TextToImport;
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
// Import the nodes
TSet<UEdGraphNode*> PastedNodes;
FEdGraphUtilities::ImportNodesFromText(EdGraph, TextToImport, PastedNodes);
//Average position of nodes so we can move them while still maintaining relative distances to each other
FVector2D AvgNodePosition(0.0f, 0.0f);
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
{
UEdGraphNode* Node = *It;
AvgNodePosition.X += Node->NodePosX;
AvgNodePosition.Y += Node->NodePosY;
}
float InvNumNodes = 1.0f / float(PastedNodes.Num());
AvgNodePosition.X *= InvNumNodes;
AvgNodePosition.Y *= InvNumNodes;
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
{
UEdGraphNode* Node = *It;
CurrentGraphEditor->SetNodeSelection(Node, true);
Node->NodePosX = (Node->NodePosX - AvgNodePosition.X) + Location.X;
Node->NodePosY = (Node->NodePosY - AvgNodePosition.Y) + Location.Y;
Node->SnapToGrid(16);
// Give new node a different Guid from the old one
Node->CreateNewGuid();
}
}
// Update UI
CurrentGraphEditor->NotifyGraphChanged();
UObject* GraphOwner = EdGraph->GetOuter();
if (GraphOwner)
{
GraphOwner->PostEditChange();
GraphOwner->MarkPackageDirty();
}
}
bool FAssetEditor_GenericGraph::CanPasteNodes()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
if (!CurrentGraphEditor.IsValid())
{
return false;
}
FString ClipboardContent;
FPlatformApplicationMisc::ClipboardPaste(ClipboardContent);
return FEdGraphUtilities::CanImportNodesFromText(CurrentGraphEditor->GetCurrentGraph(), ClipboardContent);
}
void FAssetEditor_GenericGraph::DuplicateNodes()
{
CopySelectedNodes();
PasteNodes();
}
bool FAssetEditor_GenericGraph::CanDuplicateNodes()
{
return CanCopyNodes();
}
void FAssetEditor_GenericGraph::GraphSettings()
{
PropertyWidget->SetObject(EditingGraph);
}
bool FAssetEditor_GenericGraph::CanGraphSettings() const
{
return true;
}
void FAssetEditor_GenericGraph::AutoArrange()
{
UEdGraph_GenericGraph* EdGraph = Cast<UEdGraph_GenericGraph>(EditingGraph->EdGraph);
check(EdGraph != nullptr);
const FScopedTransaction Transaction(LOCTEXT("GenericGraphEditorAutoArrange", "Generic Graph Editor: Auto Arrange"));
EdGraph->Modify();
UAutoLayoutStrategy* LayoutStrategy = nullptr;
switch (GenricGraphEditorSettings->AutoLayoutStrategy)
{
case EAutoLayoutStrategy::Tree:
LayoutStrategy = NewObject<UAutoLayoutStrategy>(EdGraph, UTreeLayoutStrategy::StaticClass());
break;
case EAutoLayoutStrategy::ForceDirected:
LayoutStrategy = NewObject<UAutoLayoutStrategy>(EdGraph, UForceDirectedLayoutStrategy::StaticClass());
break;
default:
break;
}
if (LayoutStrategy != nullptr)
{
LayoutStrategy->Settings = GenricGraphEditorSettings;
LayoutStrategy->Layout(EdGraph);
LayoutStrategy->ConditionalBeginDestroy();
}
else
{
LOG_ERROR(TEXT("FAssetEditor_GenericGraph::AutoArrange LayoutStrategy is null."));
}
}
bool FAssetEditor_GenericGraph::CanAutoArrange() const
{
return EditingGraph != nullptr && Cast<UEdGraph_GenericGraph>(EditingGraph->EdGraph) != nullptr;
}
void FAssetEditor_GenericGraph::OnRenameNode()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
if (CurrentGraphEditor.IsValid())
{
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(*NodeIt);
if (SelectedNode != NULL && SelectedNode->bCanRenameNode)
{
CurrentGraphEditor->IsNodeTitleVisible(SelectedNode, true);
break;
}
}
}
}
bool FAssetEditor_GenericGraph::CanRenameNodes() const
{
UEdGraph_GenericGraph* EdGraph = Cast<UEdGraph_GenericGraph>(EditingGraph->EdGraph);
check(EdGraph != nullptr);
UGenericGraph* Graph = EdGraph->GetGenericGraph();
check(Graph != nullptr)
return Graph->bCanRenameNode && GetSelectedNodes().Num() == 1;
}
void FAssetEditor_GenericGraph::OnSelectedNodesChanged(const TSet<class UObject*>& NewSelection)
{
TArray<UObject*> Selection;
for (UObject* SelectionEntry : NewSelection)
{
Selection.Add(SelectionEntry);
}
if (Selection.Num() == 0)
{
PropertyWidget->SetObject(EditingGraph);
}
else
{
PropertyWidget->SetObjects(Selection);
}
}
void FAssetEditor_GenericGraph::OnNodeDoubleClicked(UEdGraphNode* Node)
{
}
void FAssetEditor_GenericGraph::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent)
{
if (EditingGraph == nullptr)
return;
EditingGraph->EdGraph->GetSchema()->ForceVisualizationCacheClear();
}
#if ENGINE_MAJOR_VERSION < 5
void FAssetEditor_GenericGraph::OnPackageSaved(const FString& PackageFileName, UObject* Outer)
{
RebuildGenericGraph();
}
#else // #if ENGINE_MAJOR_VERSION < 5
void FAssetEditor_GenericGraph::OnPackageSavedWithContext(const FString& PackageFileName, UPackage* Package, FObjectPostSaveContext ObjectSaveContext)
{
RebuildGenericGraph();
}
#endif // #else // #if ENGINE_MAJOR_VERSION < 5
void FAssetEditor_GenericGraph::RegisterToolbarTab(const TSharedRef<class FTabManager>& InTabManager)
{
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,476 @@
#include "GenericGraphAssetEditor/AssetGraphSchema_GenericGraph.h"
#include "ToolMenus.h"
#include "GenericGraphEditorPCH.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphEdge.h"
#include "GenericGraphAssetEditor/ConnectionDrawingPolicy_GenericGraph.h"
#include "GraphEditorActions.h"
#include "Framework/Commands/GenericCommands.h"
#include "AutoLayout/ForceDirectedLayoutStrategy.h"
#include "AutoLayout/TreeLayoutStrategy.h"
#define LOCTEXT_NAMESPACE "AssetSchema_GenericGraph"
int32 UAssetGraphSchema_GenericGraph::CurrentCacheRefreshID = 0;
class FNodeVisitorCycleChecker
{
public:
/** Check whether a loop in the graph would be caused by linking the passed-in nodes */
bool CheckForLoop(UEdGraphNode* StartNode, UEdGraphNode* EndNode)
{
VisitedNodes.Add(StartNode);
return TraverseNodes(EndNode);
}
private:
bool TraverseNodes(UEdGraphNode* Node)
{
VisitedNodes.Add(Node);
for (auto MyPin : Node->Pins)
{
if (MyPin->Direction == EGPD_Output)
{
for (auto OtherPin : MyPin->LinkedTo)
{
UEdGraphNode* OtherNode = OtherPin->GetOwningNode();
if (VisitedNodes.Contains(OtherNode))
{
// Only an issue if this is a back-edge
return false;
}
else if (!FinishedNodes.Contains(OtherNode))
{
// Only should traverse if this node hasn't been traversed
if (!TraverseNodes(OtherNode))
return false;
}
}
}
}
VisitedNodes.Remove(Node);
FinishedNodes.Add(Node);
return true;
};
TSet<UEdGraphNode*> VisitedNodes;
TSet<UEdGraphNode*> FinishedNodes;
};
UEdGraphNode* FAssetSchemaAction_GenericGraph_NewNode::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode /*= true*/)
{
UEdGraphNode* ResultNode = nullptr;
if (NodeTemplate != nullptr)
{
const FScopedTransaction Transaction(LOCTEXT("GenericGraphEditorNewNode", "Generic Graph Editor: New Node"));
ParentGraph->Modify();
if (FromPin != nullptr)
FromPin->Modify();
NodeTemplate->Rename(nullptr, ParentGraph);
ParentGraph->AddNode(NodeTemplate, true, bSelectNewNode);
NodeTemplate->CreateNewGuid();
NodeTemplate->PostPlacedNewNode();
NodeTemplate->AllocateDefaultPins();
NodeTemplate->AutowireNewNode(FromPin);
NodeTemplate->NodePosX = Location.X;
NodeTemplate->NodePosY = Location.Y;
NodeTemplate->GenericGraphNode->SetFlags(RF_Transactional);
NodeTemplate->SetFlags(RF_Transactional);
ResultNode = NodeTemplate;
}
return ResultNode;
}
void FAssetSchemaAction_GenericGraph_NewNode::AddReferencedObjects(FReferenceCollector& Collector)
{
FEdGraphSchemaAction::AddReferencedObjects(Collector);
Collector.AddReferencedObject(NodeTemplate);
}
UEdGraphNode* FAssetSchemaAction_GenericGraph_NewEdge::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode /*= true*/)
{
UEdGraphNode* ResultNode = nullptr;
if (NodeTemplate != nullptr)
{
const FScopedTransaction Transaction(LOCTEXT("GenericGraphEditorNewEdge", "Generic Graph Editor: New Edge"));
ParentGraph->Modify();
if (FromPin != nullptr)
FromPin->Modify();
NodeTemplate->Rename(nullptr, ParentGraph);
ParentGraph->AddNode(NodeTemplate, true, bSelectNewNode);
NodeTemplate->CreateNewGuid();
NodeTemplate->PostPlacedNewNode();
NodeTemplate->AllocateDefaultPins();
NodeTemplate->AutowireNewNode(FromPin);
NodeTemplate->NodePosX = Location.X;
NodeTemplate->NodePosY = Location.Y;
NodeTemplate->GenericGraphEdge->SetFlags(RF_Transactional);
NodeTemplate->SetFlags(RF_Transactional);
ResultNode = NodeTemplate;
}
return ResultNode;
}
void FAssetSchemaAction_GenericGraph_NewEdge::AddReferencedObjects(FReferenceCollector& Collector)
{
FEdGraphSchemaAction::AddReferencedObjects(Collector);
Collector.AddReferencedObject(NodeTemplate);
}
void UAssetGraphSchema_GenericGraph::GetBreakLinkToSubMenuActions(UToolMenu* Menu, UEdGraphPin* InGraphPin)
{
// Make sure we have a unique name for every entry in the list
TMap< FString, uint32 > LinkTitleCount;
FToolMenuSection& Section = Menu->FindOrAddSection("GenericGraphAssetGraphSchemaPinActions");
// Add all the links we could break from
for (TArray<class UEdGraphPin*>::TConstIterator Links(InGraphPin->LinkedTo); Links; ++Links)
{
UEdGraphPin* Pin = *Links;
FString TitleString = Pin->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView).ToString();
FText Title = FText::FromString(TitleString);
if (Pin->PinName != TEXT(""))
{
TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *Pin->PinName.ToString());
// Add name of connection if possible
FFormatNamedArguments Args;
Args.Add(TEXT("NodeTitle"), Title);
Args.Add(TEXT("PinName"), Pin->GetDisplayName());
Title = FText::Format(LOCTEXT("BreakDescPin", "{NodeTitle} ({PinName})"), Args);
}
uint32& Count = LinkTitleCount.FindOrAdd(TitleString);
FText Description;
FFormatNamedArguments Args;
Args.Add(TEXT("NodeTitle"), Title);
Args.Add(TEXT("NumberOfNodes"), Count);
if (Count == 0)
{
Description = FText::Format(LOCTEXT("BreakDesc", "Break link to {NodeTitle}"), Args);
}
else
{
Description = FText::Format(LOCTEXT("BreakDescMulti", "Break link to {NodeTitle} ({NumberOfNodes})"), Args);
}
++Count;
Section.AddMenuEntry(NAME_None, Description, Description, FSlateIcon(), FUIAction(
FExecuteAction::CreateUObject(this, &UAssetGraphSchema_GenericGraph::BreakSinglePinLink, const_cast<UEdGraphPin*>(InGraphPin), *Links)));
}
}
EGraphType UAssetGraphSchema_GenericGraph::GetGraphType(const UEdGraph* TestEdGraph) const
{
return GT_StateMachine;
}
void UAssetGraphSchema_GenericGraph::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const
{
UGenericGraph* Graph = CastChecked<UGenericGraph>(ContextMenuBuilder.CurrentGraph->GetOuter());
if (Graph->NodeType == nullptr)
{
return;
}
const bool bNoParent = (ContextMenuBuilder.FromPin == NULL);
const FText AddToolTip = LOCTEXT("NewGenericGraphNodeTooltip", "Add node here");
TSet<TSubclassOf<UGenericGraphNode> > Visited;
FText Desc = Graph->NodeType.GetDefaultObject()->ContextMenuName;
if (Desc.IsEmpty())
{
FString Title = Graph->NodeType->GetName();
Title.RemoveFromEnd("_C");
Desc = FText::FromString(Title);
}
if (!Graph->NodeType->HasAnyClassFlags(CLASS_Abstract))
{
TSharedPtr<FAssetSchemaAction_GenericGraph_NewNode> NewNodeAction(new FAssetSchemaAction_GenericGraph_NewNode(LOCTEXT("GenericGraphNodeAction", "Generic Graph Node"), Desc, AddToolTip, 0));
NewNodeAction->NodeTemplate = NewObject<UEdNode_GenericGraphNode>(ContextMenuBuilder.OwnerOfTemporaries);
NewNodeAction->NodeTemplate->GenericGraphNode = NewObject<UGenericGraphNode>(NewNodeAction->NodeTemplate, Graph->NodeType);
NewNodeAction->NodeTemplate->GenericGraphNode->Graph = Graph;
ContextMenuBuilder.AddAction(NewNodeAction);
Visited.Add(Graph->NodeType);
}
for (TObjectIterator<UClass> It; It; ++It)
{
if (It->IsChildOf(Graph->NodeType) && !It->HasAnyClassFlags(CLASS_Abstract) && !Visited.Contains(*It))
{
TSubclassOf<UGenericGraphNode> NodeType = *It;
if (It->GetName().StartsWith("REINST") || It->GetName().StartsWith("SKEL"))
continue;
if (!Graph->GetClass()->IsChildOf(NodeType.GetDefaultObject()->CompatibleGraphType))
continue;
Desc = NodeType.GetDefaultObject()->ContextMenuName;
if (Desc.IsEmpty())
{
FString Title = NodeType->GetName();
Title.RemoveFromEnd("_C");
Desc = FText::FromString(Title);
}
TSharedPtr<FAssetSchemaAction_GenericGraph_NewNode> Action(new FAssetSchemaAction_GenericGraph_NewNode(LOCTEXT("GenericGraphNodeAction", "Generic Graph Node"), Desc, AddToolTip, 0));
Action->NodeTemplate = NewObject<UEdNode_GenericGraphNode>(ContextMenuBuilder.OwnerOfTemporaries);
Action->NodeTemplate->GenericGraphNode = NewObject<UGenericGraphNode>(Action->NodeTemplate, NodeType);
Action->NodeTemplate->GenericGraphNode->Graph = Graph;
ContextMenuBuilder.AddAction(Action);
Visited.Add(NodeType);
}
}
}
void UAssetGraphSchema_GenericGraph::GetContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
{
if (Context->Pin)
{
{
FToolMenuSection& Section = Menu->AddSection("GenericGraphAssetGraphSchemaNodeActions", LOCTEXT("PinActionsMenuHeader", "Pin Actions"));
// Only display the 'Break Links' option if there is a link to break!
if (Context->Pin->LinkedTo.Num() > 0)
{
Section.AddMenuEntry(FGraphEditorCommands::Get().BreakPinLinks);
// add sub menu for break link to
if (Context->Pin->LinkedTo.Num() > 1)
{
Section.AddSubMenu(
"BreakLinkTo",
LOCTEXT("BreakLinkTo", "Break Link To..."),
LOCTEXT("BreakSpecificLinks", "Break a specific link..."),
FNewToolMenuDelegate::CreateUObject((UAssetGraphSchema_GenericGraph* const)this, &UAssetGraphSchema_GenericGraph::GetBreakLinkToSubMenuActions, const_cast<UEdGraphPin*>(Context->Pin)));
}
else
{
((UAssetGraphSchema_GenericGraph* const)this)->GetBreakLinkToSubMenuActions(Menu, const_cast<UEdGraphPin*>(Context->Pin));
}
}
}
}
else if (Context->Node)
{
{
FToolMenuSection& Section = Menu->AddSection("GenericGraphAssetGraphSchemaNodeActions", LOCTEXT("ClassActionsMenuHeader", "Node Actions"));
Section.AddMenuEntry(FGenericCommands::Get().Delete);
Section.AddMenuEntry(FGenericCommands::Get().Cut);
Section.AddMenuEntry(FGenericCommands::Get().Copy);
Section.AddMenuEntry(FGenericCommands::Get().Duplicate);
Section.AddMenuEntry(FGraphEditorCommands::Get().BreakNodeLinks);
}
}
Super::GetContextMenuActions(Menu, Context);
}
const FPinConnectionResponse UAssetGraphSchema_GenericGraph::CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const
{
// Make sure the pins are not on the same node
if (A->GetOwningNode() == B->GetOwningNode())
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorSameNode", "Can't connect node to itself"));
}
const UEdGraphPin *Out = A;
const UEdGraphPin *In = B;
UEdNode_GenericGraphNode* EdNode_Out = Cast<UEdNode_GenericGraphNode>(Out->GetOwningNode());
UEdNode_GenericGraphNode* EdNode_In = Cast<UEdNode_GenericGraphNode>(In->GetOwningNode());
if (EdNode_Out == nullptr || EdNode_In == nullptr)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinError", "Not a valid UGenericGraphEdNode"));
}
//Determine if we can have cycles or not
bool bAllowCycles = false;
auto EdGraph = Cast<UEdGraph_GenericGraph>(Out->GetOwningNode()->GetGraph());
if (EdGraph != nullptr)
{
bAllowCycles = EdGraph->GetGenericGraph()->bCanBeCyclical;
}
// check for cycles
FNodeVisitorCycleChecker CycleChecker;
if (!bAllowCycles && !CycleChecker.CheckForLoop(Out->GetOwningNode(), In->GetOwningNode()))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorCycle", "Can't create a graph cycle"));
}
FText ErrorMessage;
if (!EdNode_Out->GenericGraphNode->CanCreateConnectionTo(EdNode_In->GenericGraphNode, EdNode_Out->GetOutputPin()->LinkedTo.Num(), ErrorMessage))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, ErrorMessage);
}
if (!EdNode_In->GenericGraphNode->CanCreateConnectionFrom(EdNode_Out->GenericGraphNode, EdNode_In->GetInputPin()->LinkedTo.Num(), ErrorMessage))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, ErrorMessage);
}
if (EdNode_Out->GenericGraphNode->GetGraph()->bEdgeEnabled)
{
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, LOCTEXT("PinConnect", "Connect nodes with edge"));
}
else
{
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, LOCTEXT("PinConnect", "Connect nodes"));
}
}
bool UAssetGraphSchema_GenericGraph::TryCreateConnection(UEdGraphPin* A, UEdGraphPin* B) const
{
// We don't actually care about the pin, we want the node that is being dragged between
UEdNode_GenericGraphNode* NodeA = Cast<UEdNode_GenericGraphNode>(A->GetOwningNode());
UEdNode_GenericGraphNode* NodeB = Cast<UEdNode_GenericGraphNode>(B->GetOwningNode());
// Check that this edge doesn't already exist
for (UEdGraphPin *TestPin : NodeA->GetOutputPin()->LinkedTo)
{
UEdGraphNode* ChildNode = TestPin->GetOwningNode();
if (UEdNode_GenericGraphEdge* EdNode_Edge = Cast<UEdNode_GenericGraphEdge>(ChildNode))
{
ChildNode = EdNode_Edge->GetEndNode();
}
if (ChildNode == NodeB)
return false;
}
if (NodeA && NodeB)
{
// Always create connections from node A to B, don't allow adding in reverse
Super::TryCreateConnection(NodeA->GetOutputPin(), NodeB->GetInputPin());
return true;
}
else
{
return false;
}
}
bool UAssetGraphSchema_GenericGraph::CreateAutomaticConversionNodeAndConnections(UEdGraphPin* A, UEdGraphPin* B) const
{
UEdNode_GenericGraphNode* NodeA = Cast<UEdNode_GenericGraphNode>(A->GetOwningNode());
UEdNode_GenericGraphNode* NodeB = Cast<UEdNode_GenericGraphNode>(B->GetOwningNode());
// Are nodes and pins all valid?
if (!NodeA || !NodeA->GetOutputPin() || !NodeB || !NodeB->GetInputPin())
return false;
UGenericGraph* Graph = NodeA->GenericGraphNode->GetGraph();
FVector2D InitPos((NodeA->NodePosX + NodeB->NodePosX) / 2, (NodeA->NodePosY + NodeB->NodePosY) / 2);
FAssetSchemaAction_GenericGraph_NewEdge Action;
Action.NodeTemplate = NewObject<UEdNode_GenericGraphEdge>(NodeA->GetGraph());
Action.NodeTemplate->SetEdge(NewObject<UGenericGraphEdge>(Action.NodeTemplate, Graph->EdgeType));
UEdNode_GenericGraphEdge* EdgeNode = Cast<UEdNode_GenericGraphEdge>(Action.PerformAction(NodeA->GetGraph(), nullptr, InitPos, false));
// Always create connections from node A to B, don't allow adding in reverse
EdgeNode->CreateConnections(NodeA, NodeB);
return true;
}
class FConnectionDrawingPolicy* UAssetGraphSchema_GenericGraph::CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const
{
return new FConnectionDrawingPolicy_GenericGraph(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements, InGraphObj);
}
FLinearColor UAssetGraphSchema_GenericGraph::GetPinTypeColor(const FEdGraphPinType& PinType) const
{
return FColor::White;
}
void UAssetGraphSchema_GenericGraph::BreakNodeLinks(UEdGraphNode& TargetNode) const
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakNodeLinks", "Break Node Links"));
Super::BreakNodeLinks(TargetNode);
}
void UAssetGraphSchema_GenericGraph::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakPinLinks", "Break Pin Links"));
Super::BreakPinLinks(TargetPin, bSendsNodeNotifcation);
}
void UAssetGraphSchema_GenericGraph::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakSinglePinLink", "Break Pin Link"));
Super::BreakSinglePinLink(SourcePin, TargetPin);
}
UEdGraphPin* UAssetGraphSchema_GenericGraph::DropPinOnNode(UEdGraphNode* InTargetNode, const FName& InSourcePinName, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection) const
{
UEdNode_GenericGraphNode* EdNode = Cast<UEdNode_GenericGraphNode>(InTargetNode);
switch (InSourcePinDirection)
{
case EGPD_Input:
return EdNode->GetOutputPin();
case EGPD_Output:
return EdNode->GetInputPin();
default:
return nullptr;
}
}
bool UAssetGraphSchema_GenericGraph::SupportsDropPinOnNode(UEdGraphNode* InTargetNode, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection, FText& OutErrorMessage) const
{
return Cast<UEdNode_GenericGraphNode>(InTargetNode) != nullptr;
}
bool UAssetGraphSchema_GenericGraph::IsCacheVisualizationOutOfDate(int32 InVisualizationCacheID) const
{
return CurrentCacheRefreshID != InVisualizationCacheID;
}
int32 UAssetGraphSchema_GenericGraph::GetCurrentVisualizationCacheID() const
{
return CurrentCacheRefreshID;
}
void UAssetGraphSchema_GenericGraph::ForceVisualizationCacheClear() const
{
++CurrentCacheRefreshID;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,149 @@
#include "GenericGraphAssetEditor/ConnectionDrawingPolicy_GenericGraph.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphEdge.h"
FConnectionDrawingPolicy_GenericGraph::FConnectionDrawingPolicy_GenericGraph(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj)
: FConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements)
, GraphObj(InGraphObj)
{
}
void FConnectionDrawingPolicy_GenericGraph::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params)
{
Params.AssociatedPin1 = OutputPin;
Params.AssociatedPin2 = InputPin;
Params.WireThickness = 1.5f;
const bool bDeemphasizeUnhoveredPins = HoveredPins.Num() > 0;
if (bDeemphasizeUnhoveredPins)
{
ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Params.WireThickness, /*inout*/ Params.WireColor);
}
}
void FConnectionDrawingPolicy_GenericGraph::Draw(TMap<TSharedRef<SWidget>, FArrangedWidget>& InPinGeometries, FArrangedChildren& ArrangedNodes)
{
// Build an acceleration structure to quickly find geometry for the nodes
NodeWidgetMap.Empty();
for (int32 NodeIndex = 0; NodeIndex < ArrangedNodes.Num(); ++NodeIndex)
{
FArrangedWidget& CurWidget = ArrangedNodes[NodeIndex];
TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(CurWidget.Widget);
NodeWidgetMap.Add(ChildNode->GetNodeObj(), NodeIndex);
}
// Now draw
FConnectionDrawingPolicy::Draw(InPinGeometries, ArrangedNodes);
}
void FConnectionDrawingPolicy_GenericGraph::DrawPreviewConnector(const FGeometry& PinGeometry, const FVector2D& StartPoint, const FVector2D& EndPoint, UEdGraphPin* Pin)
{
FConnectionParams Params;
DetermineWiringStyle(Pin, nullptr, /*inout*/ Params);
if (Pin->Direction == EEdGraphPinDirection::EGPD_Output)
{
DrawSplineWithArrow(FGeometryHelper::FindClosestPointOnGeom(PinGeometry, EndPoint), EndPoint, Params);
}
else
{
DrawSplineWithArrow(FGeometryHelper::FindClosestPointOnGeom(PinGeometry, StartPoint), StartPoint, Params);
}
}
void FConnectionDrawingPolicy_GenericGraph::DrawSplineWithArrow(const FVector2D& StartAnchorPoint, const FVector2D& EndAnchorPoint, const FConnectionParams& Params)
{
// bUserFlag1 indicates that we need to reverse the direction of connection (used by debugger)
const FVector2D& P0 = Params.bUserFlag1 ? EndAnchorPoint : StartAnchorPoint;
const FVector2D& P1 = Params.bUserFlag1 ? StartAnchorPoint : EndAnchorPoint;
Internal_DrawLineWithArrow(P0, P1, Params);
}
void FConnectionDrawingPolicy_GenericGraph::Internal_DrawLineWithArrow(const FVector2D& StartAnchorPoint, const FVector2D& EndAnchorPoint, const FConnectionParams& Params)
{
//@TODO: Should this be scaled by zoom factor?
const float LineSeparationAmount = 4.5f;
const FVector2D DeltaPos = EndAnchorPoint - StartAnchorPoint;
const FVector2D UnitDelta = DeltaPos.GetSafeNormal();
const FVector2D Normal = FVector2D(DeltaPos.Y, -DeltaPos.X).GetSafeNormal();
// Come up with the final start/end points
const FVector2D DirectionBias = Normal * LineSeparationAmount;
const FVector2D LengthBias = ArrowRadius.X * UnitDelta;
const FVector2D StartPoint = StartAnchorPoint + DirectionBias + LengthBias;
const FVector2D EndPoint = EndAnchorPoint + DirectionBias - LengthBias;
// Draw a line/spline
DrawConnection(WireLayerID, StartPoint, EndPoint, Params);
// Draw the arrow
const FVector2D ArrowDrawPos = EndPoint - ArrowRadius;
const float AngleInRadians = FMath::Atan2(DeltaPos.Y, DeltaPos.X);
FSlateDrawElement::MakeRotatedBox(
DrawElementsList,
ArrowLayerID,
FPaintGeometry(ArrowDrawPos, ArrowImage->ImageSize * ZoomFactor, ZoomFactor),
ArrowImage,
ESlateDrawEffect::None,
AngleInRadians,
TOptional<FVector2D>(),
FSlateDrawElement::RelativeToElement,
Params.WireColor
);
}
void FConnectionDrawingPolicy_GenericGraph::DrawSplineWithArrow(const FGeometry& StartGeom, const FGeometry& EndGeom, const FConnectionParams& Params)
{
// Get a reasonable seed point (halfway between the boxes)
const FVector2D StartCenter = FGeometryHelper::CenterOf(StartGeom);
const FVector2D EndCenter = FGeometryHelper::CenterOf(EndGeom);
const FVector2D SeedPoint = (StartCenter + EndCenter) * 0.5f;
// Find the (approximate) closest points between the two boxes
const FVector2D StartAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(StartGeom, SeedPoint);
const FVector2D EndAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(EndGeom, SeedPoint);
DrawSplineWithArrow(StartAnchorPoint, EndAnchorPoint, Params);
}
FVector2D FConnectionDrawingPolicy_GenericGraph::ComputeSplineTangent(const FVector2D& Start, const FVector2D& End) const
{
const FVector2D Delta = End - Start;
const FVector2D NormDelta = Delta.GetSafeNormal();
return NormDelta;
}
void FConnectionDrawingPolicy_GenericGraph::DetermineLinkGeometry(FArrangedChildren& ArrangedNodes, TSharedRef<SWidget>& OutputPinWidget,
UEdGraphPin* OutputPin, UEdGraphPin* InputPin, FArrangedWidget*& StartWidgetGeometry, FArrangedWidget*& EndWidgetGeometry)
{
if (UEdNode_GenericGraphEdge* EdgeNode = Cast<UEdNode_GenericGraphEdge>(InputPin->GetOwningNode()))
{
UEdNode_GenericGraphNode* Start = EdgeNode->GetStartNode();
UEdNode_GenericGraphNode* End = EdgeNode->GetEndNode();
if (Start != nullptr && End != nullptr)
{
int32* StartNodeIndex = NodeWidgetMap.Find(Start);
int32* EndNodeIndex = NodeWidgetMap.Find(End);
if (StartNodeIndex != nullptr && EndNodeIndex != nullptr)
{
StartWidgetGeometry = &(ArrangedNodes[*StartNodeIndex]);
EndWidgetGeometry = &(ArrangedNodes[*EndNodeIndex]);
}
}
}
else
{
StartWidgetGeometry = PinGeometries->Find(OutputPinWidget);
if (TSharedPtr<SGraphPin>* pTargetWidget = PinToPinWidgetMap.Find(InputPin))
{
TSharedRef<SGraphPin> InputWidget = (*pTargetWidget).ToSharedRef();
EndWidgetGeometry = PinGeometries->Find(InputWidget);
}
}
}

View File

@ -0,0 +1,205 @@
#include "GenericGraphAssetEditor/EdGraph_GenericGraph.h"
#include "GenericGraphEditorPCH.h"
#include "GenericGraph.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphEdge.h"
UEdGraph_GenericGraph::UEdGraph_GenericGraph()
{
}
UEdGraph_GenericGraph::~UEdGraph_GenericGraph()
{
}
void UEdGraph_GenericGraph::RebuildGenericGraph()
{
LOG_INFO(TEXT("UGenericGraphEdGraph::RebuildGenericGraph has been called"));
UGenericGraph* Graph = GetGenericGraph();
Clear();
for (int i = 0; i < Nodes.Num(); ++i)
{
if (UEdNode_GenericGraphNode* EdNode = Cast<UEdNode_GenericGraphNode>(Nodes[i]))
{
if (EdNode->GenericGraphNode == nullptr)
continue;
UGenericGraphNode* GenericGraphNode = EdNode->GenericGraphNode;
NodeMap.Add(GenericGraphNode, EdNode);
Graph->AllNodes.Add(GenericGraphNode);
for (int PinIdx = 0; PinIdx < EdNode->Pins.Num(); ++PinIdx)
{
UEdGraphPin* Pin = EdNode->Pins[PinIdx];
if (Pin->Direction != EEdGraphPinDirection::EGPD_Output)
continue;
for (int LinkToIdx = 0; LinkToIdx < Pin->LinkedTo.Num(); ++LinkToIdx)
{
UGenericGraphNode* ChildNode = nullptr;
if (UEdNode_GenericGraphNode* EdNode_Child = Cast<UEdNode_GenericGraphNode>(Pin->LinkedTo[LinkToIdx]->GetOwningNode()))
{
ChildNode = EdNode_Child->GenericGraphNode;
}
else if (UEdNode_GenericGraphEdge* EdNode_Edge = Cast<UEdNode_GenericGraphEdge>(Pin->LinkedTo[LinkToIdx]->GetOwningNode()))
{
UEdNode_GenericGraphNode* Child = EdNode_Edge->GetEndNode();;
if (Child != nullptr)
{
ChildNode = Child->GenericGraphNode;
}
}
if (ChildNode != nullptr)
{
GenericGraphNode->ChildrenNodes.Add(ChildNode);
ChildNode->ParentNodes.Add(GenericGraphNode);
}
else
{
LOG_ERROR(TEXT("UEdGraph_GenericGraph::RebuildGenericGraph can't find child node"));
}
}
}
}
else if (UEdNode_GenericGraphEdge* EdgeNode = Cast<UEdNode_GenericGraphEdge>(Nodes[i]))
{
UEdNode_GenericGraphNode* StartNode = EdgeNode->GetStartNode();
UEdNode_GenericGraphNode* EndNode = EdgeNode->GetEndNode();
UGenericGraphEdge* Edge = EdgeNode->GenericGraphEdge;
if (StartNode == nullptr || EndNode == nullptr || Edge == nullptr)
{
LOG_ERROR(TEXT("UEdGraph_GenericGraph::RebuildGenericGraph add edge failed."));
continue;
}
EdgeMap.Add(Edge, EdgeNode);
Edge->Graph = Graph;
Edge->Rename(nullptr, Graph, REN_DontCreateRedirectors | REN_DoNotDirty);
Edge->StartNode = StartNode->GenericGraphNode;
Edge->EndNode = EndNode->GenericGraphNode;
Edge->StartNode->Edges.Add(Edge->EndNode, Edge);
}
}
for (int i = 0; i < Graph->AllNodes.Num(); ++i)
{
UGenericGraphNode* Node = Graph->AllNodes[i];
if (Node->ParentNodes.Num() == 0)
{
Graph->RootNodes.Add(Node);
SortNodes(Node);
}
Node->Graph = Graph;
Node->Rename(nullptr, Graph, REN_DontCreateRedirectors | REN_DoNotDirty);
}
Graph->RootNodes.Sort([&](const UGenericGraphNode& L, const UGenericGraphNode& R)
{
UEdNode_GenericGraphNode* EdNode_LNode = NodeMap[&L];
UEdNode_GenericGraphNode* EdNode_RNode = NodeMap[&R];
return EdNode_LNode->NodePosX < EdNode_RNode->NodePosX;
});
}
UGenericGraph* UEdGraph_GenericGraph::GetGenericGraph() const
{
return CastChecked<UGenericGraph>(GetOuter());
}
bool UEdGraph_GenericGraph::Modify(bool bAlwaysMarkDirty /*= true*/)
{
bool Rtn = Super::Modify(bAlwaysMarkDirty);
GetGenericGraph()->Modify();
for (int32 i = 0; i < Nodes.Num(); ++i)
{
Nodes[i]->Modify();
}
return Rtn;
}
void UEdGraph_GenericGraph::Clear()
{
UGenericGraph* Graph = GetGenericGraph();
Graph->ClearGraph();
NodeMap.Reset();
EdgeMap.Reset();
for (int i = 0; i < Nodes.Num(); ++i)
{
if (UEdNode_GenericGraphNode* EdNode = Cast<UEdNode_GenericGraphNode>(Nodes[i]))
{
UGenericGraphNode* GenericGraphNode = EdNode->GenericGraphNode;
if (GenericGraphNode)
{
GenericGraphNode->ParentNodes.Reset();
GenericGraphNode->ChildrenNodes.Reset();
GenericGraphNode->Edges.Reset();
}
}
}
}
void UEdGraph_GenericGraph::SortNodes(UGenericGraphNode* RootNode)
{
int Level = 0;
TArray<UGenericGraphNode*> CurrLevelNodes = { RootNode };
TArray<UGenericGraphNode*> NextLevelNodes;
TSet<UGenericGraphNode*> Visited;
while (CurrLevelNodes.Num() != 0)
{
int32 LevelWidth = 0;
for (int i = 0; i < CurrLevelNodes.Num(); ++i)
{
UGenericGraphNode* Node = CurrLevelNodes[i];
Visited.Add(Node);
auto Comp = [&](const UGenericGraphNode& L, const UGenericGraphNode& R)
{
UEdNode_GenericGraphNode* EdNode_LNode = NodeMap[&L];
UEdNode_GenericGraphNode* EdNode_RNode = NodeMap[&R];
return EdNode_LNode->NodePosX < EdNode_RNode->NodePosX;
};
Node->ChildrenNodes.Sort(Comp);
Node->ParentNodes.Sort(Comp);
for (int j = 0; j < Node->ChildrenNodes.Num(); ++j)
{
UGenericGraphNode* ChildNode = Node->ChildrenNodes[j];
if(!Visited.Contains(ChildNode))
NextLevelNodes.Add(Node->ChildrenNodes[j]);
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
++Level;
}
}
void UEdGraph_GenericGraph::PostEditUndo()
{
Super::PostEditUndo();
NotifyGraphChanged();
}

View File

@ -0,0 +1,97 @@
#include "GenericGraphAssetEditor/EdNode_GenericGraphEdge.h"
#include "GenericGraphEdge.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#define LOCTEXT_NAMESPACE "EdNode_GenericGraphEdge"
UEdNode_GenericGraphEdge::UEdNode_GenericGraphEdge()
{
bCanRenameNode = true;
}
void UEdNode_GenericGraphEdge::SetEdge(UGenericGraphEdge* Edge)
{
GenericGraphEdge = Edge;
}
void UEdNode_GenericGraphEdge::AllocateDefaultPins()
{
UEdGraphPin* Inputs = CreatePin(EGPD_Input, TEXT("Edge"), FName(), TEXT("In"));
Inputs->bHidden = true;
UEdGraphPin* Outputs = CreatePin(EGPD_Output, TEXT("Edge"), FName(), TEXT("Out"));
Outputs->bHidden = true;
}
FText UEdNode_GenericGraphEdge::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if (GenericGraphEdge)
{
return GenericGraphEdge->GetNodeTitle();
}
return FText();
}
void UEdNode_GenericGraphEdge::PinConnectionListChanged(UEdGraphPin* Pin)
{
if (Pin->LinkedTo.Num() == 0)
{
// Commit suicide; transitions must always have an input and output connection
Modify();
// Our parent graph will have our graph in SubGraphs so needs to be modified to record that.
if (UEdGraph* ParentGraph = GetGraph())
{
ParentGraph->Modify();
}
DestroyNode();
}
}
void UEdNode_GenericGraphEdge::PrepareForCopying()
{
GenericGraphEdge->Rename(nullptr, this, REN_DontCreateRedirectors | REN_DoNotDirty);
}
void UEdNode_GenericGraphEdge::CreateConnections(UEdNode_GenericGraphNode* Start, UEdNode_GenericGraphNode* End)
{
Pins[0]->Modify();
Pins[0]->LinkedTo.Empty();
Start->GetOutputPin()->Modify();
Pins[0]->MakeLinkTo(Start->GetOutputPin());
// This to next
Pins[1]->Modify();
Pins[1]->LinkedTo.Empty();
End->GetInputPin()->Modify();
Pins[1]->MakeLinkTo(End->GetInputPin());
}
UEdNode_GenericGraphNode* UEdNode_GenericGraphEdge::GetStartNode()
{
if (Pins[0]->LinkedTo.Num() > 0)
{
return Cast<UEdNode_GenericGraphNode>(Pins[0]->LinkedTo[0]->GetOwningNode());
}
else
{
return nullptr;
}
}
UEdNode_GenericGraphNode* UEdNode_GenericGraphEdge::GetEndNode()
{
if (Pins[1]->LinkedTo.Num() > 0)
{
return Cast<UEdNode_GenericGraphNode>(Pins[1]->LinkedTo[0]->GetOwningNode());
}
else
{
return nullptr;
}
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,84 @@
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/EdGraph_GenericGraph.h"
#include "Kismet2/Kismet2NameValidators.h"
#include "Kismet2/BlueprintEditorUtils.h"
#define LOCTEXT_NAMESPACE "EdNode_GenericGraph"
UEdNode_GenericGraphNode::UEdNode_GenericGraphNode()
{
bCanRenameNode = true;
}
UEdNode_GenericGraphNode::~UEdNode_GenericGraphNode()
{
}
void UEdNode_GenericGraphNode::AllocateDefaultPins()
{
CreatePin(EGPD_Input, "MultipleNodes", FName(), TEXT("In"));
CreatePin(EGPD_Output, "MultipleNodes", FName(), TEXT("Out"));
}
UEdGraph_GenericGraph* UEdNode_GenericGraphNode::GetGenericGraphEdGraph()
{
return Cast<UEdGraph_GenericGraph>(GetGraph());
}
FText UEdNode_GenericGraphNode::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if (GenericGraphNode == nullptr)
{
return Super::GetNodeTitle(TitleType);
}
else
{
return GenericGraphNode->GetNodeTitle();
}
}
void UEdNode_GenericGraphNode::PrepareForCopying()
{
GenericGraphNode->Rename(nullptr, this, REN_DontCreateRedirectors | REN_DoNotDirty);
}
void UEdNode_GenericGraphNode::AutowireNewNode(UEdGraphPin* FromPin)
{
Super::AutowireNewNode(FromPin);
if (FromPin != nullptr)
{
if (GetSchema()->TryCreateConnection(FromPin, GetInputPin()))
{
FromPin->GetOwningNode()->NodeConnectionListChanged();
}
}
}
void UEdNode_GenericGraphNode::SetGenericGraphNode(UGenericGraphNode* InNode)
{
GenericGraphNode = InNode;
}
FLinearColor UEdNode_GenericGraphNode::GetBackgroundColor() const
{
return GenericGraphNode == nullptr ? FLinearColor::Black : GenericGraphNode->GetBackgroundColor();
}
UEdGraphPin* UEdNode_GenericGraphNode::GetInputPin() const
{
return Pins[0];
}
UEdGraphPin* UEdNode_GenericGraphNode::GetOutputPin() const
{
return Pins[1];
}
void UEdNode_GenericGraphNode::PostEditUndo()
{
UEdGraphNode::PostEditUndo();
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,11 @@
#include "GenericGraphAssetEditor/EditorCommands_GenericGraph.h"
#define LOCTEXT_NAMESPACE "EditorCommands_GenericGraph"
void FEditorCommands_GenericGraph::RegisterCommands()
{
UI_COMMAND(GraphSettings, "Graph Settings", "Graph Settings", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(AutoArrange, "Auto Arrange", "Auto Arrange", EUserInterfaceActionType::Button, FInputChord());
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,325 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "GenericGraphAssetEditor/GenericGraphDragConnection.h"
#include "Widgets/SBoxPanel.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Images/SImage.h"
#include "EdGraph/EdGraph.h"
#include "SGraphPanel.h"
#include "ScopedTransaction.h"
TSharedRef<FGenericGraphDragConnection> FGenericGraphDragConnection::New(const TSharedRef<SGraphPanel>& GraphPanel, const FDraggedPinTable& DraggedPins)
{
TSharedRef<FGenericGraphDragConnection> Operation = MakeShareable(new FGenericGraphDragConnection(GraphPanel, DraggedPins));
Operation->Construct();
return Operation;
}
void FGenericGraphDragConnection::OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent)
{
GraphPanel->OnStopMakingConnection();
Super::OnDrop(bDropWasHandled, MouseEvent);
}
void FGenericGraphDragConnection::OnDragged(const class FDragDropEvent& DragDropEvent)
{
FVector2D TargetPosition = DragDropEvent.GetScreenSpacePosition();
// Reposition the info window wrt to the drag
CursorDecoratorWindow->MoveWindowTo(DragDropEvent.GetScreenSpacePosition() + DecoratorAdjust);
// Request the active panel to scroll if required
GraphPanel->RequestDeferredPan(TargetPosition);
}
void FGenericGraphDragConnection::HoverTargetChanged()
{
TArray<FPinConnectionResponse> UniqueMessages;
if (UEdGraphPin* TargetPinObj = GetHoveredPin())
{
TArray<UEdGraphPin*> ValidSourcePins;
ValidateGraphPinList(/*out*/ ValidSourcePins);
// Check the schema for connection responses
for (UEdGraphPin* StartingPinObj : ValidSourcePins)
{
// The Graph object in which the pins reside.
UEdGraph* GraphObj = StartingPinObj->GetOwningNode()->GetGraph();
// Determine what the schema thinks about the wiring action
const FPinConnectionResponse Response = GraphObj->GetSchema()->CanCreateConnection(StartingPinObj, TargetPinObj);
if (Response.Response == ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW)
{
TSharedPtr<SGraphNode> NodeWidget = TargetPinObj->GetOwningNode()->DEPRECATED_NodeWidget.Pin();
if (NodeWidget.IsValid())
{
NodeWidget->NotifyDisallowedPinConnection(StartingPinObj, TargetPinObj);
}
}
UniqueMessages.AddUnique(Response);
}
}
else if (UEdNode_GenericGraphNode* TargetNodeObj = Cast<UEdNode_GenericGraphNode>(GetHoveredNode()))
{
TArray<UEdGraphPin*> ValidSourcePins;
ValidateGraphPinList(/*out*/ ValidSourcePins);
// Check the schema for connection responses
for (UEdGraphPin* StartingPinObj : ValidSourcePins)
{
FPinConnectionResponse Response;
FText ResponseText;
const UEdGraphSchema *Schema = StartingPinObj->GetSchema();
UEdGraphPin *TargetPin = TargetNodeObj->GetInputPin();
if (Schema && TargetPin)
{
Response = Schema->CanCreateConnection(StartingPinObj, TargetPin);
if (Response.Response == ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW)
{
TSharedPtr<SGraphNode> NodeWidget = TargetPin->GetOwningNode()->DEPRECATED_NodeWidget.Pin();
if (NodeWidget.IsValid())
{
NodeWidget->NotifyDisallowedPinConnection(StartingPinObj, TargetPinObj);
}
}
}
else
{
#define LOCTEXT_NAMESPACE "AssetSchema_GenericGraph"
Response = FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinError", "Not a valid UGenericGraphEdNode"));
#undef LOCTEXT_NAMESPACE
}
UniqueMessages.AddUnique(Response);
}
}
else if (UEdGraph* CurrentHoveredGraph = GetHoveredGraph())
{
TArray<UEdGraphPin*> ValidSourcePins;
ValidateGraphPinList(/*out*/ ValidSourcePins);
for (UEdGraphPin* StartingPinObj : ValidSourcePins)
{
// Let the schema describe the connection we might make
FPinConnectionResponse Response = CurrentHoveredGraph->GetSchema()->CanCreateNewNodes(StartingPinObj);
if (!Response.Message.IsEmpty())
{
UniqueMessages.AddUnique(Response);
}
}
}
// Let the user know the status of dropping now
if (UniqueMessages.Num() == 0)
{
// Display the place a new node icon, we're not over a valid pin and have no message from the schema
SetSimpleFeedbackMessage(
FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.NewNode")),
FLinearColor::White,
NSLOCTEXT("GraphEditor.Feedback", "PlaceNewNode", "Place a new node."));
}
else
{
// Take the unique responses and create visual feedback for it
TSharedRef<SVerticalBox> FeedbackBox = SNew(SVerticalBox);
for (auto ResponseIt = UniqueMessages.CreateConstIterator(); ResponseIt; ++ResponseIt)
{
// Determine the icon
const FSlateBrush* StatusSymbol = NULL;
switch (ResponseIt->Response)
{
case CONNECT_RESPONSE_MAKE:
case CONNECT_RESPONSE_BREAK_OTHERS_A:
case CONNECT_RESPONSE_BREAK_OTHERS_B:
case CONNECT_RESPONSE_BREAK_OTHERS_AB:
StatusSymbol = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK"));
break;
case CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE:
StatusSymbol = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.ViaCast"));
break;
case CONNECT_RESPONSE_DISALLOW:
default:
StatusSymbol = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"));
break;
}
// Add a new message row
FeedbackBox->AddSlot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(3.0f)
.VAlign(VAlign_Center)
[
SNew(SImage).Image(StatusSymbol)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock).Text(ResponseIt->Message)
]
];
}
SetFeedbackMessage(FeedbackBox);
}
}
FGenericGraphDragConnection::FGenericGraphDragConnection(const TSharedRef<SGraphPanel>& GraphPanelIn, const FDraggedPinTable& DraggedPinsIn)
: GraphPanel(GraphPanelIn)
, DraggingPins(DraggedPinsIn)
, DecoratorAdjust(FSlateApplication::Get().GetCursorSize())
{
if (DraggingPins.Num() > 0)
{
const UEdGraphPin* PinObj = FDraggedPinTable::TConstIterator(DraggedPinsIn)->GetPinObj(*GraphPanelIn);
if (PinObj && PinObj->Direction == EGPD_Input)
{
DecoratorAdjust *= FVector2D(-1.0f, 1.0f);
}
}
for (const FGraphPinHandle& DraggedPin : DraggedPinsIn)
{
GraphPanelIn->OnBeginMakingConnection(DraggedPin);
}
}
FReply FGenericGraphDragConnection::DroppedOnPin(FVector2D ScreenPosition, FVector2D GraphPosition)
{
TArray<UEdGraphPin*> ValidSourcePins;
ValidateGraphPinList(/*out*/ ValidSourcePins);
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_CreateConnection", "Create Pin Link"));
UEdGraphPin* PinB = GetHoveredPin();
bool bError = false;
TSet<UEdGraphNode*> NodeList;
for (UEdGraphPin* PinA : ValidSourcePins)
{
if ((PinA != NULL) && (PinB != NULL))
{
UEdGraph* MyGraphObj = PinA->GetOwningNode()->GetGraph();
if (MyGraphObj->GetSchema()->TryCreateConnection(PinA, PinB))
{
if (!PinA->IsPendingKill())
{
NodeList.Add(PinA->GetOwningNode());
}
if (!PinB->IsPendingKill())
{
NodeList.Add(PinB->GetOwningNode());
}
}
}
else
{
bError = true;
}
}
// Send all nodes that received a new pin connection a notification
for (auto It = NodeList.CreateConstIterator(); It; ++It)
{
UEdGraphNode* Node = (*It);
Node->NodeConnectionListChanged();
}
if (bError)
{
return FReply::Unhandled();
}
return FReply::Handled();
}
FReply FGenericGraphDragConnection::DroppedOnNode(FVector2D ScreenPosition, FVector2D GraphPosition)
{
bool bHandledPinDropOnNode = false;
UEdGraphNode* NodeOver = GetHoveredNode();
if (NodeOver)
{
// Gather any source drag pins
TArray<UEdGraphPin*> ValidSourcePins;
ValidateGraphPinList(/*out*/ ValidSourcePins);
if (ValidSourcePins.Num())
{
for (UEdGraphPin* SourcePin : ValidSourcePins)
{
// Check for pin drop support
FText ResponseText;
if (SourcePin->GetOwningNode() != NodeOver && SourcePin->GetSchema()->SupportsDropPinOnNode(NodeOver, SourcePin->PinType, SourcePin->Direction, ResponseText))
{
bHandledPinDropOnNode = true;
// Find which pin name to use and drop the pin on the node
const FName PinName = SourcePin->PinFriendlyName.IsEmpty() ? SourcePin->PinName : *SourcePin->PinFriendlyName.ToString();
const FScopedTransaction Transaction((SourcePin->Direction == EGPD_Output) ? NSLOCTEXT("UnrealEd", "AddInParam", "Add In Parameter") : NSLOCTEXT("UnrealEd", "AddOutParam", "Add Out Parameter"));
UEdGraphPin* EdGraphPin = NodeOver->GetSchema()->DropPinOnNode(GetHoveredNode(), PinName, SourcePin->PinType, SourcePin->Direction);
// This can invalidate the source pin due to node reconstruction, abort in that case
if (SourcePin->GetOwningNodeUnchecked() && EdGraphPin)
{
SourcePin->Modify();
EdGraphPin->Modify();
SourcePin->GetSchema()->TryCreateConnection(SourcePin, EdGraphPin);
}
}
// If we have not handled the pin drop on node and there is an error message, do not let other actions occur.
if (!bHandledPinDropOnNode && !ResponseText.IsEmpty())
{
bHandledPinDropOnNode = true;
}
}
}
}
return bHandledPinDropOnNode ? FReply::Handled() : FReply::Unhandled();
}
FReply FGenericGraphDragConnection::DroppedOnPanel(const TSharedRef< SWidget >& Panel, FVector2D ScreenPosition, FVector2D GraphPosition, UEdGraph& Graph)
{
// Gather any source drag pins
TArray<UEdGraphPin*> PinObjects;
ValidateGraphPinList(/*out*/ PinObjects);
// Create a context menu
TSharedPtr<SWidget> WidgetToFocus = GraphPanel->SummonContextMenu(ScreenPosition, GraphPosition, NULL, NULL, PinObjects);
// Give the context menu focus
return (WidgetToFocus.IsValid())
? FReply::Handled().SetUserFocus(WidgetToFocus.ToSharedRef(), EFocusCause::SetDirectly)
: FReply::Handled();
}
void FGenericGraphDragConnection::ValidateGraphPinList(TArray<UEdGraphPin*>& OutValidPins)
{
OutValidPins.Empty(DraggingPins.Num());
for (const FGraphPinHandle& PinHandle : DraggingPins)
{
if (UEdGraphPin* GraphPin = PinHandle.GetPinObj(*GraphPanel))
{
OutValidPins.Add(GraphPin);
}
}
}

View File

@ -0,0 +1,54 @@
#include "GenericGraphAssetEditor/GenericGraphEditorStyle.h"
#include "Styling/SlateStyleRegistry.h"
#include "Styling/SlateTypes.h"
#include "Misc/Paths.h"
TSharedPtr<FSlateStyleSet> FGenericGraphEditorStyle::StyleSet = nullptr;
#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ )
#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ )
void FGenericGraphEditorStyle::Initialize()
{
const FVector2D Icon20x20(20.0f, 20.0f);
const FVector2D Icon40x40(40.0f, 40.0f);
const FVector2D Icon64x64(64.0f, 64.0f);
if (StyleSet.IsValid())
{
return;
}
StyleSet = MakeShareable(new FSlateStyleSet("GenericGraphEditorStyle"));
StyleSet->SetContentRoot(FPaths::ProjectPluginsDir() / TEXT("GenericGraph/Resources"));
StyleSet->Set("GenericGraphEditor.AutoArrange", new IMAGE_BRUSH("AutoArrangeIcon", Icon40x40));
StyleSet->Set("GenericGraphEditor.AutoArrange.Small", new IMAGE_BRUSH( "AutoArrangeIcon", Icon20x20 ) );
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get());
}
void FGenericGraphEditorStyle::Shutdown()
{
if (StyleSet.IsValid())
{
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get());
ensure(StyleSet.IsUnique());
StyleSet.Reset();
}
}
const FName& FGenericGraphEditorStyle::GetStyleSetName()
{
return StyleSet->GetStyleSetName();
}
#undef IMAGE_BRUSH
#undef BOX_BRUSH
#undef BORDER_BRUSH
#undef TTF_FONT
#undef OTF_FONT

View File

@ -0,0 +1,199 @@
#include "GenericGraphAssetEditor/SEdNode_GenericGraphEdge.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/SToolTip.h"
#include "SGraphPanel.h"
#include "EdGraphSchema_K2.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphEdge.h"
#include "GenericGraphAssetEditor/ConnectionDrawingPolicy_GenericGraph.h"
#define LOCTEXT_NAMESPACE "SGenericGraphEdge"
void SEdNode_GenericGraphEdge::Construct(const FArguments& InArgs, UEdNode_GenericGraphEdge* InNode)
{
this->GraphNode = InNode;
this->UpdateGraphNode();
}
bool SEdNode_GenericGraphEdge::RequiresSecondPassLayout() const
{
return true;
}
void SEdNode_GenericGraphEdge::PerformSecondPassLayout(const TMap< UObject*, TSharedRef<SNode> >& NodeToWidgetLookup) const
{
UEdNode_GenericGraphEdge* EdgeNode = CastChecked<UEdNode_GenericGraphEdge>(GraphNode);
FGeometry StartGeom;
FGeometry EndGeom;
UEdNode_GenericGraphNode* Start = EdgeNode->GetStartNode();
UEdNode_GenericGraphNode* End = EdgeNode->GetEndNode();
if (Start != nullptr && End != nullptr)
{
const TSharedRef<SNode>* pFromWidget = NodeToWidgetLookup.Find(Start);
const TSharedRef<SNode>* pToWidget = NodeToWidgetLookup.Find(End);
if (pFromWidget != nullptr && pToWidget != nullptr)
{
const TSharedRef<SNode>& FromWidget = *pFromWidget;
const TSharedRef<SNode>& ToWidget = *pToWidget;
StartGeom = FGeometry(FVector2D(Start->NodePosX, Start->NodePosY), FVector2D::ZeroVector, FromWidget->GetDesiredSize(), 1.0f);
EndGeom = FGeometry(FVector2D(End->NodePosX, End->NodePosY), FVector2D::ZeroVector, ToWidget->GetDesiredSize(), 1.0f);
}
}
PositionBetweenTwoNodesWithOffset(StartGeom, EndGeom, 0, 1);
}
void SEdNode_GenericGraphEdge::OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo)
{
SGraphNode::OnNameTextCommited(InText, CommitInfo);
UEdNode_GenericGraphEdge* MyNode = CastChecked<UEdNode_GenericGraphEdge>(GraphNode);
if (MyNode != nullptr && MyNode->GenericGraphEdge != nullptr)
{
const FScopedTransaction Transaction(LOCTEXT("GenericGraphEditorRenameEdge", "Generic Graph Editor: Rename Edge"));
MyNode->Modify();
MyNode->GenericGraphEdge->SetNodeTitle(InText);
UpdateGraphNode();
}
}
void SEdNode_GenericGraphEdge::UpdateGraphNode()
{
InputPins.Empty();
OutputPins.Empty();
RightNodeBox.Reset();
LeftNodeBox.Reset();
TSharedPtr<SNodeTitle> NodeTitle = SNew(SNodeTitle, GraphNode);
this->ContentScale.Bind( this, &SGraphNode::GetContentScale );
this->GetOrAddSlot( ENodeZone::Center )
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SOverlay)
+ SOverlay::Slot()
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Graph.TransitionNode.ColorSpill"))
.ColorAndOpacity(this, &SEdNode_GenericGraphEdge::GetEdgeColor)
]
+ SOverlay::Slot()
[
SNew(SImage)
.Image(this, &SEdNode_GenericGraphEdge::GetEdgeImage)
.Visibility(this, &SEdNode_GenericGraphEdge::GetEdgeImageVisibility)
]
+ SOverlay::Slot()
.Padding(FMargin(4.0f, 4.0f, 4.0f, 4.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.HAlign(HAlign_Center)
.AutoHeight()
[
SAssignNew(InlineEditableText, SInlineEditableTextBlock)
.ColorAndOpacity(FLinearColor::Black)
.Visibility(this, &SEdNode_GenericGraphEdge::GetEdgeTitleVisbility)
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 12))
.Text(NodeTitle.Get(), &SNodeTitle::GetHeadTitle)
.OnTextCommitted(this, &SEdNode_GenericGraphEdge::OnNameTextCommited)
]
+ SVerticalBox::Slot()
.AutoHeight()
[
NodeTitle.ToSharedRef()
]
]
];
}
void SEdNode_GenericGraphEdge::PositionBetweenTwoNodesWithOffset(const FGeometry& StartGeom, const FGeometry& EndGeom, int32 NodeIndex, int32 MaxNodes) const
{
// Get a reasonable seed point (halfway between the boxes)
const FVector2D StartCenter = FGeometryHelper::CenterOf(StartGeom);
const FVector2D EndCenter = FGeometryHelper::CenterOf(EndGeom);
const FVector2D SeedPoint = (StartCenter + EndCenter) * 0.5f;
// Find the (approximate) closest points between the two boxes
const FVector2D StartAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(StartGeom, SeedPoint);
const FVector2D EndAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(EndGeom, SeedPoint);
// Position ourselves halfway along the connecting line between the nodes, elevated away perpendicular to the direction of the line
const float Height = 30.0f;
const FVector2D DesiredNodeSize = GetDesiredSize();
FVector2D DeltaPos(EndAnchorPoint - StartAnchorPoint);
if (DeltaPos.IsNearlyZero())
{
DeltaPos = FVector2D(10.0f, 0.0f);
}
const FVector2D Normal = FVector2D(DeltaPos.Y, -DeltaPos.X).GetSafeNormal();
const FVector2D NewCenter = StartAnchorPoint + (0.5f * DeltaPos) + (Height * Normal);
FVector2D DeltaNormal = DeltaPos.GetSafeNormal();
// Calculate node offset in the case of multiple transitions between the same two nodes
// MultiNodeOffset: the offset where 0 is the centre of the transition, -1 is 1 <size of node>
// towards the PrevStateNode and +1 is 1 <size of node> towards the NextStateNode.
const float MutliNodeSpace = 0.2f; // Space between multiple transition nodes (in units of <size of node> )
const float MultiNodeStep = (1.f + MutliNodeSpace); //Step between node centres (Size of node + size of node spacer)
const float MultiNodeStart = -((MaxNodes - 1) * MultiNodeStep) / 2.f;
const float MultiNodeOffset = MultiNodeStart + (NodeIndex * MultiNodeStep);
// Now we need to adjust the new center by the node size, zoom factor and multi node offset
const FVector2D NewCorner = NewCenter - (0.5f * DesiredNodeSize) + (DeltaNormal * MultiNodeOffset * DesiredNodeSize.Size());
GraphNode->NodePosX = NewCorner.X;
GraphNode->NodePosY = NewCorner.Y;
}
FSlateColor SEdNode_GenericGraphEdge::GetEdgeColor() const
{
UEdNode_GenericGraphEdge* EdgeNode = CastChecked<UEdNode_GenericGraphEdge>(GraphNode);
if (EdgeNode != nullptr && EdgeNode->GenericGraphEdge != nullptr)
{
return EdgeNode->GenericGraphEdge->GetEdgeColour();
}
return FLinearColor(0.9f, 0.9f, 0.9f, 1.0f);
}
const FSlateBrush* SEdNode_GenericGraphEdge::GetEdgeImage() const
{
return FAppStyle::GetBrush("Graph.TransitionNode.Icon");
}
EVisibility SEdNode_GenericGraphEdge::GetEdgeImageVisibility() const
{
UEdNode_GenericGraphEdge* EdgeNode = CastChecked<UEdNode_GenericGraphEdge>(GraphNode);
if (EdgeNode && EdgeNode->GenericGraphEdge && EdgeNode->GenericGraphEdge->bShouldDrawTitle)
return EVisibility::Hidden;
return EVisibility::Visible;
}
EVisibility SEdNode_GenericGraphEdge::GetEdgeTitleVisbility() const
{
UEdNode_GenericGraphEdge* EdgeNode = CastChecked<UEdNode_GenericGraphEdge>(GraphNode);
if (EdgeNode && EdgeNode->GenericGraphEdge && EdgeNode->GenericGraphEdge->bShouldDrawTitle)
return EVisibility::Visible;
return EVisibility::Collapsed;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,336 @@
#include "GenericGraphAssetEditor/SEdNode_GenericGraphNode.h"
#include "GenericGraphEditorPCH.h"
#include "GenericGraphAssetEditor/Colors_GenericGraph.h"
#include "SLevelOfDetailBranchNode.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "SCommentBubble.h"
#include "SlateOptMacros.h"
#include "SGraphPin.h"
#include "GraphEditorSettings.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/GenericGraphDragConnection.h"
#define LOCTEXT_NAMESPACE "EdNode_GenericGraph"
//////////////////////////////////////////////////////////////////////////
class SGenericGraphPin : public SGraphPin
{
public:
SLATE_BEGIN_ARGS(SGenericGraphPin) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UEdGraphPin* InPin)
{
this->SetCursor(EMouseCursor::Default);
bShowLabel = true;
GraphPinObj = InPin;
check(GraphPinObj != nullptr);
const UEdGraphSchema* Schema = GraphPinObj->GetSchema();
check(Schema);
SBorder::Construct(SBorder::FArguments()
.BorderImage(this, &SGenericGraphPin::GetPinBorder)
.BorderBackgroundColor(this, &SGenericGraphPin::GetPinColor)
.OnMouseButtonDown(this, &SGenericGraphPin::OnPinMouseDown)
.Cursor(this, &SGenericGraphPin::GetPinCursor)
.Padding(FMargin(5.0f))
);
}
protected:
virtual FSlateColor GetPinColor() const override
{
return GenericGraphColors::Pin::Default;
}
virtual TSharedRef<SWidget> GetDefaultValueWidget() override
{
return SNew(STextBlock);
}
const FSlateBrush* GetPinBorder() const
{
return FAppStyle::GetBrush(TEXT("Graph.StateNode.Body"));
}
virtual TSharedRef<FDragDropOperation> SpawnPinDragEvent(const TSharedRef<class SGraphPanel>& InGraphPanel, const TArray< TSharedRef<SGraphPin> >& InStartingPins) override
{
FGenericGraphDragConnection::FDraggedPinTable PinHandles;
PinHandles.Reserve(InStartingPins.Num());
// since the graph can be refreshed and pins can be reconstructed/replaced
// behind the scenes, the DragDropOperation holds onto FGraphPinHandles
// instead of direct widgets/graph-pins
for (const TSharedRef<SGraphPin>& PinWidget : InStartingPins)
{
PinHandles.Add(PinWidget->GetPinObj());
}
return FGenericGraphDragConnection::New(InGraphPanel, PinHandles);
}
};
//////////////////////////////////////////////////////////////////////////
void SEdNode_GenericGraphNode::Construct(const FArguments& InArgs, UEdNode_GenericGraphNode* InNode)
{
GraphNode = InNode;
UpdateGraphNode();
InNode->SEdNode = this;
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SEdNode_GenericGraphNode::UpdateGraphNode()
{
const FMargin NodePadding = FMargin(5);
const FMargin NamePadding = FMargin(2);
InputPins.Empty();
OutputPins.Empty();
// Reset variables that are going to be exposed, in case we are refreshing an already setup node.
RightNodeBox.Reset();
LeftNodeBox.Reset();
const FSlateBrush *NodeTypeIcon = GetNameIcon();
FLinearColor TitleShadowColor(0.6f, 0.6f, 0.6f);
TSharedPtr<SErrorText> ErrorText;
TSharedPtr<SVerticalBox> NodeBody;
TSharedPtr<SNodeTitle> NodeTitle = SNew(SNodeTitle, GraphNode);
this->ContentScale.Bind(this, &SGraphNode::GetContentScale);
this->GetOrAddSlot(ENodeZone::Center)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Center)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("Graph.StateNode.Body"))
.Padding(0.0f)
.BorderBackgroundColor(this, &SEdNode_GenericGraphNode::GetBorderBackgroundColor)
[
SNew(SOverlay)
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SVerticalBox)
// Input Pin Area
+ SVerticalBox::Slot()
.FillHeight(1)
[
SAssignNew(LeftNodeBox, SVerticalBox)
]
// Output Pin Area
+ SVerticalBox::Slot()
.FillHeight(1)
[
SAssignNew(RightNodeBox, SVerticalBox)
]
]
+ SOverlay::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(8.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("Graph.StateNode.ColorSpill"))
.BorderBackgroundColor(TitleShadowColor)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Visibility(EVisibility::SelfHitTestInvisible)
.Padding(6.0f)
[
SAssignNew(NodeBody, SVerticalBox)
// Title
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
// Error message
+ SHorizontalBox::Slot()
.AutoWidth()
[
SAssignNew(ErrorText, SErrorText)
.BackgroundColor(this, &SEdNode_GenericGraphNode::GetErrorColor)
.ToolTipText(this, &SEdNode_GenericGraphNode::GetErrorMsgToolTip)
]
// Icon
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(NodeTypeIcon)
]
// Node Title
+ SHorizontalBox::Slot()
.Padding(FMargin(4.0f, 0.0f, 4.0f, 0.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(InlineEditableText, SInlineEditableTextBlock)
.Style(FAppStyle::Get(), "Graph.StateNode.NodeTitleInlineEditableText")
.Text(NodeTitle.Get(), &SNodeTitle::GetHeadTitle)
.OnVerifyTextChanged(this, &SEdNode_GenericGraphNode::OnVerifyNameTextChanged)
.OnTextCommitted(this, &SEdNode_GenericGraphNode::OnNameTextCommited)
.IsReadOnly(this, &SEdNode_GenericGraphNode::IsNameReadOnly)
.IsSelected(this, &SEdNode_GenericGraphNode::IsSelectedExclusively)
]
+ SVerticalBox::Slot()
.AutoHeight()
[
NodeTitle.ToSharedRef()
]
]
]
]
]
]
];
// Create comment bubble
TSharedPtr<SCommentBubble> CommentBubble;
const FSlateColor CommentColor = GetDefault<UGraphEditorSettings>()->DefaultCommentNodeTitleColor;
SAssignNew(CommentBubble, SCommentBubble)
.GraphNode(GraphNode)
.Text(this, &SGraphNode::GetNodeComment)
.OnTextCommitted(this, &SGraphNode::OnCommentTextCommitted)
.ColorAndOpacity(CommentColor)
.AllowPinning(true)
.EnableTitleBarBubble(true)
.EnableBubbleCtrls(true)
.GraphLOD(this, &SGraphNode::GetCurrentLOD)
.IsGraphNodeHovered(this, &SGraphNode::IsHovered);
GetOrAddSlot(ENodeZone::TopCenter)
.SlotOffset(TAttribute<FVector2D>(CommentBubble.Get(), &SCommentBubble::GetOffset))
.SlotSize(TAttribute<FVector2D>(CommentBubble.Get(), &SCommentBubble::GetSize))
.AllowScaling(TAttribute<bool>(CommentBubble.Get(), &SCommentBubble::IsScalingAllowed))
.VAlign(VAlign_Top)
[
CommentBubble.ToSharedRef()
];
ErrorReporting = ErrorText;
ErrorReporting->SetError(ErrorMsg);
CreatePinWidgets();
}
void SEdNode_GenericGraphNode::CreatePinWidgets()
{
UEdNode_GenericGraphNode* StateNode = CastChecked<UEdNode_GenericGraphNode>(GraphNode);
for (int32 PinIdx = 0; PinIdx < StateNode->Pins.Num(); PinIdx++)
{
UEdGraphPin* MyPin = StateNode->Pins[PinIdx];
if (!MyPin->bHidden)
{
TSharedPtr<SGraphPin> NewPin = SNew(SGenericGraphPin, MyPin);
AddPin(NewPin.ToSharedRef());
}
}
}
void SEdNode_GenericGraphNode::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
PinToAdd->SetOwner(SharedThis(this));
const UEdGraphPin* PinObj = PinToAdd->GetPinObj();
const bool bAdvancedParameter = PinObj && PinObj->bAdvancedView;
if (bAdvancedParameter)
{
PinToAdd->SetVisibility( TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced) );
}
TSharedPtr<SVerticalBox> PinBox;
if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Input)
{
PinBox = LeftNodeBox;
InputPins.Add(PinToAdd);
}
else // Direction == EEdGraphPinDirection::EGPD_Output
{
PinBox = RightNodeBox;
OutputPins.Add(PinToAdd);
}
if (PinBox)
{
PinBox->AddSlot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.FillHeight(1.0f)
//.Padding(6.0f, 0.0f)
[
PinToAdd
];
}
}
bool SEdNode_GenericGraphNode::IsNameReadOnly() const
{
UEdNode_GenericGraphNode* EdNode_Node = Cast<UEdNode_GenericGraphNode>(GraphNode);
check(EdNode_Node != nullptr);
UGenericGraph* GenericGraph = EdNode_Node->GenericGraphNode->Graph;
check(GenericGraph != nullptr);
return (!GenericGraph->bCanRenameNode || !EdNode_Node->GenericGraphNode->IsNameEditable()) || SGraphNode::IsNameReadOnly();
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SEdNode_GenericGraphNode::OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo)
{
SGraphNode::OnNameTextCommited(InText, CommitInfo);
UEdNode_GenericGraphNode* MyNode = CastChecked<UEdNode_GenericGraphNode>(GraphNode);
if (MyNode != nullptr && MyNode->GenericGraphNode != nullptr)
{
const FScopedTransaction Transaction(LOCTEXT("GenericGraphEditorRenameNode", "Generic Graph Editor: Rename Node"));
MyNode->Modify();
MyNode->GenericGraphNode->Modify();
MyNode->GenericGraphNode->SetNodeTitle(InText);
UpdateGraphNode();
}
}
FSlateColor SEdNode_GenericGraphNode::GetBorderBackgroundColor() const
{
UEdNode_GenericGraphNode* MyNode = CastChecked<UEdNode_GenericGraphNode>(GraphNode);
return MyNode ? MyNode->GetBackgroundColor() : GenericGraphColors::NodeBorder::HighlightAbortRange0;
}
FSlateColor SEdNode_GenericGraphNode::GetBackgroundColor() const
{
return GenericGraphColors::NodeBody::Default;
}
EVisibility SEdNode_GenericGraphNode::GetDragOverMarkerVisibility() const
{
return EVisibility::Visible;
}
const FSlateBrush* SEdNode_GenericGraphNode::GetNameIcon() const
{
return FAppStyle::GetBrush(TEXT("BTEditor.Graph.BTNode.Icon"));
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,24 @@
#include "GenericGraphAssetEditor/Settings_GenericGraphEditor.h"
UGenericGraphEditorSettings::UGenericGraphEditorSettings()
{
AutoLayoutStrategy = EAutoLayoutStrategy::Tree;
bFirstPassOnly = false;
bRandomInit = false;
OptimalDistance = 100.f;
MaxIteration = 50;
InitTemperature = 10.f;
CoolDownRate = 10.f;
}
UGenericGraphEditorSettings::~UGenericGraphEditorSettings()
{
}

View File

@ -0,0 +1,55 @@
#include "GenericGraphEditor.h"
#include "GenericGraphNodeFactory.h"
#include "AssetTypeActions_GenericGraph.h"
#include "GenericGraphAssetEditor/GenericGraphEditorStyle.h"
DEFINE_LOG_CATEGORY(GenericGraphEditor)
#define LOCTEXT_NAMESPACE "Editor_GenericGraph"
void FGenericGraphEditor::StartupModule()
{
FGenericGraphEditorStyle::Initialize();
GraphPanelNodeFactory_GenericGraph = MakeShareable(new FGraphPanelNodeFactory_GenericGraph());
FEdGraphUtilities::RegisterVisualNodeFactory(GraphPanelNodeFactory_GenericGraph);
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
GenericGraphAssetCategoryBit = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("GenericGraph")), LOCTEXT("GenericGraphAssetCategory", "GenericGraph"));
RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_GenericGraph(GenericGraphAssetCategoryBit)));
}
void FGenericGraphEditor::ShutdownModule()
{
// Unregister all the asset types that we registered
if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
{
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
for (int32 Index = 0; Index < CreatedAssetTypeActions.Num(); ++Index)
{
AssetTools.UnregisterAssetTypeActions(CreatedAssetTypeActions[Index].ToSharedRef());
}
}
if (GraphPanelNodeFactory_GenericGraph.IsValid())
{
FEdGraphUtilities::UnregisterVisualNodeFactory(GraphPanelNodeFactory_GenericGraph);
GraphPanelNodeFactory_GenericGraph.Reset();
}
FGenericGraphEditorStyle::Shutdown();
}
void FGenericGraphEditor::RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef<IAssetTypeActions> Action)
{
AssetTools.RegisterAssetTypeActions(Action);
CreatedAssetTypeActions.Add(Action);
}
IMPLEMENT_MODULE(FGenericGraphEditor, GenericGraphEditor)
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,117 @@
#include "GenericGraphFactory.h"
#include "GenericGraph.h"
#include "ClassViewerModule.h"
#include "ClassViewerFilter.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Kismet2/SClassPickerDialog.h"
#define LOCTEXT_NAMESPACE "GenericGraphFactory"
class FAssetClassParentFilter : public IClassViewerFilter
{
public:
FAssetClassParentFilter()
: DisallowedClassFlags(CLASS_None), bDisallowBlueprintBase(false)
{}
/** All children of these classes will be included unless filtered out by another setting. */
TSet< const UClass* > AllowedChildrenOfClasses;
/** Disallowed class flags. */
EClassFlags DisallowedClassFlags;
/** Disallow blueprint base classes. */
bool bDisallowBlueprintBase;
virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override
{
bool bAllowed= !InClass->HasAnyClassFlags(DisallowedClassFlags)
&& InFilterFuncs->IfInChildOfClassesSet(AllowedChildrenOfClasses, InClass) != EFilterReturn::Failed;
if (bAllowed && bDisallowBlueprintBase)
{
if (FKismetEditorUtilities::CanCreateBlueprintOfClass(InClass))
{
return false;
}
}
return bAllowed;
}
virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override
{
if (bDisallowBlueprintBase)
{
return false;
}
return !InUnloadedClassData->HasAnyClassFlags(DisallowedClassFlags)
&& InFilterFuncs->IfInChildOfClassesSet(AllowedChildrenOfClasses, InUnloadedClassData) != EFilterReturn::Failed;
}
};
UGenericGraphFactory::UGenericGraphFactory()
{
bCreateNew = true;
bEditAfterNew = true;
SupportedClass = UGenericGraph::StaticClass();
}
UGenericGraphFactory::~UGenericGraphFactory()
{
}
bool UGenericGraphFactory::ConfigureProperties()
{
// nullptr the GenericGraphClass so we can check for selection
GenericGraphClass = nullptr;
// Load the classviewer module to display a class picker
FClassViewerModule& ClassViewerModule = FModuleManager::LoadModuleChecked<FClassViewerModule>("ClassViewer");
// Fill in options
FClassViewerInitializationOptions Options;
Options.Mode = EClassViewerMode::ClassPicker;
#if ENGINE_MAJOR_VERSION < 5
TSharedPtr<FAssetClassParentFilter> Filter = MakeShareable(new FAssetClassParentFilter);
Options.ClassFilter = Filter;
#else // #if ENGINE_MAJOR_VERSION < 5
TSharedRef<FAssetClassParentFilter> Filter = MakeShareable(new FAssetClassParentFilter);
Options.ClassFilters.Add(Filter);
#endif // #else // #if ENGINE_MAJOR_VERSION < 5
Filter->DisallowedClassFlags = CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists | CLASS_HideDropDown;
Filter->AllowedChildrenOfClasses.Add(UGenericGraph::StaticClass());
const FText TitleText = LOCTEXT("CreateGenericGraphAssetOptions", "Pick Generic Graph Class");
UClass* ChosenClass = nullptr;
const bool bPressedOk = SClassPickerDialog::PickClass(TitleText, Options, ChosenClass, UGenericGraph::StaticClass());
if ( bPressedOk )
{
GenericGraphClass = ChosenClass;
}
return bPressedOk;
}
UObject* UGenericGraphFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
if (GenericGraphClass != nullptr)
{
return NewObject<UGenericGraph>(InParent, GenericGraphClass, Name, Flags | RF_Transactional);
}
else
{
check(Class->IsChildOf(UGenericGraph::StaticClass()));
return NewObject<UObject>(InParent, Class, Name, Flags | RF_Transactional);
}
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,20 @@
#include "GenericGraphNodeFactory.h"
#include <EdGraph/EdGraphNode.h>
#include "GenericGraphAssetEditor/SEdNode_GenericGraphEdge.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/SEdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphEdge.h"
TSharedPtr<class SGraphNode> FGraphPanelNodeFactory_GenericGraph::CreateNode(UEdGraphNode* Node) const
{
if (UEdNode_GenericGraphNode* EdNode_GraphNode = Cast<UEdNode_GenericGraphNode>(Node))
{
return SNew(SEdNode_GenericGraphNode, EdNode_GraphNode);
}
else if (UEdNode_GenericGraphEdge* EdNode_Edge = Cast<UEdNode_GenericGraphEdge>(Node))
{
return SNew(SEdNode_GenericGraphEdge, EdNode_Edge);
}
return nullptr;
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"
class GENERICGRAPHEDITOR_API FAssetTypeActions_GenericGraph : public FAssetTypeActions_Base
{
public:
FAssetTypeActions_GenericGraph(EAssetTypeCategories::Type InAssetCategory);
virtual FText GetName() const override;
virtual FColor GetTypeColor() const override;
virtual UClass* GetSupportedClass() const override;
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
virtual uint32 GetCategories() override;
private:
EAssetTypeCategories::Type MyAssetCategory;
};

View File

@ -0,0 +1,40 @@
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraph.h"
#include "GenericGraph.h"
#include "GenericGraphAssetEditor/EdGraph_GenericGraph.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphNode.h"
#include "GenericGraphAssetEditor/EdNode_GenericGraphEdge.h"
#include "GenericGraphAssetEditor/Settings_GenericGraphEditor.h"
#include "AutoLayoutStrategy.generated.h"
UCLASS(abstract)
class GENERICGRAPHEDITOR_API UAutoLayoutStrategy : public UObject
{
GENERATED_BODY()
public:
UAutoLayoutStrategy();
virtual ~UAutoLayoutStrategy();
virtual void Layout(UEdGraph* G) {};
class UGenericGraphEditorSettings* Settings;
protected:
int32 GetNodeWidth(UEdNode_GenericGraphNode* EdNode);
int32 GetNodeHeight(UEdNode_GenericGraphNode* EdNode);
FBox2D GetNodeBound(UEdGraphNode* EdNode);
FBox2D GetActualBounds(UGenericGraphNode* RootNode);
virtual void RandomLayoutOneTree(UGenericGraphNode* RootNode, const FBox2D& Bound);
protected:
UGenericGraph* Graph;
UEdGraph_GenericGraph* EdGraph;
int32 MaxIteration;
int32 OptimalDistance;
};

View File

@ -0,0 +1,24 @@
#pragma once
#include "CoreMinimal.h"
#include "AutoLayoutStrategy.h"
#include "ForceDirectedLayoutStrategy.generated.h"
UCLASS()
class GENERICGRAPHEDITOR_API UForceDirectedLayoutStrategy : public UAutoLayoutStrategy
{
GENERATED_BODY()
public:
UForceDirectedLayoutStrategy();
virtual ~UForceDirectedLayoutStrategy();
virtual void Layout(UEdGraph* EdGraph) override;
protected:
virtual FBox2D LayoutOneTree(UGenericGraphNode* RootNode, const FBox2D& PreTreeBound);
protected:
bool bRandomInit;
float InitTemperature;
float CoolDownRate;
};

View File

@ -0,0 +1,29 @@
#pragma once
#include "CoreMinimal.h"
#include "AutoLayoutStrategy.h"
#include "TreeLayoutStrategy.generated.h"
UCLASS()
class GENERICGRAPHEDITOR_API UTreeLayoutStrategy : public UAutoLayoutStrategy
{
GENERATED_BODY()
public:
UTreeLayoutStrategy();
virtual ~UTreeLayoutStrategy();
virtual void Layout(UEdGraph* EdGraph) override;
protected:
void InitPass(UGenericGraphNode* RootNode, const FVector2D& Anchor);
bool ResolveConflictPass(UGenericGraphNode* Node);
bool ResolveConflict(UGenericGraphNode* LRoot, UGenericGraphNode* RRoot);
void GetLeftContour(UGenericGraphNode* RootNode, int32 Level, TArray<UEdNode_GenericGraphNode*>& Contour);
void GetRightContour(UGenericGraphNode* RootNode, int32 Level, TArray<UEdNode_GenericGraphNode*>& Contour);
void ShiftSubTree(UGenericGraphNode* RootNode, const FVector2D& Offset);
void UpdateParentNodePosition(UGenericGraphNode* RootNode);
};

View File

@ -0,0 +1,24 @@
#pragma once
#include "CoreMinimal.h"
class FAssetEditor_GenericGraph;
class FExtender;
class FToolBarBuilder;
class GENERICGRAPHEDITOR_API FAssetEditorToolbar_GenericGraph : public TSharedFromThis<FAssetEditorToolbar_GenericGraph>
{
public:
FAssetEditorToolbar_GenericGraph(TSharedPtr<FAssetEditor_GenericGraph> InGenericGraphEditor)
: GenericGraphEditor(InGenericGraphEditor) {}
void AddGenericGraphToolbar(TSharedPtr<FExtender> Extender);
private:
void FillGenericGraphToolbar(FToolBarBuilder& ToolbarBuilder);
protected:
/** Pointer back to the blueprint editor tool that owns us */
TWeakPtr<FAssetEditor_GenericGraph> GenericGraphEditor;
};

View File

@ -0,0 +1,137 @@
#pragma once
#include "CoreMinimal.h"
#include "Settings_GenericGraphEditor.h"
#include "GenericGraph.h"
#if ENGINE_MAJOR_VERSION == 5
#include "UObject/ObjectSaveContext.h"
#endif // #if ENGINE_MAJOR_VERSION == 5
class FGGAssetEditorToolbar;
class GENERICGRAPHEDITOR_API FAssetEditor_GenericGraph : public FAssetEditorToolkit, public FNotifyHook, public FGCObject
{
public:
FAssetEditor_GenericGraph();
virtual ~FAssetEditor_GenericGraph();
void InitGenericGraphAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, UGenericGraph* Graph);
// IToolkit interface
virtual void RegisterTabSpawners(const TSharedRef<FTabManager>& TabManager) override;
virtual void UnregisterTabSpawners(const TSharedRef<FTabManager>& TabManager) override;
// End of IToolkit interface
// FAssetEditorToolkit
virtual FName GetToolkitFName() const override;
virtual FText GetBaseToolkitName() const override;
virtual FText GetToolkitName() const override;
virtual FText GetToolkitToolTipText() const override;
virtual FLinearColor GetWorldCentricTabColorScale() const override;
virtual FString GetWorldCentricTabPrefix() const override;
virtual FString GetDocumentationLink() const override;
virtual void SaveAsset_Execute() override;
// End of FAssetEditorToolkit
//Toolbar
void UpdateToolbar();
TSharedPtr<class FAssetEditorToolbar_GenericGraph> GetToolbarBuilder() { return ToolbarBuilder; }
void RegisterToolbarTab(const TSharedRef<class FTabManager>& TabManager);
// FSerializableObject interface
virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
// End of FSerializableObject interface
#if ENGINE_MAJOR_VERSION == 5
// FGCObject interface
virtual FString GetReferencerName() const
{
return TEXT("FAssetEditor_LTGenericGraph");
}
// ~FGCObject interface
#endif // #if ENGINE_MAJOR_VERSION == 5
UGenericGraphEditorSettings* GetSettings() const;
protected:
TSharedRef<SDockTab> SpawnTab_Viewport(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_Details(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_EditorSettings(const FSpawnTabArgs& Args);
void CreateInternalWidgets();
TSharedRef<SGraphEditor> CreateViewportWidget();
void BindCommands();
void CreateEdGraph();
void CreateCommandList();
TSharedPtr<SGraphEditor> GetCurrGraphEditor() const;
FGraphPanelSelectionSet GetSelectedNodes() const;
void RebuildGenericGraph();
// Delegates for graph editor commands
void SelectAllNodes();
bool CanSelectAllNodes();
void DeleteSelectedNodes();
bool CanDeleteNodes();
void DeleteSelectedDuplicatableNodes();
void CutSelectedNodes();
bool CanCutNodes();
void CopySelectedNodes();
bool CanCopyNodes();
void PasteNodes();
void PasteNodesHere(const FVector2D& Location);
bool CanPasteNodes();
void DuplicateNodes();
bool CanDuplicateNodes();
void GraphSettings();
bool CanGraphSettings() const;
void AutoArrange();
bool CanAutoArrange() const;
void OnRenameNode();
bool CanRenameNodes() const;
//////////////////////////////////////////////////////////////////////////
// graph editor event
void OnSelectedNodesChanged(const TSet<class UObject*>& NewSelection);
void OnNodeDoubleClicked(UEdGraphNode* Node);
void OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent);
#if ENGINE_MAJOR_VERSION < 5
void OnPackageSaved(const FString& PackageFileName, UObject* Outer);
#else // #if ENGINE_MAJOR_VERSION < 5
void OnPackageSavedWithContext(const FString& PackageFileName, UPackage* Package, FObjectPostSaveContext ObjectSaveContext);
#endif // #else // #if ENGINE_MAJOR_VERSION < 5
protected:
UGenericGraphEditorSettings* GenricGraphEditorSettings;
UGenericGraph* EditingGraph;
//Toolbar
TSharedPtr<class FAssetEditorToolbar_GenericGraph> ToolbarBuilder;
/** Handle to the registered OnPackageSave delegate */
FDelegateHandle OnPackageSavedDelegateHandle;
TSharedPtr<SGraphEditor> ViewportWidget;
TSharedPtr<class IDetailsView> PropertyWidget;
TSharedPtr<class IDetailsView> EditorSettingsWidget;
/** The command list for this editor */
TSharedPtr<FUICommandList> GraphEditorCommands;
};

View File

@ -0,0 +1,90 @@
#pragma once
#include "CoreMinimal.h"
#include "GenericGraph.h"
#include "GenericGraphNode.h"
#include "GenericGraphEdge.h"
#include "AssetGraphSchema_GenericGraph.generated.h"
class UEdNode_GenericGraphNode;
class UEdNode_GenericGraphEdge;
class UAutoLayoutStrategy;
/** Action to add a node to the graph */
USTRUCT()
struct GENERICGRAPHEDITOR_API FAssetSchemaAction_GenericGraph_NewNode : public FEdGraphSchemaAction
{
GENERATED_USTRUCT_BODY();
public:
FAssetSchemaAction_GenericGraph_NewNode(): NodeTemplate(nullptr) {}
FAssetSchemaAction_GenericGraph_NewNode(const FText& InNodeCategory, const FText& InMenuDesc, const FText& InToolTip, const int32 InGrouping)
: FEdGraphSchemaAction(InNodeCategory, InMenuDesc, InToolTip, InGrouping), NodeTemplate(nullptr) {}
virtual UEdGraphNode* PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode = true) override;
virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
UEdNode_GenericGraphNode* NodeTemplate;
};
USTRUCT()
struct GENERICGRAPHEDITOR_API FAssetSchemaAction_GenericGraph_NewEdge : public FEdGraphSchemaAction
{
GENERATED_USTRUCT_BODY();
public:
FAssetSchemaAction_GenericGraph_NewEdge(): NodeTemplate(nullptr){}
FAssetSchemaAction_GenericGraph_NewEdge(const FText& InNodeCategory, const FText& InMenuDesc, const FText& InToolTip, const int32 InGrouping)
: FEdGraphSchemaAction(InNodeCategory, InMenuDesc, InToolTip, InGrouping), NodeTemplate(nullptr) {}
virtual UEdGraphNode* PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode = true) override;
virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
UEdNode_GenericGraphEdge* NodeTemplate;
};
UCLASS(MinimalAPI)
class UAssetGraphSchema_GenericGraph : public UEdGraphSchema
{
GENERATED_BODY()
public:
void GetBreakLinkToSubMenuActions(class UToolMenu* Menu, class UEdGraphPin* InGraphPin);
virtual EGraphType GetGraphType(const UEdGraph* TestEdGraph) const override;
virtual void GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const override;
virtual void GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const override;
virtual const FPinConnectionResponse CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const override;
virtual bool TryCreateConnection(UEdGraphPin* A, UEdGraphPin* B) const override;
virtual bool CreateAutomaticConversionNodeAndConnections(UEdGraphPin* A, UEdGraphPin* B) const override;
virtual class FConnectionDrawingPolicy* CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const override;
virtual FLinearColor GetPinTypeColor(const FEdGraphPinType& PinType) const override;
virtual void BreakNodeLinks(UEdGraphNode& TargetNode) const override;
virtual void BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const override;
virtual void BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const override;
virtual UEdGraphPin* DropPinOnNode(UEdGraphNode* InTargetNode, const FName& InSourcePinName, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection) const override;
virtual bool SupportsDropPinOnNode(UEdGraphNode* InTargetNode, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection, FText& OutErrorMessage) const override;
virtual bool IsCacheVisualizationOutOfDate(int32 InVisualizationCacheID) const override;
virtual int32 GetCurrentVisualizationCacheID() const override;
virtual void ForceVisualizationCacheClear() const override;
private:
static int32 CurrentCacheRefreshID;
};

View File

@ -0,0 +1,45 @@
#pragma once
#include "CoreMinimal.h"
namespace GenericGraphColors
{
namespace NodeBody
{
const FLinearColor Default(0.1f, 0.1f, 0.1f);
const FLinearColor Root(0.5f, 0.5f, 0.5f, 0.1f);
const FLinearColor Error(1.0f, 0.0f, 0.0f);
}
namespace NodeBorder
{
const FLinearColor Inactive(0.08f, 0.08f, 0.08f);
const FLinearColor Root(0.2f, 0.2f, 0.2f, 0.2f);
const FLinearColor Selected(1.00f, 0.08f, 0.08f);
const FLinearColor ActiveDebugging(1.0f, 1.0f, 0.0f);
const FLinearColor InactiveDebugging(0.4f, 0.4f, 0.0f);
const FLinearColor HighlightAbortRange0(0.0f, 0.22f, 0.4f);
const FLinearColor HighlightAbortRange1(0.0f, 0.4f, 0.22f);
const FLinearColor Disconnected(0.f, 0.f, 0.f);
const FLinearColor BrokenWithParent(1.f, 0.f, 1.f);
const FLinearColor QuickFind(0.f, 0.8f, 0.f);
}
namespace Pin
{
const FLinearColor Diff(0.9f, 0.2f, 0.15f);
const FLinearColor Hover(1.0f, 0.7f, 0.0f);
const FLinearColor Default(0.02f, 0.02f, 0.02f);
const FLinearColor SingleNode(0.02f, 0.02f, 0.02f);
}
namespace Connection
{
const FLinearColor Default(1.0f, 1.0f, 1.0f);
}
namespace Action
{
const FLinearColor DragMarker(1.0f, 1.0f, 0.2f);
}
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "CoreMinimal.h"
#include "ConnectionDrawingPolicy.h"
class GENERICGRAPHEDITOR_API FConnectionDrawingPolicy_GenericGraph : public FConnectionDrawingPolicy
{
protected:
UEdGraph* GraphObj;
TMap<UEdGraphNode*, int32> NodeWidgetMap;
public:
FConnectionDrawingPolicy_GenericGraph(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj);
// FConnectionDrawingPolicy interface
virtual void DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params) override;
virtual void Draw(TMap<TSharedRef<SWidget>, FArrangedWidget>& PinGeometries, FArrangedChildren& ArrangedNodes) override;
virtual void DrawSplineWithArrow(const FGeometry& StartGeom, const FGeometry& EndGeom, const FConnectionParams& Params) override;
virtual void DrawSplineWithArrow(const FVector2D& StartPoint, const FVector2D& EndPoint, const FConnectionParams& Params) override;
virtual void DrawPreviewConnector(const FGeometry& PinGeometry, const FVector2D& StartPoint, const FVector2D& EndPoint, UEdGraphPin* Pin) override;
virtual FVector2D ComputeSplineTangent(const FVector2D& Start, const FVector2D& End) const override;
virtual void DetermineLinkGeometry(FArrangedChildren& ArrangedNodes, TSharedRef<SWidget>& OutputPinWidget, UEdGraphPin* OutputPin, UEdGraphPin* InputPin, FArrangedWidget*& StartWidgetGeometry, FArrangedWidget*& EndWidgetGeometry) override;
// End of FConnectionDrawingPolicy interface
protected:
void Internal_DrawLineWithArrow(const FVector2D& StartAnchorPoint, const FVector2D& EndAnchorPoint, const FConnectionParams& Params);
};

View File

@ -0,0 +1,39 @@
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph_GenericGraph.generated.h"
class UGenericGraph;
class UGenericGraphNode;
class UGenericGraphEdge;
class UEdNode_GenericGraphNode;
class UEdNode_GenericGraphEdge;
UCLASS()
class GENERICGRAPHEDITOR_API UEdGraph_GenericGraph : public UEdGraph
{
GENERATED_BODY()
public:
UEdGraph_GenericGraph();
virtual ~UEdGraph_GenericGraph();
virtual void RebuildGenericGraph();
UGenericGraph* GetGenericGraph() const;
virtual bool Modify(bool bAlwaysMarkDirty = true) override;
virtual void PostEditUndo() override;
UPROPERTY(Transient)
TMap<UGenericGraphNode*, UEdNode_GenericGraphNode*> NodeMap;
UPROPERTY(Transient)
TMap<UGenericGraphEdge*, UEdNode_GenericGraphEdge*> EdgeMap;
protected:
void Clear();
void SortNodes(UGenericGraphNode* RootNode);
};

View File

@ -0,0 +1,42 @@
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraphNode.h"
#include "EdNode_GenericGraphEdge.generated.h"
class UGenericGraphNode;
class UGenericGraphEdge;
class UEdNode_GenericGraphNode;
UCLASS(MinimalAPI)
class UEdNode_GenericGraphEdge : public UEdGraphNode
{
GENERATED_BODY()
public:
UEdNode_GenericGraphEdge();
UPROPERTY()
class UEdGraph* Graph;
UPROPERTY(VisibleAnywhere, Instanced, Category = "GenericGraph")
UGenericGraphEdge* GenericGraphEdge;
void SetEdge(UGenericGraphEdge* Edge);
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual void PinConnectionListChanged(UEdGraphPin* Pin) override;
virtual void PrepareForCopying() override;
virtual UEdGraphPin* GetInputPin() const { return Pins[0]; }
virtual UEdGraphPin* GetOutputPin() const { return Pins[1]; }
void CreateConnections(UEdNode_GenericGraphNode* Start, UEdNode_GenericGraphNode* End);
UEdNode_GenericGraphNode* GetStartNode();
UEdNode_GenericGraphNode* GetEndNode();
};

View File

@ -0,0 +1,42 @@
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraphNode.h"
#include "GenericGraphNode.h"
#include "EdNode_GenericGraphNode.generated.h"
class UEdNode_GenericGraphEdge;
class UEdGraph_GenericGraph;
class SEdNode_GenericGraphNode;
UCLASS(MinimalAPI)
class UEdNode_GenericGraphNode : public UEdGraphNode
{
GENERATED_BODY()
public:
UEdNode_GenericGraphNode();
virtual ~UEdNode_GenericGraphNode();
UPROPERTY(VisibleAnywhere, Instanced, Category = "GenericGraph")
UGenericGraphNode* GenericGraphNode;
void SetGenericGraphNode(UGenericGraphNode* InNode);
UEdGraph_GenericGraph* GetGenericGraphEdGraph();
SEdNode_GenericGraphNode* SEdNode;
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual void PrepareForCopying() override;
virtual void AutowireNewNode(UEdGraphPin* FromPin) override;
virtual FLinearColor GetBackgroundColor() const;
virtual UEdGraphPin* GetInputPin() const;
virtual UEdGraphPin* GetOutputPin() const;
#if WITH_EDITOR
virtual void PostEditUndo() override;
#endif
};

View File

@ -0,0 +1,18 @@
#pragma once
#include "CoreMinimal.h"
class GENERICGRAPHEDITOR_API FEditorCommands_GenericGraph : public TCommands<FEditorCommands_GenericGraph>
{
public:
/** Constructor */
FEditorCommands_GenericGraph()
: TCommands<FEditorCommands_GenericGraph>("GenericGraphEditor", NSLOCTEXT("Contexts", "GenericGraphEditor", "Generic Graph Editor"), NAME_None, FAppStyle::GetAppStyleSetName())
{
}
TSharedPtr<FUICommandInfo> GraphSettings;
TSharedPtr<FUICommandInfo> AutoArrange;
virtual void RegisterCommands() override;
};

View File

@ -0,0 +1,52 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Input/DragAndDrop.h"
#include "Input/Reply.h"
#include "Widgets/SWidget.h"
#include "SGraphPin.h"
#include "GraphEditorDragDropAction.h"
class SGraphPanel;
class UEdGraph;
class FGenericGraphDragConnection : public FGraphEditorDragDropAction
{
public:
DRAG_DROP_OPERATOR_TYPE(FDragConnection, FGraphEditorDragDropAction)
typedef TArray<FGraphPinHandle> FDraggedPinTable;
static TSharedRef<FGenericGraphDragConnection> New(const TSharedRef<SGraphPanel>& InGraphPanel, const FDraggedPinTable& InStartingPins);
// FDragDropOperation interface
virtual void OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) override;
// End of FDragDropOperation interface
// FGraphEditorDragDropAction interface
virtual void HoverTargetChanged() override;
virtual FReply DroppedOnPin(FVector2D ScreenPosition, FVector2D GraphPosition) override;
virtual FReply DroppedOnNode(FVector2D ScreenPosition, FVector2D GraphPosition) override;
virtual FReply DroppedOnPanel(const TSharedRef< SWidget >& Panel, FVector2D ScreenPosition, FVector2D GraphPosition, UEdGraph& Graph) override;
virtual void OnDragged(const class FDragDropEvent& DragDropEvent) override;
// End of FGraphEditorDragDropAction interface
/*
* Function to check validity of graph pins in the StartPins list. This check helps to prevent processing graph pins which are outdated.
*/
virtual void ValidateGraphPinList(TArray<UEdGraphPin*>& OutValidPins);
protected:
typedef FGraphEditorDragDropAction Super;
// Constructor: Make sure to call Construct() after factorying one of these
FGenericGraphDragConnection(const TSharedRef<SGraphPanel>& GraphPanel, const FDraggedPinTable& DraggedPins);
protected:
TSharedPtr<SGraphPanel> GraphPanel;
FDraggedPinTable DraggingPins;
/** Offset information for the decorator widget */
FVector2D DecoratorAdjust;
};

View File

@ -0,0 +1,16 @@
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateStyle.h"
class GENERICGRAPHEDITOR_API FGenericGraphEditorStyle
{
public:
static void Initialize();
static void Shutdown();
static const FName& GetStyleSetName();
private:
static TSharedPtr<FSlateStyleSet> StyleSet;
};

View File

@ -0,0 +1,40 @@
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateColor.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SWidget.h"
#include "SNodePanel.h"
#include "SGraphNode.h"
class SToolTip;
class UEdNode_GenericGraphEdge;
class GENERICGRAPHEDITOR_API SEdNode_GenericGraphEdge : public SGraphNode
{
public:
SLATE_BEGIN_ARGS(SEdNode_GenericGraphEdge){}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UEdNode_GenericGraphEdge* InNode);
virtual bool RequiresSecondPassLayout() const override;
virtual void PerformSecondPassLayout(const TMap< UObject*, TSharedRef<SNode> >& NodeToWidgetLookup) const override;
virtual void UpdateGraphNode() override;
// Calculate position for multiple nodes to be placed between a start and end point, by providing this nodes index and max expected nodes
void PositionBetweenTwoNodesWithOffset(const FGeometry& StartGeom, const FGeometry& EndGeom, int32 NodeIndex, int32 MaxNodes) const;
void OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo);
protected:
FSlateColor GetEdgeColor() const;
const FSlateBrush* GetEdgeImage() const;
EVisibility GetEdgeImageVisibility() const;
EVisibility GetEdgeTitleVisbility() const;
private:
TSharedPtr<STextEntryPopup> TextEntryWidget;
};

View File

@ -0,0 +1,32 @@
#pragma once
#include "CoreMinimal.h"
#include "SGraphNode.h"
class UEdNode_GenericGraphNode;
class GENERICGRAPHEDITOR_API SEdNode_GenericGraphNode : public SGraphNode
{
public:
SLATE_BEGIN_ARGS(SEdNode_GenericGraphNode) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UEdNode_GenericGraphNode* InNode);
virtual void UpdateGraphNode() override;
virtual void CreatePinWidgets() override;
virtual void AddPin(const TSharedRef<SGraphPin>& PinToAdd) override;
virtual bool IsNameReadOnly() const override;
void OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo);
virtual FSlateColor GetBorderBackgroundColor() const;
virtual FSlateColor GetBackgroundColor() const;
virtual EVisibility GetDragOverMarkerVisibility() const;
virtual const FSlateBrush* GetNameIcon() const;
protected:
};

View File

@ -0,0 +1,42 @@
#pragma once
#include "CoreMinimal.h"
#include "Settings_GenericGraphEditor.generated.h"
UENUM(BlueprintType)
enum class EAutoLayoutStrategy : uint8
{
Tree,
ForceDirected,
};
UCLASS()
class GENERICGRAPHEDITOR_API UGenericGraphEditorSettings : public UObject
{
GENERATED_BODY()
public:
UGenericGraphEditorSettings();
virtual ~UGenericGraphEditorSettings();
UPROPERTY(EditDefaultsOnly, Category = "AutoArrange")
float OptimalDistance;
UPROPERTY(EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
EAutoLayoutStrategy AutoLayoutStrategy;
UPROPERTY(EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
int32 MaxIteration;
UPROPERTY(EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
bool bFirstPassOnly;
UPROPERTY(EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
bool bRandomInit;
UPROPERTY(EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
float InitTemperature;
UPROPERTY(EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
float CoolDownRate;
};

View File

@ -0,0 +1,23 @@
#pragma once
#include "Modules/ModuleManager.h"
#include "GenericGraphEditorModule.h"
#include <IAssetTools.h>
#include <EdGraphUtilities.h>
class FGenericGraphEditor : public IGenericGraphEditor
{
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef<IAssetTypeActions> Action);
private:
TArray< TSharedPtr<IAssetTypeActions> > CreatedAssetTypeActions;
EAssetTypeCategories::Type GenericGraphAssetCategoryBit;
TSharedPtr<FGraphPanelNodeFactory> GraphPanelNodeFactory_GenericGraph;
};

View File

@ -0,0 +1,34 @@
#include"Modules/ModuleManager.h"
DECLARE_LOG_CATEGORY_EXTERN(GenericGraphEditor, Log, All);
/**
* The public interface to this module
*/
class IGenericGraphEditor : public IModuleInterface
{
public:
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static IGenericGraphEditor& Get()
{
return FModuleManager::LoadModuleChecked< IGenericGraphEditor >("GenericGraphEditor");
}
/**
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
*
* @return True if the module is loaded and ready to use
*/
static bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("GenericGraphEditor");
}
};

View File

@ -0,0 +1,13 @@
#pragma once
#include "GenericGraph.h"
#include "GenericGraphNode.h"
#include "GenericGraphEdge.h"
// You should place include statements to your module's private header files here. You only need to
// add includes for headers that are used in most of your module's source files though.
#include "GenericGraphEditor.h"
#define LOG_INFO(FMT, ...) UE_LOG(GenericGraphEditor, Display, (FMT), ##__VA_ARGS__)
#define LOG_WARNING(FMT, ...) UE_LOG(GenericGraphEditor, Warning, (FMT), ##__VA_ARGS__)
#define LOG_ERROR(FMT, ...) UE_LOG(GenericGraphEditor, Error, (FMT), ##__VA_ARGS__)

View File

@ -0,0 +1,22 @@
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "GenericGraph.h"
#include "GenericGraphFactory.generated.h"
UCLASS()
class GENERICGRAPHEDITOR_API UGenericGraphFactory : public UFactory
{
GENERATED_BODY()
public:
UGenericGraphFactory();
virtual ~UGenericGraphFactory();
UPROPERTY(EditAnywhere, Category=DataAsset)
TSubclassOf<UGenericGraph> GenericGraphClass;
virtual bool ConfigureProperties() override;
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
};

View File

@ -0,0 +1,8 @@
#pragma once
#include <EdGraphUtilities.h>
#include <EdGraph/EdGraphNode.h>
class FGraphPanelNodeFactory_GenericGraph : public FGraphPanelNodeFactory
{
virtual TSharedPtr<class SGraphNode> CreateNode(UEdGraphNode* Node) const override;
};

View File

@ -0,0 +1,51 @@
using UnrealBuildTool;
public class GenericGraphRuntime : ModuleRules
{
public GenericGraphRuntime(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
bLegacyPublicIncludePaths = false;
ShadowVariableWarningLevel = WarningLevel.Error;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
"GenericGraphRuntime/Private",
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
// ... add private dependencies that you statically link with here ...
"Slate",
"SlateCore",
"GameplayTags"
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@ -0,0 +1,136 @@
#include "GenericGraph.h"
#include "GenericGraphRuntimePCH.h"
#include "Engine/Engine.h"
#define LOCTEXT_NAMESPACE "GenericGraph"
UGenericGraph::UGenericGraph()
{
NodeType = UGenericGraphNode::StaticClass();
EdgeType = UGenericGraphEdge::StaticClass();
bEdgeEnabled = true;
#if WITH_EDITORONLY_DATA
EdGraph = nullptr;
bCanRenameNode = true;
#endif
}
UGenericGraph::~UGenericGraph()
{
}
void UGenericGraph::Print(bool ToConsole /*= true*/, bool ToScreen /*= true*/)
{
int Level = 0;
TArray<UGenericGraphNode*> CurrLevelNodes = RootNodes;
TArray<UGenericGraphNode*> NextLevelNodes;
while (CurrLevelNodes.Num() != 0)
{
for (int i = 0; i < CurrLevelNodes.Num(); ++i)
{
UGenericGraphNode* Node = CurrLevelNodes[i];
check(Node != nullptr);
FString Message = FString::Printf(TEXT("%s, Level %d"), *Node->GetDescription().ToString(), Level);
if (ToConsole)
{
LOG_INFO(TEXT("%s"), *Message);
}
if (ToScreen && GEngine != nullptr)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Blue, Message);
}
for (int j = 0; j < Node->ChildrenNodes.Num(); ++j)
{
NextLevelNodes.Add(Node->ChildrenNodes[j]);
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
++Level;
}
}
int UGenericGraph::GetLevelNum() const
{
int Level = 0;
TArray<UGenericGraphNode*> CurrLevelNodes = RootNodes;
TArray<UGenericGraphNode*> NextLevelNodes;
while (CurrLevelNodes.Num() != 0)
{
for (int i = 0; i < CurrLevelNodes.Num(); ++i)
{
UGenericGraphNode* Node = CurrLevelNodes[i];
check(Node != nullptr);
for (int j = 0; j < Node->ChildrenNodes.Num(); ++j)
{
NextLevelNodes.Add(Node->ChildrenNodes[j]);
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
++Level;
}
return Level;
}
void UGenericGraph::GetNodesByLevel(int Level, TArray<UGenericGraphNode*>& Nodes)
{
int CurrLEvel = 0;
TArray<UGenericGraphNode*> NextLevelNodes;
Nodes = RootNodes;
while (Nodes.Num() != 0)
{
if (CurrLEvel == Level)
break;
for (int i = 0; i < Nodes.Num(); ++i)
{
UGenericGraphNode* Node = Nodes[i];
check(Node != nullptr);
for (int j = 0; j < Node->ChildrenNodes.Num(); ++j)
{
NextLevelNodes.Add(Node->ChildrenNodes[j]);
}
}
Nodes = NextLevelNodes;
NextLevelNodes.Reset();
++CurrLEvel;
}
}
void UGenericGraph::ClearGraph()
{
for (int i = 0; i < AllNodes.Num(); ++i)
{
UGenericGraphNode* Node = AllNodes[i];
if (Node)
{
Node->ParentNodes.Empty();
Node->ChildrenNodes.Empty();
Node->Edges.Empty();
}
}
AllNodes.Empty();
RootNodes.Empty();
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,23 @@
#include "GenericGraphEdge.h"
UGenericGraphEdge::UGenericGraphEdge()
{
}
UGenericGraphEdge::~UGenericGraphEdge()
{
}
UGenericGraph* UGenericGraphEdge::GetGraph() const
{
return Graph;
}
#if WITH_EDITOR
void UGenericGraphEdge::SetNodeTitle(const FText& NewTitle)
{
NodeTitle = NewTitle;
}
#endif // #if WITH_EDITOR

View File

@ -0,0 +1,90 @@
#include "GenericGraphNode.h"
#include "GenericGraph.h"
#define LOCTEXT_NAMESPACE "GenericGraphNode"
UGenericGraphNode::UGenericGraphNode()
{
#if WITH_EDITORONLY_DATA
CompatibleGraphType = UGenericGraph::StaticClass();
BackgroundColor = FLinearColor::Black;
#endif
}
UGenericGraphNode::~UGenericGraphNode()
{
}
UGenericGraphEdge* UGenericGraphNode::GetEdge(UGenericGraphNode* ChildNode)
{
return Edges.Contains(ChildNode) ? Edges.FindChecked(ChildNode) : nullptr;
}
FText UGenericGraphNode::GetDescription_Implementation() const
{
return LOCTEXT("NodeDesc", "Generic Graph Node");
}
#if WITH_EDITOR
bool UGenericGraphNode::IsNameEditable() const
{
return true;
}
FLinearColor UGenericGraphNode::GetBackgroundColor() const
{
return BackgroundColor;
}
FText UGenericGraphNode::GetNodeTitle() const
{
return NodeTitle.IsEmpty() ? GetDescription() : NodeTitle;
}
void UGenericGraphNode::SetNodeTitle(const FText& NewTitle)
{
NodeTitle = NewTitle;
}
bool UGenericGraphNode::CanCreateConnection(UGenericGraphNode* Other, FText& ErrorMessage)
{
return true;
}
bool UGenericGraphNode::CanCreateConnectionTo(UGenericGraphNode* Other, int32 NumberOfChildrenNodes, FText& ErrorMessage)
{
if (ChildrenLimitType == ENodeLimit::Limited && NumberOfChildrenNodes >= ChildrenLimit)
{
ErrorMessage = FText::FromString("Children limit exceeded");
return false;
}
return CanCreateConnection(Other, ErrorMessage);
}
bool UGenericGraphNode::CanCreateConnectionFrom(UGenericGraphNode* Other, int32 NumberOfParentNodes, FText& ErrorMessage)
{
if (ParentLimitType == ENodeLimit::Limited && NumberOfParentNodes >= ParentLimit)
{
ErrorMessage = FText::FromString("Parent limit exceeded");
return false;
}
return true;
}
#endif
bool UGenericGraphNode::IsLeafNode() const
{
return ChildrenNodes.Num() == 0;
}
UGenericGraph* UGenericGraphNode::GetGraph() const
{
return Graph;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,29 @@
#include "GenericGraphRuntimePCH.h"
DEFINE_LOG_CATEGORY(GenericGraphRuntime)
class FGenericGraphRuntime : public IGenericGraphRuntime
{
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
IMPLEMENT_MODULE( FGenericGraphRuntime, GenericGraphRuntime )
void FGenericGraphRuntime::StartupModule()
{
// This code will execute after your module is loaded into memory (but after global variables are initialized, of course.)
}
void FGenericGraphRuntime::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}

View File

@ -0,0 +1,12 @@
#pragma once
// #include "CoreUObject.h"
// #include "Engine.h"
// You should place include statements to your module's private header files here. You only need to
// add includes for headers that are used in most of your module's source files though.
#include "IGenericGraphRuntime.h"
#define LOG_INFO(FMT, ...) UE_LOG(GenericGraphRuntime, Display, (FMT), ##__VA_ARGS__)
#define LOG_WARNING(FMT, ...) UE_LOG(GenericGraphRuntime, Warning, (FMT), ##__VA_ARGS__)
#define LOG_ERROR(FMT, ...) UE_LOG(GenericGraphRuntime, Error, (FMT), ##__VA_ARGS__)

View File

@ -0,0 +1,60 @@
#pragma once
#include "CoreMinimal.h"
#include "GenericGraphNode.h"
#include "GenericGraphEdge.h"
#include "GameplayTagContainer.h"
#include "GenericGraph.generated.h"
UCLASS(Blueprintable)
class GENERICGRAPHRUNTIME_API UGenericGraph : public UObject
{
GENERATED_BODY()
public:
UGenericGraph();
virtual ~UGenericGraph();
UPROPERTY(EditDefaultsOnly, Category = "GenericGraph")
FString Name;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraph")
TSubclassOf<UGenericGraphNode> NodeType;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraph")
TSubclassOf<UGenericGraphEdge> EdgeType;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GenericGraph")
FGameplayTagContainer GraphTags;
UPROPERTY(BlueprintReadOnly, Category = "GenericGraph")
TArray<UGenericGraphNode*> RootNodes;
UPROPERTY(BlueprintReadOnly, Category = "GenericGraph")
TArray<UGenericGraphNode*> AllNodes;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GenericGraph")
bool bEdgeEnabled;
UFUNCTION(BlueprintCallable, Category = "GenericGraph")
void Print(bool ToConsole = true, bool ToScreen = true);
UFUNCTION(BlueprintCallable, Category = "GenericGraph")
int GetLevelNum() const;
UFUNCTION(BlueprintCallable, Category = "GenericGraph")
void GetNodesByLevel(int Level, TArray<UGenericGraphNode*>& Nodes);
void ClearGraph();
#if WITH_EDITORONLY_DATA
UPROPERTY()
class UEdGraph* EdGraph;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraph_Editor")
bool bCanRenameNode;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraph_Editor")
bool bCanBeCyclical;
#endif
};

View File

@ -0,0 +1,48 @@
#pragma once
#include "CoreMinimal.h"
#include "GenericGraphNode.h"
#include "GenericGraphEdge.generated.h"
class UGenericGraph;
UCLASS(Blueprintable)
class GENERICGRAPHRUNTIME_API UGenericGraphEdge : public UObject
{
GENERATED_BODY()
public:
UGenericGraphEdge();
virtual ~UGenericGraphEdge();
UPROPERTY(VisibleAnywhere, Category = "GenericGraphNode")
UGenericGraph* Graph;
UPROPERTY(BlueprintReadOnly, Category = "GenericGraphEdge")
UGenericGraphNode* StartNode;
UPROPERTY(BlueprintReadOnly, Category = "GenericGraphEdge")
UGenericGraphNode* EndNode;
UFUNCTION(BlueprintPure, Category = "GenericGraphEdge")
UGenericGraph* GetGraph() const;
#if WITH_EDITORONLY_DATA
UPROPERTY(EditDefaultsOnly, Category = "GenericGraphNode_Editor")
bool bShouldDrawTitle = false;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraphNode_Editor")
FText NodeTitle;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraphEdge")
FLinearColor EdgeColour = FLinearColor(0.9f, 0.9f, 0.9f, 1.0f);
#endif
#if WITH_EDITOR
virtual FText GetNodeTitle() const { return NodeTitle; }
FLinearColor GetEdgeColour() { return EdgeColour; }
virtual void SetNodeTitle(const FText& NewTitle);
#endif
};

View File

@ -0,0 +1,94 @@
#pragma once
#include "CoreMinimal.h"
#include "Templates/SubclassOf.h"
#include "GenericGraphNode.generated.h"
class UGenericGraph;
class UGenericGraphEdge;
UENUM(BlueprintType)
enum class ENodeLimit : uint8
{
Unlimited,
Limited
};
UCLASS(Blueprintable)
class GENERICGRAPHRUNTIME_API UGenericGraphNode : public UObject
{
GENERATED_BODY()
public:
UGenericGraphNode();
virtual ~UGenericGraphNode();
UPROPERTY(VisibleDefaultsOnly, Category = "GenericGraphNode")
UGenericGraph* Graph;
UPROPERTY(BlueprintReadOnly, Category = "GenericGraphNode")
TArray<UGenericGraphNode*> ParentNodes;
UPROPERTY(BlueprintReadOnly, Category = "GenericGraphNode")
TArray<UGenericGraphNode*> ChildrenNodes;
UPROPERTY(BlueprintReadOnly, Category = "GenericGraphNode")
TMap<UGenericGraphNode*, UGenericGraphEdge*> Edges;
UFUNCTION(BlueprintCallable, Category = "GenericGraphNode")
virtual UGenericGraphEdge* GetEdge(UGenericGraphNode* ChildNode);
UFUNCTION(BlueprintCallable, Category = "GenericGraphNode")
bool IsLeafNode() const;
UFUNCTION(BlueprintCallable, Category = "GenericGraphNode")
UGenericGraph* GetGraph() const;
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "GenericGraphNode")
FText GetDescription() const;
virtual FText GetDescription_Implementation() const;
//////////////////////////////////////////////////////////////////////////
#if WITH_EDITORONLY_DATA
UPROPERTY(EditDefaultsOnly, Category = "GenericGraphNode_Editor")
FText NodeTitle;
UPROPERTY(VisibleDefaultsOnly, Category = "GenericGraphNode_Editor")
TSubclassOf<UGenericGraph> CompatibleGraphType;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraphNode_Editor")
FLinearColor BackgroundColor;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraphNode_Editor")
FText ContextMenuName;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraphNode_Editor")
ENodeLimit ParentLimitType;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraphNode_Editor" ,meta = (ClampMin = "0",EditCondition = "ParentLimitType == ENodeLimit::Limited", EditConditionHides))
int32 ParentLimit;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraphNode_Editor")
ENodeLimit ChildrenLimitType;
UPROPERTY(EditDefaultsOnly, Category = "GenericGraphNode_Editor" ,meta = (ClampMin = "0",EditCondition = "ChildrenLimitType == ENodeLimit::Limited", EditConditionHides))
int32 ChildrenLimit;
#endif
#if WITH_EDITOR
virtual bool IsNameEditable() const;
virtual FLinearColor GetBackgroundColor() const;
virtual FText GetNodeTitle() const;
virtual void SetNodeTitle(const FText& NewTitle);
virtual bool CanCreateConnection(UGenericGraphNode* Other, FText& ErrorMessage);
virtual bool CanCreateConnectionTo(UGenericGraphNode* Other, int32 NumberOfChildrenNodes, FText& ErrorMessage);
virtual bool CanCreateConnectionFrom(UGenericGraphNode* Other, int32 NumberOfParentNodes, FText& ErrorMessage);
#endif
};

View File

@ -0,0 +1,36 @@
#pragma once
#include "Modules/ModuleManager.h"
DECLARE_LOG_CATEGORY_EXTERN(GenericGraphRuntime, Log, All);
/**
* The public interface to this module
*/
class IGenericGraphRuntime : public IModuleInterface
{
public:
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static IGenericGraphRuntime& Get()
{
return FModuleManager::LoadModuleChecked< IGenericGraphRuntime >("GenericGraphRuntime");
}
/**
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
*
* @return True if the module is loaded and ready to use
*/
static bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded( "GenericGraphRuntime" );
}
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,51 @@
GenericGraphPlugin
==================
Generic graph data structure plugin for ue4
.. image:: docs/images/GenericGraph.png
Feature
-------
* Custom asset type
* UE4 BehaviorTree-like asset editor
* Extendable graph node type
* Extendable graph edge type
* Extendable graph type(new asset type with generic graph editor, C++ only)
Usage
-----
* Ability system
* Dialogue system
* Quest system
* Etc
Install
-------
#. Clone this project to ${YourProject}/Plugins/
#. Generate project file
#. Compile
Tutorial
--------
`Dialogue System`_ (WIP)
Example
-------
Dialogue System and ability system: SRPGTemplate_
.. image:: docs/images/dialogue/dialogue01.png
.. image:: docs/images/dialogue/dialogue02.png
.. image:: docs/images/dialogue/dialogue03.png
.. image:: docs/images/ability-graph.png
.. _Dialogue System: https://jinyuliao.github.io/blog/html/2017/12/15/ue4_dialogue_system_part1.html
.. _SRPGTemplate: https://github.com/jinyuliao/SRPGTemplate