Okay so with he help of and his tutorial Twitch I finally managed to create a new tabbed window with a horizontal tree, pretty nice!
It is possible to make a vertical tree but more code is needed.
For the benefit of the community here is the source code:
YaraGameEditor.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "Engine.h"
#include "ModuleInterface.h"
#include "EdGraphUtilities.h"
class YaraGameEditorModule : public IModuleInterface
{
public:
YaraGameEditorModule();
private:
// IModuleInterface
void StartupModule() override;
void ShutdownModule() override;
// Member functions
static void InvokePlannerDebugger(UClass * Class);
TSharedRef<SDockTab> SpawnPlannerViewerTab(const FSpawnTabArgs& SpawnTabArgs);
static FName m_PlannerViewerTabId;
TSharedPtr< FGraphPanelNodeFactory > m_GraphPanelNodeFactory;
};
YaraGameEditor.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "YaraGameEditorPCH.h"
#include "YaraGameEditor.h"
#include "LevelEditor.h"
#include "PropertyEditorModule.h"
#include "MultiBoxExtender.h"
#include "PlannerDebugger.h"
#include "PlannerViewer.h"
#include "SDockTab.h"
#include "PlannerNode.h"
#include "EdGraphNode_PlannerDebugger.h"
#define LOCTEXT_NAMESPACE "YaraGameEditorModule"
FName YaraGameEditorModule::m_PlannerViewerTabId = "PlannerViewer";
class FGraphPanelNodeFactory_PlannerDebugger : public FGraphPanelNodeFactory
{
virtual TSharedPtr< class SGraphNode > CreateNode( UEdGraphNode* Node ) const override
{
if ( UEdGraphNode_PlannerDebugger* DependencyNode = Cast< UEdGraphNode_PlannerDebugger >( Node ) )
{
return SNew( UPlannerNode, DependencyNode );
}
return NULL;
}
};
YaraGameEditorModule::YaraGameEditorModule()
{
}
void YaraGameEditorModule::StartupModule()
{
FLevelEditorModule & LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >( TEXT("LevelEditor") );
struct Local
{
static void AddMenuCommands( FMenuBuilder & MenuBuilder)
{
FString FriendlyName = "Planner Debugger";
FText MenuDescription = FText::Format( LOCTEXT( "ToolMenuDescription", "{0}" ), FText::FromString( FriendlyName ) );
FText MenuToolTip = FText::Format( LOCTEXT( "ToolMenuToolTip", "Execute the {0} tool" ), FText::FromString( FriendlyName ) );
FUIAction Action(FExecuteAction::CreateStatic( &YaraGameEditorModule::InvokePlannerDebugger, UPlannerDebugger::StaticClass() ) );
MenuBuilder.AddMenuEntry( MenuDescription, MenuToolTip, FSlateIcon(), Action );
}
};
TSharedRef< FExtender > MenuExtender( new FExtender() );
MenuExtender->AddMenuExtension( "LevelEditor", EExtensionHook::After, nullptr, FMenuExtensionDelegate::CreateStatic( &Local::AddMenuCommands ) );
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender( MenuExtender );
// This overrides the node creation process and enables to make the link between the node and the slate representation of the node
m_GraphPanelNodeFactory = MakeShareable( new FGraphPanelNodeFactory_PlannerDebugger() );
FEdGraphUtilities::RegisterVisualNodeFactory(m_GraphPanelNodeFactory);
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(m_PlannerViewerTabId, FOnSpawnTab::CreateRaw(this, &YaraGameEditorModule::SpawnPlannerViewerTab))
.SetDisplayName( LOCTEXT( "PlannerDebuggerTitle", "Planner Debugger" ) )
.SetMenuType( ETabSpawnerMenuType::Hidden );
}
void YaraGameEditorModule::InvokePlannerDebugger( UClass * ToolClass )
{
TSharedRef< SDockTab > DockTabRef = FGlobalTabmanager::Get()->InvokeTab( m_PlannerViewerTabId );
TSharedRef< UPlannerViewer > PlannerViewer = StaticCastSharedRef< UPlannerViewer >( DockTabRef->GetContent() );
}
TSharedRef<SDockTab> YaraGameEditorModule::SpawnPlannerViewerTab( const FSpawnTabArgs& SpawnTabArgs )
{
TSharedRef<SDockTab> NewTab = SNew( SDockTab )
.TabRole( ETabRole::NomadTab );
NewTab->SetContent( SNew( UPlannerViewer ) );
return NewTab;
}
void YaraGameEditorModule::ShutdownModule()
{
if ( !UObjectInitialized() )
{
return;
}
if ( m_GraphPanelNodeFactory.IsValid() )
{
FEdGraphUtilities::UnregisterVisualNodeFactory( m_GraphPanelNodeFactory );
m_GraphPanelNodeFactory.Reset();
}
}
IMPLEMENT_GAME_MODULE( YaraGameEditorModule, YaraGameEditor );
#undef LOCTEXT_NAMESPACE
PlannerViewer.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "Engine.h"
#include "GraphEditor.h"
class UPlannerDebugger;
class UPlannerViewer : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS( UPlannerViewer ){}
SLATE_END_ARGS()
~UPlannerViewer();
/** Constructs this widget with InArgs */
void Construct(const FArguments& InArgs);
// SWidget implementation
void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
// End SWidget implementation
private:
private:
TSharedPtr< SGraphEditor > m_GraphEditorPtr;
UPlannerDebugger * m_PlannerDebugger;
};
PlannerViewer.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "YaraGameEditorPCH.h"
#include "PlannerViewer.h"
#include "PlannerDebugger.h"
UPlannerViewer::~UPlannerViewer()
{
if ( !GExitPurge )
{
if ( ensure( m_PlannerDebugger ) )
{
m_PlannerDebugger->RemoveFromRoot();
}
}
}
void UPlannerViewer::Construct( const FArguments& InArgs )
{
// Create an action list and register commands
//RegisterActions();
// Create the graph
m_PlannerDebugger = ConstructObject< UPlannerDebugger >( UPlannerDebugger::StaticClass() );
m_PlannerDebugger->Schema = UEdGraphSchema::StaticClass();
m_PlannerDebugger->AddToRoot();
// Create the graph editor
m_GraphEditorPtr = SNew(SGraphEditor)
.GraphToEdit(m_PlannerDebugger);
ChildSlot
[
SNew(SVerticalBox)
// Graph
+ SVerticalBox::Slot()
.FillHeight(1.f)
[
SNew(SOverlay)
+ SOverlay::Slot()
[
m_GraphEditorPtr.ToSharedRef()
]
]
];
m_PlannerDebugger->RefreshGraph();
}
void UPlannerViewer::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
}
PlannerNode.h
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "SGraphNode.h"
class UEdGraphNode_PlannerDebugger;
class UPlannerNode : public SGraphNode
{
public:
SLATE_BEGIN_ARGS( UPlannerNode ){}
SLATE_END_ARGS()
// Constructs this widget with InArgs
void Construct( const FArguments& InArgs, UEdGraphNode_PlannerDebugger* InNode );
// SGraphNode implementation
virtual void UpdateGraphNode() override;
// End SGraphNode implementation
private:
};
PlannerNode.cpp
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "YaraGameEditorPCH.h"
#include "PlannerNode.h"
#include "EdGraphNode_PlannerDebugger.h"
#include "Text/SInlineEditableTextBlock.h"
#define LOCTEXT_NAMESPACE "PlannerDebugger"
void UPlannerNode::Construct(const FArguments& InArgs, UEdGraphNode_PlannerDebugger* InNode)
{
GraphNode = InNode;
UpdateGraphNode();
}
void UPlannerNode::UpdateGraphNode()
{
OutputPins.Empty();
// Reset variables that are going to be exposed, in case we are refreshing an already setup node.
RightNodeBox.Reset();
LeftNodeBox.Reset();
UpdateErrorInfo();
//
// ______________________
// | TITLE AREA |
// +-------+------+-------+
// | (>) L | | R (>) |
// | (>) E | | I (>) |
// | (>) F | | G (>) |
// | (>) T | | H (>) |
// | | | T (>) |
// |_______|______|_______|
//
TSharedPtr<SVerticalBox> MainVerticalBox;
TSharedPtr<SErrorText> ErrorText;
TSharedPtr<SNodeTitle> NodeTitle = SNew( SNodeTitle, GraphNode );
// No idea what that is
ContentScale.Bind(this, &UPlannerNode::GetContentScale);
GetOrAddSlot(ENodeZone::Center)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SAssignNew( MainVerticalBox, SVerticalBox )
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Graph.Node.Body"))
.Padding(0)
[
SNew(SVerticalBox)
.ToolTipText(this, &UPlannerNode::GetNodeTooltip)
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
[
SNew(SOverlay)
+ SOverlay::Slot()
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("Graph.Node.TitleGloss"))
]
+ SOverlay::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Graph.Node.ColorSpill"))
// The extra margin on the right
// is for making the color spill stretch well past the node title
.Padding(FMargin(10, 5, 30, 3))
.BorderBackgroundColor(this, &UPlannerNode::GetNodeTitleColor)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew( STextBlock )
.Text( NodeTitle.Get(), &SNodeTitle::GetHeadTitle )
]
+ SVerticalBox::Slot()
.AutoHeight()
[
NodeTitle.ToSharedRef()
]
]
]
+ SOverlay::Slot()
.VAlign(VAlign_Top)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Graph.Node.TitleHighlight"))
.Visibility(EVisibility::HitTestInvisible)
[
SNew(SSpacer)
.Size(FVector2D(20, 20))
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(1.0f)
[
// POPUP ERROR MESSAGE
SAssignNew(ErrorText, SErrorText)
.BackgroundColor(this, &UPlannerNode::GetErrorColor)
.ToolTipText(this, &UPlannerNode::GetErrorMsgToolTip)
]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
[
// NODE CONTENT AREA
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("NoBorder"))
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding(FMargin(0, 3))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
// LEFT
SNew(SBox)
.WidthOverride(40)
[
SAssignNew(LeftNodeBox, SVerticalBox)
]
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.FillWidth(1.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew( STextBlock )
.Text( static_cast< UEdGraphNode_PlannerDebugger * >( GraphNode )->GetFirstLine() )
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(static_cast< UEdGraphNode_PlannerDebugger * >(GraphNode)->GetSecondLine())
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
// RIGHT
SNew(SBox)
.WidthOverride(40)
[
SAssignNew(RightNodeBox, SVerticalBox)
]
]
]
]
]
]
];
//ErrorReporting = ErrorText;
//ErrorReporting->SetError(ErrorMsg);
CreateBelowWidgetControls(MainVerticalBox);
CreatePinWidgets();
}
#undef LOCTEXT_NAMESPACE
PlannerDebugger.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "GraphEditor.h"
#include "PlannerDebugger.generated.h"
UCLASS()
class UPlannerDebugger : public UEdGraph
{
GENERATED_UCLASS_BODY()
public:
void RefreshGraph();
private:
void RemoveAllNodes();
};
PlannerDebugger.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "YaraGameEditorPCH.h"
#include "PlannerDebugger.h"
#include "EdGraphNode_PlannerDebugger.h"
#define LOCTEXT_NAMESPACE "PlannerDebugger"
UPlannerDebugger::UPlannerDebugger( const FObjectInitializer& ObjectInitializer )
: Super( ObjectInitializer )
{
}
void UPlannerDebugger::RefreshGraph()
{
RemoveAllNodes();
UEdGraphNode_PlannerDebugger *const PlannerDebuggerNodeA = static_cast< UEdGraphNode_PlannerDebugger * >( CreateNode( UEdGraphNode_PlannerDebugger::StaticClass(), false ) );
UEdGraphNode_PlannerDebugger *const PlannerDebuggerNodeB = static_cast< UEdGraphNode_PlannerDebugger * >( CreateNode( UEdGraphNode_PlannerDebugger::StaticClass(), false ) );
UEdGraphNode_PlannerDebugger *const PlannerDebuggerNodeC = static_cast< UEdGraphNode_PlannerDebugger * >( CreateNode( UEdGraphNode_PlannerDebugger::StaticClass(), false ) );
PlannerDebuggerNodeA->SetupNode(FIntPoint(0, 50), "AndCondition" );
PlannerDebuggerNodeB->SetupNode(FIntPoint(200, 0), "Action", "Goal: RetrieveItem(TeddyBear)", "Method: PickupItem" );
PlannerDebuggerNodeC->SetupNode(FIntPoint(200, 100), "Action", "Goal: MoveToArea( SpotReception5 )", "Method: Walking" );
PlannerDebuggerNodeA->GetChildrenPin()->MakeLinkTo( PlannerDebuggerNodeB->GetParentPin() );
PlannerDebuggerNodeA->GetChildrenPin()->MakeLinkTo( PlannerDebuggerNodeC->GetParentPin() );
}
void UPlannerDebugger::RemoveAllNodes()
{
TArray< UEdGraphNode* > NodesToRemove = Nodes;
for (int32 NodeIndex = 0; NodeIndex < NodesToRemove.Num(); ++NodeIndex)
{
RemoveNode( NodesToRemove[NodeIndex] );
}
}
#undef LOCTEXT_NAMESPACE
EdGraphNode_PlannerDebugger.cpp
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "YaraGameEditorPCH.h"
#include "EdGraphNode_PlannerDebugger.h"
#define LOCTEXT_NAMESPACE "PlannerDebugger"
//////////////////////////////////////////////////////////////////////////
// UEdGraphNode_Reference
UEdGraphNode_PlannerDebugger::UEdGraphNode_PlannerDebugger( const FObjectInitializer& ObjectInitializer )
: Super( ObjectInitializer )
, m_NodeTitle( )
{
m_ChildrenPin = nullptr;
m_ParentPin = nullptr;
}
void UEdGraphNode_PlannerDebugger::SetupNode(const FIntPoint& NodePosition, const FString & Title, const FString & FirstLine, const FString & SecondLine)
{
m_NodeTitle = FText::FromString( Title );
m_FirstLine = FirstLine;
m_SecondLine = SecondLine;
NodePosX = NodePosition.X;
NodePosY = NodePosition.Y;
AllocateDefaultPins();
}
void UEdGraphNode_PlannerDebugger::AddChild(UEdGraphNode_PlannerDebugger* ChildPlannerNode)
{
UEdGraphPin* ParentPinOfChild = ChildPlannerNode->GetParentPin();
if ( ensure( ParentPinOfChild ) )
{
ParentPinOfChild->bHidden = false;
m_ChildrenPin->bHidden = false;
m_ChildrenPin->MakeLinkTo( ParentPinOfChild );
}
}
FText UEdGraphNode_PlannerDebugger::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
return m_NodeTitle;
}
void UEdGraphNode_PlannerDebugger::AllocateDefaultPins()
{
m_ChildrenPin = CreatePin( EEdGraphPinDirection::EGPD_Output, TEXT(""), TEXT(""), NULL, false, false, TEXT("") );
m_ParentPin = CreatePin( EEdGraphPinDirection::EGPD_Input, TEXT(""), TEXT(""), NULL, false, false, TEXT("") );
m_ChildrenPin->bHidden = false;
m_ParentPin->bHidden = false;
}
UEdGraphPin* UEdGraphNode_PlannerDebugger::GetChildrenPin()
{
return m_ChildrenPin;
}
UEdGraphPin* UEdGraphNode_PlannerDebugger::GetParentPin()
{
return m_ParentPin;
}
#undef LOCTEXT_NAMESPACE
EdGraphNode_PlannerDebugger.h
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "EdGraphNode_PlannerDebugger.generated.h"
UCLASS( )
class UEdGraphNode_PlannerDebugger : public UEdGraphNode
{
GENERATED_UCLASS_BODY()
public :
void AddChild( UEdGraphNode_PlannerDebugger* ChildPlannerNode );
void SetupNode(const FIntPoint& NodePosition, const FString & Title, const FString & FirstLine = FString(""), const FString & SecondLine = FString(""));
virtual UEdGraphPin* GetChildrenPin();
virtual UEdGraphPin* GetParentPin();
const FString & GetFirstLine()const { return m_FirstLine; }
const FString & GetSecondLine()const { return m_SecondLine; }
private:
// UEdGraphNode implementation
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual void AllocateDefaultPins() override;
// End UEdGraphNode implementation
private:
FText m_NodeTitle;
FString m_FirstLine;
FString m_SecondLine;
UEdGraphPin* m_ChildrenPin;
UEdGraphPin* m_ParentPin;
};