From ea4dd54ec9583312317607fc3363a587447b9092 Mon Sep 17 00:00:00 2001 From: Jamie Greunbaum Date: Wed, 27 Sep 2023 00:21:59 -0400 Subject: [PATCH] Trying to add a combo graph editor. --- Source/ComboInput/ComboInput.Build.cs | 1 + .../ComboInput/Private/ComboActionGraph.cpp | 315 ++++ .../Data/ComboActionAdditionalData.cpp | 6 + .../Private/Data/ComboActionContext.cpp | 222 +++ .../Decorators/ComboActionDecoratorBase.cpp | 55 + .../ComboActionGraphManagerInterface.cpp | 6 + .../Interfaces/ComboActionInterface.cpp | 6 + .../Private/Nodes/ComboActionGraphEdge.cpp | 6 + .../Private/Nodes/ComboActionGraphNode.cpp | 396 +++++ .../ComboActionGraphNode_ActionNodeBase.cpp | 268 +++ .../Nodes/ComboActionGraphNode_StartNode.cpp | 81 + Source/ComboInput/Public/ComboActionGraph.h | 172 ++ .../Public/Data/ComboActionAdditionalData.h | 14 + .../Public/Data/ComboActionContext.h | 203 +++ .../Public/Data/ComboActionGraphDataTypes.h | 27 + .../Decorators/ComboActionDecoratorBase.h | 254 +++ .../ComboActionGraphManagerInterface.h | 233 +++ .../Public/Interfaces/ComboActionInterface.h | 199 +++ .../Public/Nodes/ComboActionGraphEdge.h | 40 + .../Public/Nodes/ComboActionGraphNode.h | 397 +++++ .../ComboActionGraphNode_ActionNodeBase.h | 126 ++ .../Nodes/ComboActionGraphNode_StartNode.h | 35 + .../ComboInputEditor.build.cs | 3 +- .../Private/ComboActionGraphSchema.cpp | 434 +++++ .../Private/ComboActionGraphSchema.h | 164 ++ .../Private/Ed/EdComboActionGraph.cpp | 209 +++ .../Private/Ed/EdComboActionGraph.h | 49 + .../Private/Ed/EdComboActionGraphEdge.cpp | 83 + .../Private/Ed/EdComboActionGraphEdge.h | 45 + .../Private/Ed/EdComboActionGraphNode.cpp | 169 ++ .../Private/Ed/EdComboActionGraphNode.h | 60 + .../Private/Ed/SEdComboActionGraphNode.cpp | 1442 +++++++++++++++++ .../Private/Ed/SEdComboActionGraphNode.h | 89 + .../Ed/SEdComboActionGraphNodeIndex.cpp | 36 + .../Private/Ed/SEdComboActionGraphNodeIndex.h | 33 + ...nnectionDrawingPolicy_ComboActionGraph.cpp | 208 +++ ...ConnectionDrawingPolicy_ComboActionGraph.h | 36 + .../Private/Helpers/ComboActionEditorBFC.h | 86 + .../Private/Helpers/ComboActionGraphColors.h | 92 ++ .../ComboActionGraphEditorSettings.cpp | 40 + .../Settings/ComboActionGraphEditorSettings.h | 292 ++++ .../Settings/FComboActionGraphEditorStyle.cpp | 138 ++ .../Settings/FComboActionGraphEditorStyle.h | 40 + 43 files changed, 6809 insertions(+), 1 deletion(-) create mode 100644 Source/ComboInput/Private/ComboActionGraph.cpp create mode 100644 Source/ComboInput/Private/Data/ComboActionAdditionalData.cpp create mode 100644 Source/ComboInput/Private/Data/ComboActionContext.cpp create mode 100644 Source/ComboInput/Private/Decorators/ComboActionDecoratorBase.cpp create mode 100644 Source/ComboInput/Private/Interfaces/ComboActionGraphManagerInterface.cpp create mode 100644 Source/ComboInput/Private/Interfaces/ComboActionInterface.cpp create mode 100644 Source/ComboInput/Private/Nodes/ComboActionGraphEdge.cpp create mode 100644 Source/ComboInput/Private/Nodes/ComboActionGraphNode.cpp create mode 100644 Source/ComboInput/Private/Nodes/ComboActionGraphNode_ActionNodeBase.cpp create mode 100644 Source/ComboInput/Private/Nodes/ComboActionGraphNode_StartNode.cpp create mode 100644 Source/ComboInput/Public/ComboActionGraph.h create mode 100644 Source/ComboInput/Public/Data/ComboActionAdditionalData.h create mode 100644 Source/ComboInput/Public/Data/ComboActionContext.h create mode 100644 Source/ComboInput/Public/Data/ComboActionGraphDataTypes.h create mode 100644 Source/ComboInput/Public/Decorators/ComboActionDecoratorBase.h create mode 100644 Source/ComboInput/Public/Interfaces/ComboActionGraphManagerInterface.h create mode 100644 Source/ComboInput/Public/Interfaces/ComboActionInterface.h create mode 100644 Source/ComboInput/Public/Nodes/ComboActionGraphEdge.h create mode 100644 Source/ComboInput/Public/Nodes/ComboActionGraphNode.h create mode 100644 Source/ComboInput/Public/Nodes/ComboActionGraphNode_ActionNodeBase.h create mode 100644 Source/ComboInput/Public/Nodes/ComboActionGraphNode_StartNode.h create mode 100644 Source/ComboInputEditor/Private/ComboActionGraphSchema.cpp create mode 100644 Source/ComboInputEditor/Private/ComboActionGraphSchema.h create mode 100644 Source/ComboInputEditor/Private/Ed/EdComboActionGraph.cpp create mode 100644 Source/ComboInputEditor/Private/Ed/EdComboActionGraph.h create mode 100644 Source/ComboInputEditor/Private/Ed/EdComboActionGraphEdge.cpp create mode 100644 Source/ComboInputEditor/Private/Ed/EdComboActionGraphEdge.h create mode 100644 Source/ComboInputEditor/Private/Ed/EdComboActionGraphNode.cpp create mode 100644 Source/ComboInputEditor/Private/Ed/EdComboActionGraphNode.h create mode 100644 Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNode.cpp create mode 100644 Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNode.h create mode 100644 Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNodeIndex.cpp create mode 100644 Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNodeIndex.h create mode 100644 Source/ComboInputEditor/Private/FConnectionDrawingPolicy_ComboActionGraph.cpp create mode 100644 Source/ComboInputEditor/Private/FConnectionDrawingPolicy_ComboActionGraph.h create mode 100644 Source/ComboInputEditor/Private/Helpers/ComboActionEditorBFC.h create mode 100644 Source/ComboInputEditor/Private/Helpers/ComboActionGraphColors.h create mode 100644 Source/ComboInputEditor/Private/Settings/ComboActionGraphEditorSettings.cpp create mode 100644 Source/ComboInputEditor/Private/Settings/ComboActionGraphEditorSettings.h create mode 100644 Source/ComboInputEditor/Private/Settings/FComboActionGraphEditorStyle.cpp create mode 100644 Source/ComboInputEditor/Private/Settings/FComboActionGraphEditorStyle.h diff --git a/Source/ComboInput/ComboInput.Build.cs b/Source/ComboInput/ComboInput.Build.cs index c2d2c42..cc85d12 100644 --- a/Source/ComboInput/ComboInput.Build.cs +++ b/Source/ComboInput/ComboInput.Build.cs @@ -44,6 +44,7 @@ public class ComboInput : ModuleRules "Core", "CoreUObject", "Engine", + "GameplayTags", "Slate", "SlateCore", "DeveloperSettings", diff --git a/Source/ComboInput/Private/ComboActionGraph.cpp b/Source/ComboInput/Private/ComboActionGraph.cpp new file mode 100644 index 0000000..324d33f --- /dev/null +++ b/Source/ComboInput/Private/ComboActionGraph.cpp @@ -0,0 +1,315 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "ComboActionGraph.h" + +#include "Nodes/ComboActionGraphEdge.h" +#include "Nodes/ComboActionGraphNode.h" +#include "Nodes/ComboActionGraphNode_StartNode.h" + +#define LOCTEXT_NAMESPACE "ComboActionGraph" + +DEFINE_LOG_CATEGORY(LogComboActionGraph); + + +UComboActionGraph::UComboActionGraph() +{ + this->NodeType = UComboActionGraph::StaticClass(); + this->EdgeType = UComboActionGraph::StaticClass(); + + this->bEdgeEnabled = false; + this->GraphGUID = FGuid::NewGuid(); + +#if WITH_EDITORONLY_DATA + this->EdGraph = nullptr; + + this->bCanRenameNode = true; +#endif +} + +TArray UComboActionGraph::GetGraphDecorators() const +{ + TArray TempReturn; + TArray Return; + + for (const FComboActionDecorator &Itr : this->GraphDecorators) + { + if (Itr.DecoratorType != nullptr) + { + TempReturn.AddUnique(Itr); + } + } + + /* TODO: Cleanup duplicates + for (auto Itr : TempReturn) + { + + } + */ + + Return = TempReturn; + return Return; +} + +TArray UComboActionGraph::GetAllDecorators() const +{ + TArray TempReturn; + TArray Return; + + for (const UComboActionGraphNode *Itr : this->AllNodes) + { + if (Itr && Itr->GetNodeDecorators().Num() > 0) + { + TempReturn.Append(Itr->NodeDecorators); + } + } + + TempReturn.Append(this->GetGraphDecorators()); + + return TempReturn; +} + +bool UComboActionGraph::CanStartDialogueGraph() const +{ + bool bSatisfied = true; + + if (this->AllNodes.Num() == 0) + { + return false; + } + + for (const auto& Itr : this->AllNodes) + { + if (!Itr) + { + return false; + } + + if (Itr->ValidateNodeRuntime() == false) + { + return false; + } + } + + const TArray &Decorators = this->GetAllDecorators(); + + if (Decorators.Num() == 0) + { + return bSatisfied; + } + + TArray DecoratorValidations; + for (const FComboActionDecorator &Itr : Decorators) + { + if (Itr.ValidateDecorator(DecoratorValidations) == false) bSatisfied = false; + } + + if (DecoratorValidations.Num() > 0) + { + for(auto Itr : DecoratorValidations) + { + UE_LOG(LogComboActionGraph, Error, TEXT("%s"), *Itr.ToString()); + } + } + return bSatisfied; +} + +void UComboActionGraph::CreateGraph() +{ +#if WITH_EDITOR + // We already have existing Graph + if (this->EdGraph != nullptr) + { + return; + } + + // We already have existing Start Node + if (this->StartNode != nullptr) + { + return; + } + + this->StartNode = ConstructDialogueNode(); + if (this->StartNode != nullptr ) + { + this->StartNode->Graph = this; + + this->RootNodes.Add(this->StartNode); + this->AllNodes.Add(this->StartNode); + } +#endif +} + +void UComboActionGraph::ClearGraph() +{ + for (UComboActionGraphNode *Node : this->AllNodes) + { + Node->ParentNodes.Empty(); + Node->ChildrenNodes.Empty(); + Node->Edges.Empty(); + } + + this->AllNodes.Empty(); + this->RootNodes.Empty(); +} + +void UComboActionGraph::PostInitProperties() +{ + UObject::PostInitProperties(); + + // Ignore these cases + if (this->HasAnyFlags(EObjectFlags::RF_ClassDefaultObject | EObjectFlags::RF_NeedLoad)) + { + return; + } + +#if WITH_EDITOR + this->CreateGraph(); +#endif +} + +#if WITH_EDITOR +bool UComboActionGraph::ValidateGraph(TArray& ValidationErrors, bool RichTextFormat) +{ + bool bReturnValue = true; + + // GRAPH DECORATORS VALIDATION + { + TArray UsedNodeDecorators; + for (int i = 0; i < this->GraphDecorators.Num(); i++) + { + const FComboActionDecorator &Decorator = this->GraphDecorators[i]; + if (Decorator.DecoratorType) + { + UsedNodeDecorators.Add(Decorator.DecoratorType); + } + else + { + const FString RichTextReturn = + FString("* ") + .Append( TEXT("Dialogue Graph")) + .Append(": has ") + .Append(TEXT("invalid Node Decorator at Index: ")) + .Append(FString::FromInt(i)) + .Append("."); + + const FString TextReturn = + this->GetName() + .Append(": has ") + .Append(TEXT("INVALID Node Decorator at Index: ")) + .Append(FString::FromInt(i)) + .Append("."); + + ValidationErrors.Add(FText::FromString(RichTextFormat ? RichTextReturn : TextReturn)); + + bReturnValue = false; + } + } + + TMap DuplicatedDecoratorsMap; + for (const auto& Itr : UsedNodeDecorators) + { + int32 ClassAppearance = 1; + for (const auto& Itr2 : UsedNodeDecorators) + { + if (Itr != Itr2 && Itr->GetClass() == Itr2->GetClass()) + { + auto A = Itr->GetClass()->GetName(); + ClassAppearance++; + } + } + + if (ClassAppearance > 1 && DuplicatedDecoratorsMap.Contains(Itr->GetClass()) == false) + { + DuplicatedDecoratorsMap.Add(Itr->GetClass(), ClassAppearance); + } + } + + if (DuplicatedDecoratorsMap.Num() > 0) + { + for (const TTuple &Itr : DuplicatedDecoratorsMap) + { + bReturnValue = false; + + const FString RichTextReturn = + FString("* ") + .Append(TEXT("Dialogue Graph")) + .Append(": has Node Decorator ") + .Append("") + .Append(Itr.Key->GetName().LeftChop(2)) + .Append(" ") + .Append(FString::FromInt(Itr.Value)) + .Append("x times! Please, avoid duplicates!"); + + const FString TextReturn = + FString(TEXT("Dialogue Graph: has Node Decorator ")) + .Append( Itr.Key->GetName().LeftChop(2)) + .Append(" ") + .Append(FString::FromInt(Itr.Value)) + .Append("x times! Please, avoid duplicates!"); + + ValidationErrors.Add(FText::FromString(RichTextFormat ? RichTextReturn : TextReturn)); + } + } + } + + // GRAPH DECORATORS VALIDATION + for (const FComboActionDecorator &Itr : this->GraphDecorators) + { + TArray DecoratorErrors; + if (!Itr.ValidateDecorator(DecoratorErrors)) + { + for (auto Error : DecoratorErrors) + { + const FString ErrorTextRich = + FString("* ") + .Append(TEXT("Dialogue Graph: ")) + .Append(FString(Error.ToString())); + + const FString ErrorTextSimple = + this->GetName() + .Append(": ") + .Append(FString(Error.ToString())); + + ValidationErrors.Add(FText::FromString(RichTextFormat ? ErrorTextRich : ErrorTextSimple)); + + bReturnValue = false; + } + } + } + + if (this->StartNode == nullptr) + { + const FString RichTextReturn = + FString("* ") + .Append(TEXT("Dialogue Graph")) + .Append(": Has no Start Node!"); + + const FString TextReturn = + this->GetName() + .Append(": Has no Start Node!"); + + ValidationErrors.Add(FText::FromString(RichTextFormat ? RichTextReturn : TextReturn)); + + bReturnValue = false; + } + + for (UComboActionGraphNode *Itr : this->AllNodes) + { + if (Itr != nullptr && !Itr->ValidateNode(ValidationErrors, RichTextFormat)) + { + bReturnValue = false; + } + } + + return bReturnValue; +} + +EDataValidationResult UComboActionGraph::IsDataValid(TArray& ValidationErrors) +{ + return this->ValidateGraph(ValidationErrors, false) + ? EDataValidationResult::Valid + : EDataValidationResult::Invalid; +} +#endif + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ComboInput/Private/Data/ComboActionAdditionalData.cpp b/Source/ComboInput/Private/Data/ComboActionAdditionalData.cpp new file mode 100644 index 0000000..cd0be40 --- /dev/null +++ b/Source/ComboInput/Private/Data/ComboActionAdditionalData.cpp @@ -0,0 +1,6 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Data/ComboActionAdditionalData.h" + + + diff --git a/Source/ComboInput/Private/Data/ComboActionContext.cpp b/Source/ComboInput/Private/Data/ComboActionContext.cpp new file mode 100644 index 0000000..ba9249a --- /dev/null +++ b/Source/ComboInput/Private/Data/ComboActionContext.cpp @@ -0,0 +1,222 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Data/ComboActionContext.h" + +#include "Interfaces/ComboActionInterface.h" + + +bool UComboActionContext::IsValid() const +{ + return ActiveNode != nullptr;// && DialogueParticipant.GetInterface() != nullptr && PlayerDialogueParticipant.GetInterface() != nullptr; +} + +void UComboActionContext::SetComboActionContext(UComboActionGraphNode *NewActiveNode, const TArray NewAllowedChildNodes) +{ + //DialogueParticipant = NewParticipant; + //ActiveNode = NewActiveNode; + //AllowedChildNodes = NewAllowedChildNodes; + + //if (!DialogueParticipants.Contains(NewParticipant)) + //{ + // DialogueParticipants.Add(NewParticipant); + //} +} + +//void UComboActionContext::UpdateDialogueParticipant(const TScriptInterface NewParticipant) +//{ +// DialogueParticipant = NewParticipant; +// +// AddDialogueParticipant(NewParticipant); +//} +// +//void UComboActionContext::UpdateActiveDialogueNode(UComboActionGraphNode *NewActiveNode) +//{ +// ActiveNode = NewActiveNode; +//} +// +//void UComboActionContext::UpdateAllowedChildrenNodes(const TArray &NewNodes) +//{ +// AllowedChildNodes = NewNodes; +//} +// +//void UComboActionContext::UpdateActiveDialogueRowDataIndex(const int32 NewIndex) +//{ +// ActiveDialogueRowDataIndex = NewIndex; +//} +// +//void UComboActionContext::UpdateDialoguePlayerParticipant(const TScriptInterface NewParticipant) +//{ +// PlayerDialogueParticipant = NewParticipant; +// +// AddDialogueParticipant(NewParticipant); +//} +// +//void UComboActionContext::UpdateActiveDialogueParticipant(const TScriptInterface NewParticipant) +//{ +// if (NewParticipant != PlayerDialogueParticipant && NewParticipant != DialogueParticipant) +// { +// //TODO: Properly log this +// return; +// } +// +// ActiveDialogueParticipant = NewParticipant; +//} +// +//void UComboActionContext::AddTraversedNode(const UComboActionGraphNode *TraversedNode) +//{ +// if (!TraversedNode) return; +// +// // If we have already passed over this Node, then just increase the counter +// if (TraversedPath.Contains(TraversedNode->GetNodeGUID())) +// { +// TraversedPath[TraversedNode->GetNodeGUID()]++; +// } +// else +// { +// TraversedPath.Add(TraversedNode->GetNodeGUID(), 1); +// } +//} +// +//bool UComboActionContext::AddDialogueParticipants(const TArray>& NewParticipants) +//{ +// bool bSatisfied = true; +// for (const auto& Itr : NewParticipants) +// { +// const bool bTempSatisfied = AddDialogueParticipant(Itr); +// bSatisfied = bTempSatisfied ? bSatisfied : bTempSatisfied; +// } +// +// return bSatisfied; +//} +// +//bool UComboActionContext::AddDialogueParticipant(const TScriptInterface& NewParticipant) +//{ +// if (DialogueParticipants.Contains(NewParticipant)) +// { +// return false; +// } +// +// DialogueParticipants.Add(NewParticipant); +// return true; +//} +// +//bool UComboActionContext::RemoveDialogueParticipants(const TArray>& NewParticipants) +//{ +// bool bSatisfied = true; +// for (const auto& Itr : NewParticipants) +// { +// const bool bTempSatisfied = RemoveDialogueParticipant(Itr); +// bSatisfied = bTempSatisfied ? bSatisfied : bTempSatisfied; +// } +// +// return bSatisfied; +//} +// +//bool UComboActionContext::RemoveDialogueParticipant(const TScriptInterface& NewParticipant) +//{ +// if (DialogueParticipants.Contains(NewParticipant)) +// { +// DialogueParticipants.Remove(NewParticipant); +// return true; +// } +// +// return false; +//} +// +//void UComboActionContext::ClearDialogueParticipants() +//{ +// DialogueParticipants.Empty(); +//} + +void UComboActionContext::SetComboActionContextBP(UComboActionGraphNode *NewActiveNode,TArray NewAllowedChildNodes) +{ + //this->SetComboActionContext(NewParticipant, NewActiveNode, NewAllowedChildNodes); + + ComboActionContextUpdatedFromBlueprint.Broadcast(this); +} + +//void UComboActionContext::UpdateDialogueParticipantBP(const TScriptInterface NewParticipant) +//{ +// UpdateDialogueParticipant(NewParticipant); +// +// DialogueContextUpdatedFromBlueprint.Broadcast(this); +//} +// +//void UComboActionContext::UpdateActiveDialogueNodeBP(UComboActionGraphNode *NewActiveNode) +//{ +// UpdateActiveDialogueNode(NewActiveNode); +// +// DialogueContextUpdatedFromBlueprint.Broadcast(this); +//} +// +//void UComboActionContext::UpdateActiveDialogueRowBP(const FDialogueRow &NewActiveRow) +//{ +// UpdateActiveDialogueRow(NewActiveRow); +// +// DialogueContextUpdatedFromBlueprint.Broadcast(this); +//} +// +//void UComboActionContext::UpdateActiveDialogueRowDataIndexBP(const int32 NewIndex) +//{ +// UpdateActiveDialogueRowDataIndex(NewIndex); +// +// DialogueContextUpdatedFromBlueprint.Broadcast(this); +//} +// +//void UComboActionContext::UpdateDialoguePlayerParticipantBP(const TScriptInterface NewParticipant) +//{ +// UpdateDialoguePlayerParticipant(NewParticipant); +// +// DialogueContextUpdatedFromBlueprint.Broadcast(this); +//} +// +//void UComboActionContext::UpdateActiveDialogueParticipantBP(const TScriptInterface NewParticipant) +//{ +// UpdateActiveDialogueParticipant(NewParticipant); +// +// DialogueContextUpdatedFromBlueprint.Broadcast(this); +//} +// +//bool UComboActionContext::AddDialogueParticipantBP(const TScriptInterface &NewParticipant) +//{ +// if (AddDialogueParticipant(NewParticipant)) +// { +// DialogueContextUpdatedFromBlueprint.Broadcast(this); +// return true; +// } +// +// return false; +//} +// +//bool UComboActionContext::RemoveDialogueParticipantBP(const TScriptInterface &NewParticipant) +//{ +// if (RemoveDialogueParticipant(NewParticipant)) +// { +// DialogueContextUpdatedFromBlueprint.Broadcast(this); +// return true; +// } +// +// return false; +//} +// +//bool UComboActionContext::AddDialogueParticipantsBP(const TArray> &NewParticipants) +//{ +// if (AddDialogueParticipants(NewParticipants)) +// { +// DialogueContextUpdatedFromBlueprint.Broadcast(this); +// return true; +// } +// +// return false; +//} +// +//bool UComboActionContext::RemoveDialogueParticipantsBP(const TArray> &NewParticipants) +//{ +// if (RemoveDialogueParticipants(NewParticipants)) +// { +// DialogueContextUpdatedFromBlueprint.Broadcast(this); +// return true; +// } +// +// return false; +//} diff --git a/Source/ComboInput/Private/Decorators/ComboActionDecoratorBase.cpp b/Source/ComboInput/Private/Decorators/ComboActionDecoratorBase.cpp new file mode 100644 index 0000000..33a711f --- /dev/null +++ b/Source/ComboInput/Private/Decorators/ComboActionDecoratorBase.cpp @@ -0,0 +1,55 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Decorators/ComboActionDecoratorBase.h" + +#if WITH_EDITOR +#include "Editor.h" +#endif + +DEFINE_LOG_CATEGORY(LogComboActionDecoratorBase); + +#define LOCTEXT_NAMESPACE "ComboActionDecoratorBase" + + +bool UComboActionDecoratorBase::ValidateDecorator_Implementation(TArray& ValidationMessages) +{ + bool bSatisfied = true; + const FText Name = FText::FromString(GetName()); + + // This is to ensure we are not throwing InvalidWorld errors in Editor with no Gameplay. + bool bIsEditorCall = false; +#if WITH_EDITOR + if (GEditor != nullptr) + { + bIsEditorCall = !GEditor->GetPlayInEditorSessionInfo().IsSet(); + } +#endif + + if (GetOwningWorld() == nullptr && bIsEditorCall == false) + { + const FText TempText = FText::Format(LOCTEXT("ComboActionDecorator_Base_Validation_World", "[{0}]: No valid World!"), Name); + ValidationMessages.Add(TempText); + + bSatisfied = false; + } + + if (DecoratorState == EComboActionDecoratorState::Uninitialized && bIsEditorCall == false) + { + const FText TempText = FText::Format(LOCTEXT("ComboActionDecorator_Base_Validation_State", "[{0}]: Not Initialized properly!"), Name); + ValidationMessages.Add(TempText); + + bSatisfied = false; + } + + if (GetOwner() == nullptr) + { + const FText TempText = FText::Format(LOCTEXT("ComboActionDecorator_Base_Validation_Owner", "[{0}]: No valid Owner!"), Name); + ValidationMessages.Add(TempText); + + bSatisfied = false; + } + + return bSatisfied; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ComboInput/Private/Interfaces/ComboActionGraphManagerInterface.cpp b/Source/ComboInput/Private/Interfaces/ComboActionGraphManagerInterface.cpp new file mode 100644 index 0000000..6282875 --- /dev/null +++ b/Source/ComboInput/Private/Interfaces/ComboActionGraphManagerInterface.cpp @@ -0,0 +1,6 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Interfaces/ComboActionGraphManagerInterface.h" + + + diff --git a/Source/ComboInput/Private/Interfaces/ComboActionInterface.cpp b/Source/ComboInput/Private/Interfaces/ComboActionInterface.cpp new file mode 100644 index 0000000..2a61e81 --- /dev/null +++ b/Source/ComboInput/Private/Interfaces/ComboActionInterface.cpp @@ -0,0 +1,6 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Interfaces/ComboActionInterface.h" + + + diff --git a/Source/ComboInput/Private/Nodes/ComboActionGraphEdge.cpp b/Source/ComboInput/Private/Nodes/ComboActionGraphEdge.cpp new file mode 100644 index 0000000..9100abf --- /dev/null +++ b/Source/ComboInput/Private/Nodes/ComboActionGraphEdge.cpp @@ -0,0 +1,6 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Nodes/ComboActionGraphEdge.h" + + + diff --git a/Source/ComboInput/Private/Nodes/ComboActionGraphNode.cpp b/Source/ComboInput/Private/Nodes/ComboActionGraphNode.cpp new file mode 100644 index 0000000..98455c4 --- /dev/null +++ b/Source/ComboInput/Private/Nodes/ComboActionGraphNode.cpp @@ -0,0 +1,396 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Nodes/ComboActionGraphNode.h" + +#include "ComboActionGraph.h" + +#include "Data/ComboActionContext.h" +#include "Decorators/ComboActionDecoratorBase.h" +#include "Interfaces/ComboActionGraphManagerInterface.h" + +DEFINE_LOG_CATEGORY(LogComboActionGraphNode); + +#define LOCTEXT_NAMESPACE "ComboActionGraphNode" + + +UComboActionGraphNode::UComboActionGraphNode() +{ + NodeGUID = FGuid::NewGuid(); + bInheritGraphDecorators = true; + +#if WITH_EDITORONLY_DATA + CompatibleGraphType = UComboActionGraph::StaticClass(); + + BackgroundColor = FLinearColor::Black; + + bAllowInputNodes = true; + bAllowOutputNodes = true; + + bAllowCopy = true; + bAllowCut = true; + bAllowDelete = true; + bAllowPaste = true; + bAllowManualCreate = true; + + NodeTypeName = LOCTEXT("ComboActionGraphNode_InternalName", "ComboActionGraphNode"); + NodeTooltipText = LOCTEXT("ComboActionGraphNode_Tooltip", "Combo action graph base node. Child nodes provide more information."); +#endif + + bAutoStarts = false; +} + +void UComboActionGraphNode::SetNewWorld(UWorld *NewWorld) +{ + if (!NewWorld) return; + if (NewWorld == this->OwningWorld) return; + + this->OwningWorld = NewWorld; +} + +void UComboActionGraphNode::InitializeNode_Implementation(UWorld *InWorld) +{ + this->SetNewWorld(InWorld); + + if (this->Graph) + { + this->SetNodeIndex(this->Graph->AllNodes.Find(this)); + } +} + +void UComboActionGraphNode::ProcessNode() +{ + if (!this->GetWorld()) + { + UE_LOG(LogComboActionGraphNode, Error, TEXT("[ProcessNode] Cannot find World!")); + return; + } + + if (!this->GetGraph()) + { + UE_LOG(LogComboActionGraphNode, Error, TEXT("[ProcessNode] Invalid owning Graph!!")); + return; + } + + //UComboActionContext *Context = Manager->GetDialogueContext(); + //if (!Context || !UComboActionSystemBFC::IsContextValid(Context)) + //{ + // Manager->GetDialogueFailedEventHandle().Broadcast(TEXT("[ProcessNode] Invalid Dialogue Context!!")); + // return; + //} + // + //UComboActionSystemBFC::ExecuteDecorators(this, Context); + // + //Manager->GetDialogueNodeStartedEventHandle().Broadcast(Context); +} + +TArray UComboActionGraphNode::GetNodeDecorators() const +{ + TArray TempReturn; + TArray Return; + + for (auto Itr : NodeDecorators) + { + if (Itr.DecoratorType != nullptr) + { + TempReturn.AddUnique(Itr); + } + } + + /* TODO: Cleanup duplicates + for (auto Itr : TempReturn) + { + + } + */ + + Return = TempReturn; + return Return; +} + +bool UComboActionGraphNode::CanStartNode() const +{ + return this->EvaluateDecorators(); +} + +bool UComboActionGraphNode::EvaluateDecorators() const +{ + if (this->GetGraph() == nullptr) + { + UE_LOG(LogComboActionGraphNode, Error, TEXT("[EvaluateDecorators] Graph is null (invalid)!")) + return false; + } + + bool bSatisfied = true; + TArray AllDecorators; + if (this->bInheritGraphDecorators) + { + // Add those Decorators rather than asking Graph to evaluate, because Nodes might introduce specific context + AllDecorators.Append(GetGraph()->GetGraphDecorators()); + } + + AllDecorators.Append(GetNodeDecorators()); + + if (AllDecorators.Num() == 0) return bSatisfied; + + for (auto Itr : AllDecorators) + { + if (Itr.EvaluateDecorator() == false) bSatisfied = false; + } + + return bSatisfied; +} + +void UComboActionGraphNode::SetNodeIndex(const int32 NewIndex) +{ + check(NewIndex>INDEX_NONE); + this->NodeIndex = NewIndex; +} + +#if WITH_EDITOR + +FText UComboActionGraphNode::GetDescription_Implementation() const +{ + return LOCTEXT("NodeDesc", "Mountea Dialogue Graph Node"); +} + +FText UComboActionGraphNode::GetNodeCategory_Implementation() const +{ + return LOCTEXT("NodeCategory", "Mountea Dialogue Tree Node"); +} + +FString UComboActionGraphNode::GetNodeDocumentationLink_Implementation() const +{ + return TEXT("A link to the documentation for this plugin will go here."); +} + +FText UComboActionGraphNode::GetNodeTooltipText_Implementation() const +{ + return FText::Format(LOCTEXT("ComboActionGraphNode_FinalTooltip", "{0}\n\n{1}"), this->GetDefaultTooltipBody(), this->NodeTooltipText); +} + +bool UComboActionGraphNode::CanCreateConnection(UComboActionGraphNode *Other, enum EEdGraphPinDirection Direction, FText &ErrorMessage) +{ + if (Other == nullptr) + { + ErrorMessage = FText::FromString("Invalid Other Node!"); + } + + if (Other->GetMaxChildNodes() > -1 && Other->ChildrenNodes.Num() >= Other->GetMaxChildNodes()) + { + const FString TextReturn = + FString(Other->GetNodeTitle().ToString()). + Append(": Cannot have more than ").Append(FString::FromInt(Other->GetMaxChildNodes())).Append(" Children Nodes!"); + + ErrorMessage = FText::FromString(TextReturn); + return false; + } + + if (Direction == EEdGraphPinDirection::EGPD_Output) + { + + // Fast checking for native classes + if ( AllowedInputClasses.Contains(Other->GetClass()) ) + { + return true; + } + + // Slower iterative checking for child classes + for (auto Itr : AllowedInputClasses) + { + if (Other->GetClass()->IsChildOf(Itr)) + { + return true; + } + } + + ErrorMessage = FText::FromString("Invalid Node Connection!"); + return false; + } + + return true; +} + +bool UComboActionGraphNode::ValidateNode(TArray &ValidationsMessages, const bool RichFormat) +{ + bool bResult = true; + if (this->ParentNodes.Num() == 0 && this->ChildrenNodes.Num() == 0) + { + bResult = false; + + const FString RichTextReturn = + FString("* ") + .Append("") + .Append(this->NodeTitle.ToString()) + .Append("") + .Append(": This Node has no Connections!"); + + const FString TextReturn = + FString(this->NodeTitle.ToString()) + .Append(": This Node has no Connections!"); + + ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + } + + if (this->bAllowInputNodes && this->ParentNodes.Num() == 0) + { + bResult = false; + + const FString RichTextReturn = + FString("* ") + .Append("") + .Append(this->NodeTitle.ToString()) + .Append("") + .Append(": This Node requires Inputs, however, none are found!"); + + const FString TextReturn = + FString(this->NodeTitle.ToString()) + .Append(": This Node requires Inputs, however, none are found!"); + + ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + } + + // DECORATORS VALIDATION + { + TArray UsedNodeDecorators; + for (int i = 0; i < this->NodeDecorators.Num(); i++) + { + if (this->NodeDecorators.IsValidIndex(i) && this->NodeDecorators[i].DecoratorType && !UsedNodeDecorators.Contains(this->NodeDecorators[i].DecoratorType)) + { + UsedNodeDecorators.Add(NodeDecorators[i].DecoratorType); + } + else + { + const FString RichTextReturn = + FString("* ") + .Append( TEXT("")) + .Append(GetNodeTitle().ToString()) + .Append(TEXT("")) + .Append(": has ") + .Append(TEXT("invalid Node Decorator at Index: ")) + .Append(FString::FromInt(i)) + .Append("."); + + FString TextReturn = FString(GetNodeTitle().ToString()) + .Append(": has ") + .Append(TEXT("INVALID Node Decorator at Index: ")) + .Append(FString::FromInt(i)) + .Append("."); + + ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + + bResult = false; + } + } + + TMap DuplicatedDecoratorsMap; + for (const UComboActionDecoratorBase *Itr : UsedNodeDecorators) + { + int32 ClassAppearance = 1; + for (const UComboActionDecoratorBase *Itr2 : UsedNodeDecorators) + { + if (Itr != Itr2 && Itr->GetClass() == Itr2->GetClass()) + { + auto A = Itr->GetClass()->GetName(); + ClassAppearance++; + } + } + + if (ClassAppearance > 1 && DuplicatedDecoratorsMap.Contains(Itr->GetClass()) == false) + { + DuplicatedDecoratorsMap.Add(Itr->GetClass(), ClassAppearance); + } + } + + if (DuplicatedDecoratorsMap.Num() > 0) + { + for (const TTuple &Itr : DuplicatedDecoratorsMap) + { + bResult = false; + + const FString RichTextReturn = + FString("* ") + .Append("") + .Append(this->NodeTitle.ToString()) + .Append("") + .Append(": has Node Decorator ") + .Append("") + .Append(Itr.Key->GetName().LeftChop(2)) + .Append(" ") + .Append(FString::FromInt(Itr.Value)) + .Append("x times! Please, avoid duplicates!"); + + const FString TextReturn = + FString(this->NodeTitle.ToString()) + .Append(this->NodeTitle.ToString()) + .Append(": has Node Decorator ") + .Append(Itr.Key->GetName().LeftChop(2)) + .Append(" ") + .Append(FString::FromInt(Itr.Value)) + .Append("x times! Please, avoid duplicates!"); + + ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + } + } + + for (const FComboActionDecorator &Itr : this->GetNodeDecorators()) + { + TArray DecoratorErrors; + if (!Itr.ValidateDecorator(DecoratorErrors)) + { + for (auto Error : DecoratorErrors) + { + const FString ErrorTextRich = + FString("* ") + .Append("") + .Append(this->NodeTitle.ToString()) + .Append(": ") + .Append(FString(Error.ToString())); + + const FString ErrorTextSimple = + FString(this->GetClass()->GetDisplayNameText().ToString()) + .Append(": ") + .Append(FString(Error.ToString())); + + ValidationsMessages.Add(FText::FromString(RichFormat ? ErrorTextRich : ErrorTextSimple)); + + bResult = false; + } + } + } + } + + return bResult; +} + +void UComboActionGraphNode::OnPasted() +{ + NodeGUID = FGuid::NewGuid(); + + ParentNodes.Empty(); + ChildrenNodes.Empty(); + Edges.Empty(); +} + +FText UComboActionGraphNode::GetDefaultTooltipBody() const +{ + const FText InheritsValue = this->bInheritGraphDecorators ? LOCTEXT("True","Yes") : LOCTEXT("False","No"); + const FText Inherits = FText::Format(LOCTEXT("UComboActionGraphNode_InheritsTooltip", "Inherits Graph Decorators: {0}"), InheritsValue); + + FText ImplementsNumber; + if (this->NodeDecorators.Num() == 0) + { + ImplementsNumber = LOCTEXT("None", "-"); + } + else + { + ImplementsNumber = FText::FromString(FString::FromInt(this->NodeDecorators.Num())); + } + + const FText Implements = FText::Format(LOCTEXT("UComboActionGraphNode_ImplementsTooltip", "Implements Decorators: {0}"), ImplementsNumber); + + return FText::Format(LOCTEXT("UComboActionGraphNode_BaseTooltip", "{0}\n\n{1}\n{2}"), this->NodeTypeName, Inherits, Implements); +} + +#endif + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ComboInput/Private/Nodes/ComboActionGraphNode_ActionNodeBase.cpp b/Source/ComboInput/Private/Nodes/ComboActionGraphNode_ActionNodeBase.cpp new file mode 100644 index 0000000..85ffb3e --- /dev/null +++ b/Source/ComboInput/Private/Nodes/ComboActionGraphNode_ActionNodeBase.cpp @@ -0,0 +1,268 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + + +#include "Nodes/ComboActionGraphNode_ActionNodeBase.h" + +//#include "Helpers/ComboActionSystemBFC.h" + +#define LOCTEXT_NAMESPACE "ComboActionGraphNode_ActionNodeBase" + + +UComboActionGraphNode_ActionNodeBase::UComboActionGraphNode_ActionNodeBase() +{ +#if WITH_EDITORONLY_DATA + this->NodeTitle = LOCTEXT("ComboActionGraphNode_ActionNodeBaseTitle", "Action Node Base"); + this->NodeTypeName = LOCTEXT("ComboActionGraphNode_ActionNodeBaseInternalTitle", "Action Node Base"); + this->ContextMenuName = LOCTEXT("ComboActionGraphNode_ActionNodeBaseContextMenu", "Action Node"); + this->BackgroundColor = FLinearColor(FColor::Orange); + + this->NodeTooltipText = LOCTEXT("ComboActionGraphNode_BaseTooltip", "* Abstract class, should not appear in graph editor.\n* Enhances 'ComboActionGraphNode' Base class with action data."); +#endif + + this->bAutoStarts = false; + this->bUseGameplayTags = true; +} + +void UComboActionGraphNode_ActionNodeBase::ProcessNode() +{ + //if (Manager) + //{ + // if (UComboActionContext *Context = Manager->GetDialogueContext()) + // { + // this->GetWorld()->GetTimerManager().ClearTimer(Manager->GetDialogueRowTimerHandle()); + + // const FDialogueRow DialogueRow = UComboActionSystemBFC::GetDialogueRow(Context->ActiveNode); + // if (UComboActionSystemBFC::IsDialogueRowValid(DialogueRow) && DialogueRow.DialogueRowData.Array().IsValidIndex(Context->GetActiveDialogueRowDataIndex())) + // { + // Context->UpdateActiveDialogueRow(DialogueRow); + // Context->UpdateActiveDialogueRowDataIndex(Context->ActiveDialogueRowDataIndex); + // Manager->GetDialogueContextUpdatedEventHande().Broadcast(Context); + // } + // } + //} + + Super::ProcessNode(); +} + +void UComboActionGraphNode_ActionNodeBase::PreProcessNode() +{ + //if (bUseGameplayTags) + //{ + // // Switch Participants based on Tags + // if (Manager.GetInterface()) + // { + // if (const auto TempContext = Manager->GetDialogueContext()) + // { + // const TScriptInterface BestMatchingParticipant = UComboActionSystemBFC::FindBestMatchingParticipant(Manager.GetObject(), TempContext); + // + // TempContext->UpdateActiveDialogueParticipant(BestMatchingParticipant); + // } + // } + //} + + Super::PreProcessNode(); +} + +UDataTable *UComboActionGraphNode_ActionNodeBase::GetDataTable() const +{ + return DataTable; +} + +bool UComboActionGraphNode_ActionNodeBase::ValidateNodeRuntime_Implementation() const +{ + //if (DataTable == nullptr) + //{ + // return false; + //} + + //if (RowName.IsNone()) + //{ + // return false; + //} + + //if (MaxChildrenNodes > -1 && ChildrenNodes.Num() > MaxChildrenNodes) + //{ + // return false; + //} + + //const FString Context; + //const FDialogueRow* SelectedRow = DataTable->FindRow(RowName, Context); + + //if (SelectedRow == nullptr) + //{ + // return false; + //} + + //if (SelectedRow) + //{ + // if (SelectedRow->DialogueRowData.Num() == 0) + // { + // return false; + // } + //} + + return true; +} + +#if WITH_EDITOR + +bool UComboActionGraphNode_ActionNodeBase::ValidateNode(TArray& ValidationsMessages, const bool RichFormat) +{ + bool bResult = Super::ValidateNode(ValidationsMessages, RichFormat); + + //if (DataTable == nullptr) + //{ + // bResult = false; + + // const FString RichTextReturn = + // FString("* "). + // Append(""). + // Append(NodeTitle.ToString()). + // Append(""). + // Append(": Does not contain any Dialogue Data Table!"); + + // const FString TextReturn = + // FString(NodeTitle.ToString()). + // Append(": Does not contain any Dialogue Data Table!"); + // + // ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + //} + + //if (RowName.IsNone()) + //{ + // bResult = false; + + // const FString RichTextReturn = + // FString("* "). + // Append(""). + // Append(NodeTitle.ToString()). + // Append(""). + // Append(": Does not contain valid Dialogue Row!"); + + // const FString TextReturn = + // FString(NodeTitle.ToString()). + // Append(": Does not contain valid Dialogue Row!"); + // + // ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + //} + + //if (MaxChildrenNodes > -1 && ChildrenNodes.Num() > MaxChildrenNodes) + //{ + // const FString RichTextReturn = + // FString("* "). + // Append(""). + // Append(NodeTitle.ToString()). + // Append(""). + // Append(": Has more than "). + // Append(""). + // Append(FString::FromInt(MaxChildrenNodes)). + // Append(""). + // Append(" Children Nodes!"); + + // const FString TextReturn = + // FString(NodeTitle.ToString()). + // Append(": Has more than ").Append(FString::FromInt(MaxChildrenNodes)).Append(" Children Nodes!"); + // + // ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + //} + + //const FString Context; + //const FDialogueRow* SelectedRow = DataTable!=nullptr ? DataTable->FindRow(RowName, Context) : nullptr; + + //if (SelectedRow == nullptr) + //{ + // bResult = false; + + // const FString RichTextReturn = + // FString("* "). + // Append(""). + // Append(NodeTitle.ToString()). + // Append(""). + // Append(": Invalid Selected Row!"); + + // const FString TextReturn = + // FString(NodeTitle.ToString()). + // Append(": Invalid Selected Row!"); + // + // ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + //} + + //if (SelectedRow) + //{ + // if (SelectedRow->DialogueRowData.Num() == 0) + // { + // bResult = false; + + // const FString RichTextReturn = + // FString("* "). + // Append(""). + // Append(NodeTitle.ToString()). + // Append(""). + // Append(": Invalid Selected Row! No Dialogue Data Rows inside!"); + + // const FString TextReturn = + // FString(NodeTitle.ToString()). + // Append(": Invalid Selected Row! No Dialogue Data Rows inside!"); + // + // ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + // } + //} + + return bResult; +} + +void UComboActionGraphNode_ActionNodeBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UComboActionGraphNode_ActionNodeBase, DataTable)) + { + RowName = FName(""); + Preview.Empty(); + PreviewsUpdated.ExecuteIfBound(); + } + + if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UComboActionGraphNode_ActionNodeBase, RowName)) + { + UpdatePreviews(); + PreviewsUpdated.ExecuteIfBound(); + } +} + +FText UComboActionGraphNode_ActionNodeBase::GetDescription_Implementation() const +{ + return LOCTEXT("ComboActionGraphNode_BaseDescription", "Action base node has no logic tied to itself."); +} + +TArray UComboActionGraphNode_ActionNodeBase::GetPreviews() const +{ + TArray ReturnValues; + + //const auto Row = UComboActionSystemBFC::GetDialogueRow( this ); + //if (UComboActionSystemBFC::IsDialogueRowValid(Row)) + //{ + // for (auto Itr : Row.DialogueRowData.Array()) + // { + // ReturnValues.Add( Itr.RowText ); + // } + //} + //else + //{ + // ReturnValues.Empty(); + //} + + return ReturnValues; +} + +void UComboActionGraphNode_ActionNodeBase::UpdatePreviews() +{ + if (!DataTable) Preview.Empty(); + + Preview.Empty(); + + Preview = GetPreviews(); +} + +#endif + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ComboInput/Private/Nodes/ComboActionGraphNode_StartNode.cpp b/Source/ComboInput/Private/Nodes/ComboActionGraphNode_StartNode.cpp new file mode 100644 index 0000000..a2a7b61 --- /dev/null +++ b/Source/ComboInput/Private/Nodes/ComboActionGraphNode_StartNode.cpp @@ -0,0 +1,81 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Nodes/ComboActionGraphNode_StartNode.h" + +#define LOCTEXT_NAMESPACE "ComboActionGraphNode_StartNode" + + +UComboActionGraphNode_StartNode::UComboActionGraphNode_StartNode() +{ +#if WITH_EDITORONLY_DATA + this->bAllowInputNodes = false; + this->NodeTitle = LOCTEXT("ComboActionGraphNode_StartNodeTitle", "Start Action"); + this->NodeTypeName = LOCTEXT("ComboActionGraphNode_StartNodeInternalTitle", "Start Action"); + this->ContextMenuName = LOCTEXT("ComboActionGraphNode_StartNodeContextMenuName", "Start Action"); + this->BackgroundColor = FLinearColor(0, 1, 0, 1); + + this->bAllowCopy = false; + this->bAllowCut = false; + this->bAllowPaste = false; + this->bAllowDelete = false; + this->bAllowManualCreate = false; + + this->NodeTooltipText = LOCTEXT("ComboActionGraphNode_CompleteTooltip", "* This Node will be added to the graph automatically.\n* This Node cannot be created manually.\n* This Node cannot be deleted.\n* Does not implement any logic."); +#endif + + // TODO: Once there are Conditional Decorators, this will be replaced + MaxChildrenNodes = 1; +} + +#if WITH_EDITOR + +FText UComboActionGraphNode_StartNode::GetDescription_Implementation() const +{ + return LOCTEXT("ComboActionGraphNode_StartNodeDescription", "Start node is automatically placed and cannot be deleted."); +} + +bool UComboActionGraphNode_StartNode::ValidateNode(TArray& ValidationsMessages, const bool RichFormat) +{ + bool bResult = Super::ValidateNode(ValidationsMessages, RichFormat); + + if (ChildrenNodes.Num() == 0) + { + bResult = false; + + const FString RichTextReturn = + FString("* ") + .Append("") + .Append(this->NodeTitle.ToString()) + .Append("") + .Append(": Does not have any child nodes!"); + + const FString TextReturn = + FString(this->NodeTitle.ToString()) + .Append(": Does not have any child nodes!"); + + ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + } + + if (ChildrenNodes.Num() > 1) + { + bResult = false; + + const FString RichTextReturn = + FString("* ") + .Append("") + .Append(this->NodeTitle.ToString()) + .Append("") + .Append(": Has more than 1 child node."); + + const FString TextReturn = FString(this->NodeTitle.ToString()) + .Append(": Has more than 1 child node."); + + ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); + } + + return bResult; +} + +#endif + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ComboInput/Public/ComboActionGraph.h b/Source/ComboInput/Public/ComboActionGraph.h new file mode 100644 index 0000000..7fba9f7 --- /dev/null +++ b/Source/ComboInput/Public/ComboActionGraph.h @@ -0,0 +1,172 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "GameplayTagContainer.h" +#include "Decorators/ComboActionDecoratorBase.h" + +#include "ComboActionGraph.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogComboActionGraph, Log, All); + + +/** + * Combo Action graph. + * + * Can be manually created from Content Browser, using Combo Action category. + * Comes with a node editor, which provides easy to follow visual way to create combo strings. + */ +UCLASS(BlueprintType, ClassGroup=("Combo Input|Action"), DisplayName="Combo Action Tree", HideCategories=("Hidden", "Private", "Base"), AutoExpandCategories=("Combo Input", "Action")) +class COMBOINPUT_API UComboActionGraph : public UObject +{ + GENERATED_BODY() + +public: + + UComboActionGraph(); + +#pragma region Variables + +protected: + + /** + * The list of decorators for the dialogue graph. + * Decorators are used to add extra functionality or behavior to the nodes in the graph. + * This array should contain an instance of each decorator used in the graph. + * The order of the decorators in this array determines the order in which they will be applied to the nodes. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Combo Input|Action", NoClear, meta=(NoResetToDefault)) + TArray GraphDecorators; + /** + * A set of gameplay tags associated with this dialogue graph. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Combo Input|Action") + FGameplayTagContainer GraphTags; + /** + * GUID for this Mountea Dialogue Graph. + *❗ Unique identifier for this Dialogue Graph instance. + *❔ Can be used for debugging and tracing purposes. + */ + UPROPERTY(BlueprintReadOnly, Category="Combo Input") + FGuid GraphGUID; + +public: + + // Pointer to the starting node of the dialogue graph. + UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") + class UComboActionGraphNode *StartNode = nullptr; + /** + * The class of the dialogue node represented by this instance. + */ + UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") + TSubclassOf NodeType; + /** + * The class of the dialogue edge represented by this instance. + */ + UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") + TSubclassOf EdgeType; + /** + * An array of root nodes in the dialogue graph. These are the nodes that do not have any incoming connections. + */ + UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") + TArray RootNodes; + /** + * Array containing all the nodes in the graph, including both root nodes and child nodes. + */ + UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") + TArray AllNodes; + // Flag indicating whether an edge is enabled + UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") + bool bEdgeEnabled; + +#pragma endregion + +#pragma region Functions + +public: + /** + * Returns the GUID of the graph. + * + * @return The GUID of the graph. + */ + UFUNCTION(BlueprintCallable, Category="Combo Input|Action") + FGuid GetGraphGUID() const { return this->GraphGUID; } + /** + * Returns an array containing all nodes in the dialogue graph. + * @return An array of all nodes in the dialogue graph. + */ + UFUNCTION(BlueprintCallable, Category="Combo Input|Action") + TArray GetAllNodes() const { return this->AllNodes; } + + UFUNCTION(BlueprintCallable, Category="Combo Input|Action") + TArray GetRootNodes() const { return this->RootNodes; } + /** + * Returns the root nodes of the dialogue graph. + * + * @return An array of all root nodes. + */ + UFUNCTION(BlueprintCallable, Category="Combo Input|Action") + UComboActionGraphNode *GetStartNode() const { return this->StartNode; } + /** + * Returns the array of decorators that are associated with this graph. + * + * @return The array of decorators. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + TArray GetGraphDecorators() const; + /** + * Returns the array of decorators that are associated with this graph and its nodes. + * + * @return The array of decorators. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + TArray GetAllDecorators() const; + /** + * Determines whether the dialogue graph can be started. + * + * @return true if the graph can be started, false otherwise. + */ + bool CanStartDialogueGraph() const; + +public: + void CreateGraph(); + void ClearGraph(); + + FORCEINLINE bool IsEdgeEnabled() const { return bEdgeEnabled; } + + virtual void PostInitProperties() override; + +#pragma endregion + +#if WITH_EDITORONLY_DATA + +public: + UPROPERTY() + class UEdGraph *EdGraph; + + UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action|Editor") + bool bCanRenameNode; + +#endif + +#if WITH_EDITOR + + virtual bool ValidateGraph(TArray &ValidationErrors, bool RichTextFormat); + virtual EDataValidationResult IsDataValid(TArray &ValidationErrors) override; + +public: + + // Construct and initialize a node within this Dialogue. + template + T* ConstructDialogueNode(TSubclassOf DialogueNodeClass = T::StaticClass()) + { + // Set flag to be transactional so it registers with undo system + T *DialogueNode = NewObject(this, DialogueNodeClass, NAME_None, EObjectFlags::RF_Transactional); + DialogueNode->OnCreatedInEditor(); + return DialogueNode; + } + +#endif +}; diff --git a/Source/ComboInput/Public/Data/ComboActionAdditionalData.h b/Source/ComboInput/Public/Data/ComboActionAdditionalData.h new file mode 100644 index 0000000..1f09cd0 --- /dev/null +++ b/Source/ComboInput/Public/Data/ComboActionAdditionalData.h @@ -0,0 +1,14 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "ComboActionAdditionalData.generated.h" + + +UCLASS(Abstract, BlueprintType, meta=(UsesHierarchy=true), Blueprintable, ClassGroup="Combo Input|Action") +class COMBOINPUT_API UComboActionAdditionalData : public UObject +{ + GENERATED_BODY() +}; diff --git a/Source/ComboInput/Public/Data/ComboActionContext.h b/Source/ComboInput/Public/Data/ComboActionContext.h new file mode 100644 index 0000000..78b5c25 --- /dev/null +++ b/Source/ComboInput/Public/Data/ComboActionContext.h @@ -0,0 +1,203 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "Data/ComboActionGraphDataTypes.h" +#include "Nodes/ComboActionGraphNode.h" +#include "UObject/Object.h" + +#include "ComboActionContext.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FComboActionContextUpdatedFromBlueprint, class UComboActionContext *, Context); + + +/** + * Dialogue Context. + * + * Contains information needed to successfully start Dialogue. + * Also helps tracking Dialogue Specific data. Is recycled for whole Dialogue Graph. + * + * In Dialogue Manager Component is used as Transient object, which is nullified once Dialogue ends and is never saved. + */ +UCLASS() +class COMBOINPUT_API UComboActionContext : public UObject +{ + GENERATED_BODY() + +public: + + ///** + // * Active Dialogue Participant Interface reference. + // * + // * This is the Participant who is Active right now. + // * ❔ Lead Node sets this value to Dialogue Participant. + // * ❔ Answer Node sets this value to Player Participant. + // * ❗ Might be invalid + // */ + //UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action") + // TScriptInterface ActionParticipant; + + ///** + // * Player Dialogue Participant Interface reference. + // * + // * This is the Participant who represent the Player. + // * ❗ Might be invalid + // */ + //UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action") + // TScriptInterface PlayerDialogueParticipant; + + //UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action") + // TScriptInterface DialogueParticipant; + + //UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action") + // TArray> DialogueParticipants; + + /** + * Pointer to the Node which is currently active. + * ❗ Might be null + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action") + class UComboActionGraphNode *ActiveNode = nullptr; + + /** + * List of Nodes that can be accessed from Active Node. + * Already filtered to contain only those that can be triggered. + * + * ❔ Filter is done by 'CanStartNode', which can have its own logic and can be driven by Decorators as well. + * ❗ Might be empty + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action") + TArray AllowedChildNodes; + + /** + * Index of currently used Dialogue Row Data row. + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action") + int32 ActiveDialogueRowDataIndex = 0; + + /** + * Contains mapped list of Traversed Nodes by GUIDs. + * Each time Dialogue is updated, like node is selected or starts itself, this Path is updated. + * Updates Participant once Dialogue is done. + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action", meta=(NoResetToDefault)) + TMap TraversedPath; + +public: + + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Debug") + virtual bool IsValid() const; + + //TScriptInterface GetDialoguePlayerParticipant() const { return this->PlayerDialogueParticipant; } + //TScriptInterface GetDialogueParticipant() const { return this->DialogueParticipant; } + //TArray> GetDialogueParticipants() const { return this->DialogueParticipants; } + + /** + * Returns the Active Node object. + * ❗ Might be null + * + * @return Active Node if any specified + */ + class UComboActionGraphNode *GetActiveNode() const { return this->ActiveNode; } + + /** + * Returns lsit of Children Nodes from Active Node. + * ❗ Might be empty + * + * @return List of allowed Children Nodes + */ + TArray GetChildrenNodes() const { return this->AllowedChildNodes; } + ///** + // *Returns the Active Dialogue Row Data Index. + // * + // * @return Active Row Index + // */ + //int32 GetActiveDialogueRowDataIndex() const { return this->ActiveDialogueRowDataIndex; } + /** + * Returns the map of nodes traversed during this dialogue instance. + * + * @return The map of nodes traversed during this dialogue instance. + */ + TMap GetTraversedPath() const { return TraversedPath; } + + virtual void SetComboActionContext(class UComboActionGraphNode *NewActiveNode, TArray NewAllowedChildNodes); + //virtual void UpdateDialogueParticipant(TScriptInterface NewParticipant); + //virtual void UpdateActiveDialogueNode(class UComboActionGraphNode *NewActiveNode); + //virtual void UpdateAllowedChildrenNodes(const TArray &NewNodes); + //virtual void UpdateActiveDialogueRowDataIndex(int32 NewIndex); + //void UpdateDialoguePlayerParticipant(TScriptInterface NewParticipant); + //void UpdateActiveDialogueParticipant(TScriptInterface NewParticipant); + //void AddTraversedNode(const class UComboActionGraphNode* TraversedNode); + + //virtual bool AddDialogueParticipants(const TArray> &NewParticipants); + //virtual bool AddDialogueParticipant(const TScriptInterface &NewParticipant); + //virtual bool RemoveDialogueParticipants(const TArray> &NewParticipants); + //virtual bool RemoveDialogueParticipant(const TScriptInterface &NewParticipant); + //virtual void ClearDialogueParticipants(); + + /** + * Sets the dialogue context. + * + * @param NewParticipant The new dialogue participant. + * @param NewActiveNode The new active dialogue node. + * @param NewAllowedChildNodes The new allowed child dialogue nodes. + */ + UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="SetDialogueContext")) + virtual void SetComboActionContextBP(class UComboActionGraphNode *NewActiveNode, TArray NewAllowedChildNodes); + + ///** + // * Updates Dialogue Participant. + // * + // * @param NewParticipant - new Dialogue Participant. + // * ❗ Must implement IComboActionParticipantInterface. + // */ + //UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="UpdateDialogueParticipant")) + // virtual void UpdateDialogueParticipantBP(TScriptInterface NewParticipant); + /** + * Updates Active Dialogue Node in Context. + * + * @param NewActiveNode - New Active Dialogue Node to update to. + * ❗ Must not be Null + */ + //UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="UpdateActiveDialogueNode")) + // virtual void UpdateActiveDialogueNodeBP(class UComboActionGraphNode *NewActiveNode); + /** + * Updates the active dialogue row Data Index. + * + * @param NewIndex - The new active dialogue data row Index. + */ + //UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="UpdateActiveDialogueRowDataIndex")) + // virtual void UpdateActiveDialogueRowDataIndexBP(int32 NewIndex); + ///** + // * Updates Dialogue Player Participant. + // * + // * @param NewParticipant - new Dialogue Player Participant. + // * ❗ Must implement IMounteaDialogueParticipantInterface. + // */ + //UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="UpdateDialoguePlayerParticipant")) + // void UpdateDialoguePlayerParticipantBP(TScriptInterface NewParticipant); + ///** + // * Updates Dialogue Active Participant. + // * + // * @param NewParticipant - new Dialogue Active Participant. + // * ❗ Must implement IMounteaDialogueParticipantInterface. + // */ + //UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="UpdateActiveDialogueParticipant")) + // void UpdateActiveDialogueParticipantBP(TScriptInterface NewParticipant); + + //UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="AddDialogueParticipant")) + // virtual bool AddDialogueParticipantBP(const TScriptInterface& NewParticipant); + // + //UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="RemoveDialogueParticipant")) + // virtual bool RemoveDialogueParticipantBP(const TScriptInterface& NewParticipant); + + //UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="AddDialogueParticipants")) + // virtual bool AddDialogueParticipantsBP(const TArray>& NewParticipants); + // + //UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="RemoveDialogueParticipants")) + // virtual bool RemoveDialogueParticipantsBP(const TArray>& NewParticipants); + + FComboActionContextUpdatedFromBlueprint ComboActionContextUpdatedFromBlueprint; +}; diff --git a/Source/ComboInput/Public/Data/ComboActionGraphDataTypes.h b/Source/ComboInput/Public/Data/ComboActionGraphDataTypes.h new file mode 100644 index 0000000..f5d9f35 --- /dev/null +++ b/Source/ComboInput/Public/Data/ComboActionGraphDataTypes.h @@ -0,0 +1,27 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ComboActionAdditionalData.h" +#include "GameplayTagContainer.h" +#include "Blueprint/UserWidget.h" +#include "Engine/DataTable.h" +#include "UObject/Object.h" + +#include "Fonts/SlateFontInfo.h" +#include "Styling/CoreStyle.h" + +#include "ComboActionGraphDataTypes.generated.h" + + +/** + * Combo Action Manager state + */ +UENUM(BlueprintType) +enum class EComboActionManagerState : uint8 +{ + Disabled UMETA(DisplayName="Disabled", Tooltip="Disabled. Combo action cannot be used."), + Enabled UMETA(DisplayName="Enabled", Tooltip="Enabled. Combo action can start."), + Active UMETA(DisplayName="Activated", Tooltip="Combo action is activated"), +}; diff --git a/Source/ComboInput/Public/Decorators/ComboActionDecoratorBase.h b/Source/ComboInput/Public/Decorators/ComboActionDecoratorBase.h new file mode 100644 index 0000000..d800c55 --- /dev/null +++ b/Source/ComboInput/Public/Decorators/ComboActionDecoratorBase.h @@ -0,0 +1,254 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Level.h" + +#include "ComboActionDecoratorBase.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogComboActionDecoratorBase, Log, All); + +#define LOCTEXT_NAMESPACE "NodeDecoratorBase" + + +UENUM(BlueprintType) +enum class EComboActionDecoratorState : uint8 +{ + Uninitialized, + Initialized +}; + +/** + * Combo Action Decorator + * + * Decorators are instanced and exist only as "triggers". + * Could be used to start audio, play animation or do some logic behind the curtains, like triggering Cutscene etc. + */ +UCLASS(Abstract, Blueprintable, BlueprintType, EditInlineNew, ClassGroup=("Combo Input|Action"), AutoExpandCategories=("Combo Input, Action")) +class COMBOINPUT_API UComboActionDecoratorBase : public UObject +{ + GENERATED_BODY() + +public: + FORCEINLINE ULevel *GetLevel() const { return this->GetTypedOuter(); } + +public: + virtual UWorld *GetWorld() const override + { + if (this->OwningWorld) + { + return this->OwningWorld; + } + + // CDO objects do not belong to a world + // If the actors outer is destroyed or unreachable we are shutting down and the world should be nullptr + if (!this->HasAnyFlags(EObjectFlags::RF_ClassDefaultObject) && ensureMsgf(this->GetOuter(), TEXT("Actor: %s has a null OuterPrivate in AActor::GetWorld()"), *this->GetFullName()) && + !this->GetOuter()->HasAnyFlags(EObjectFlags::RF_BeginDestroyed) && !this->GetOuter()->IsUnreachable()) + { + if (ULevel *Level = this->GetLevel()) + { + return Level->OwningWorld; + } + } + + return nullptr; + } + + UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators") + FString GetDecoratorDocumentationLink() const; + virtual FString GetDecoratorDocumentationLink_Implementation() const { return TEXT("This is a place where a link to the documentation will go eventually."); } + +public: + + /** + * Initializes the Decorator. + * In C++ saves the World for later use. + * In Blueprints should be used to cache values to avoid overhead in 'ExecuteDecorator'. + */ + UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators") + void InitializeDecorator(UWorld *World); + virtual void InitializeDecorator_Implementation(UWorld *World) + { + this->OwningWorld = World; + if (World) + { + this->DecoratorState = EComboActionDecoratorState::Initialized; + } + } + + /** + * Cleans up the Decorator. + * In Blueprints should be used to reset cached values to avoid blocking garbage collector. + */ + UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators") + void CleanupDecorator(); + virtual void CleanupDecorator_Implementation() { this->DecoratorState = EComboActionDecoratorState::Uninitialized; } + + /** + * Validates the Decorator. + * Called for each Node it is attached to. + * Works as safety measure to avoid calling broken Decorators with invalid references. + * + * False value stops Dialogue whatsoever. + * Validation is called before Context is initialized! + */ + UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators") + bool ValidateDecorator(TArray &ValidationMessages); + virtual bool ValidateDecorator_Implementation(TArray &ValidationMessages); + + /** + * Evaluates the Decorator. + * Called for each Node it is attached to. + * Could enhance Node's 'CanStartNode'. Example would be: BP_RequireItem decorator, which would return true if Player has specific Item in inventory. Otherwise it returns false and its Node is not available in Selection of Answers. + */ + UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators") + bool EvaluateDecorator(); + virtual bool EvaluateDecorator_Implementation() { return this->OwningWorld != nullptr; } + + /** + * Executes the Decorator. + * Useful for triggering special events per Node, for instance, switching dialogue cameras. + */ + UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators") + void ExecuteDecorator(); + virtual bool ExecuteDecorator_Implementation() { return true; } + + /** + * Stores reference to World. + * World is needed to perform World affecting tasks. + */ + UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Decorators") + void StoreWorldReference(UWorld *World) { this->OwningWorld = World; } + + /** + * Returns Owning World this Decorator belongs to. + * + * ❗ Should not return Null, but possibly can. + */ + UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="World")) + UWorld *GetOwningWorld() const { return this->OwningWorld; } + + /** + * Returns Owning Node of this Decorator. + * + * ❗ Might return Null if this Decorator is owned by Graph! + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="OwningNode")) + class UComboActionGraphNode *GetOwningNode() const { return this->GetTypedOuter(); } + /** + * Returns Owning Graph of this Decorator. + * + * ❗ Might return Null if this Decorator is owned by Node! + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="OwningGraph")) + class UComboActionGraph *GetOwningGraph() const { return this->GetTypedOuter(); } + /** + * Returns Owning Object of this Decorator. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="Owner")) + UObject *GetOwner() const { return this->GetOuter(); } + + FText GetDecoratorName() const + { + #if WITH_EDITORONLY_DATA + return this->GetClass()->GetDisplayNameText(); + #else + return FText::FromString(GetName()); + #endif + } + +private: + + UPROPERTY() EComboActionDecoratorState DecoratorState = EComboActionDecoratorState::Uninitialized; + + UPROPERTY() UWorld *OwningWorld = nullptr; +}; + + +/** + * Dialogue Decorator Structure. + * Holds reference to its Instanced Decorator. + */ +USTRUCT(BlueprintType) +struct FComboActionDecorator +{ + GENERATED_BODY() + + FComboActionDecorator() : DecoratorType(nullptr) {}; + +public: + + void InitializeDecorator(UWorld *World) const + { + if (this->DecoratorType) + { + this->DecoratorType->InitializeDecorator(World); + } + else + { + UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[InitializeDecorator] DecoratorType is null (invalid)!")); + } + } + + bool ValidateDecorator(TArray& ValidationMessages) const + { + if (this->DecoratorType) + { + return this->DecoratorType->ValidateDecorator(ValidationMessages); + } + + UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[EvaluateDecorator] DecoratorType is null (invalid)!")); + return false; + } + + void CleanupDecorator() const + { + if (this->DecoratorType) + { + this->DecoratorType->CleanupDecorator(); + } + else + { + UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[CleanupDecorator] DecoratorType is null (invalid)!")); + } + } + + bool EvaluateDecorator() const + { + if (this->DecoratorType) + { + return this->DecoratorType->EvaluateDecorator(); + } + + UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[EvaluateDecorator] DecoratorType is null (invalid)!")); + return false; + } + + void ExecuteDecorator() const + { + if (this->DecoratorType) + { + this->DecoratorType->ExecuteDecorator(); + } + else + { + UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[ExecuteDecorator] DecoratorType is null (invalid)!")); + } + } + +public: + + /** + * Decorators can help out with enhancing the Dialogue flow. + * Those Decorators are instanced and exist only as "triggers". + * Could be used to start audio, play animation or do some logic behind the curtains, like triggering Cutscene etc. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "Combo Input|Action", meta=(NoResetToDefault, AllowAbstract = "false", BlueprintBaseOnly = "true")) + UComboActionDecoratorBase *DecoratorType = nullptr; + + bool operator==(const FComboActionDecorator &Other) const { return this->DecoratorType == Other.DecoratorType; } + +}; + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ComboInput/Public/Interfaces/ComboActionGraphManagerInterface.h b/Source/ComboInput/Public/Interfaces/ComboActionGraphManagerInterface.h new file mode 100644 index 0000000..a44794b --- /dev/null +++ b/Source/ComboInput/Public/Interfaces/ComboActionGraphManagerInterface.h @@ -0,0 +1,233 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Data/ComboActionGraphDataTypes.h" +#include "UObject/Interface.h" +#include "ComboActionGraphManagerInterface.generated.h" + + +// This class does not need to be modified. +UINTERFACE(MinimalAPI, BlueprintType, Blueprintable) +class UComboActionGraphManagerInterface : public UInterface +{ + GENERATED_BODY() +}; + + +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueInitialized, UMounteaDialogueContext*, Context); +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueEvent, UMounteaDialogueContext*, Context); +// +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueContextUpdated, UMounteaDialogueContext*, Context); +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDialogueUserInterfaceChanged, TSubclassOf, DialogueWidgetClass, UUserWidget*, DialogueWidget); +// +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueNodeEvent, UMounteaDialogueContext*, Context); +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueRowEvent, UMounteaDialogueContext*, Context); +// +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueFailed, const FString&, ErrorMessage); +// +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueManagerStateChanged, const EDialogueManagerState&, NewState); +// +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueVoiceEvent, class USoundBase*, NewDialogueVoice); + +/** + * Mountea Dialogue Manager Interface. + * + * Should attached directly to Player Controller or used for Components that are attached to some Controller. + * Provides options to start and stop dialogue as well as ability to select dialogue options. + */ +class COMBOINPUT_API IComboActionGraphManagerInterface +{ + GENERATED_BODY() + +public: + + /** + * Notifies the Dialogue that a node has been selected. + * + * @param NodeGUID The GUID of the selected node. + */ + //UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Combo Input|Action", meta=(Keywords="select, chosen, option")) + // void CallDialogueNodeSelected(const FGuid &NodeGUID); + + /** + * Starts the Dialogue if possible. + */ + //virtual void StartDialogue() = 0; + /** + * Closes the Dialogue if is active. + */ + //virtual void CloseDialogue() = 0; + + /** + * Tries to Invoke Dialogue UI. + * This function servers a purpose to try showing Dialogue UI to player. + * ❔ If this function fails, Message will be populated with error message explaining what went wrong. + * + * @param Message InMessage to be populated with error message explaining why returns false + * @return true if UI can be added to screen, false if cannot + */ + //virtual bool InvokeDialogueUI(FString& Message) = 0; + /** + * Gets the widget class used to display Dialogue UI. + * + * @return The widget class used to display Dialogue UI. + */ + //virtual TSubclassOf GetDialogueWidgetClass() const = 0; + /** + * Sets the widget class for the Dialogue UI. + * ❗ This is a pure virtual function that must be implemented in derived classes. + * + * @param NewWidgetClass The new widget class to set. + */ + //virtual void SetDialogueWidgetClass(TSubclassOf NewWidgetClass) = 0; + /** + * Returns Dialogue UI pointer. + * + * ❗ Could be null + * @return UserWidget pointer to created UI + */ + //virtual UUserWidget *GetDialogueUIPtr() const = 0; + /** + * Sets Dialogue UI pointer. + * + * ❔ Using null value resets saved value + * @param DialogueUIPtr UserWidget pointer to be saved as Dialogue UI + */ + //virtual void SetDialogueUIPtr(UUserWidget *DialogueUIPtr) = 0; + + /** + * Starts Dialogue Row execution. + * ❔ Dialogue Data contain Dialogue Data Rows, which are individual dialogue lines, which can be skipped. + * ❔ Once all Dialogue Data Rows are finished, Dialogue Data is finished as well. + */ + //virtual void StartExecuteDialogueRow() = 0; + /** + * Function responsible for cleanup once Dialogue Row is finished. + * ❔ Dialogue Data contain Dialogue Data Rows, which are individual dialogue lines, which can be skipped. + * ❔ Once all Dialogue Data Rows are finished, Dialogue Data is finished as well. + */ + //virtual void FinishedExecuteDialogueRow() = 0; + + /** + * Retrieves the current dialogue context associated with this dialogue instance. + * + * @return The dialogue context object for this instance. + */ + //UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action") + //UMounteaDialogueContext* GetDialogueContextEvent() const; + //UMounteaDialogueContext* GetDialogueContextEvent_Implementation() const + //{ + // return GetDialogueContext(); + //} + + /** + * Returns the widget used to display the current dialogue. + * + * @return The widget used to display the current dialogue. + */ + //UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Combo Input|Action", meta=(Keywords="UI, Widget")) + //UUserWidget* GetDialogueWidget(); + //UUserWidget* GetDialogueWidget_Implementation() + //{ + // return GetDialogueWidget(); + //}; + + /** + * Returns the owning actor for this Dialogue Manager Component. + * + * @return The owning actor for this Dialogue Manager Component. + */ + //UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action") + //AActor* GetOwningActor() const; + //virtual AActor* GetOwningActor_Implementation() const + //{ + // return nullptr; + //}; + + /** + * Prepares the node for execution. + * Asks Active Node to 'PreProcessNode' and then to 'ProcessNode'. + * In this preparation stage, Nodes are asked to process all Decorators. + */ + //UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action") + //void PrepareNode(); + //virtual void PrepareNode_Implementation() {}; + + /** + * Retrieves current Dialogue Context. + * + * ❗ Could be null + * @return DialogueContext Dialogue Context is transient data holder for current dialogue instance. + */ + //virtual UMounteaDialogueContext* GetDialogueContext() const = 0; + /** + * Sets new Dialogue Context. + * + * ❔ Null value clears saved data + * @param NewContext Dialogue Context to be set as Dialogue Context + */ + //virtual void SetDialogueContext(UMounteaDialogueContext* NewContext) = 0; + + /** + * Interface call. + * Retrieves current Dialogue Manager State. + * State defines whether Manager can start/close dialogue or not. + * + * @return ManagerState Manager state value + */ + //UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action") + //EDialogueManagerState GetState() const; + //EDialogueManagerState GetState_Implementation() const + //{ return GetDialogueManagerState(); }; + /** + * Retrieves current Dialogue Manager State. + * State defines whether Manager can start/close dialogue or not. + * + * @return ManagerState Manager state value + */ + //virtual EDialogueManagerState GetDialogueManagerState() const = 0; + /** + * Sets new Dialogue Manager State. + * + * @param NewState Manager State to be set as Manager State + */ + //virtual void SetDialogueManagerState(const EDialogueManagerState NewState) = 0; + /** + * Retrieves current Default Dialogue Manager State. + * Default Dialogue Manager State sets Dialogue Manager state upon BeginPlay and is used as fallback once Dialogue ends. + * + * @return ManagerState Default Manager state value + */ + //virtual EDialogueManagerState GetDefaultDialogueManagerState() const = 0; + /** + * Sets new Default Dialogue Manager State. + * + * @param NewState Manager State to be set as Default Manager State + */ + //virtual void SetDefaultDialogueManagerState(const EDialogueManagerState NewState) = 0; + // + //virtual FDialogueInitialized& GetDialogueInitializedEventHandle() = 0; + //virtual FDialogueEvent& GetDialogueStartedEventHandle() = 0; + //virtual FDialogueEvent& GetDialogueClosedEventHandle() = 0; + // + //virtual FDialogueContextUpdated& GetDialogueContextUpdatedEventHande() = 0; + //virtual FDialogueUserInterfaceChanged& GetDialogueUserInterfaceChangedEventHandle() = 0; + + //virtual FDialogueNodeEvent& GetDialogueNodeSelectedEventHandle() = 0; + + //virtual FDialogueNodeEvent& GetDialogueNodeStartedEventHandle() = 0; + //virtual FDialogueNodeEvent& GetDialogueNodeFinishedEventHandle() = 0; + //virtual FDialogueRowEvent& GetDialogueRowStartedEventHandle() = 0; + //virtual FDialogueRowEvent& GetDialogueRowFinishedEventHandle() = 0; + + //virtual FDialogueFailed& GetDialogueFailedEventHandle() = 0; + + //virtual FDialogueManagerStateChanged& GetDialogueManagerStateChangedEventHandle() = 0; + + //virtual FDialogueVoiceEvent& GetDialogueVoiceStartRequestEventHandle() = 0; + //virtual FDialogueVoiceEvent& GetDialogueVoiceSkipRequestEventHandle() = 0; + + //virtual FTimerHandle& GetDialogueRowTimerHandle() = 0; +}; diff --git a/Source/ComboInput/Public/Interfaces/ComboActionInterface.h b/Source/ComboInput/Public/Interfaces/ComboActionInterface.h new file mode 100644 index 0000000..aaeadf8 --- /dev/null +++ b/Source/ComboInput/Public/Interfaces/ComboActionInterface.h @@ -0,0 +1,199 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Data/ComboActionGraphDataTypes.h" +#include "UObject/Interface.h" + +#include "ComboActionInterface.generated.h" + + +UINTERFACE(MinimalAPI, BlueprintType, Blueprintable) +class UComboActionInterface : public UInterface +{ + GENERATED_BODY() +}; + +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueGraphChanged, UMounteaDialogueGraph*, NewGraph); +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueParticipantStateChanged, const EDialogueParticipantState&, NewState); +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueParticipantAudioComponentChanged, const UAudioComponent*, NewAudioComp); +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FParticipantStartingNodeSaved, const UMounteaDialogueGraphNode*, NewSavedNode); + +/** + * Combo Action Interface + */ +class COMBOINPUT_API IComboActionInterface +{ + GENERATED_BODY() + +public: + +#pragma region EventFunctions + + /* + * A way to determine whether the Dialogue can even start. + * It does come with Native C++ implementation, which can be overriden in child C++ classes. + * ❗ If you are using Blueprint implementation, don't forget to call Parent Node, which contains all parent implementations. + */ + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action") + bool CanStartDialogueEvent() const; + + /** + * Returns the owning actor for this Dialogue Participant Component. + * + * @return The owning actor for this Dialogue Participant Component. + */ + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action") + AActor *GetOwningActor() const; + + /** + * Saves the starting node for this Dialogue Participant Component. + * + * @param NewStartingNode The node to set as the starting node + */ + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action") + void SaveStartingNode(class UComboActionGraphNode *NewStartingNode); + /** + * Saves the traversed path for this Dialogue Participant Component. + * This function is called once Dialogue ends and is updated from Dialogue Context. + * + * @param InPath The traversed path of the dialogue graph to be saved. + */ + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action") + void SaveTraversedPath(TMap &InPath); + + /** + * Interface call. + * Retrieves current Dialogue Participant State. + * State defines whether Participant can start/close dialogue or not. + * + * @return ParticipantState Participant state value + */ + //UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action") + // EDialogueParticipantState GetState() const; + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action") + FGameplayTag GetTag() const; +#pragma endregion + +protected: + +#pragma region EventFunctions_Implementations + + bool CanStartDialogueEvent_Implementation() const { return CanStartDialogue(); } + + virtual void SaveStartingNode_Implementation(UComboActionGraphNode *NewStartingNode){} + + virtual AActor *GetOwningActor_Implementation() const { return nullptr; } + + virtual void SaveTraversedPath_Implementation(TMap &InPath){} + + //EDialogueParticipantState GetState_Implementation() const { return GetParticipantState(); } + + FGameplayTag GetTag_Implementation() const { return GetParticipantTag(); } + +#pragma endregion + +public: + +#pragma region Functions + + /** + * Checks if the Participant can be start Dialogue. + * ❔ To enhance this, you can implement 'CanStartDialogueEvent' and add custom checks to that function. + * + * @return Whether the dialogue can be started + */ + virtual bool CanStartDialogue() const = 0; + + /** + * Gets the saved starting node for this Dialogue Participant. + * ❗ Could be null + * + * @return The saved starting node, or nullptr if there is none + */ + virtual UComboActionGraphNode *GetSavedStartingNode() const = 0; + + /** + * Tries to play the specified sound as the voice of this dialogue participant. + * + * @param ParticipantVoice The sound to play as the voice of this dialogue participant + */ + virtual void PlayParticipantVoice(USoundBase *ParticipantVoice) = 0; + /** + * Tries to skip the specified sound this participant is playing as voice. + * + * @param ParticipantVoice The sound to skip this participant is playing as voice. + */ + virtual void SkipParticipantVoice(USoundBase *ParticipantVoice) = 0; + + /** + * Returns the dialogue graph assigned to this Participant. + * ❔ Could be updated using 'SetDialogueGraph', providing ability to swith Dialogue graphs on fly + * ❗ Could be null + * + * @return A pointer to the dialogue graph + */ + virtual class UComboActionGraph *GetDialogueGraph() const = 0; + /** + * Sets new Dialogue graph for this Participant. + * ❗ Should not be null + * + * @param NewDialogueGraph A pointer to the dialogue graph to be used + */ + virtual void SetDialogueGraph(class UComboActionGraph *NewDialogueGraph) = 0; + + /** + * Returns the current state of the Dialogue Participant. + */ + //virtual EDialogueParticipantState GetParticipantState() const = 0; + /** + * Sets the state of the dialogue participant to the given state. + * + * @param NewState The new state to set the dialogue participant to + */ + //virtual void SetParticipantState(const EDialogueParticipantState NewState) = 0; + /** + * Returns the default state of the Dialogue Participant. + */ + //virtual EDialogueParticipantState GetDefaultParticipantState() const = 0; + /** + * Sets the Default state of the dialogue participant to the given state. + * + * @param NewState The new state to set the dialogue participant to + */ + //virtual void SetDefaultParticipantState(const EDialogueParticipantState NewState) = 0; + + /** + * Returns the audio component used to play the participant voices. + * ❗ Could be null + */ + virtual UAudioComponent *GetAudioComponent() const = 0; + /** + * Sets the audio component used to play dialogue audio. + * + * @param NewAudioComponent The new audio component to use for dialogue audio. + */ + virtual void SetAudioComponent(UAudioComponent *NewAudioComponent) = 0; + + /** + * Returns the map of nodes traversed during the dialogue. + * + * @return The map of nodes traversed during the dialogue. + */ + virtual TMap GetTraversedPath() const = 0; + + virtual FGameplayTag GetParticipantTag() const = 0; + +#pragma endregion + +#pragma region EventHandles + + //virtual FDialogueGraphChanged &GetDialogueGraphChangedEventHandle() = 0; + //virtual FDialogueParticipantStateChanged &GetDialogueParticipantStateChangedEventHandle() = 0; + //virtual FDialogueParticipantAudioComponentChanged &GetDialogueParticipantAudioComponentChangedEventHandle() = 0; + //virtual FParticipantStartingNodeSaved &GetParticipantStartingNodeSavedEventHandle() = 0; + +#pragma endregion +}; diff --git a/Source/ComboInput/Public/Nodes/ComboActionGraphEdge.h b/Source/ComboInput/Public/Nodes/ComboActionGraphEdge.h new file mode 100644 index 0000000..02fa357 --- /dev/null +++ b/Source/ComboInput/Public/Nodes/ComboActionGraphEdge.h @@ -0,0 +1,40 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "ComboActionGraphEdge.generated.h" + + +/** + * Combo Action edges. + * + * Currently those edges are just connecting objects with no advanced logic. + */ +UCLASS(ClassGroup=("Combo Input|Action"), NotBlueprintType) +class COMBOINPUT_API UComboActionGraphEdge : public UObject +{ + GENERATED_BODY() + +#pragma region Variables +public: + + UPROPERTY(VisibleAnywhere, Category="Combo Input|Action") + UComboActionGraph *Graph = nullptr; + + UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") + UComboActionGraphNode *StartNode = nullptr; + + UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") + UComboActionGraphNode *EndNode = nullptr; + +#pragma endregion + +#pragma region Functions +public: + //UFUNCTION(BlueprintPure, Category="Combo Input|Action") + UComboActionGraph *GetGraph() const { return Graph; } + +#pragma endregion +}; diff --git a/Source/ComboInput/Public/Nodes/ComboActionGraphNode.h b/Source/ComboInput/Public/Nodes/ComboActionGraphNode.h new file mode 100644 index 0000000..c4c9e3d --- /dev/null +++ b/Source/ComboInput/Public/Nodes/ComboActionGraphNode.h @@ -0,0 +1,397 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Decorators/ComboActionDecoratorBase.h" + +#include "ComboActionGraphNode.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogComboActionGraphNode, Log, All); + + +/** + * Combo Action graph Node abstract base class. + * + * Provides generic functionality to be enhanced or overriden by Child Classes. + * Does come with ability to define Colours, Name, Description and Title. + * Contains information about Parent and Children Nodes. + */ +UCLASS(Abstract, BlueprintType, ClassGroup=("Combo Input|Action"), AutoExpandCategories=("Combo Input", "Action", "Combo Input|Action")) +class COMBOINPUT_API UComboActionGraphNode : public UObject +{ + GENERATED_BODY() + +public: + UComboActionGraphNode(); + +#pragma region Variables + +#pragma region ReadOnly +public: + /** + * Array of parent nodes for the current active node in the combo string. + *❗ Parent nodes are nodes that have a directed edge pointing to the current active node. + */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Private") + TArray ParentNodes; + /** + * The array of child nodes of the current dialogue node. + */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Private") + TArray ChildrenNodes; + /** + * Map of edges connecting this Node in the Combo Action graph. + *❗ The key of the map is the source node, and the value is the edge connecting it to its target node. + *❔ Can be used to traverse the graph, get information about node connections... + */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Private") + TMap Edges; + /** + * Pointer to the parent dialogue graph of this node. + */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Private", meta=(DisplayThumbnail=false)) + TObjectPtr Graph; + /** + * Temporary NodeIndex. + * This variable will be deleted. + */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Private") + int32 NodeIndex = INDEX_NONE; + + +protected: + /** + * The unique identifier for this Dialogue Graph Node. + *❗ This is used to differentiate between nodes, and must be unique within the graph. + *❔ Can be used for debugging and tracing purposes. + */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Private") + FGuid NodeGUID; + +private: + /** + * The world that owns this Dialogue Graph Node. + *❗ This is the world in which this Dialogue Graph Node is currently running. + *❔ Can be used for accessing world-related functionality. + */ + UPROPERTY(VisibleAnywhere, Category="Private") + TObjectPtr OwningWorld; + +#pragma endregion + +#pragma region Editable +public: + /** + * The array of allowed input classes for this Dialogue Node. + *❗ Only nodes with classes from this array can be connected as inputs to this node. + *❔ Can be used to restrict the types of inputs this node can accept. + */ + UPROPERTY(SaveGame, EditDefaultsOnly, BlueprintReadOnly, Category="Combo Input|Action") + TArray> AllowedInputClasses; + + /** Defines whether this Node will start automatically or if requires input.*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Combo Input|Action") + uint8 bAutoStarts : 1; + /** + * The maximum number of children nodes that this node can have. + *❗ If this value is -1, then there is no limit on the number of children nodes. + *❔ Can be used to enforce a maximum number of connections for certain types of nodes. + */ + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category="Combo Input|Action") + int32 MaxChildrenNodes = -1; + /** + * Indicates whether this node inherits the decorators from its parent Graph. + *❗ If true, the decorators of the parent Graph will be inherited and applied to this node during processing. + *❔ This flag can be used to control the inheritance of decorators for nodes in the dialogue graph. + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Combo Input|Action") + uint8 bInheritGraphDecorators : 1; + + /** + * A list of Decorators that can help out with enhancing the Dialogue flow. + * Those Decorators are instanced and exist only as "triggers". + * Could be used to start audio, play animation or do some logic behind the curtains, like triggering Cutscene etc. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Combo Input|Action", NoClear, meta=(NoResetToDefault)) + TArray NodeDecorators; + +#pragma endregion + +#pragma endregion + +#pragma region Functions + +public: + /** + * Initializes the node with the given world. + * + * @param InWorld The world to use for initialization. + */ + UFUNCTION(BlueprintNativeEvent, Category = "Combo Input|Action") + void InitializeNode(UWorld *InWorld); + virtual void InitializeNode_Implementation(UWorld *InWorld); + /** + * Checks if the node should automatically start when the dialogue is played. + * @return true if the node should automatically start, false otherwise. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + virtual bool DoesAutoStart() const { return bAutoStarts; } + + virtual void PreProcessNode(){} + virtual void ProcessNode(); + /** + * Gets the decorators for this Dialogue Graph Node. + *❔ Returns only Valid decorators! + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + TArray GetNodeDecorators() const; + /** + * Returns true if the node can be started. + *❗ The implementation of this function is up to the subclass. + *❔ Can be used to validate if a node can be started before attempting to start it. + *❔ This can be further enhanced by Decorators. + * @return True if the node can be started, false otherwise. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + virtual bool CanStartNode() const; + virtual bool EvaluateDecorators() const; + /** + * Returns whether this node inherits decorators from the dialogue graph. + * If this is set to true, this node will receive all decorators assigned to the graph. + * If it's set to false, the node will only have its own decorators. + * + * @return Whether this node inherits decorators from the graph. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + bool DoesInheritDecorators() const { return bInheritGraphDecorators; } + /** + * Returns how many Children Nodes this Node allows to have. + *❔ -1 means no limits. + * + * @return MaxChildrenNodes + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + int32 GetMaxChildNodes() const { return MaxChildrenNodes; } + + + /** + * Gets the index of the node within the dialogue graph. + * + * @return The index of the node. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + FORCEINLINE int32 GetNodeIndex() const { return NodeIndex; } + /** + * Sets the index of this dialogue node in the dialogue graph. + * + * @param NewIndex The new index to set. + */ + void SetNodeIndex(const int32 NewIndex); + + /** + * Gets the GUID of the node. + * + * @return The GUID of the node. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + FORCEINLINE FGuid GetNodeGUID() const { return NodeGUID; } + + /** + * Gets the owning Graph of the node. + *❗ Might be null + * + * @return The owning Graph of the node. + */ + UFUNCTION(BlueprintCallable, Category = "Combo Input|Action") + class UComboActionGraph *GetGraph() const { return this->Graph; } + + + /** + * Gets children Nodes this one has, + *❗ Might be empty + * + * @return Amount of children Nodes + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + FORCEINLINE TArray GetChildrenNodes() const { return ChildrenNodes; } + /** + * Gets how many parent Nodes point to this one + *❗ Might be empty + * + * @return Amount of how parent Nodes + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + FORCEINLINE TArray GetParentNodes() const { return ParentNodes; } + + /** + * Serves purpose of validating Node before Dialogue gets Started. + * Any broken Node results in non-starting Dialogue to avoid crashes. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Combo Input|Action") + bool ValidateNodeRuntime() const; + virtual bool ValidateNodeRuntime_Implementation() const { return true; } + +public: + + FORCEINLINE ULevel* GetLevel() const { return GetTypedOuter(); } + + /** + * Provides a way to update Node's owning World. + * Useful for Loading sub-levels. + */ + UFUNCTION(BlueprintCallable, Category="Combo Input|Action") + virtual void SetNewWorld(UWorld* NewWorld); + virtual UWorld* GetWorld() const override + { + if (OwningWorld) return OwningWorld; + + // CDO objects do not belong to a world + // If the actors outer is destroyed or unreachable we are shutting down and the world should be nullptr + if ( + !HasAnyFlags(RF_ClassDefaultObject) && ensureMsgf(GetOuter(), TEXT("Actor: %s has a null OuterPrivate in AActor::GetWorld()"), *GetFullName()) + && !GetOuter()->HasAnyFlags(RF_BeginDestroyed) && !GetOuter()->IsUnreachable() + ) + { + if (ULevel* Level = GetLevel()) + { + return Level->OwningWorld; + } + } + return nullptr; + } + +#pragma endregion + + +#if WITH_EDITORONLY_DATA + + // Defines whether this Node type allows inputs + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + bool bAllowInputNodes; + + // Defines whether this Node type allows outputs + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + bool bAllowOutputNodes; + + // Defines whether this Node can be copied + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + bool bAllowCopy; + + // Defines whether this Node can be cut + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + bool bAllowCut; + + // Defines whether this Node can be pasted + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + bool bAllowPaste; + + // Defines whether this Node can be deleted + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + bool bAllowDelete; + + // Defines whether this Node can be manually created + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + bool bAllowManualCreate; + + // Display title of the Node + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + FText NodeTitle; + + // Display name of the Node menu category + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + FText ContextMenuName; + + // List of compatible graph types + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Editor") + TSubclassOf CompatibleGraphType; + + // Defines background colour of this Node + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + FLinearColor BackgroundColor; + + // Contains Node Tooltip text + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + FText NodeTooltipText; + + // User friendly node type name + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor") + FText NodeTypeName; + +#endif + +#if WITH_EDITOR + + /** + * Returns the tooltip text for this graph node. + * + * @return The tooltip text for this node. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true)) + FText GetNodeTooltipText() const; + virtual FText GetNodeTooltipText_Implementation() const; + + /** + * Returns the Title text for this graph node. + * + * @return The Title text for this node. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true)) + FText GetNodeTitle() const; + virtual FText GetNodeTitle_Implementation() const { return this->NodeTitle; } + + /** + * Returns the Description text for this graph node. + * + * @return The Description text for this node. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true)) + FText GetDescription() const; + virtual FText GetDescription_Implementation() const; + + /** + * Returns the Node Category text for this graph node. + * + * @return The Node Category text for this node. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true)) + FText GetNodeCategory() const; + virtual FText GetNodeCategory_Implementation() const; + + /** + * Returns the Documentation Link for this graph node. + * + * @return The Documentation Link for this node. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true)) + FString GetNodeDocumentationLink() const; + virtual FString GetNodeDocumentationLink_Implementation() const; + + /** + * Returns the Background Colour for this graph node. + * + * @return The Background Colour for this node. + */ + virtual FLinearColor GetBackgroundColor() const { return this->BackgroundColor; } + FText GetInternalName() const { return this->NodeTypeName; } + + // Allows setting up the Node Title + virtual void SetNodeTitle(const FText &NewTitle) { this->NodeTitle = NewTitle; } + + // Allows advanced filtering if Node can be connected from other Node + virtual bool CanCreateConnection(UComboActionGraphNode *Other, enum EEdGraphPinDirection Direction, FText& ErrorMessage); + + // Validation function responsible for generating user friendly validation messages + virtual bool ValidateNode(TArray &ValidationsMessages, const bool RichFormat); + + // Once Node is pasted, this function is called + virtual void OnPasted(); + + // Generates default Tooltip body text used for all Nodes + UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true)) + FText GetDefaultTooltipBody() const; + virtual void OnCreatedInEditor() {}; + +#endif + +}; diff --git a/Source/ComboInput/Public/Nodes/ComboActionGraphNode_ActionNodeBase.h b/Source/ComboInput/Public/Nodes/ComboActionGraphNode_ActionNodeBase.h new file mode 100644 index 0000000..bade51e --- /dev/null +++ b/Source/ComboInput/Public/Nodes/ComboActionGraphNode_ActionNodeBase.h @@ -0,0 +1,126 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "Engine/DataTable.h" +//#include "Helpers/ComboActionGraphHelpers.h" +#include "Nodes/ComboActionGraphNode.h" +#include "UObject/Object.h" + +#include "ComboActionGraphNode_ActionNodeBase.generated.h" + + +/** + * Combo Action Graph Node abstract Base class. + * + * Enhances 'ComboActionGraphNode' Base class with action data. + */ +UCLASS(Abstract, ClassGroup=("Combo Input|Action"), AutoExpandCategories=("Combo Input", "Action", "Combo Input|Action")) +class COMBOINPUT_API UComboActionGraphNode_ActionNodeBase : public UComboActionGraphNode +{ + GENERATED_BODY() + +public: + UComboActionGraphNode_ActionNodeBase(); + + virtual void ProcessNode() override; + virtual void PreProcessNode() override; + + /** + * Returns the Dialogue Data Table for this graph node. + * ❗ Might be null + * + * @return The Dialogue Data Table for this graph node. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + virtual UDataTable *GetDataTable() const; + + /** + * Returns the Dialogue Data Row name. + * ❗ Might be invalid + * + * @return The Dialogue Data Row name. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") + virtual FName GetRowName() const { return RowName; } + + virtual bool ValidateNodeRuntime_Implementation() const override; + +public: + +#if WITH_EDITORONLY_DATA + + /** + * Shows read-only Texts with localization of selected Dialogue Row. + */ + UPROPERTY(Transient, VisibleAnywhere, Category="Base", meta=(MultiLine=true, ShowOnlyInnerProperties)) + TArray Preview; + + FSimpleDelegate PreviewsUpdated; + +#endif + +protected: + + /** + * The data table containing the dialogue rows. + * ❗ Strongly suggested to use 'DialogueRow' based Data Tables + */ + UPROPERTY(SaveGame, Category="Combo Input|Action", EditAnywhere, BlueprintReadOnly, meta=(DisplayThumbnail=false, NoResetToDefault)) + UDataTable *DataTable; + + /** Name of row in the table that we want */ + UPROPERTY(SaveGame, Category="Combo Input|Action", EditAnywhere, BlueprintReadOnly, meta=(GetOptions ="GetRowNames", NoResetToDefault, EditCondition="DataTable!=nullptr")) + FName RowName; + + /** + * Flag defining how the Participant is searched for. + * Default: False + * + * If True: + * * Participant will be found by its Gameplay Tag, compared to Dialogue Row Data. + * * ❗Only exact match is considered success + * * ❗ First found is used, so use unique Tags when working with multiple Participants (Player01, Player02, NPC.Andrew etc.) + * + * If False: + * * Participant will be found using Node Type + * * Lead Node will use NPC + * * Answer Node will use Player + * * ❗ This system will be deprecated + * + * ❗ New feature in version 1.0.5.X. + * ❔ Each unique dialogue Participant should be using different Tag, if generic, then use something like `Dialogue.NPC` + */ + UPROPERTY(SaveGame, Category="Combo Input|Action", EditAnywhere, BlueprintReadOnly, meta=(NoResetToDefault)) + uint8 bUseGameplayTags : 1; + +#if WITH_EDITOR + + virtual bool ValidateNode(TArray& ValidationsMessages, const bool RichFormat) override; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + virtual FText GetDescription_Implementation() const override; + +public: + TArray GetPreviews() const; +#endif + +#if WITH_EDITORONLY_DATA +public: + virtual void UpdatePreviews(); +#endif + +private: + + UFUNCTION() + TArray GetRowNames() const + { + if (DataTable) + { + return DataTable->GetRowNames(); + } + + return TArray(); + } +}; diff --git a/Source/ComboInput/Public/Nodes/ComboActionGraphNode_StartNode.h b/Source/ComboInput/Public/Nodes/ComboActionGraphNode_StartNode.h new file mode 100644 index 0000000..005392e --- /dev/null +++ b/Source/ComboInput/Public/Nodes/ComboActionGraphNode_StartNode.h @@ -0,0 +1,35 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "Nodes/ComboActionGraphNode.h" +#include "UObject/Object.h" + +#include "ComboActionGraphNode_StartNode.generated.h" + + +/** + * Combo Action Start Node. + * + * This Node will be added to the graph automatically when created. + * This node cannot be created manually. + * This node cannot be deleted. + * Does not implement any logic. + */ +UCLASS(ClassGroup=("Combo Input|Action"), DisplayName="Start Node", meta=(ToolTip="Combo Action Tree: Start Node")) +class COMBOINPUT_API UComboActionGraphNode_StartNode : public UComboActionGraphNode +{ + GENERATED_BODY() + +public: + + UComboActionGraphNode_StartNode(); + +#if WITH_EDITOR + virtual FText GetDescription_Implementation() const override; + + virtual bool ValidateNode(TArray& ValidationsMessages, const bool RichFormat); +#endif +}; diff --git a/Source/ComboInputEditor/ComboInputEditor.build.cs b/Source/ComboInputEditor/ComboInputEditor.build.cs index 4e53f05..49071fc 100644 --- a/Source/ComboInputEditor/ComboInputEditor.build.cs +++ b/Source/ComboInputEditor/ComboInputEditor.build.cs @@ -27,7 +27,8 @@ public class ComboInputEditor : ModuleRules "CoreUObject", "AssetTools", "SlateCore", - "Projects" + "Projects", + "GraphEditor" } ); diff --git a/Source/ComboInputEditor/Private/ComboActionGraphSchema.cpp b/Source/ComboInputEditor/Private/ComboActionGraphSchema.cpp new file mode 100644 index 0000000..9850f9d --- /dev/null +++ b/Source/ComboInputEditor/Private/ComboActionGraphSchema.cpp @@ -0,0 +1,434 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "ComboActionGraphSchema.h" + +#include "ComboActionGraph.h" +#include "FConnectionDrawingPolicy_ComboActionGraph.h" +#include "GraphEditorActions.h" + +#include "Ed/EdComboActionGraphEdge.h" +#include "Ed/EdComboActionGraphNode.h" +#include "Framework/Commands/GenericCommands.h" +#include "Nodes/ComboActionGraphEdge.h" +#include "Nodes/ComboActionGraphNode.h" + +#define LOCTEXT_NAMESPACE "ComboActionGraph" + + +UEdGraphNode *FAssetSchemaAction_ComboActionGraphSchema_NewNode::PerformAction(UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode) +{ + UEdGraphNode *ResultNode = nullptr; + + if (NodeTemplate != nullptr) + { + const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorNewNode", "Combo Action 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->ComboActionGraphNode->SetFlags(EObjectFlags::RF_Transactional); + NodeTemplate->SetFlags(EObjectFlags::RF_Transactional); + + ResultNode = NodeTemplate; + } + + return ResultNode; +} + +void FAssetSchemaAction_ComboActionGraphSchema_NewNode::AddReferencedObjects(FReferenceCollector &Collector) +{ + FEdGraphSchemaAction::AddReferencedObjects(Collector); + Collector.AddReferencedObject(NodeTemplate); +} + +UEdGraphNode *FAssetSchemaAction_ComboActionGraphSchema_NewEdge::PerformAction(UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode) +{ + UEdGraphNode *ResultNode = nullptr; + + if (NodeTemplate != nullptr) + { + const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorNewEdge", "Combo Action 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->ComboActionGraphEdge->SetFlags(EObjectFlags::RF_Transactional); + NodeTemplate->SetFlags(EObjectFlags::RF_Transactional); + + ResultNode = NodeTemplate; + } + + return ResultNode; +} + +void FAssetSchemaAction_ComboActionGraphSchema_NewEdge::AddReferencedObjects(FReferenceCollector &Collector) +{ + FEdGraphSchemaAction::AddReferencedObjects(Collector); + Collector.AddReferencedObject(NodeTemplate); +} + + +void UComboActionGraphSchema::GetBreakLinkToSubMenuActions(UToolMenu *Menu, UEdGraphPin *InGraphPin) +{ + // Make sure we have a unique name for every entry in the list + TMap LinkTitleCount; + + FToolMenuSection &Section = Menu->FindOrAddSection("ComboActionGraphAssetGraphSchemaPinActions"); + + // Add all the links we could break from + for (TArray::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, &UComboActionGraphSchema::BreakSinglePinLink, const_cast(InGraphPin), *Links))); + } +} + +void UComboActionGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder &ContextMenuBuilder) const +{ + UComboActionGraph *Graph = CastChecked(ContextMenuBuilder.CurrentGraph->GetOuter()); + + if (Graph->NodeType == nullptr) + { + return; + } + + const bool bNoParent = (ContextMenuBuilder.FromPin == NULL); + + FText AddToolTip = LOCTEXT("NewMoutneaDialogueGraphNodeTooltip", "Add Dialogue Node here"); + + TSet> Visited; + + FText Desc = Graph->NodeType.GetDefaultObject()->ContextMenuName; + FText NodeCategory = Graph->NodeType.GetDefaultObject()->GetNodeCategory(); + + if (Desc.IsEmpty()) + { + FString Title = Graph->NodeType->GetName(); + Title.RemoveFromEnd("_C"); + Desc = FText::FromString(Title); + } + + if (!Graph->NodeType->HasAnyClassFlags(CLASS_Abstract)) + { + TSharedPtr NewNodeAction(new FAssetSchemaAction_ComboActionGraphSchema_NewNode(NodeCategory, Desc, AddToolTip, 0)); + NewNodeAction->NodeTemplate = NewObject(ContextMenuBuilder.OwnerOfTemporaries); + NewNodeAction->NodeTemplate->ComboActionGraphNode = NewObject(NewNodeAction->NodeTemplate, Graph->NodeType); + NewNodeAction->NodeTemplate->ComboActionGraphNode->Graph = Graph; + ContextMenuBuilder.AddAction(NewNodeAction); + + Visited.Add(Graph->NodeType); + } + + for (TObjectIterator It; It; ++It) + { + if (It->IsChildOf(Graph->NodeType) && !It->HasAnyClassFlags(CLASS_Abstract) && !Visited.Contains(*It)) + { + TSubclassOf NodeType = *It; + + if (It->GetName().StartsWith("REINST") || It->GetName().StartsWith("SKEL")) + continue; + + if (!Graph->GetClass()->IsChildOf(NodeType.GetDefaultObject()->CompatibleGraphType)) + continue; + + if (!NodeType.GetDefaultObject()->bAllowManualCreate) + continue; + + Desc = NodeType.GetDefaultObject()->ContextMenuName; + AddToolTip = NodeType.GetDefaultObject()->GetDescription(); + + NodeCategory = NodeType.GetDefaultObject()->GetNodeCategory(); + + if (Desc.IsEmpty()) + { + FString Title = NodeType->GetName(); + Title.RemoveFromEnd("_C"); + Desc = FText::FromString(Title); + } + + TSharedPtr Action(new FAssetSchemaAction_ComboActionGraphSchema_NewNode(NodeCategory, Desc, AddToolTip, 0)); + Action->NodeTemplate = NewObject(ContextMenuBuilder.OwnerOfTemporaries); + Action->NodeTemplate->ComboActionGraphNode = NewObject(Action->NodeTemplate, NodeType); + Action->NodeTemplate->ComboActionGraphNode->Graph = Graph; + ContextMenuBuilder.AddAction(Action); + + Visited.Add(NodeType); + } + } +} + +void UComboActionGraphSchema::GetContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const +{ + if (Context->Pin) + { + { + FToolMenuSection& Section = Menu->AddSection("ComboActionGraphAssetGraphSchemaNodeActions", 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((UComboActionGraphSchema *const)this, &UComboActionGraphSchema::GetBreakLinkToSubMenuActions, const_cast(Context->Pin))); + } + else + { + ((UComboActionGraphSchema *const)this)->GetBreakLinkToSubMenuActions(Menu, const_cast(Context->Pin)); + } + } + } + } + else if (Context->Node) + { + { + FToolMenuSection& Section = Menu->AddSection("ComboActionGraphAssetGraphSchemaNodeActions", LOCTEXT("ClassActionsMenuHeader", "Node Actions")); + Section.AddSeparator(FName(TEXT("Main Options"))); + Section.AddMenuEntry(FGenericCommands::Get().Rename); + Section.AddSeparator(FName(TEXT("Other Options"))); + 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 UComboActionGraphSchema::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", "Both are on the same node")); + } + + // Compare the directions + if ((A->Direction == EGPD_Input) && (B->Direction == EGPD_Input)) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorInput", "Can't connect input node to input node")); + } + else if ((A->Direction == EGPD_Output) && (B->Direction == EGPD_Output)) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorOutput", "Can't connect output node to output node")); + } + + // check for cycles + FComboActionNodeVisitorCycleChecker CycleChecker; + if (!CycleChecker.CheckForLoop(A->GetOwningNode(), B->GetOwningNode())) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorCycle", "Can't create a graph cycle")); + } + if (!CycleChecker.CheckForLoop(B->GetOwningNode(), A->GetOwningNode())) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorCycle", "Can't create a graph cycle")); + } + + UEdComboActionGraphNode* EdNode_A = Cast(A->GetOwningNode()); + UEdComboActionGraphNode* EdNode_B = Cast(B->GetOwningNode()); + + if (EdNode_A == nullptr || EdNode_B == nullptr) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinError", "Not a valid UComboActionGraphEdNode")); + } + + FText ErrorMessage; + if (A->Direction == EGPD_Input) + { + if (!EdNode_A->ComboActionGraphNode->CanCreateConnection(EdNode_B->ComboActionGraphNode, EGPD_Input, ErrorMessage)) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, ErrorMessage); + } + } + else + { + if (!EdNode_B->ComboActionGraphNode->CanCreateConnection(EdNode_A->ComboActionGraphNode, EGPD_Output, ErrorMessage)) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, ErrorMessage); + } + } + + if (EdNode_A->ComboActionGraphNode->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 UComboActionGraphSchema::CreateAutomaticConversionNodeAndConnections(UEdGraphPin* A, UEdGraphPin* B) const +{ + UEdComboActionGraphNode *NodeA = Cast(A->GetOwningNode()); + UEdComboActionGraphNode *NodeB = Cast(B->GetOwningNode()); + + if (NodeA == nullptr || NodeB == nullptr) + return false; + + if (NodeA->GetInputPin() == nullptr || NodeA->GetOutputPin() == nullptr || NodeB->GetInputPin() == nullptr || NodeB->GetOutputPin() == nullptr) + return false; + + UComboActionGraph* Graph = NodeA->ComboActionGraphNode->GetGraph(); + + FVector2D InitPos((NodeA->NodePosX + NodeB->NodePosX) / 2, (NodeA->NodePosY + NodeB->NodePosY) / 2); + + FAssetSchemaAction_ComboActionGraphSchema_NewEdge Action; + Action.NodeTemplate = NewObject(NodeA->GetGraph()); + Action.NodeTemplate->SetEdge(NewObject(Action.NodeTemplate, Graph->EdgeType)); + UEdComboActionGraphEdge *EdgeNode = Cast(Action.PerformAction(NodeA->GetGraph(), nullptr, InitPos, false)); + + if (A->Direction == EEdGraphPinDirection::EGPD_Output) + { + EdgeNode->CreateConnections(NodeA, NodeB); + } + else + { + EdgeNode->CreateConnections(NodeB, NodeA); + } + + return true; +} + +FConnectionDrawingPolicy *UComboActionGraphSchema::CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj) const +{ + /* + if (const UMounteaDialogueGraphEditorSettings* MounteaDialogueGraphEditorSettings = GetMutableDefault()) + { + if (MounteaDialogueGraphEditorSettings->AllowAdvancedWiring()) + { + return new FConnectionDrawingPolicy_AdvancedMounteaDialogueGraph(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements, InGraphObj); + } + } + */ + + return new FConnectionDrawingPolicy_ComboActionGraph(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements, InGraphObj); +} + +FLinearColor UComboActionGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const +{ + return FColor::White; +} + +void UComboActionGraphSchema::BreakNodeLinks(UEdGraphNode& TargetNode) const +{ + const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakNodeLinks", "Break Node Links")); + + Super::BreakNodeLinks(TargetNode); +} + +void UComboActionGraphSchema::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const +{ + const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakPinLinks", "Break Pin Links")); + + Super::BreakPinLinks(TargetPin, bSendsNodeNotifcation); +} + +void UComboActionGraphSchema::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const +{ + const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakSinglePinLink", "Break Pin Link")); + + Super::BreakSinglePinLink(SourcePin, TargetPin); +} + +UEdGraphPin *UComboActionGraphSchema::DropPinOnNode(UEdGraphNode* InTargetNode, const FName& InSourcePinName, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection) const +{ + UEdComboActionGraphNode* EdNode = Cast(InTargetNode); + switch (InSourcePinDirection) + { + case EEdGraphPinDirection::EGPD_Input: + return EdNode->GetOutputPin(); + case EEdGraphPinDirection::EGPD_Output: + return EdNode->GetInputPin(); + default: + return nullptr; + } +} + +bool UComboActionGraphSchema::SupportsDropPinOnNode(UEdGraphNode* InTargetNode, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection, FText& OutErrorMessage) const +{ + return Cast(InTargetNode) != nullptr; +} + +bool UComboActionGraphSchema::IsCacheVisualizationOutOfDate(int32 InVisualizationCacheID) const +{ + return CurrentCacheRefreshID != InVisualizationCacheID; +} + +int32 UComboActionGraphSchema::GetCurrentVisualizationCacheID() const +{ + return CurrentCacheRefreshID; +} + +void UComboActionGraphSchema::ForceVisualizationCacheClear() const +{ + CurrentCacheRefreshID++; +} + +void UComboActionGraphSchema::CreateDefaultNodesForGraph(UEdGraph& Graph) const +{ + Super::CreateDefaultNodesForGraph(Graph); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ComboInputEditor/Private/ComboActionGraphSchema.h b/Source/ComboInputEditor/Private/ComboActionGraphSchema.h new file mode 100644 index 0000000..8fb55e6 --- /dev/null +++ b/Source/ComboInputEditor/Private/ComboActionGraphSchema.h @@ -0,0 +1,164 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "ComboActionGraphSchema.generated.h" + + +class FComboActionNodeVisitorCycleChecker +{ +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); + + if (!QuiCheckDirectConnections(StartNode, EndNode)) + { + return false; + } + + return TraverseInputNodesToRoot(EndNode); + } + +private: + bool TraverseInputNodesToRoot(UEdGraphNode *Node) + { + VisitedNodes.Add(Node); + + for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) + { + UEdGraphPin *MyPin = Node->Pins[PinIndex]; + + if (MyPin->Direction == EGPD_Output) + { + for (int32 LinkedPinIndex = 0; LinkedPinIndex < MyPin->LinkedTo.Num(); ++LinkedPinIndex) + { + UEdGraphPin *OtherPin = MyPin->LinkedTo[LinkedPinIndex]; + if (OtherPin) + { + UEdGraphNode *OtherNode = OtherPin->GetOwningNode(); + if (VisitedNodes.Contains(OtherNode)) + { + return false; + } + else + { + return TraverseInputNodesToRoot(OtherNode); + } + } + } + } + } + + return true; + } + + bool QuiCheckDirectConnections(UEdGraphNode *A, UEdGraphNode *B) + { + for (auto Itr : B->Pins) + { + if (A->Pins.Contains(Itr)) + { + return false; + } + } + + for (auto Itr : A->Pins) + { + if (B->Pins.Contains(Itr)) + { + return false; + } + } + + return true; + } + + TSet VisitedNodes; +}; + +/** Action to add a node to the graph */ +USTRUCT() +struct FAssetSchemaAction_ComboActionGraphSchema_NewNode : public FEdGraphSchemaAction +{ + GENERATED_BODY(); + +public: + FAssetSchemaAction_ComboActionGraphSchema_NewNode() : NodeTemplate(nullptr){} + + FAssetSchemaAction_ComboActionGraphSchema_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; + + class UEdComboActionGraphNode *NodeTemplate; +}; + +USTRUCT() +struct FAssetSchemaAction_ComboActionGraphSchema_NewEdge : public FEdGraphSchemaAction +{ + GENERATED_BODY(); + +public: + FAssetSchemaAction_ComboActionGraphSchema_NewEdge() : NodeTemplate(nullptr) + {} + + FAssetSchemaAction_ComboActionGraphSchema_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; + + class UEdComboActionGraphEdge *NodeTemplate; +}; + + +UCLASS(MinimalAPI) +class UComboActionGraphSchema : public UEdGraphSchema +{ + GENERATED_BODY() + +public: + void GetBreakLinkToSubMenuActions(class UToolMenu* Menu, class UEdGraphPin* InGraphPin); + + virtual EGraphType GetGraphType(const UEdGraph* TestEdGraph) const override { return EGraphType::GT_StateMachine; } + + 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 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; + + virtual void CreateDefaultNodesForGraph(UEdGraph& Graph) const override; + +private: + + static int32 CurrentCacheRefreshID; +}; diff --git a/Source/ComboInputEditor/Private/Ed/EdComboActionGraph.cpp b/Source/ComboInputEditor/Private/Ed/EdComboActionGraph.cpp new file mode 100644 index 0000000..2a71353 --- /dev/null +++ b/Source/ComboInputEditor/Private/Ed/EdComboActionGraph.cpp @@ -0,0 +1,209 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "Ed/EdComboActionGraph.h" + +#include "Ed/EdComboActionGraphEdge.h" +#include "Ed/EdComboActionGraphNode.h" +#include "ComboActionGraph.h" +#include "Nodes/ComboActionGraphNode.h" +#include "Nodes/ComboActionGraphEdge.h" +//#include "Helpers/MounteaDialogueGraphEditorHelpers.h" +//#include "Helpers/MounteaDialogueGraphEditorUtilities.h" +//#include "Helpers/MounteaDialogueSystemEditorBFC.h" + +DEFINE_LOG_CATEGORY(LogEdComboActionGraph); + + +void UEdComboActionGraph::RebuildComboActionGraph() +{ + UE_LOG(LogEdComboActionGraph, Warning, TEXT("UEdComboActionGraph::RebuildComboActionGraph has been called")); + + UComboActionGraph *Graph = GetComboActionGraph(); + + Clear(); + + for (int i = 0; i < Nodes.Num(); ++i) + { + if (UEdComboActionGraphNode *EdNode = Cast(Nodes[i])) + { + if (EdNode->ComboActionGraphNode == nullptr) + continue; + + UComboActionGraphNode *ComboActionGraphNode = EdNode->ComboActionGraphNode; + + NodeMap.Add(ComboActionGraphNode, EdNode); + + Graph->AllNodes.Add(ComboActionGraphNode); + + //EdNode->SetDialogueNodeIndex( Graph->AllNodes.Find(EdNode->ComboActionGraphNode) ); + + 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) + { + UComboActionGraphNode *ChildNode = nullptr; + if (UEdComboActionGraphNode *EdNode_Child = Cast(Pin->LinkedTo[LinkToIdx]->GetOwningNode())) + { + ChildNode = EdNode_Child->ComboActionGraphNode; + } + else if (UEdComboActionGraphEdge *EdNode_Edge = Cast(Pin->LinkedTo[LinkToIdx]->GetOwningNode())) + { + UEdComboActionGraphNode *Child = EdNode_Edge->GetEndNode();; + if (Child != nullptr) + { + ChildNode = Child->ComboActionGraphNode; + } + } + + if (ChildNode != nullptr) + { + ComboActionGraphNode->ChildrenNodes.Add(ChildNode); + + ChildNode->ParentNodes.Add(ComboActionGraphNode); + } + else + { + UE_LOG(LogEdComboActionGraph, Error, TEXT("[RebuildComboActionGraph] Can't find child node")); + } + } + } + } + else if (UEdComboActionGraphEdge *EdgeNode = Cast(Nodes[i])) + { + UEdComboActionGraphNode *StartNode = EdgeNode->GetStartNode(); + UEdComboActionGraphNode *EndNode = EdgeNode->GetEndNode(); + UComboActionGraphEdge *Edge = EdgeNode->ComboActionGraphEdge; + + if (StartNode == nullptr || EndNode == nullptr || Edge == nullptr) + { + UE_LOG(LogEdComboActionGraph, Error, TEXT("[RebuildMounteaDialogueGraph] Add edge failed.")); + continue; + } + + EdgeMap.Add(Edge, EdgeNode); + + Edge->Graph = Graph; + Edge->Rename(nullptr, Graph, REN_DontCreateRedirectors | REN_DoNotDirty); + Edge->StartNode = StartNode->ComboActionGraphNode; + Edge->EndNode = EndNode->ComboActionGraphNode; + Edge->StartNode->Edges.Add(Edge->EndNode, Edge); + } + } + + for (int i = 0; i < Graph->AllNodes.Num(); ++i) + { + UComboActionGraphNode *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 UComboActionGraphNode &L, const UComboActionGraphNode &R) + { + UEdComboActionGraphNode* EdNode_LNode = NodeMap[&L]; + UEdComboActionGraphNode* EdNode_RNode = NodeMap[&R]; + return EdNode_LNode->NodePosX < EdNode_RNode->NodePosX; + }); +} + +UComboActionGraph *UEdComboActionGraph::GetComboActionGraph() const +{ + return CastChecked(GetOuter()); +} + +bool UEdComboActionGraph::Modify(bool bAlwaysMarkDirty) +{ + bool Rtn = Super::Modify(bAlwaysMarkDirty); + + this->GetComboActionGraph()->Modify(); + + for (int32 i = 0; i < Nodes.Num(); ++i) + { + Nodes[i]->Modify(); + } + + return Rtn; +} + +void UEdComboActionGraph::PostEditUndo() +{ + NotifyGraphChanged(); + + Super::PostEditUndo(); +} + +//void UEdComboActionGraph::SetDialogueEditorPtr(TWeakPtr NewPtr) +//{ +// DialogueEditorPtr = NewPtr; +//} + +//bool UEdComboActionGraph::JumpToNode(const UMounteaDialogueGraphNode* Node) +//{ +// return FComboActionGraphEditorUtilities::OpenEditorAndJumpToGraphNode(DialogueEditorPtr, *NodeMap.Find(Node)); +//} + +void UEdComboActionGraph::Clear() +{ + UComboActionGraph *Graph = this->GetComboActionGraph(); + + Graph->ClearGraph(); + this->NodeMap.Reset(); + this->EdgeMap.Reset(); + + for (int i = 0; i < this->Nodes.Num(); ++i) + { + if (UEdComboActionGraphNode *EdNode = Cast(Nodes[i])) + { + UComboActionGraphNode *MounteaDialogueGraphNode = EdNode->ComboActionGraphNode; + MounteaDialogueGraphNode->ParentNodes.Reset(); + MounteaDialogueGraphNode->ChildrenNodes.Reset(); + MounteaDialogueGraphNode->Edges.Reset(); + } + } +} + +void UEdComboActionGraph::SortNodes(UComboActionGraphNode *RootNode) +{ + int Level = 0; + TArray CurrLevelNodes = { RootNode }; + TArray NextLevelNodes; + + while (CurrLevelNodes.Num() != 0) + { + int32 LevelWidth = 0; + for (int i = 0; i < CurrLevelNodes.Num(); ++i) + { + UComboActionGraphNode *Node = CurrLevelNodes[i]; + + auto Comp = [&](const UComboActionGraphNode &L, const UComboActionGraphNode &R) + { + UEdComboActionGraphNode *EdNode_LNode = NodeMap[&L]; + UEdComboActionGraphNode *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) + { + NextLevelNodes.Add(Node->ChildrenNodes[j]); + } + } + + CurrLevelNodes = NextLevelNodes; + NextLevelNodes.Reset(); + ++Level; + } +} diff --git a/Source/ComboInputEditor/Private/Ed/EdComboActionGraph.h b/Source/ComboInputEditor/Private/Ed/EdComboActionGraph.h new file mode 100644 index 0000000..e7e069e --- /dev/null +++ b/Source/ComboInputEditor/Private/Ed/EdComboActionGraph.h @@ -0,0 +1,49 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EdGraph/EdGraph.h" + +#include "EdComboActionGraph.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogEdComboActionGraph, Log, All); + + +UCLASS() +class COMBOINPUT_API UEdComboActionGraph : public UEdGraph +{ + GENERATED_BODY() + +public: + virtual void RebuildComboActionGraph(); + + UComboActionGraph *GetComboActionGraph() const; + + virtual bool Modify(bool bAlwaysMarkDirty) override; + virtual void PostEditUndo() override; + + //TWeakPtr GetDialogueEditorPtr() const { return DialogueEditorPtr; } + //void SetDialogueEditorPtr(TWeakPtr NewPtr); + //void ResetDialogueEditorPtr() { DialogueEditorPtr.Reset(); } + + //bool JumpToNode(const class UComboActionGraphNode *Node); + +public: + + UPROPERTY(Transient) + TMap NodeMap; + + UPROPERTY(Transient) + TMap EdgeMap; + +protected: + + void Clear(); + void SortNodes(UComboActionGraphNode *RootNode); + +private: + + /** Pointer back to the Dialogue editor that owns us */ + //TWeakPtr DialogueEditorPtr; +}; diff --git a/Source/ComboInputEditor/Private/Ed/EdComboActionGraphEdge.cpp b/Source/ComboInputEditor/Private/Ed/EdComboActionGraphEdge.cpp new file mode 100644 index 0000000..eef99c7 --- /dev/null +++ b/Source/ComboInputEditor/Private/Ed/EdComboActionGraphEdge.cpp @@ -0,0 +1,83 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "EdComboActionGraphEdge.h" + +#include "EdComboActionGraphNode.h" +#include "Nodes/ComboActionGraphEdge.h" + + +void UEdComboActionGraphEdge::SetEdge(UComboActionGraphEdge *Edge) +{ + this->ComboActionGraphEdge = Edge; +} + +void UEdComboActionGraphEdge::AllocateDefaultPins() +{ + UEdGraphPin* Inputs = this->CreatePin(EGPD_Input, TEXT("Edge"), FName(), TEXT("In")); + Inputs->bHidden = true; + UEdGraphPin* Outputs = this->CreatePin(EGPD_Output, TEXT("Edge"), FName(), TEXT("Out")); + Outputs->bHidden = true; +} + +FText UEdComboActionGraphEdge::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + return Super::GetNodeTitle(TitleType); +} + +void UEdComboActionGraphEdge::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 UEdComboActionGraphEdge::PrepareForCopying() +{ + this->ComboActionGraphEdge->Rename(nullptr, this, REN_DontCreateRedirectors | REN_DoNotDirty); +} + +void UEdComboActionGraphEdge::CreateConnections(UEdComboActionGraphNode *Start, UEdComboActionGraphNode *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()); +} + +UEdComboActionGraphNode *UEdComboActionGraphEdge::GetStartNode() +{ + if (Pins[0]->LinkedTo.Num() > 0) + { + return Cast(Pins[0]->LinkedTo[0]->GetOwningNode()); + } + + return nullptr; +} + +UEdComboActionGraphNode *UEdComboActionGraphEdge::GetEndNode() +{ + if (Pins[1]->LinkedTo.Num() > 0) + { + return Cast(Pins[1]->LinkedTo[0]->GetOwningNode()); + } + + return nullptr; +} diff --git a/Source/ComboInputEditor/Private/Ed/EdComboActionGraphEdge.h b/Source/ComboInputEditor/Private/Ed/EdComboActionGraphEdge.h new file mode 100644 index 0000000..cec220b --- /dev/null +++ b/Source/ComboInputEditor/Private/Ed/EdComboActionGraphEdge.h @@ -0,0 +1,45 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EdGraph/EdGraphNode.h" + +#include "EdComboActionGraphEdge.generated.h" + + +/** + * + */ +UCLASS(MinimalAPI) +class UEdComboActionGraphEdge : public UEdGraphNode +{ + GENERATED_BODY() + +public: + UPROPERTY() + class UEdGraph *Graph; + + UPROPERTY(VisibleAnywhere, Instanced, Category="Combo Action Graph") + class UComboActionGraphEdge *ComboActionGraphEdge; + +public: + + void SetEdge(class UComboActionGraphEdge *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(class UEdComboActionGraphNode *Start, class UEdComboActionGraphNode* End); + + class UEdComboActionGraphNode *GetStartNode(); + class UEdComboActionGraphNode *GetEndNode(); +}; diff --git a/Source/ComboInputEditor/Private/Ed/EdComboActionGraphNode.cpp b/Source/ComboInputEditor/Private/Ed/EdComboActionGraphNode.cpp new file mode 100644 index 0000000..e8b6fbb --- /dev/null +++ b/Source/ComboInputEditor/Private/Ed/EdComboActionGraphNode.cpp @@ -0,0 +1,169 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "EdComboActionGraphNode.h" + +#include "Ed/EdComboActionGraph.h" +#include "Helpers/ComboActionEditorBFC.h" +#include "Nodes/ComboActionGraphNode.h" +#include "Settings/ComboActionGraphEditorSettings.h" +#include "Settings/FComboActionGraphEditorStyle.h" + +#define LOCTEXT_NAMESPACE "UEdComboActionGraphNode" + +DEFINE_LOG_CATEGORY(LogEdComboActionGraphNode); + + +UEdComboActionGraphNode::UEdComboActionGraphNode() +{ + bCanRenameNode = true; + + bAllowCopy = true; + bAllowDelete = true; + bAllowDuplicate = true; + bAllowPaste = true; +} + +UEdComboActionGraph *UEdComboActionGraphNode::GetEdComboActionGraph() const +{ + return Cast(this->GetGraph()); +}; + +void UEdComboActionGraphNode::AllocateDefaultPins() +{ + if (this->ComboActionGraphNode == nullptr) + { + UE_LOG(LogEdComboActionGraphNode, Error, TEXT("[AllocateDefaultPins] Cannot find Owning Graph Node!")); + return; + } + + if (this->ComboActionGraphNode->bAllowInputNodes) + { + this->CreatePin(EGPD_Input, "MultipleNodes", FName(), TEXT("In")); + } + + if (this->ComboActionGraphNode->bAllowOutputNodes) + { + this->CreatePin(EGPD_Output, "MultipleNodes", FName(), TEXT("Out")); + } +} + +FText UEdComboActionGraphNode::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + return UComboActionEditorBFC::GetNodeTitle(this->ComboActionGraphNode); +} + +void UEdComboActionGraphNode::PrepareForCopying() +{ + this->ComboActionGraphNode->Rename(nullptr, this, REN_DontCreateRedirectors | REN_DoNotDirty); +} + +void UEdComboActionGraphNode::AutowireNewNode(UEdGraphPin* FromPin) +{ + Super::AutowireNewNode(FromPin); + + if (FromPin != nullptr) + { + if (GetSchema()->TryCreateConnection(FromPin, GetInputPin())) + { + FromPin->GetOwningNode()->NodeConnectionListChanged(); + } + } +} + +FLinearColor UEdComboActionGraphNode::GetBackgroundColor() const +{ + // Getting Node colour based on the Settings if any found, otherwise use this logic + if (UComboActionGraphEditorSettings *GraphEditorSettings = GetMutableDefault()) + { + FLinearColor ReturnColour; + if (GraphEditorSettings->FindNodeBackgroundColourOverride(this->ComboActionGraphNode->GetClass(), ReturnColour)) + { + return ReturnColour; + } + } + + return this->ComboActionGraphNode ? this->ComboActionGraphNode->GetBackgroundColor() : FLinearColor::Black; +} + +UEdGraphPin* UEdComboActionGraphNode::GetInputPin() const +{ + return Pins[0]; +} + +UEdGraphPin* UEdComboActionGraphNode::GetOutputPin() const +{ + if (Pins.IsValidIndex(1)) + { + return Pins[1]; + } + + return Pins[0]; +} + +bool UEdComboActionGraphNode::CanUserDeleteNode() const +{ + if(!Super::CanUserDeleteNode()) + { + return false; + } + + if (this->ComboActionGraphNode) + { + return this->ComboActionGraphNode->bAllowDelete; + } + return bAllowDelete; +} + +bool UEdComboActionGraphNode::CanDuplicateNode() const +{ + if(!Super::CanUserDeleteNode()) + { + return false; + } + + if (this->ComboActionGraphNode) + { + return this->ComboActionGraphNode->bAllowCopy; + } + + return bAllowCopy; +} + +bool UEdComboActionGraphNode::CanUserPasteNodes() const +{ + if (this->ComboActionGraphNode) + { + return this->ComboActionGraphNode->bAllowPaste; + } + + return bAllowPaste; +} + +FText UEdComboActionGraphNode::GetTooltipText() const +{ + if (this->ComboActionGraphNode) + { + return this->ComboActionGraphNode->GetNodeTooltipText(); + } + + return NSLOCTEXT("UEdComboActionGraphNode", "DefaultToolTip", "Mountea Dialogue Node"); +} + +FSlateIcon UEdComboActionGraphNode::GetIconAndTint(FLinearColor& OutColor) const +{ + static const FSlateIcon Icon = FSlateIcon(FComboActionGraphEditorStyle::GetAppStyleSetName(), "MDSStyleSet.Node.Icon.small"); + OutColor = this->ComboActionGraphNode->GetBackgroundColor(); + return Icon; +} + +void UEdComboActionGraphNode::PostEditUndo() +{ + Super::PostEditUndo(); +} + +void UEdComboActionGraphNode::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ComboInputEditor/Private/Ed/EdComboActionGraphNode.h b/Source/ComboInputEditor/Private/Ed/EdComboActionGraphNode.h new file mode 100644 index 0000000..ad9c75e --- /dev/null +++ b/Source/ComboInputEditor/Private/Ed/EdComboActionGraphNode.h @@ -0,0 +1,60 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "SEdComboActionGraphNode.h" +#include "EdGraph/EdGraphNode.h" + +#include "EdComboActionGraphNode.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogEdComboActionGraphNode, Log, All); + + +/** + * + */ +UCLASS(MinimalAPI) +class UEdComboActionGraphNode : public UEdGraphNode +{ + GENERATED_BODY() + +public: + UEdComboActionGraphNode(); + + class UEdComboActionGraph *GetEdComboActionGraph() const; + + 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; + + virtual bool CanUserDeleteNode() const override; + virtual bool CanDuplicateNode() const override; + + virtual bool CanUserPasteNodes() const; + + virtual FText GetTooltipText() const override; + virtual FSlateIcon GetIconAndTint(FLinearColor &OutColor) const override; + +#if WITH_EDITOR + virtual void PostEditUndo() override; + virtual void PostEditChangeProperty(FPropertyChangedEvent &PropertyChangedEvent) override; +#endif + + UPROPERTY(VisibleAnywhere, Instanced, Category="Combo Action Graph") + class UComboActionGraphNode *ComboActionGraphNode; + + SEdComboActionGraphNode *SEdNode; + +private: + bool bAllowCopy; + bool bAllowDelete; + bool bAllowDuplicate; + bool bAllowPaste; +}; diff --git a/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNode.cpp b/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNode.cpp new file mode 100644 index 0000000..7503a13 --- /dev/null +++ b/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNode.cpp @@ -0,0 +1,1442 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "SEdComboActionGraphNode.h" + +#include "Nodes/ComboActionGraphNode.h" +#include "Helpers/ComboActionGraphColors.h" +#include "Ed/SEdComboActionGraphNodeIndex.h" +#include "Ed/EdComboActionGraphNode.h" + +//#include "SLevelOfDetailBranchNode.h" +#include "Widgets/Text/SInlineEditableTextBlock.h" +#include "SCommentBubble.h" +#include "SlateOptMacros.h" +#include "SGraphPin.h" +#include "GraphEditorSettings.h" +#include "Blueprint/UserWidget.h" +#include "ComboActionGraph.h" +#include "Settings/ComboActionGraphEditorSettings.h" +#include "Settings/FComboActionGraphEditorStyle.h" +#include "Widgets/Layout/SGridPanel.h" +#include "Widgets/Layout/SScaleBox.h" + + +#define LOCTEXT_NAMESPACE "EdComboActionGraph" + +#pragma region Pin + +class SComboActionGraphPin : public SGraphPin +{ +public: + SLATE_BEGIN_ARGS(SComboActionGraphPin) {} + 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); + + // Pins Out/In Border + SBorder::Construct(SBorder::FArguments() + .BorderImage(this, &SComboActionGraphPin::GetPinBorder) + .BorderBackgroundColor(this, &SComboActionGraphPin::GetPinColor) + .OnMouseButtonDown(this, &SComboActionGraphPin::OnPinMouseDown) + .Cursor(this, &SComboActionGraphPin::GetPinCursor) + .Padding(FMargin(5.0f)) + ); + } + +protected: + virtual FSlateColor GetPinColor() const override + { + if (const UComboActionGraphEditorSettings *GraphEditorSettings = GetMutableDefault()) + { + switch (GraphEditorSettings->GetNodeTheme()) + { + case EComboActionNodeTheme::DarkTheme: + return ComboActionGraphColors::PinsDock::LightTheme; + case EComboActionNodeTheme::LightTheme: + return ComboActionGraphColors::PinsDock::DarkTheme; + default: + return ComboActionGraphColors::PinsDock::LightTheme; + } + } + + return ComboActionGraphColors::Pin::Default; + } + + virtual TSharedRef GetDefaultValueWidget() override + { + return SNew(STextBlock); + } + + const FSlateBrush *GetPinBorder() const + { + if (const UComboActionGraphEditorSettings *GraphEditorSettings = GetMutableDefault()) + { + switch (GraphEditorSettings->GetNodeType()) + { + case EComboActionNodeType::SoftCorners: + return FComboActionGraphEditorStyle::GetBrush("MDSStyleSet.Node.TextSoftEdges"); + case EComboActionNodeType::HardCorners: + return FComboActionGraphEditorStyle::GetBrush("MDSStyleSet.Node.TextHardEdges"); + } + } + + return FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Graph.PinDocksOverlay")); + } +}; + +#pragma endregion + +void SEdComboActionGraphNode::Construct(const FArguments &InArgs, UEdComboActionGraphNode *InNode) +{ + this->GraphNode = InNode; + this->UpdateGraphNode(); + InNode->SEdNode = this; + //bIsHovered = false; + + this->GraphEditorSettings = GetMutableDefault(); +} + +void SEdComboActionGraphNode::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + //bIsHovered = true; + + SetToolTipText(GetTooltipText()); + OnVisualizeTooltip(GetToolTip()->AsWidget()); + + SGraphNode::OnMouseEnter(MyGeometry, MouseEvent); +} + +void SEdComboActionGraphNode::OnMouseLeave(const FPointerEvent& MouseEvent) +{ + //bIsHovered = false; + + SetToolTipText(FText::GetEmpty()); + OnToolTipClosing(); + + SGraphNode::OnMouseLeave(MouseEvent); +} + +const FSlateBrush *SEdComboActionGraphNode::GetIndexBrush() const +{ + return FComboActionGraphEditorStyle::GetBrush("MDSStyleSet.Node.TextSoftEdges"); +} + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void SEdComboActionGraphNode::UpdateGraphNode() +{ + const FMargin NodePadding = FMargin(2.0f); + const FMargin UnifiedRowsPadding = FMargin(0.f, 1.15f, 0.f, 0.f); + + const FSlateColor DefaultFontColor = ComboActionGraphColors::TextColors::Normal; + + 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(); + OutputPinBox.Reset(); + + TSharedPtr ErrorText; + TSharedPtr NodeTitle = SNew(SNodeTitle, GraphNode); + + TSharedPtr StackBox; + TSharedPtr UniformBox; + + this->ContentScale.Bind(this, &SGraphNode::GetContentScale); + + const FSlateBrush* CircleBrush = FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Node.IndexCircle")); + this->GetOrAddSlot(ENodeZone::Left) + .SlotOffset(TAttribute(this, &SEdComboActionGraphNode::GetIndexSlotOffset)) + .SlotSize(TAttribute(this, &SEdComboActionGraphNode::GetIndexSlotSize)) + [ + SNew(SOverlay) + +SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SBox) + .WidthOverride(CircleBrush->ImageSize.X) + .HeightOverride(CircleBrush->ImageSize.Y) + ] + + +SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SBorder) + .BorderImage(CircleBrush) + .BorderBackgroundColor(FLinearColor::Gray) + .Padding(FMargin(4.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) + .Text(this, &SEdComboActionGraphNode::GetIndexText) + .Visibility(this, &SEdComboActionGraphNode::GetIndexSlotVisibility) + ] + ] + ]; + + this->GetOrAddSlot(ENodeZone::Center) + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SBox) + [ + // OUTER STYLE + SNew(SBorder) + .BorderImage(this, &SEdComboActionGraphNode::GetNodeTypeBrush) + .Padding(3.0f) + .BorderBackgroundColor(this, &SEdComboActionGraphNode::GetBorderBackgroundColor) + [ + + SNew(SOverlay) + + // Adding some colours so its not so boring + + SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + // INNER STYLE + SNew(SBorder) + .BorderImage(this, &SEdComboActionGraphNode::GetNodeTypeBrush) + .HAlign(HAlign_Fill) + .VAlign(VAlign_Center) + .Visibility(EVisibility::SelfHitTestInvisible) + .BorderBackgroundColor(this, &SEdComboActionGraphNode::GetBorderFrontColor) + ] + + // Pins and node details + + SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SVerticalBox) + + // INPUT PIN AREA + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBox) + .MinDesiredHeight(NodePadding.Top) + [ + SAssignNew(LeftNodeBox, SVerticalBox) + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SSpacer) + .Size(FVector2D(0.f, 10.f)) + ] + + + SVerticalBox::Slot() + .Padding(FMargin(NodePadding.Left, 0.0f, NodePadding.Right, 0.0f)) + .VAlign(VAlign_Fill) + [ + SNew(SVerticalBox) + +#pragma region Stack + + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SBox) + .MinDesiredWidth(FOptionalSize(145.f)) + .Visibility(this, &SEdComboActionGraphNode::GetStackVisibility) + [ + SNew(SVerticalBox) +#pragma region InheritanceOnly + + SVerticalBox::Slot() + .VAlign(VAlign_Fill) + [ + SNew(SBorder) + .BorderImage(this, &SEdComboActionGraphNode::GetTextNodeTypeBrush) + .BorderBackgroundColor(this, &SEdComboActionGraphNode::GetDecoratorsBackgroundColor) + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + .Visibility(this, &SEdComboActionGraphNode::ShowInheritsDecoratorsSlot_Stack) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(FMargin(4.0f, 0.f, 4.0f, 0.f)) + .HAlign(HAlign_Fill) + [ + SNew(STextBlock) + .Text(this, &SEdComboActionGraphNode::GetDecoratorsInheritanceText) + .Justification(ETextJustify::Center) + ] + ] + ] + ] +#pragma endregion + +#pragma region ImplementsOnly + + SVerticalBox::Slot() + .VAlign(VAlign_Fill) + [ + SNew(SBorder) + .BorderImage(this, &SEdComboActionGraphNode::GetTextNodeTypeBrush) + .BorderBackgroundColor(this, &SEdComboActionGraphNode::GetDecoratorsBackgroundColor) + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + .Visibility(this, &SEdComboActionGraphNode::ShowImplementsOnlySlot_Stack) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Fill) + .Padding(FMargin(4.0f, 0.f, 4.0f, 0.f)) + [ + SNew(STextBlock) + .Text(this, &SEdComboActionGraphNode::GetDecoratorsText) + .Justification(ETextJustify::Center) + ] + ] + ] + ] +#pragma endregion + ] + ] + +#pragma endregion + + + SVerticalBox::Slot() + .AutoHeight() + [ + SAssignNew(NodeBody, SBorder) + .BorderImage(this, &SEdComboActionGraphNode::GetTextNodeTypeBrush) + .BorderBackgroundColor(this, &SEdComboActionGraphNode::GetNodeTitleBackgroundColor) + .HAlign(HAlign_Fill) + .VAlign(VAlign_Center) + .Visibility(EVisibility::SelfHitTestInvisible) + [ + SNew(SBox) + .MinDesiredWidth(FOptionalSize(145.f)) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SOverlay) + + SOverlay::Slot() + .HAlign(HAlign_Center) + .VAlign(VAlign_Fill) + [ + SNew(SVerticalBox) +#pragma region NameSlot + // NAME SLOT + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + // POPUP ERROR MESSAGE + SAssignNew(ErrorText, SErrorText) + .BackgroundColor( + this, + &SEdComboActionGraphNode::GetErrorColor) + .ToolTipText( + this, + &SEdComboActionGraphNode::GetErrorMsgToolTip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(FMargin(4.0f, 0.0f, 4.0f, 0.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SAssignNew(InlineEditableText, SInlineEditableTextBlock) + .Style(FComboActionGraphEditorStyle::Get(), "MDSStyleSet.NodeTitleInlineEditableText") + .Text(NodeTitle.Get(), &SNodeTitle::GetHeadTitle) + .OnVerifyTextChanged( + this, &SEdComboActionGraphNode::OnVerifyNameTextChanged) + .OnTextCommitted( + this, &SEdComboActionGraphNode::OnNameTextCommitted) + .IsReadOnly(this, &SEdComboActionGraphNode::IsNameReadOnly) + .IsSelected(this, &SEdComboActionGraphNode::IsSelectedExclusively) + .Justification(ETextJustify::Center) + .Visibility(EVisibility::Visible) + ] + + + SVerticalBox::Slot() + .AutoHeight() + [ + NodeTitle.ToSharedRef() + ] + ] + ] + ] +#pragma endregion + ] + ] +#pragma region Unified + + SVerticalBox::Slot() + .VAlign(VAlign_Fill) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::GetUnifiedVisibility) + .HAlign(HAlign_Fill) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowDecoratorsBottomPadding) + [ + SNew(SSpacer) + .Size(FVector2D(0.f, 2.5f)) + ] + ] + +#pragma region InheritanceOnly + // INHERITS ONLY + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + .Padding(FMargin(8.0f, 0.f, 8.0f, 0.f)) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowInheritsDecoratorsSlot_Unified) + .MaxDesiredWidth(FOptionalSize(130.f)) + .HAlign(HAlign_Fill) + [ + SNew(SGridPanel) + .Visibility(EVisibility::HitTestInvisible) + .FillColumn(0, 2.f) + .FillColumn(1, 1.f) +#pragma region Title + + SGridPanel::Slot(0,0) + .HAlign(HAlign_Fill) + [ + SNew(STextBlock) + .Text(LOCTEXT("A", "DECORATORS")) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 8)) + .ColorAndOpacity(DefaultFontColor) + ] +#pragma endregion + +#pragma region Inherits + + SGridPanel::Slot(0,1) + .HAlign(HAlign_Fill) + .Padding(UnifiedRowsPadding) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowInheritsDecoratorsSlot_Unified) + .HAlign(HAlign_Left) + [ + SNew(SScaleBox) + .Stretch(EStretch::ScaleToFit) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Center) + [ + SNew(SScaleBox) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .Stretch(EStretch::ScaleToFit) + [ + SNew(SBox) + .MaxAspectRatio(FOptionalSize(1)) + .MaxDesiredHeight(FOptionalSize(6.f)) + .MaxDesiredWidth(FOptionalSize(6.f)) + [ + SNew(SImage) + .Image(this, &SEdComboActionGraphNode::GetBulletPointImageBrush) + ] + ] + ] + + +SHorizontalBox::Slot() + [ + SNew(SSpacer) + .Size(FVector2D(1.f, 0.f)) + ] + + +SHorizontalBox::Slot() + .HAlign(HAlign_Fill) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("B", "inherits")) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) + .Justification(ETextJustify::Left) + .ColorAndOpacity(DefaultFontColor) + ] + ] + ] + ] + + SGridPanel::Slot(1,1) + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + .Padding(UnifiedRowsPadding) + [ + SNew(SScaleBox) + .Stretch(EStretch::ScaleToFit) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowInheritsDecoratorsSlot_Unified) + .MaxAspectRatio(FOptionalSize(1)) + .MaxDesiredHeight(FOptionalSize(12.f)) + .MaxDesiredWidth(FOptionalSize(12.f)) + [ + SNew(SImage) + .Image(this, &SEdComboActionGraphNode::GetInheritsImageBrush) + .ColorAndOpacity(this, &SEdComboActionGraphNode::GetInheritsImageTint) + ] + ] + ] +#pragma endregion + ] + ] +#pragma endregion + +#pragma region ImplementsOnly + // IMPLEMENTS ONLY + + SVerticalBox::Slot() + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + .Padding(FMargin(8.0f, 0.f, 8.0f, 0.f)) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowImplementsOnlySlot_Unified) + .MaxDesiredWidth(FOptionalSize(130.f)) + .HAlign(HAlign_Fill) + [ + SNew(SGridPanel) + .Visibility(EVisibility::HitTestInvisible) + .FillColumn(0, 2.f) + .FillColumn(1, 1.f) +#pragma region Title + + SGridPanel::Slot(0,0) + .HAlign(HAlign_Fill) + [ + SNew(STextBlock) + .Text(LOCTEXT("A", "DECORATORS")) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 8)) + .ColorAndOpacity(DefaultFontColor) + ] +#pragma endregion + +#pragma region Implements + + SGridPanel::Slot(0,1) + .HAlign(HAlign_Fill) + .Padding(UnifiedRowsPadding) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowImplementsOnlySlot_Unified) + .HAlign(HAlign_Left) + [ + SNew(SScaleBox) + .Stretch(EStretch::ScaleToFit) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Center) + [ + SNew(SScaleBox) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .Stretch(EStretch::ScaleToFit) + [ + SNew(SBox) + .MaxAspectRatio(FOptionalSize(1)) + .MaxDesiredHeight(FOptionalSize(6.f)) + .MaxDesiredWidth(FOptionalSize(6.f)) + [ + SNew(SImage) + .Image(this, &SEdComboActionGraphNode::GetBulletPointImageBrush) + .ColorAndOpacity(this, &SEdComboActionGraphNode::GetBulletPointsImagePointColor) + ] + ] + ] + + +SHorizontalBox::Slot() + [ + SNew(SSpacer) + .Size(FVector2D(1.f, 0.f)) + ] + + +SHorizontalBox::Slot() + .HAlign(HAlign_Fill) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("C", "implements")) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) + .Justification(ETextJustify::Left) + .ColorAndOpacity(this, &SEdComboActionGraphNode::GetImplementsRowColor) + ] + ] + ] + ] + + SGridPanel::Slot(1,1) + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + .Padding(UnifiedRowsPadding) + [ + SNew(SOverlay) + +SOverlay::Slot() + [ + SNew(SScaleBox) + .Stretch(EStretch::ScaleToFit) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowImplementsOnlySlot_Unified) + .MaxAspectRatio(FOptionalSize(1)) + .MaxDesiredHeight(FOptionalSize(12.f)) + .MaxDesiredWidth(FOptionalSize(12.f)) + .WidthOverride(12.f) + ] + ] + + +SOverlay::Slot() + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(this, &SEdComboActionGraphNode::GetNumberOfDecorators) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) + .ColorAndOpacity(this, &SEdComboActionGraphNode::GetImplementsRowColor) + .Justification(ETextJustify::Center) + ] + ] +#pragma endregion + ] + ] +#pragma endregion + +#pragma region Both + // BOTH + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + .Padding(FMargin(8.0f, 0.f, 8.0f, 0.f)) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowAllDecorators) + .MaxDesiredWidth(FOptionalSize(130.f)) + .HAlign(HAlign_Fill) + [ + SNew(SGridPanel) + .Visibility(EVisibility::HitTestInvisible) + .FillColumn(0, 2.f) + .FillColumn(1, 1.f) +#pragma region Title + + SGridPanel::Slot(0,0) + .HAlign(HAlign_Fill) + [ + SNew(STextBlock) + .Text(LOCTEXT("A", "DECORATORS")) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 8)) + .ColorAndOpacity(DefaultFontColor) + ] +#pragma endregion + +#pragma region Inherits + + SGridPanel::Slot(0,1) + .HAlign(HAlign_Fill) + .Padding(UnifiedRowsPadding) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowAllDecorators) + .HAlign(HAlign_Left) + [ + SNew(SScaleBox) + .Stretch(EStretch::ScaleToFit) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Center) + [ + SNew(SScaleBox) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .Stretch(EStretch::ScaleToFit) + [ + SNew(SBox) + .MaxAspectRatio(FOptionalSize(1)) + .MaxDesiredHeight(FOptionalSize(6.f)) + .MaxDesiredWidth(FOptionalSize(6.f)) + [ + SNew(SImage) + .Image(this, &SEdComboActionGraphNode::GetBulletPointImageBrush) + ] + ] + ] + + +SHorizontalBox::Slot() + [ + SNew(SSpacer) + .Size(FVector2D(1.f, 0.f)) + ] + + +SHorizontalBox::Slot() + .HAlign(HAlign_Fill) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("B", "inherits")) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) + .Justification(ETextJustify::Left) + .ColorAndOpacity(DefaultFontColor) + ] + ] + ] + ] + + SGridPanel::Slot(1,1) + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + .Padding(UnifiedRowsPadding) + [ + SNew(SScaleBox) + .Stretch(EStretch::ScaleToFit) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowAllDecorators) + .MaxAspectRatio(FOptionalSize(1)) + .MaxDesiredHeight(FOptionalSize(12.f)) + .MaxDesiredWidth(FOptionalSize(12.f)) + [ + SNew(SImage) + .Image(this, &SEdComboActionGraphNode::GetInheritsImageBrush) + .ColorAndOpacity(this, &SEdComboActionGraphNode::GetInheritsImageTint) + ] + ] + ] +#pragma endregion + +#pragma region Implements + + SGridPanel::Slot(0,2) + .HAlign(HAlign_Fill) + .Padding(UnifiedRowsPadding) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowAllDecorators) + .HAlign(HAlign_Left) + [ + SNew(SScaleBox) + .Stretch(EStretch::ScaleToFit) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Center) + [ + SNew(SScaleBox) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .Stretch(EStretch::ScaleToFit) + [ + SNew(SBox) + .MaxAspectRatio(FOptionalSize(1)) + .MaxDesiredHeight(FOptionalSize(6.f)) + .MaxDesiredWidth(FOptionalSize(6.f)) + [ + SNew(SImage) + .Image(this, &SEdComboActionGraphNode::GetBulletPointImageBrush) + .ColorAndOpacity(this, &SEdComboActionGraphNode::GetBulletPointsImagePointColor) + ] + ] + ] + + +SHorizontalBox::Slot() + [ + SNew(SSpacer) + .Size(FVector2D(1.f, 0.f)) + ] + + +SHorizontalBox::Slot() + .HAlign(HAlign_Fill) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("C", "implements")) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) + .Justification(ETextJustify::Left) + .ColorAndOpacity(this, &SEdComboActionGraphNode::GetImplementsRowColor) + ] + ] + ] + ] + + SGridPanel::Slot(1,2) + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + .Padding(UnifiedRowsPadding) + [ + SNew(SOverlay) + +SOverlay::Slot() + [ + SNew(SScaleBox) + .Stretch(EStretch::ScaleToFit) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .Visibility(this, &SEdComboActionGraphNode::ShowAllDecorators) + .MaxAspectRatio(FOptionalSize(1)) + .MaxDesiredHeight(FOptionalSize(12.f)) + .MaxDesiredWidth(FOptionalSize(12.f)) + .WidthOverride(12.f) + ] + ] + + +SOverlay::Slot() + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(this, &SEdComboActionGraphNode::GetNumberOfDecorators) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) + .ColorAndOpacity(this, &SEdComboActionGraphNode::GetImplementsRowColor) + .Justification(ETextJustify::Center) + ] + ] +#pragma endregion + ] + ] +#pragma endregion + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .VAlign(VAlign_Fill) + [ + SNew(SSpacer) + .Size(FVector2D(0.f, 2.5f)) + ] + ] + ] +#pragma endregion + ] + ] + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SSpacer) + .Size(FVector2D(0.f, 10.f)) + ] + + // OUTPUT PIN AREA + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBox) + .MinDesiredHeight(NodePadding.Bottom) + [ + SAssignNew(RightNodeBox, SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + .Padding(20.0f, 0.0f) + .FillHeight(1.0f) + [ + SAssignNew(OutputPinBox, SHorizontalBox) + ] + ] + ] + ] + ] + ] + ]; + + // Create comment bubble + TSharedPtr CommentBubble; + const FSlateColor CommentColor = GetDefault()->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(CommentBubble.Get(), &SCommentBubble::GetOffset)) + .SlotSize(TAttribute(CommentBubble.Get(), &SCommentBubble::GetSize)) + .AllowScaling(TAttribute(CommentBubble.Get(), &SCommentBubble::IsScalingAllowed)) + .VAlign(VAlign_Top) + [ + CommentBubble.ToSharedRef() + ]; + + ErrorReporting = ErrorText; + ErrorReporting->SetError(ErrorMsg); + CreatePinWidgets(); +} + +void SEdComboActionGraphNode::CreatePinWidgets() +{ + UEdComboActionGraphNode *StateNode = CastChecked(GraphNode); + + for (int32 PinIdx = 0; PinIdx < StateNode->Pins.Num(); PinIdx++) + { + UEdGraphPin* MyPin = StateNode->Pins[PinIdx]; + if (!MyPin->bHidden) + { + TSharedPtr NewPin = SNew(SComboActionGraphPin, MyPin); + + AddPin(NewPin.ToSharedRef()); + } + } +} + +void SEdComboActionGraphNode::AddPin(const TSharedRef& PinToAdd) +{ + PinToAdd->SetOwner(SharedThis(this)); + + const UEdGraphPin* PinObj = PinToAdd->GetPinObj(); + const bool bAdvancedParameter = PinObj && PinObj->bAdvancedView; + if (bAdvancedParameter) + { + PinToAdd->SetVisibility( TAttribute(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced) ); + } + + if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Input) + { + LeftNodeBox->AddSlot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + .FillHeight(1.0f) + .Padding(20.0f,0.0f) + [ + PinToAdd + ]; + InputPins.Add(PinToAdd); + } + else // Direction == EEdGraphPinDirection::EGPD_Output + { + OutputPinBox->AddSlot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + .FillWidth(1.0f) + [ + PinToAdd + ]; + OutputPins.Add(PinToAdd); + } +} + +bool SEdComboActionGraphNode::IsNameReadOnly() const +{ + UEdComboActionGraphNode *EdNode_Node = Cast(this->GraphNode); + check(EdNode_Node != nullptr); + + UComboActionGraph *ComboActionGraphNode = EdNode_Node->ComboActionGraphNode->Graph; + check(this->GraphNode != nullptr); + + return !this->GraphNode->bCanRenameNode || SGraphNode::IsNameReadOnly(); +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void SEdComboActionGraphNode::OnNameTextCommitted(const FText& InText, ETextCommit::Type CommitInfo) +{ + if (InText.IsEmpty()) return; + + SGraphNode::OnNameTextCommited(InText, CommitInfo); + + UEdComboActionGraphNode *MyNode = CastChecked(GraphNode); + + if (MyNode != nullptr && MyNode->ComboActionGraphNode != nullptr) + { + const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorRenameNode", "Combo Action Editor: Rename Node")); + MyNode->Modify(); + MyNode->ComboActionGraphNode->Modify(); + MyNode->ComboActionGraphNode->SetNodeTitle(InText); + UpdateGraphNode(); + } +} + +const FSlateBrush *SEdComboActionGraphNode::GetNodeTypeBrush() const +{ + if (GraphEditorSettings) + { + switch (GraphEditorSettings->GetNodeType()) + { + case EComboActionNodeType::SoftCorners: + return FComboActionGraphEditorStyle::GetBrush("MDSStyleSet.Node.SoftEdges"); + case EComboActionNodeType::HardCorners: + return FComboActionGraphEditorStyle::GetBrush("MDSStyleSet.Node.HardEdges"); + } + } + + return FComboActionGraphEditorStyle::GetBrush("MDSStyleSet.Node.SoftEdges"); +} + +const FSlateBrush *SEdComboActionGraphNode::GetTextNodeTypeBrush() const +{ + if (GraphEditorSettings) + { + switch (GraphEditorSettings->GetNodeType()) + { + case EComboActionNodeType::SoftCorners: + return FComboActionGraphEditorStyle::GetBrush("MDSStyleSet.Node.TextSoftEdges"); + case EComboActionNodeType::HardCorners: + return FComboActionGraphEditorStyle::GetBrush("MDSStyleSet.Node.TextHardEdges"); + } + } + + return FComboActionGraphEditorStyle::GetBrush("MDSStyleSet.Node.TextSoftEdges"); +} + +FSlateColor SEdComboActionGraphNode::GetBorderBackgroundColor() const +{ + UEdComboActionGraphNode *MyNode = CastChecked(this->GraphNode); + return MyNode ? MyNode->GetBackgroundColor() : ComboActionGraphColors::NodeBorder::HighlightAbortRange0; +} + +FSlateColor SEdComboActionGraphNode::GetBorderFrontColor() const +{ + if (GraphEditorSettings) + { + switch (GraphEditorSettings->GetNodeTheme()) + { + case EComboActionNodeTheme::DarkTheme: + return ComboActionGraphColors::Overlay::DarkTheme; + case EComboActionNodeTheme::LightTheme: + return ComboActionGraphColors::Overlay::LightTheme; + } + } + + return ComboActionGraphColors::Overlay::DarkTheme; +} + +FSlateColor SEdComboActionGraphNode::GetNodeTitleBackgroundColor() const +{ + return ComboActionGraphColors::NodeBody::Default; +} + +FSlateColor SEdComboActionGraphNode::GetDecoratorsBackgroundColor() const +{ + return ComboActionGraphColors::DecoratorsBody::Default; +} + +FSlateColor SEdComboActionGraphNode::GetPinsDockColor() const +{ + if (GraphEditorSettings) + { + switch (GraphEditorSettings->GetNodeTheme()) + { + case EComboActionNodeTheme::DarkTheme: + return ComboActionGraphColors::PinsDock::DarkTheme; + case EComboActionNodeTheme::LightTheme: + return ComboActionGraphColors::PinsDock::LightTheme; + } + } + + return ComboActionGraphColors::Overlay::DarkTheme; +} + +EVisibility SEdComboActionGraphNode::GetDragOverMarkerVisibility() const +{ + return EVisibility::Visible; +} + +const FSlateBrush*SEdComboActionGraphNode::GetNameIcon() const +{ + return FAppStyle::GetBrush(TEXT("BTEditor.Graph.BTNode.Icon")); +} + +const FSlateBrush*SEdComboActionGraphNode::GetInheritsImageBrush() const +{ + bool bHasDecorators = false; + if (const UEdComboActionGraphNode *EdParentNode = Cast(this->GraphNode)) + { + if (EdParentNode->ComboActionGraphNode) + { + bHasDecorators = EdParentNode->ComboActionGraphNode->DoesInheritDecorators() ; + } + } + + return FComboActionGraphEditorStyle::GetBrush( bHasDecorators ? "MDSStyleSet.Icon.OK" : "MDSStyleSet.Icon.Error" ); +} + +FSlateColor SEdComboActionGraphNode::GetInheritsImageTint() const +{ + bool bHasDecorators = false; + if (const UEdComboActionGraphNode *EdParentNode = Cast(GraphNode)) + { + if (EdParentNode->ComboActionGraphNode) + { + bHasDecorators = EdParentNode->ComboActionGraphNode->DoesInheritDecorators() ; + } + } + + return bHasDecorators ? FSlateColor(FLinearColor::Green) : FSlateColor(FLinearColor::Red); +} + +const FSlateBrush*SEdComboActionGraphNode::GetBulletPointImageBrush() const +{ + return FComboActionGraphEditorStyle::GetBrush( "MDSStyleSet.Icon.BulletPoint" ); +} + +FText SEdComboActionGraphNode::GetIndexOverlayTooltipText() const +{ + return LOCTEXT("NodeIndexTooltip", "Node index"); +} + +FText SEdComboActionGraphNode::GetIndexText() const +{ + if (const UEdComboActionGraphNode *EdParentNode = Cast(GraphNode)) + { + if (const auto Node = EdParentNode->ComboActionGraphNode) + { + if (const auto Graph = Node->Graph) + { + const int32 Index = Graph->AllNodes.Find(Node); + return FText::AsNumber(Index); + } + + return FText::AsNumber(EdParentNode->ComboActionGraphNode->GetNodeIndex()); + } + } + return FText::AsNumber(INDEX_NONE); +} + +EVisibility SEdComboActionGraphNode::GetIndexSlotVisibility() const +{ + if (IsHovered()) + { + return EVisibility::SelfHitTestInvisible; + } + + return EVisibility::Collapsed; +} + +FVector2D SEdComboActionGraphNode::GetIndexSlotOffset() const +{ + if (IsHovered()) + { + return FVector2D(-20.f); + } + + return FVector2D(-15.f); +} + +FVector2D SEdComboActionGraphNode::GetIndexSlotSize() const +{ + if (IsHovered()) + { + return FVector2D(24.f); + } + + return FVector2D(12.f); +} + +void SEdComboActionGraphNode::OnIndexHoverStateChanged(bool bArg) const +{ + // TODO something +} + +FSlateColor SEdComboActionGraphNode::GetOverlayWidgetBackgroundColor(bool bArg) const +{ + return bArg ? ComboActionGraphColors::IndexBorder::HoveredState : ComboActionGraphColors::IndexBorder::NormalState; +} + +bool SEdComboActionGraphNode::HasGraphDecorators() const +{ + if (const UEdComboActionGraphNode *EdParentNode = Cast(GraphNode)) + { + if (EdParentNode->ComboActionGraphNode && EdParentNode->ComboActionGraphNode->Graph) + { + for (const auto& Itr : EdParentNode->ComboActionGraphNode->Graph->GetGraphDecorators()) + { + if (Itr.DecoratorType != nullptr) + { + return true; + } + } + } + } + + return false; +} + +bool SEdComboActionGraphNode::HasNodeDecorators() const +{ + if (const UEdComboActionGraphNode *EdParentNode = Cast(GraphNode)) + { + if (EdParentNode->ComboActionGraphNode) + { + if (EdParentNode->ComboActionGraphNode->GetNodeDecorators().Num() > 0) + { + bool bAllValid = true; + + for (const auto& Itr : EdParentNode->ComboActionGraphNode->GetNodeDecorators()) + { + if (Itr.DecoratorType == nullptr) + { + bAllValid = false; + } + } + return bAllValid; + } + } + } + + return false; +} + +EVisibility SEdComboActionGraphNode::ShowImplementsOnlySlot_Unified() const +{ + if (GraphEditorSettings) + { + if (GraphEditorSettings->ShowDetailedInfo_NumDecorators() && !GraphEditorSettings->ShowDetailedInfo_InheritsDecorators()) + { + return EVisibility::SelfHitTestInvisible; //return HasGraphDecorators() ? EVisibility::Visible : EVisibility::Collapsed; + } + } + return EVisibility::Collapsed; +} + +FText SEdComboActionGraphNode::GetDecoratorsText() const +{ + if (const UEdComboActionGraphNode *EdParentNode = Cast(GraphNode)) + { + if (EdParentNode->ComboActionGraphNode) + { + FString Number = FString::FromInt(EdParentNode->ComboActionGraphNode->GetNodeDecorators().Num()); + FString ReturnText = FString(TEXT("DECORATORS: ")); + return FText::FromString( ReturnText.Append(Number) ); + } + } + return FText::FromString("DECORATORS: none"); +} + +FText SEdComboActionGraphNode::GetNumberOfDecorators() const +{ + if (const UEdComboActionGraphNode *EdParentNode = Cast(GraphNode)) + { + if (EdParentNode->ComboActionGraphNode) + { + const int32 Number = EdParentNode->ComboActionGraphNode->GetNodeDecorators().Num(); + if (Number <= 0) return FText::FromString(TEXT("-")); + if (Number > 9) return FText::FromString(TEXT("9+")); + + return FText::FromString(FString::FromInt(Number)); + } + } + return FText::FromString("-"); +} + +EVisibility SEdComboActionGraphNode::ShowInheritsDecoratorsSlot_Unified() const +{ + if (GraphEditorSettings) + { + if (GraphEditorSettings->GetDecoratorsStyle() == EComboActionDecoratorsInfoStyle::Unified) + { + if (GraphEditorSettings->ShowDetailedInfo_InheritsDecorators() && !GraphEditorSettings->ShowDetailedInfo_NumDecorators()) + { + return EVisibility::SelfHitTestInvisible; //return HasGraphDecorators() ? EVisibility::Visible : EVisibility::Collapsed; + } + } + } + return EVisibility::Collapsed; +} + +EVisibility SEdComboActionGraphNode::ShowImplementsOnlySlot_Stack() const +{ + if (GraphEditorSettings) + { + if (GraphEditorSettings->GetDecoratorsStyle() == EComboActionDecoratorsInfoStyle::Stack) + { + if (GraphEditorSettings->ShowDetailedInfo_NumDecorators()) + { + return EVisibility::SelfHitTestInvisible; //return HasGraphDecorators() ? EVisibility::Visible : EVisibility::Collapsed; + } + } + } + return EVisibility::Collapsed; +} + +EVisibility SEdComboActionGraphNode::ShowInheritsDecoratorsSlot_Stack() const +{ + if (GraphEditorSettings) + { + if (GraphEditorSettings->GetDecoratorsStyle() == EComboActionDecoratorsInfoStyle::Stack) + { + if (GraphEditorSettings->ShowDetailedInfo_InheritsDecorators()) + { + return EVisibility::SelfHitTestInvisible; //return HasGraphDecorators() ? EVisibility::Visible : EVisibility::Collapsed; + } + } + } + return EVisibility::Collapsed; +} + +EVisibility SEdComboActionGraphNode::ShowAllDecorators() const +{ + if (GraphEditorSettings) + { + if (GraphEditorSettings->GetDecoratorsStyle() == EComboActionDecoratorsInfoStyle::Unified) + { + if (GraphEditorSettings->ShowDetailedInfo_InheritsDecorators() && GraphEditorSettings->ShowDetailedInfo_NumDecorators()) + { + return EVisibility::SelfHitTestInvisible; //return HasGraphDecorators() ? EVisibility::Visible : EVisibility::Collapsed; + } + } + } + return EVisibility::Collapsed; +} + +EVisibility SEdComboActionGraphNode::ShowDecoratorsBottomPadding() const +{ + if (GraphEditorSettings) + { + if (GraphEditorSettings->ShowDetailedInfo_InheritsDecorators() || GraphEditorSettings->ShowDetailedInfo_NumDecorators()) + { + return EVisibility::SelfHitTestInvisible; + } + } + return EVisibility::Collapsed; +} + +FSlateColor SEdComboActionGraphNode::GetImplementsRowColor() const +{ + if (const UEdComboActionGraphNode *EdParentNode = Cast(GraphNode)) + { + if (EdParentNode->ComboActionGraphNode) + { + if (EdParentNode->ComboActionGraphNode->GetNodeDecorators().Num() > 0) + { + return ComboActionGraphColors::TextColors::Normal; + } + + return ComboActionGraphColors::TextColors::Disabled; + } + } + return ComboActionGraphColors::TextColors::Normal; +} + +FSlateColor SEdComboActionGraphNode::GetBulletPointsImagePointColor() const +{ + if (const UEdComboActionGraphNode *EdParentNode = Cast(GraphNode)) + { + if (EdParentNode->ComboActionGraphNode) + { + if (EdParentNode->ComboActionGraphNode->GetNodeDecorators().Num() > 0) + { + return ComboActionGraphColors::BulletPointsColors::Normal; + } + + return ComboActionGraphColors::BulletPointsColors::Disabled; + } + } + return ComboActionGraphColors::BulletPointsColors::Normal; +} + +FText SEdComboActionGraphNode::GetDecoratorsInheritanceText() const +{ + if (const UEdComboActionGraphNode *EdParentNode = Cast(GraphNode)) + { + if (EdParentNode->ComboActionGraphNode) + { + FString Result = EdParentNode->ComboActionGraphNode->DoesInheritDecorators() ? TEXT("yes") : TEXT("no") ; + return FText::FromString( FString(TEXT("INHERITS: ")).Append(Result) ); + } + } + return FText::FromString("invalid"); +} + +EComboActionDecoratorsInfoStyle SEdComboActionGraphNode::GetDecoratorsStyle() const +{ + if (this->GraphEditorSettings) + { + return GraphEditorSettings->GetDecoratorsStyle(); + } + + if (const auto TempSettings = GetMutableDefault()) + { + return TempSettings->GetDecoratorsStyle(); + } + + return EComboActionDecoratorsInfoStyle::Stack; +} + +EVisibility SEdComboActionGraphNode::GetStackVisibility() const +{ + if (this->GetDecoratorsStyle() == EComboActionDecoratorsInfoStyle::Stack) + { + if (GraphEditorSettings) + { + if (GraphEditorSettings->ShowDetailedInfo_InheritsDecorators() || GraphEditorSettings->ShowDetailedInfo_NumDecorators()) + { + return EVisibility::SelfHitTestInvisible; + } + return EVisibility::Collapsed; + } + + const auto TempSettings = GetMutableDefault(); + if (TempSettings) return GetDecoratorsStyle() == EComboActionDecoratorsInfoStyle::Stack ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed; + { + if (TempSettings->ShowDetailedInfo_InheritsDecorators() || TempSettings->ShowDetailedInfo_NumDecorators()) + { + return EVisibility::SelfHitTestInvisible; + } + return EVisibility::Collapsed; + } + + } + return EVisibility::Collapsed; +} + +EVisibility SEdComboActionGraphNode::GetUnifiedVisibility() const +{ + return GetDecoratorsStyle() == EComboActionDecoratorsInfoStyle::Unified ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed; +} + +FText SEdComboActionGraphNode::GetTooltipText() const +{ + if (const UEdComboActionGraphNode *EdParentNode = Cast(GraphNode)) + { + return EdParentNode->GetTooltipText(); + } + return LOCTEXT("SEdComboActionGraphNode_Tooltip", "invalid node selected"); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNode.h b/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNode.h new file mode 100644 index 0000000..b2e9e7e --- /dev/null +++ b/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNode.h @@ -0,0 +1,89 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "SGraphNode.h" +#include "Settings\ComboActionGraphEditorSettings.h" + + +class SEdComboActionGraphNode : public SGraphNode +{ +public: + SLATE_BEGIN_ARGS(SEdComboActionGraphNode) {} + SLATE_END_ARGS() + + void Construct(const FArguments &InArgs, class UEdComboActionGraphNode *InNode); + + virtual void OnMouseEnter(const FGeometry &MyGeometry, const FPointerEvent &MouseEvent) override; + virtual void OnMouseLeave(const FPointerEvent &MouseEvent) override; + + const FSlateBrush *GetIndexBrush() const; + + virtual void UpdateGraphNode() override; + virtual void CreatePinWidgets() override; + virtual void AddPin(const TSharedRef &PinToAdd) override; + virtual bool IsNameReadOnly() const override; + + void OnNameTextCommitted(const FText &InText, ETextCommit::Type CommitInfo); + + virtual const FSlateBrush *GetNodeTypeBrush () const; + virtual const FSlateBrush *GetTextNodeTypeBrush () const; + virtual FSlateColor GetBorderBackgroundColor() const; + virtual FSlateColor GetBorderFrontColor() const; + virtual FSlateColor GetNodeTitleBackgroundColor() const; + virtual FSlateColor GetDecoratorsBackgroundColor() const; + + virtual FSlateColor GetPinsDockColor() const; + + virtual EVisibility GetDragOverMarkerVisibility() const; + + virtual const FSlateBrush *GetNameIcon() const; + + virtual const FSlateBrush *GetInheritsImageBrush() const; + virtual FSlateColor GetInheritsImageTint() const; + + const FSlateBrush *GetBulletPointImageBrush() const; + + virtual FText GetIndexOverlayTooltipText() const; + virtual FText GetIndexText() const; + EVisibility GetIndexSlotVisibility() const; + FVector2D GetIndexSlotOffset() const; + FVector2D GetIndexSlotSize() const; + + virtual void OnIndexHoverStateChanged(bool bArg) const; + virtual FSlateColor GetOverlayWidgetBackgroundColor(bool bArg) const; + + bool HasGraphDecorators() const; + bool HasNodeDecorators() const; + + virtual FText GetDecoratorsText() const; + virtual FText GetNumberOfDecorators() const; + virtual FText GetDecoratorsInheritanceText() const; + + EVisibility ShowImplementsOnlySlot_Unified() const; + EVisibility ShowInheritsDecoratorsSlot_Unified() const; + EVisibility ShowImplementsOnlySlot_Stack() const; + EVisibility ShowInheritsDecoratorsSlot_Stack() const; + EVisibility ShowAllDecorators() const; + EVisibility ShowDecoratorsBottomPadding() const; + + FSlateColor GetImplementsRowColor() const; + FSlateColor GetBulletPointsImagePointColor() const; + + virtual EComboActionDecoratorsInfoStyle GetDecoratorsStyle() const; + EVisibility GetStackVisibility() const; + EVisibility GetUnifiedVisibility() const; + + FText GetTooltipText() const; + +protected: + TSharedPtr NodeBody; + TSharedPtr OutputPinBox; + + class UComboActionGraphEditorSettings *GraphEditorSettings = nullptr; + + FLinearColor NodeInnerColor; + FLinearColor PinsDockColor; +}; diff --git a/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNodeIndex.cpp b/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNodeIndex.cpp new file mode 100644 index 0000000..8e1a8b8 --- /dev/null +++ b/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNodeIndex.cpp @@ -0,0 +1,36 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "SEdComboActionGraphNodeIndex.h" + +#include "Settings/FComboActionGraphEditorStyle.h" + + +void SEdComboActionGraphNodeIndex::Construct(const FArguments &InArgs) +{ + const FSlateBrush* CircleBrush = FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Node.IndexCircle")); + ChildSlot + [ + SNew(SOverlay) + +SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + // Add a dummy box here to make sure the widget doesnt get smaller than the brush + SNew(SBox) + .WidthOverride(CircleBrush->ImageSize.X) + .HeightOverride(CircleBrush->ImageSize.Y) + ] + + +SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SBorder) + .BorderImage(CircleBrush) + .BorderBackgroundColor(this, &SEdComboActionGraphNodeIndex::GetBackgroundColor) + .Padding(FMargin(4.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + ] + ]; +} diff --git a/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNodeIndex.h b/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNodeIndex.h new file mode 100644 index 0000000..75afcb0 --- /dev/null +++ b/Source/ComboInputEditor/Private/Ed/SEdComboActionGraphNodeIndex.h @@ -0,0 +1,33 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +class SEdComboActionGraphNodeIndex : public SCompoundWidget +{ +public: + /** Delegate event fired when the hover state of this widget changes */ + DECLARE_DELEGATE_OneParam(FOnHoverStateChanged, bool /* bHovered */); + + /** Delegate used to receive the background color of the node, depending on hover state and state of other siblings */ + DECLARE_DELEGATE_RetVal_OneParam(FSlateColor, FOnGetBackgroundColor, bool /* bHovered */); + + SLATE_BEGIN_ARGS(SEdComboActionGraphNodeIndex) {} + SLATE_ATTRIBUTE(TSharedPtr, OverlayBody) + + // Events + SLATE_EVENT(FOnHoverStateChanged, OnHoverStateChanged) + SLATE_EVENT(FOnGetBackgroundColor, OnGetBackgroundColor) + SLATE_END_ARGS() + + void Construct(const FArguments &InArgs); + + /** Get the color we use to display the rounded border */ + FSlateColor GetBackgroundColor() const { return FSlateColor::UseForeground(); } + +private: + /** The OverlayBody used for this widget*/ + TSharedPtr OverlayBody; +}; diff --git a/Source/ComboInputEditor/Private/FConnectionDrawingPolicy_ComboActionGraph.cpp b/Source/ComboInputEditor/Private/FConnectionDrawingPolicy_ComboActionGraph.cpp new file mode 100644 index 0000000..49866cd --- /dev/null +++ b/Source/ComboInputEditor/Private/FConnectionDrawingPolicy_ComboActionGraph.cpp @@ -0,0 +1,208 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "FConnectionDrawingPolicy_ComboActionGraph.h" + +#include "Ed/EdComboActionGraphEdge.h" +#include "Ed/EdComboActionGraphNode.h" +#include "Settings/ComboActionGraphEditorSettings.h" +#include "Settings/FComboActionGraphEditorStyle.h" + +DEFINE_LOG_CATEGORY(LogConnectionDrawingPolicy_ComboActionGraph); + + +FConnectionDrawingPolicy_ComboActionGraph::FConnectionDrawingPolicy_ComboActionGraph(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect &InClippingRect, FSlateWindowElementList &InDrawElements, UEdGraph *InGraphObj) + : FKismetConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements, InGraphObj) + , GraphObj(InGraphObj) +{ + if (const UComboActionGraphEditorSettings* GraphEditorSettings = GetMutableDefault()) + { + switch (GraphEditorSettings->GetArrowType()) + { + case EComboActionArrowType::SimpleArrow: + ArrowImage = FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Graph.SimpleArrow")); + break; + case EComboActionArrowType::HollowArrow: + ArrowImage = FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Graph.HollowArrow")); + break; + case EComboActionArrowType::FancyArrow: + ArrowImage = FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Graph.FancyArrow")); + break; + case EComboActionArrowType::Bubble: + ArrowImage = FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Graph.Bubble")); + break; + case EComboActionArrowType::None: + default: + ArrowImage = nullptr; + } + } + else + { + ArrowImage = FAppStyle::GetBrush( TEXT("GenericPlay") ); + } + + ArrowRadius = ArrowImage ? ArrowImage->ImageSize * ZoomFactor * 0.5f : FVector2D(0.f); + MidpointImage = nullptr; + MidpointRadius = FVector2D::ZeroVector; + HoverDeemphasisDarkFraction = 0.8f; + + BubbleImage = FAppStyle::GetBrush( TEXT("Graph.Arrow") ); +} + +void FConnectionDrawingPolicy_ComboActionGraph::DetermineWiringStyle(UEdGraphPin *OutputPin, UEdGraphPin *InputPin, FConnectionParams &Params) +{ + Params.AssociatedPin1 = OutputPin; + Params.AssociatedPin2 = InputPin; + + const UComboActionGraphEditorSettings *MounteaDialogueGraphEditorSettings = GetDefault(); + if (MounteaDialogueGraphEditorSettings) + { + Params.WireThickness = MounteaDialogueGraphEditorSettings->GetWireWidth(); + } + else + { + Params.WireThickness = 1.f; + } + + const bool bDeemphasizeUnhoveredPins = HoveredPins.Num() > 0; + if (bDeemphasizeUnhoveredPins) + { + ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Params.WireThickness, /*inout*/ Params.WireColor); + } +} + +void FConnectionDrawingPolicy_ComboActionGraph::Draw(TMap, 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 ChildNode = StaticCastSharedRef(CurWidget.Widget); + NodeWidgetMap.Add(ChildNode->GetNodeObj(), NodeIndex); + } + + // Now draw + FConnectionDrawingPolicy::Draw(InPinGeometries, ArrangedNodes); +} + +void FConnectionDrawingPolicy_ComboActionGraph::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); + + this->DrawSplineWithArrow(StartAnchorPoint, EndAnchorPoint, Params); +} + +void FConnectionDrawingPolicy_ComboActionGraph::DrawSplineWithArrow(const FVector2D &StartPoint, const FVector2D &EndPoint, const FConnectionParams &Params) +{ + // bUserFlag1 indicates that we need to reverse the direction of connection (used by debugger) + const FVector2D &P0 = Params.bUserFlag1 ? EndPoint : StartPoint; + const FVector2D &P1 = Params.bUserFlag1 ? StartPoint : EndPoint; + + UE_LOG(LogConnectionDrawingPolicy_ComboActionGraph, Verbose, TEXT("%s I %s"), *P0.ToString(), *P1.ToString()); + + FConnectionParams NewParams = Params; + //NewParams.bDrawBubbles = true; + + if (const UComboActionGraphEditorSettings *MounteaDialogueGraphEditorSettings = GetMutableDefault()) + { + NewParams.WireThickness = MounteaDialogueGraphEditorSettings->GetWireWidth(); + } + + Internal_DrawLineWithArrow(P0, P1, NewParams); +} + +void FConnectionDrawingPolicy_ComboActionGraph::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); + } +} + +FVector2D FConnectionDrawingPolicy_ComboActionGraph::ComputeSplineTangent(const FVector2D &Start, const FVector2D &End) const +{ + const FVector2D Delta = End - Start; + const FVector2D NormDelta = Delta.GetSafeNormal(); + + return NormDelta; +} + +void FConnectionDrawingPolicy_ComboActionGraph::DetermineLinkGeometry(FArrangedChildren &ArrangedNodes, TSharedRef &OutputPinWidget, UEdGraphPin *OutputPin, UEdGraphPin *InputPin, FArrangedWidget *&StartWidgetGeometry, FArrangedWidget *&EndWidgetGeometry) +{ + if (UEdComboActionGraphEdge *EdgeNode = Cast(InputPin->GetOwningNode())) + { + UEdComboActionGraphNode* Start = EdgeNode->GetStartNode(); + UEdComboActionGraphNode* 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* pTargetWidget = PinToPinWidgetMap.Find(InputPin)) + { + TSharedRef InputWidget = (*pTargetWidget).ToSharedRef(); + EndWidgetGeometry = PinGeometries->Find(InputWidget); + } + } +} + +void FConnectionDrawingPolicy_ComboActionGraph::Internal_DrawLineWithArrow(const FVector2D &StartAnchorPoint, const FVector2D &EndAnchorPoint, const FConnectionParams &Params) +{ + 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 + if (ArrowImage) + { + 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(), + FSlateDrawElement::RelativeToElement, + Params.WireColor + ); + } +} diff --git a/Source/ComboInputEditor/Private/FConnectionDrawingPolicy_ComboActionGraph.h b/Source/ComboInputEditor/Private/FConnectionDrawingPolicy_ComboActionGraph.h new file mode 100644 index 0000000..6f45bc6 --- /dev/null +++ b/Source/ComboInputEditor/Private/FConnectionDrawingPolicy_ComboActionGraph.h @@ -0,0 +1,36 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintConnectionDrawingPolicy.h" +#include "ConnectionDrawingPolicy.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogConnectionDrawingPolicy_ComboActionGraph, Log, All); + + +class FConnectionDrawingPolicy_ComboActionGraph : public FKismetConnectionDrawingPolicy +{ + +public: + FConnectionDrawingPolicy_ComboActionGraph(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj); + virtual ~FConnectionDrawingPolicy_ComboActionGraph() {}; + + // FConnectionDrawingPolicy interface + virtual void DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params) override; + virtual void Draw(TMap, FArrangedWidget>& InPinGeometries, 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& 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); + +protected: + UEdGraph *GraphObj; + TMap NodeWidgetMap; + +}; diff --git a/Source/ComboInputEditor/Private/Helpers/ComboActionEditorBFC.h b/Source/ComboInputEditor/Private/Helpers/ComboActionEditorBFC.h new file mode 100644 index 0000000..e08b777 --- /dev/null +++ b/Source/ComboInputEditor/Private/Helpers/ComboActionEditorBFC.h @@ -0,0 +1,86 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "Ed/EdComboActionGraphNode.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Nodes/ComboActionGraphNode.h" +#include "Nodes/ComboActionGraphNode_ActionNodeBase.h" +#include "Settings/ComboActionGraphEditorSettings.h" + +#include "ComboActionEditorBFC.generated.h" + + +/** + * Editor Only helper functions. + */ +UCLASS() +class UComboActionEditorBFC : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + +#if WITH_EDITOR + static FText GetNodeTitle(UComboActionGraphNode *Node) + { + if (!Node) return FText::FromString("Invalid Node"); + + const UComboActionGraphEditorSettings *Settings = GetDefault(); + if (Settings) + { + if (Settings->ShowAutomaticNames()) + { + if (const UComboActionGraphNode_ActionNodeBase *DialogueNodeBase = Cast(Node)) + { + if (DialogueNodeBase->GetDataTable()) + { + FString ReturnString; + DialogueNodeBase->GetRowName().ToString(ReturnString); + + return FText::FromString(ReturnString); + } + } + + return Node->GetInternalName(); + } + } + + return Node->GetNodeTitle(); + } + + static EComboActionNodeTheme GetNodeTheme() + { + const UComboActionGraphEditorSettings *Settings = GetDefault(); + if (Settings != nullptr) + { + return Settings->GetNodeTheme(); + } + + return EComboActionNodeTheme::DarkTheme; + } + + static void TriggerPreviewRefresh(TArray NodeObjects) + { + for (auto Itr : NodeObjects) + { + UEdComboActionGraphNode *SelectedNode = Cast(Itr); + if (!SelectedNode || !SelectedNode->ComboActionGraphNode) + { + continue; + } + + UComboActionGraphNode_ActionNodeBase *DialogueNodeBase = Cast(SelectedNode->ComboActionGraphNode); + if (!DialogueNodeBase) + { + continue; + } + + DialogueNodeBase->UpdatePreviews(); + } + } +#endif + +}; diff --git a/Source/ComboInputEditor/Private/Helpers/ComboActionGraphColors.h b/Source/ComboInputEditor/Private/Helpers/ComboActionGraphColors.h new file mode 100644 index 0000000..4d6f9f9 --- /dev/null +++ b/Source/ComboInputEditor/Private/Helpers/ComboActionGraphColors.h @@ -0,0 +1,92 @@ +#pragma once + +#include "CoreMinimal.h" + +namespace ComboActionGraphColors +{ + namespace NodeBody + { + constexpr FLinearColor Default(0.1f, 0.1f, 0.1f); + constexpr FLinearColor Root(0.5f, 0.5f, 0.5f, 0.1f); + constexpr FLinearColor Error(1.0f, 0.0f, 0.0f); + } + + namespace DecoratorsBody + { + constexpr FLinearColor Default(0.1f, 0.1f, 0.1f); + } + + namespace NodeBorder + { + constexpr FLinearColor Inactive(0.08f, 0.08f, 0.08f); + constexpr FLinearColor Root(0.2f, 0.2f, 0.2f, 0.2f); + constexpr FLinearColor Selected(1.00f, 0.08f, 0.08f); + constexpr FLinearColor ActiveDebugging(1.0f, 1.0f, 0.0f); + constexpr FLinearColor InactiveDebugging(0.4f, 0.4f, 0.0f); + constexpr FLinearColor HighlightAbortRange0(0.0f, 0.22f, 0.4f); + constexpr FLinearColor HighlightAbortRange1(0.0f, 0.4f, 0.22f); + constexpr FLinearColor Disconnected(0.f, 0.f, 0.f); + constexpr FLinearColor BrokenWithParent(1.f, 0.f, 1.f); + constexpr FLinearColor QuickFind(0.f, 0.8f, 0.f); + } + + namespace Pin + { + constexpr FLinearColor Diff(0.9f, 0.2f, 0.15f); + constexpr FLinearColor Hover(1.0f, 0.7f, 0.0f); + constexpr FLinearColor Default(0.02f, 0.02f, 0.02f); + constexpr FLinearColor SingleNode(0.02f, 0.02f, 0.02f); + } + + namespace Connection + { + constexpr FLinearColor Default(1.0f, 1.0f, 1.0f); + } + + namespace Action + { + constexpr FLinearColor DragMarker(1.0f, 1.0f, 0.2f); + } + + namespace Overlay + { + constexpr FLinearColor LightTheme(1.f, 1.f, 1.f, 0.45f); + constexpr FLinearColor DarkTheme(0.f, 0.f, 0.f, 0.9f); + } + + namespace PinsDock + { + constexpr FLinearColor DarkTheme(0.075f, 0.075f, 0.075f, 1.f); + constexpr FLinearColor LightTheme(0.075f, 0.075f, 0.075f, 1.f); + } + + namespace ValidationGraph + { + constexpr FLinearColor LightTheme(0.1f, 0.1f, 0.1f, 1.f); + constexpr FLinearColor DarkTheme(0.05f, 0.05f, 0.05f, 1.f); + } + + namespace IndexBorder + { + constexpr FLinearColor NormalState(0.05f, 0.05f, 0.05f, 1.f); + constexpr FLinearColor HoveredState(0.05f, 0.05f, 0.05f, 1.f); + } + + namespace BulletPointsColors + { + constexpr FLinearColor Normal(0.8f, 0.8f, 0.8f, 1.f); + constexpr FLinearColor Disabled(0.8f, 0.8f, 0.8f, 0.2f); + } + + namespace TextColors + { + constexpr FLinearColor Normal(0.8f, 0.8f, 0.8f, 1.f); + constexpr FLinearColor Disabled(0.8f, 0.8f, 0.8f, 0.2f); + } + + namespace Previews + { + constexpr FLinearColor Normal(0.8f, 0.8f, 0.8f, 0.05f); + constexpr FLinearColor Invalid(0.8f, 0.8f, 0.8f, 0.2f); + } +} \ No newline at end of file diff --git a/Source/ComboInputEditor/Private/Settings/ComboActionGraphEditorSettings.cpp b/Source/ComboInputEditor/Private/Settings/ComboActionGraphEditorSettings.cpp new file mode 100644 index 0000000..f85fa93 --- /dev/null +++ b/Source/ComboInputEditor/Private/Settings/ComboActionGraphEditorSettings.cpp @@ -0,0 +1,40 @@ +// 2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "ComboActionGraphEditorSettings.h" + +#define LOCTEXT_NAMESPACE "ComboActionGraphEditorSettings" + + +UComboActionGraphEditorSettings::UComboActionGraphEditorSettings() +{ + NodeType = EComboActionNodeType::SoftCorners; + NodeTheme = EComboActionNodeTheme::DarkTheme; + ArrowType = EComboActionArrowType::HollowArrow; + + CategoryName = TEXT("Combo Input"); + SectionName = TEXT("Combo Input (Editor)"); + + AutoLayoutStrategy = EComboActionAutoLayoutStrategyType::Tree; + + bFirstPassOnly = false; + bRandomInit = false; + OptimalDistance = 100.f; + MaxIteration = 50; + InitTemperature = 10.f; + CoolDownRate = 10.f; + + WireWidth = 0.8f; + //WireStyle = EWiringStyle::EWS_Simple; + //BubbleDrawRule = EBubbleDrawRule::EBDR_OnSelected; + + bAllowRenameNodes = true; + bDisplayAutomaticNames = false; + + bShowDetailedInfo_InheritsDecorators = true; + bShowDetailedInfo_NumDecorators = true; + DecoratorsInfoStyle = EComboActionDecoratorsInfoStyle::Unified; + + bAllowNativeDecoratorsEdit = false; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ComboInputEditor/Private/Settings/ComboActionGraphEditorSettings.h b/Source/ComboInputEditor/Private/Settings/ComboActionGraphEditorSettings.h new file mode 100644 index 0000000..34e4850 --- /dev/null +++ b/Source/ComboInputEditor/Private/Settings/ComboActionGraphEditorSettings.h @@ -0,0 +1,292 @@ +// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DeveloperSettings.h" + +#include "ComboActionGraphEditorSettings.generated.h" + + +#pragma region Enums + +UENUM(BlueprintType) +enum class EComboActionAutoLayoutStrategyType : uint8 +{ + Tree UMETA(DisplayName="Tree"), + ForceDirected UMETA(DisplayName="Force Directed"), + + Default UMETA(Hidden) +}; + +UENUM(BlueprintType) +enum class EComboActionWiringStyle : uint8 +{ + Vanilla UMETA(DisplayName="Vanilla"), + Simple UMETA(DisplayName="90° Angle"), + Complex UMETA(DisplayName="45° Angle"), + + Default UMETA(Hidden) +}; + +UENUM(BlueprintType) +enum class EComboActionBubbleDrawRule : uint8 +{ + Always UMETA(DisplayName="Always"), + OnSelected UMETA(DisplayName="When Selected") +}; + +UENUM(BlueprintType) +enum class EComboActionNodeTheme : uint8 +{ + DarkTheme UMETA(DisplayName="Dark Theme"), + LightTheme UMETA(DisplayName="Light Theme") +}; + +UENUM(BlueprintType) +enum class EComboActionDecoratorsInfoStyle : uint8 +{ + Stack UMETA(DisplayName="Stack"), + Unified UMETA(DisplayName="Unified") +}; + +UENUM(BlueprintType) +enum class EComboActionNodeType : uint8 +{ + SoftCorners UMETA(DisplayName="Soft Corners"), + HardCorners UMETA(DisplayName="Hard Corners") +}; + +UENUM(BlueprintType) +enum class EComboActionArrowType : uint8 +{ + SimpleArrow UMETA(DisplayName="Simple Arrow"), + HollowArrow UMETA(DisplayName="Hollow Arrow"), + FancyArrow UMETA(DisplayName="Fancy Arrow"), + Bubble UMETA(DisplayName="Bubble"), + None UMETA(DisplayName="Nothing") +}; + +#pragma endregion + +/** + * Combo Action Graph global settings. + */ +UCLASS(config = MounteaSettings) +class COMBOINPUT_API UComboActionGraphEditorSettings : public UDeveloperSettings +{ + GENERATED_BODY() + +public: + UComboActionGraphEditorSettings(); + +private: + +#pragma region GraphNodes + + UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings") + EComboActionNodeType NodeType; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings") + EComboActionNodeTheme NodeTheme; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings|DecoratorsInfo") + uint8 bShowDetailedInfo_NumDecorators : 1; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings|DecoratorsInfo") + uint8 bShowDetailedInfo_InheritsDecorators : 1; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings") + EComboActionDecoratorsInfoStyle DecoratorsInfoStyle; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings") + bool bDisplayAutomaticNames; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings") + bool bAllowRenameNodes; + + /** + * Select a Node Class and specify Override Colour for this Node type. + * Only non-abstract classes are allowed! + */ + UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings", meta=(ShowTreeView)) + TMap, FLinearColor> OverrideNodeBackgroundColours; + +#pragma endregion + +#pragma region GraphDecorators + + // Enables 'Edit' button for Native Code Decorators + UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings") + bool bAllowNativeDecoratorsEdit; + +#pragma endregion + +#pragma region GraphWiring + + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", meta=(UIMin=0.1f, ClampMin=0.1f, UIMax=1.5f, ClampMax=1.5f)) + float WireWidth; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring") //, meta=(ConfigRestartRequired=true)) + EComboActionArrowType ArrowType; + + /* Advanced Wiring doesn't work now + + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!")) + bool bUseAdvancedWiring; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring")) + EWiringStyle WireStyle; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring")) + uint32 HorizontalOffset = 16; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring")) + EBubbleDrawRule BubbleDrawRule; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring")) + int32 BubbleZoomThreshold; + + // Space between bubbles on the wires. Default: 20.0 + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta = (ClampMin = "10.0", ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring")) + float BubbleSpace = 20.0f; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta = (ClampMin = "10.0", ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring")) + float BubbleSize = 2.0f; + + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta = (ClampMin = "10.0", ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring")) + float BubbleSpeed = 2.0f; + + // Disable the offset for pins. Default: false + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring")) + bool DisablePinOffset = false; + + // Fix default zoomed-out wire displacement. Default: true + UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring")) + bool FixZoomDisplacement = true; + + */ + +#pragma endregion + +#pragma region GraphArrange + + UPROPERTY(config, EditDefaultsOnly, Category = "AutoArrange") + float OptimalDistance; + + UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange") + EComboActionAutoLayoutStrategyType AutoLayoutStrategy; + + UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange") + int32 MaxIteration; + + UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange") + bool bFirstPassOnly; + + UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange") + bool bRandomInit; + + UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange") + float InitTemperature; + + UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange") + float CoolDownRate; + +#pragma endregion + +#if WITH_EDITOR + virtual FText GetSectionText() const override + { + return NSLOCTEXT("ComboActionGraphEditorSystem", "ComboActionGraphEditorSection", "Combo Action Graph (Editor)"); + } + + virtual FText GetSectionDescription() const override + { + return NSLOCTEXT("ComboActionGraphEditorSystem", "ComboActionGraphEditorDescription", "Default values for Mountea Plugins (Editor)."); + } + + virtual FName GetContainerName() const override + { + return "Project"; + } +#endif + +public: + +#pragma region GraphNodes_Getters + + EComboActionNodeTheme GetNodeTheme() const { return NodeTheme; } + EComboActionNodeType GetNodeType() const { return NodeType; } + bool ShowDetailedInfo_NumDecorators() const { return bShowDetailedInfo_NumDecorators; } + bool ShowDetailedInfo_InheritsDecorators() const { return bShowDetailedInfo_InheritsDecorators; } + bool ShowAutomaticNames() const { return bDisplayAutomaticNames; } + EComboActionDecoratorsInfoStyle GetDecoratorsStyle() const { return DecoratorsInfoStyle; } + bool AllowRenameNodes() const { return bAllowRenameNodes; } + + bool FindNodeBackgroundColourOverride(const TSoftClassPtr NodeClass, FLinearColor& BackgroundColour) + { + if (OverrideNodeBackgroundColours.Contains(NodeClass)) + { + BackgroundColour = *OverrideNodeBackgroundColours.Find(NodeClass); + return true; + } + + return false; + } + +#pragma endregion + +#pragma region GraphDecorators_Getters + + bool IsNativeDecoratorsEditAllowed() const { return bAllowNativeDecoratorsEdit; } + +#pragma endregion + + +#pragma region GraphWiring_Getters + + float GetWireWidth() const { return this->WireWidth; } + + EComboActionArrowType GetArrowType() const { return this->ArrowType; } + + /* + bool AllowAdvancedWiring() const + { return bUseAdvancedWiring; }; + + EWiringStyle GetWireStyle() const + { return WireStyle; }; + + int32 GetHorizontalOffset() const + { return HorizontalOffset; }; + + EBubbleDrawRule GetBubbleDrawRule() const + { return BubbleDrawRule; }; + + int32 GetBubbleZoomThreshold() const + { return BubbleZoomThreshold; }; + + float GetBubbleSpace() const + { return BubbleSpace; }; + + float GetBubbleSpeed() const + { return BubbleSpeed; }; + + float GetBubbleSize() const + { return BubbleSize; }; + */ +#pragma endregion + + +#pragma region GraphArrange_Getters + + EComboActionAutoLayoutStrategyType GetAutoLayoutStrategy() const { return this->AutoLayoutStrategy; } + float GetOptimalDistance() const { return this->OptimalDistance; } + int32 GetMaxIteration() const { return this->MaxIteration; } + bool IsFirstPassOnly() const { return this->bFirstPassOnly; } + bool IsRandomInit() const { return this->bRandomInit; } + float GetInitTemperature() const { return this->InitTemperature; } + float GetCoolDownRate() const { return this->CoolDownRate; } + +#pragma endregion +}; + diff --git a/Source/ComboInputEditor/Private/Settings/FComboActionGraphEditorStyle.cpp b/Source/ComboInputEditor/Private/Settings/FComboActionGraphEditorStyle.cpp new file mode 100644 index 0000000..e2e5180 --- /dev/null +++ b/Source/ComboInputEditor/Private/Settings/FComboActionGraphEditorStyle.cpp @@ -0,0 +1,138 @@ +// Copyright Dominik Pavlicek 2023. All Rights Reserved. + +#include "FComboActionGraphEditorStyle.h" + +#include "Interfaces/IPluginManager.h" +#include "Misc/Paths.h" +#include "Styling/SlateStyleRegistry.h" + +#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 DEFAULT_FONT(...) FCoreStyle::GetDefaultFontStyle(__VA_ARGS__) + + +TSharedPtr FComboActionGraphEditorStyle::StyleSet = nullptr; + +void FComboActionGraphEditorStyle::Initialize() +{ + if (!StyleSet.IsValid() ) + { + Create(); + FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); + } +} + +void FComboActionGraphEditorStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); + ensure(StyleSet.IsUnique()); + StyleSet.Reset(); +} + +void FComboActionGraphEditorStyle::Create() +{ + const FVector2D Icon12x12(12.0f, 12.0f); + const FVector2D Icon16x16(16.0f, 16.0f); + const FVector2D Icon40x40(40.0f, 40.0f); + const FVector2D Icon64x64(64.0f, 64.0f); + const FVector2D Icon128x128(128.f, 128.f); + const FVector2D Icon200x70(200.f, 70.f); + + StyleSet = MakeShareable(new FSlateStyleSet(GetAppStyleSetName())); + StyleSet->SetContentRoot(IPluginManager::Get().FindPlugin("ComboInput")->GetBaseDir() / TEXT("Resources")); + StyleSet->SetCoreContentRoot(IPluginManager::Get().FindPlugin("ComboInput")->GetBaseDir() / TEXT("Content")); + + StyleSet->Set("MDSStyleSet.AutoArrange.small", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon16x16)); + StyleSet->Set("MDSStyleSet.AutoArrange", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon40x40)); + StyleSet->Set("MDSStyleSet.AutoArrange.large", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon64x64)); + + StyleSet->Set("MDSStyleSet.GraphSettings.small", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon16x16)); + StyleSet->Set("MDSStyleSet.GraphSettings", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon40x40)); + StyleSet->Set("MDSStyleSet.GraphSettings.large", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon64x64)); + + StyleSet->Set("MDSStyleSet.ValidateGraph.small", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon16x16)); + StyleSet->Set("MDSStyleSet.ValidateGraph", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon40x40)); + StyleSet->Set("MDSStyleSet.ValidateGraph.large", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon64x64)); + + StyleSet->Set("MDSStyleSet.Graph.NodeOverlay", new BOX_BRUSH( TEXT("NodeOverlay"), FMargin(8.0f/64.0f, 3.0f/32.0f, 0, 0) )); + StyleSet->Set("MDSStyleSet.Graph.PinDocksOverlay", new BOX_BRUSH( TEXT("PinDocksOverlay"), FMargin(8.0f/64.0f, 3.0f/32.0f, 0, 0) )); + + StyleSet->Set("MDSStyleSet.Graph.SimpleArrow", new IMAGE_BRUSH(TEXT("SimpleArrow"), Icon16x16)); + StyleSet->Set("MDSStyleSet.Graph.HollowArrow", new IMAGE_BRUSH(TEXT("HollowArrow"), Icon16x16)); + StyleSet->Set("MDSStyleSet.Graph.FancyArrow", new IMAGE_BRUSH(TEXT("FancyArrow"), Icon16x16)); + StyleSet->Set("MDSStyleSet.Graph.Bubble", new IMAGE_BRUSH(TEXT("Bubble"), Icon16x16)); + + StyleSet->Set("MDSStyleSet.Node.SoftEdges", new BOX_BRUSH( TEXT("NodeSoftCorners") , FMargin(16.f/64.f, 25.f/64.f, 16.f/64.f, 16.f/64.f) )); + StyleSet->Set("MDSStyleSet.Node.HardEdges", new BOX_BRUSH( TEXT("NodeHardCorners") , FMargin(16.f/64.f, 25.f/64.f, 16.f/64.f, 16.f/64.f) )); + + StyleSet->Set("MDSStyleSet.Node.TextSoftEdges", new BOX_BRUSH( TEXT("TextSoftCorners") , FMargin(16.f/64.f, 25.f/64.f, 16.f/64.f, 16.f/64.f) )); + StyleSet->Set("MDSStyleSet.Node.TextHardEdges", new BOX_BRUSH( TEXT("TextHardCorners") , FMargin(16.f/64.f, 25.f/64.f, 16.f/64.f, 16.f/64.f) )); + + StyleSet->Set("MDSStyleSet.Node.IndexCircle", new IMAGE_BRUSH(TEXT("IndexIcon"), Icon16x16)); + + StyleSet->Set("MDSStyleSet.Icon.OK", new IMAGE_BRUSH(TEXT("OKIcon"), Icon16x16)); + StyleSet->Set("MDSStyleSet.Icon.Error", new IMAGE_BRUSH(TEXT("ErroIcon"), Icon16x16)); + StyleSet->Set("MDSStyleSet.Icon.BulletPoint", new IMAGE_BRUSH(TEXT("CircleBox"), Icon16x16)); + + StyleSet->Set("MDSStyleSet.Graph.CornerImage", new IMAGE_BRUSH(TEXT("Icon128"), Icon128x128)); + + StyleSet->Set("MDSStyleSet.Icon.Browse", new IMAGE_BRUSH(TEXT("BrowseIcon"), Icon12x12)); + StyleSet->Set("MDSStyleSet.Icon.Edit", new IMAGE_BRUSH(TEXT("EditIcon"), Icon12x12)); + + StyleSet->Set("MDSStyleSet.Buttons.Documentation", new IMAGE_BRUSH(TEXT("Documentation"), Icon200x70)); + StyleSet->Set("MDSStyleSet.Buttons.Documentation.small", new IMAGE_BRUSH(TEXT("DocumentationIcon"), Icon12x12)); + + StyleSet->Set("MDSStyleSet.Node.Icon.large", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon64x64)); + StyleSet->Set("MDSStyleSet.Node.Icon", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon16x16)); + StyleSet->Set("MDSStyleSet.Node.Icon.small", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon12x12)); + + StyleSet->Set("MDSStyleSet.Icon.Close", new IMAGE_BRUSH(TEXT("CloseIcon"), Icon12x12)); + StyleSet->Set("MDSStyleSet.Icon.SupportDiscord", new IMAGE_BRUSH(TEXT("DiscordIcon"), Icon12x12)); + StyleSet->Set("MDSStyleSet.Icon.HeartIcon", new IMAGE_BRUSH(TEXT("HeartIcon"), Icon12x12)); + StyleSet->Set("MDSStyleSet.Icon.UBIcon", new IMAGE_BRUSH(TEXT("UnrealBucketIcon"), Icon12x12)); + StyleSet->Set("MDSStyleSet.Icon.MoneyIcon", new IMAGE_BRUSH(TEXT("MoneyIcon"), Icon12x12)); + + const FButtonStyle MounteaButtonStyle = FButtonStyle() + .SetNormal(BOX_BRUSH("RoundedSelection_16x", 4.0f / 16.0f, FLinearColor(1, 1, 1, 0.1f))) + .SetHovered(BOX_BRUSH("RoundedSelection_16x", 4.0f / 16.0f, FLinearColor(1, .55f, 0, 0.2f))) + .SetPressed(BOX_BRUSH("RoundedSelection_16x", 4.0f / 16.0f, FLinearColor(1, .55f, 0, 0.4f))); + + StyleSet->Set("MDSStyleSet.Buttons.Style", MounteaButtonStyle); + + { + const FScrollBarStyle ScrollBar = GetWidgetStyle( "ScrollBar" ); + + FTextBlockStyle NormalText = FTextBlockStyle() + .SetFont(DEFAULT_FONT("Regular", FCoreStyle::RegularTextSize)) + .SetColorAndOpacity(FSlateColor::UseForeground()) + .SetShadowOffset(FVector2D::ZeroVector) + .SetShadowColorAndOpacity(FLinearColor::Black) + .SetHighlightColor( FLinearColor( 0.02f, 0.3f, 0.0f ) ) + .SetHighlightShape( BOX_BRUSH( "TextBlockHighlightShape", FMargin(3.f/8.f) ) ); + + FTextBlockStyle NodeTitle = FTextBlockStyle(NormalText) + .SetFont( DEFAULT_FONT( "Bold", 14 ) ) + .SetColorAndOpacity( FLinearColor(230.0f/255.0f,230.0f/255.0f,230.0f/255.0f) ) + .SetShadowOffset( FVector2D( 2,2 ) ) + .SetShadowColorAndOpacity( FLinearColor(0.f,0.f,0.f, 0.7f) ); + StyleSet->Set( "MDSStyleSet.NodeTitle", NodeTitle ); + + FEditableTextBoxStyle NodeTitleEditableText = FEditableTextBoxStyle() + .SetFont(NormalText.Font) + .SetBackgroundImageNormal( BOX_BRUSH( "TextBox", FMargin(4.0f/16.0f) ) ) + .SetBackgroundImageHovered( BOX_BRUSH( "TextBox_Hovered", FMargin(4.0f/16.0f) ) ) + .SetBackgroundImageFocused( BOX_BRUSH( "TextBox_Hovered", FMargin(4.0f/16.0f) ) ) + .SetBackgroundImageReadOnly( BOX_BRUSH( "TextBox_ReadOnly", FMargin(4.0f/16.0f) ) ) + .SetScrollBarStyle( ScrollBar ); + StyleSet->Set( "MDSStyleSet.NodeTitleEditableText", NodeTitleEditableText ); + + StyleSet->Set( "MDSStyleSet.NodeTitleInlineEditableText", FInlineEditableTextBlockStyle() + .SetTextStyle(NodeTitle) + .SetEditableTextBoxStyle(NodeTitleEditableText) + ); + } +} + +#undef DEFAULT_FONT +#undef BOX_BRUSH +#undef IMAGE_BRUSH diff --git a/Source/ComboInputEditor/Private/Settings/FComboActionGraphEditorStyle.h b/Source/ComboInputEditor/Private/Settings/FComboActionGraphEditorStyle.h new file mode 100644 index 0000000..7c189fa --- /dev/null +++ b/Source/ComboInputEditor/Private/Settings/FComboActionGraphEditorStyle.h @@ -0,0 +1,40 @@ +// Copyright Dominik Pavlicek 2023. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + + +class FComboActionGraphEditorStyle +{ +public: + + static void Create(); + static void Initialize(); + static void Shutdown(); + static ISlateStyle &Get() + { + return *(StyleSet.Get()); + } + + static const FSlateBrush *GetBrush(FName PropertyName, const ANSICHAR *Specifier = NULL) + { + return StyleSet->GetBrush(PropertyName, Specifier); + }; + + static const FName &GetAppStyleSetName() + { + static FName StyleSetName(TEXT("ComboActionGraphEditorStyle")); + return StyleSetName; + }; + + template< class T > + static const T &GetWidgetStyle( FName PropertyName, const ANSICHAR *Specifier = NULL ) + { + return StyleSet->GetWidgetStyle< T >( PropertyName, Specifier ); + } + +private: + static TSharedPtr StyleSet; +};