├── Resources ├── Icon128.png ├── FormatString.png └── PrintStringFormatted.png ├── Source ├── SiriusUtilityNodes │ ├── Private │ │ ├── SiriusUtilityNodes.cpp │ │ └── SiriusStringLibrary.cpp │ ├── Public │ │ ├── SiriusUtilityNodes.h │ │ └── SiriusStringLibrary.h │ └── SiriusUtilityNodes.Build.cs └── SiriusUtilityNodesEditor │ ├── Public │ ├── SiriusUtilityNodesEditor.h │ ├── K2Node_SiriusPrintStringFormatted.h │ └── K2Node_SiriusFormatString.h │ ├── SiriusUtilityNodesEditor.Build.cs │ └── Private │ ├── Slate │ ├── SGraphNodeFormatString.h │ └── SGraphNodeFormatString.cpp │ ├── SiriusUtilityNodesEditor.cpp │ ├── Details │ ├── FormatStringDetails.h │ └── FormatStringDetails.cpp │ ├── K2Node_SiriusPrintStringFormatted.cpp │ └── K2Node_SiriusFormatString.cpp ├── Config └── FilterPlugin.ini ├── LICENSE ├── .gitignore ├── SiriusUtilityNodes.uplugin ├── BuildPlugin.ps1 └── README.md /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasperDeLaat94/sirius-utility-nodes/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Resources/FormatString.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasperDeLaat94/sirius-utility-nodes/HEAD/Resources/FormatString.png -------------------------------------------------------------------------------- /Resources/PrintStringFormatted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasperDeLaat94/sirius-utility-nodes/HEAD/Resources/PrintStringFormatted.png -------------------------------------------------------------------------------- /Source/SiriusUtilityNodes/Private/SiriusUtilityNodes.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #include "SiriusUtilityNodes.h" 4 | 5 | IMPLEMENT_MODULE(FSiriusUtilityNodesModule, SiriusUtilityNodes) 6 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodes/Public/SiriusUtilityNodes.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FSiriusUtilityNodesModule final : public IModuleInterface 9 | { 10 | }; 11 | -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and 3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. 4 | ; 5 | ; Examples: 6 | ; /README.txt 7 | ; /Extras/... 8 | ; /Binaries/ThirdParty/*.dll 9 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/Public/SiriusUtilityNodesEditor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FSiriusUtilityNodesEditorModule final : public IModuleInterface 9 | { 10 | public: 11 | virtual void StartupModule() override; 12 | virtual void ShutdownModule() override; 13 | }; 14 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodes/SiriusUtilityNodes.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class SiriusUtilityNodes : ModuleRules 6 | { 7 | public SiriusUtilityNodes(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | #if UE_5_2_OR_LATER 12 | IWYUSupport = IWYUSupport.Full; 13 | #else 14 | bEnforceIWYU = true; 15 | #endif 16 | 17 | PrivateDependencyModuleNames.AddRange( 18 | new string[] 19 | { 20 | "Core", 21 | "CoreUObject", 22 | "Engine" 23 | } 24 | ); 25 | } 26 | } -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/SiriusUtilityNodesEditor.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class SiriusUtilityNodesEditor : ModuleRules 6 | { 7 | public SiriusUtilityNodesEditor(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | #if UE_5_2_OR_LATER 12 | IWYUSupport = IWYUSupport.Full; 13 | #else 14 | bEnforceIWYU = true; 15 | #endif 16 | 17 | PublicDependencyModuleNames.AddRange( 18 | new string[] 19 | { 20 | "BlueprintGraph", 21 | } 22 | ); 23 | 24 | 25 | PrivateDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | "CoreUObject", 30 | "Engine", 31 | "EditorStyle", 32 | "PropertyEditor", 33 | "SlateCore", 34 | "Slate", 35 | "GraphEditor", 36 | "UnrealEd", 37 | "KismetCompiler", 38 | "SiriusUtilityNodes" 39 | } 40 | ); 41 | } 42 | } -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/Private/Slate/SGraphNodeFormatString.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Layout/Visibility.h" 7 | #include "Input/Reply.h" 8 | #include "Widgets/DeclarativeSyntaxSupport.h" 9 | #include "KismetNodes/SGraphNodeK2Base.h" 10 | 11 | /** 12 | * 13 | */ 14 | class SGraphNodeFormatString : public SGraphNodeK2Base 15 | { 16 | public: 17 | SLATE_BEGIN_ARGS(SGraphNodeFormatString) 18 | { 19 | } 20 | 21 | SLATE_END_ARGS() 22 | 23 | void Construct(const FArguments& InArgs, class UK2Node_SiriusFormatString* InNode); 24 | 25 | // SGraphNode interface 26 | virtual void CreatePinWidgets() override; 27 | 28 | protected: 29 | virtual void CreateInputSideAddButton(TSharedPtr InputBox) override; 30 | virtual EVisibility IsAddPinButtonVisible() const override; 31 | virtual FReply OnAddPin() override; 32 | // End of SGraphNode interface 33 | }; 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jasper de Laat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/Private/SiriusUtilityNodesEditor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #include "SiriusUtilityNodesEditor.h" 4 | 5 | #include "K2Node_SiriusFormatString.h" 6 | #include "PropertyEditorModule.h" 7 | #include "Details/FormatStringDetails.h" 8 | 9 | IMPLEMENT_MODULE(FSiriusUtilityNodesEditorModule, SiriusUtilityNodesEditor) 10 | 11 | void FSiriusUtilityNodesEditorModule::StartupModule() 12 | { 13 | // Register the details customizer 14 | FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); 15 | PropertyModule.RegisterCustomClassLayout(UK2Node_SiriusFormatString::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FFormatStringDetails::MakeInstance)); 16 | } 17 | 18 | void FSiriusUtilityNodesEditorModule::ShutdownModule() 19 | { 20 | // Unregister the details customization 21 | if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) 22 | { 23 | FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); 24 | PropertyModule.UnregisterCustomClassLayout(UK2Node_SiriusFormatString::StaticClass()->GetFName()); 25 | PropertyModule.NotifyCustomizationModuleChanged(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SiriusUtilityNodes.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 4, 4 | "VersionName": "1.1.3", 5 | "FriendlyName": "Sirius Utility Nodes", 6 | "Description": "A collection of utility nodes to boost your blueprint productivity with Unreal Engine projects.", 7 | "Category": "Sirius", 8 | "CreatedBy": "Jasper de Laat", 9 | "CreatedByURL": "https://jasperdelaat.com/", 10 | "DocsURL": "https://github.com/JasperDeLaat94/sirius-utility-nodes/blob/main/README.md", 11 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/d5e0fa8a4b174b0a85db3697c1d53b98", 12 | "SupportURL": "https://github.com/JasperDeLaat94/sirius-utility-nodes/issues", 13 | "CanContainContent": false, 14 | "IsBetaVersion": true, 15 | "IsExperimentalVersion": true, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "SiriusUtilityNodes", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default", 22 | "WhitelistPlatforms": [ 23 | "Win64", 24 | "Mac", 25 | "IOS", 26 | "Android", 27 | "Linux", 28 | "LinuxArm64", 29 | "TVOS" 30 | ] 31 | }, 32 | { 33 | "Name": "SiriusUtilityNodesEditor", 34 | "Type": "UncookedOnly", 35 | "LoadingPhase": "Default", 36 | "WhitelistPlatforms": [ 37 | "Win64", 38 | "Mac", 39 | "Linux" 40 | ] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /BuildPlugin.ps1: -------------------------------------------------------------------------------- 1 | # Get the location of the desired Unreal version by querying the registry. 2 | function Get-UnrealEngineLocation { 3 | param ($Version) 4 | 5 | if (!(Test-Path "HKLM:\SOFTWARE\EpicGames\Unreal Engine\$Version")) 6 | { 7 | Write-Error "Unreal Engine $Version not found!" 8 | return $null 9 | } 10 | 11 | return Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\EpicGames\Unreal Engine\$Version" -Name InstalledDirectory 12 | } 13 | 14 | # Build the plugin for a specific version of Unreal 15 | function Build-Plugin { 16 | param ($Version) 17 | 18 | $EngineRoot = Get-UnrealEngineLocation $Version 19 | if ($null -eq $EngineRoot) { exit 1 } 20 | 21 | $PLUGIN="$pwd\SiriusUtilityNodes.uplugin" 22 | $TARGETPLATFORMS="Win64+Linux+LinuxArm64+Android" 23 | 24 | & "$EngineRoot\Engine\Build\BatchFiles\RunUAT.bat" ` 25 | "BuildPlugin" ` 26 | "-Plugin=$PLUGIN" ` 27 | "-Package=D:\PluginBuilds\SiriusUtilityNodes-$Version" ` 28 | "-TargetPlatforms=$TARGETPLATFORMS" ` 29 | "-StrictIncludes" 30 | } 31 | 32 | # Build the plugin for the last three supported engine versions. 33 | # Make sure to use the appropriate linux toolchain for each version. 34 | $env:LINUX_MULTIARCH_ROOT = 'C:\UnrealToolchains\v20_clang-13.0.1-centos7\' 35 | Build-Plugin '5.0' 36 | Build-Plugin '5.1' 37 | 38 | $env:LINUX_MULTIARCH_ROOT = 'C:\UnrealToolchains\v21_clang-15.0.1-centos7\' 39 | Build-Plugin '5.2' -------------------------------------------------------------------------------- /Source/SiriusUtilityNodes/Public/SiriusStringLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Kismet/BlueprintFunctionLibrary.h" 7 | #include "SiriusStringLibrary.generated.h" 8 | 9 | UENUM(BlueprintType) 10 | enum class ESiriusStringFormatArgumentType : uint8 11 | { 12 | Int, 13 | Int64, 14 | Float, 15 | String, 16 | Double, 17 | }; 18 | 19 | /** Used to pass argument/value pairs into FString::Format. */ 20 | USTRUCT(NoExport, BlueprintInternalUseOnly) 21 | struct FSiriusStringFormatArgument 22 | { 23 | UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category=ArgumentName) 24 | FString ArgumentName; 25 | 26 | UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category=ArgumentValue) 27 | ESiriusStringFormatArgumentType ArgumentValueType; 28 | 29 | UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category=ArgumentValue) 30 | FString ArgumentValue; 31 | 32 | UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category=ArgumentValue) 33 | int32 ArgumentValueInt; 34 | 35 | UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category=ArgumentValue) 36 | int64 ArgumentValueInt64; 37 | 38 | UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category=ArgumentValue) 39 | float ArgumentValueFloat; 40 | 41 | UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category=ArgumentValue) 42 | double ArgumentValueDouble; 43 | 44 | FSiriusStringFormatArgument() 45 | { 46 | ResetValue(); 47 | } 48 | 49 | void ResetValue(); 50 | 51 | FStringFormatArg ToEngineFormatArg() const; 52 | 53 | friend void operator<<(FStructuredArchive::FSlot Slot, FSiriusStringFormatArgument& Value); 54 | }; 55 | 56 | UCLASS(meta=(BlueprintThreadSafe, ScriptName="SiriusStringLibrary")) 57 | class SIRIUSUTILITYNODES_API USiriusStringLibrary final : public UBlueprintFunctionLibrary 58 | { 59 | GENERATED_BODY() 60 | 61 | public: 62 | /* Used for formatting a string using the FString::Format function and utilized by the UK2Node_SiriusFormatString */ 63 | UFUNCTION(BlueprintPure, meta=(BlueprintInternalUseOnly = "true")) 64 | static FString Format(const FString& InPattern, const TArray& InArgs); 65 | }; 66 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/Private/Slate/SGraphNodeFormatString.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #include "SGraphNodeFormatString.h" 4 | #include "Widgets/SBoxPanel.h" 5 | #include "GraphEditorSettings.h" 6 | #include "K2Node_SiriusFormatString.h" 7 | #include "NodeFactory.h" 8 | 9 | #define LOCTEXT_NAMESPACE "FormatStringNode" 10 | 11 | void SGraphNodeFormatString::Construct(const FArguments& InArgs, UK2Node_SiriusFormatString* InNode) 12 | { 13 | this->GraphNode = InNode; 14 | this->SetCursor(EMouseCursor::CardinalCross); 15 | this->UpdateGraphNode(); 16 | } 17 | 18 | void SGraphNodeFormatString::CreatePinWidgets() 19 | { 20 | for (UEdGraphPin* Pin : GraphNode->Pins) 21 | { 22 | if (!Pin->bHidden) 23 | { 24 | TSharedPtr NewPin = FNodeFactory::CreatePinWidget(Pin); 25 | check(NewPin.IsValid()); 26 | AddPin(NewPin.ToSharedRef()); 27 | } 28 | } 29 | } 30 | 31 | void SGraphNodeFormatString::CreateInputSideAddButton(const TSharedPtr InputBox) 32 | { 33 | const TSharedRef AddPinButton = AddPinButtonContent( 34 | LOCTEXT("AddPinButton", "Add pin"), 35 | LOCTEXT("AddPinButton_Tooltip", "Adds an argument to the node"), 36 | false); 37 | 38 | FMargin AddPinPadding = Settings->GetInputPinPadding(); 39 | AddPinPadding.Top += 6.0f; 40 | 41 | InputBox->AddSlot() 42 | .AutoHeight() 43 | .VAlign(VAlign_Center) 44 | .Padding(AddPinPadding) 45 | [ 46 | AddPinButton 47 | ]; 48 | } 49 | 50 | EVisibility SGraphNodeFormatString::IsAddPinButtonVisible() const 51 | { 52 | EVisibility VisibilityState = EVisibility::Collapsed; 53 | if (const UK2Node_SiriusFormatString* FormatNode = Cast(GraphNode)) 54 | { 55 | VisibilityState = SGraphNode::IsAddPinButtonVisible(); 56 | if (VisibilityState == EVisibility::Visible) 57 | { 58 | VisibilityState = FormatNode->CanEditArguments() ? EVisibility::Visible : EVisibility::Collapsed; 59 | } 60 | } 61 | return VisibilityState; 62 | } 63 | 64 | FReply SGraphNodeFormatString::OnAddPin() 65 | { 66 | if (UK2Node_SiriusFormatString* FormatText = Cast(GraphNode)) 67 | { 68 | FormatText->AddArgumentPin(); 69 | } 70 | return FReply::Handled(); 71 | } 72 | 73 | #undef LOCTEXT_NAMESPACE 74 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/Public/K2Node_SiriusPrintStringFormatted.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "K2Node.h" 7 | #include "K2Node_SiriusPrintStringFormatted.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS(MinimalAPI) 13 | class UK2Node_SiriusPrintStringFormatted : public UK2Node 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | UK2Node_SiriusPrintStringFormatted(); 19 | 20 | //~ Begin UEdGraphNode Interface. 21 | virtual void AllocateDefaultPins() override; 22 | virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; 23 | virtual FText GetPinDisplayName(const UEdGraphPin* Pin) const override; 24 | virtual FText GetTooltipText() const override; 25 | virtual void PinConnectionListChanged(UEdGraphPin* Pin) override; 26 | virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override; 27 | virtual void PinTypeChanged(UEdGraphPin* Pin) override; 28 | //~ End UEdGraphNode Interface. 29 | 30 | //~ Begin UK2Node Interface. 31 | virtual void ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; 32 | virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; 33 | virtual FText GetMenuCategory() const override; 34 | virtual bool IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const override; 35 | virtual bool NodeCausesStructuralBlueprintChange() const override { return true; } 36 | virtual void PostReconstructNode() override; 37 | //~ End UK2Node Interface. 38 | 39 | private: 40 | UEdGraphPin* GetExecutePin() const; 41 | UEdGraphPin* GetThenPin() const; 42 | UEdGraphPin* GetFormatPin() const; 43 | UEdGraphPin* GetPrintScreenPin() const; 44 | UEdGraphPin* GetPrintLogPin() const; 45 | UEdGraphPin* GetTextColorPin() const; 46 | UEdGraphPin* GetDurationPin() const; 47 | 48 | UEdGraphPin* FindArgumentPin(const FName PinName) const; 49 | 50 | /** Synchronize the type of the given argument pin with the type its connected to, or reset it to a wildcard pin if there's no connection */ 51 | void SynchronizeArgumentPinType(UEdGraphPin* Pin) const; 52 | 53 | static const FName ExecutePinName; 54 | static const FName ThenPinName; 55 | static const FName FormatPinName; 56 | static const FName PrintScreenPinName; 57 | static const FName PrintLogPinName; 58 | static const FName TextColorPinName; 59 | static const FName DurationPinName; 60 | 61 | /** When adding arguments to the node, their names are placed here and are generated as pins during construction */ 62 | UPROPERTY() 63 | TArray PinNames; 64 | 65 | /** Tooltip text for this node. */ 66 | FText NodeTooltip; 67 | 68 | TArray CachedArgumentPins; 69 | }; 70 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodes/Private/SiriusStringLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #include "SiriusStringLibrary.h" 4 | 5 | #include "Misc/StringFormatter.h" 6 | #include "UObject/EditorObjectVersion.h" 7 | 8 | void FSiriusStringFormatArgument::ResetValue() 9 | { 10 | ArgumentValueType = ESiriusStringFormatArgumentType::String; 11 | ArgumentValue = FString(); 12 | ArgumentValueInt = 0; 13 | ArgumentValueInt64 = 0; 14 | ArgumentValueFloat = 0.0f; 15 | ArgumentValueDouble = 0.0; 16 | } 17 | 18 | FStringFormatArg FSiriusStringFormatArgument::ToEngineFormatArg() const 19 | { 20 | switch (ArgumentValueType) 21 | { 22 | case ESiriusStringFormatArgumentType::Int: 23 | return FStringFormatArg(ArgumentValueInt); 24 | case ESiriusStringFormatArgumentType::Int64: 25 | return FStringFormatArg(ArgumentValueInt64); 26 | case ESiriusStringFormatArgumentType::Float: 27 | return FStringFormatArg(ArgumentValueFloat); 28 | case ESiriusStringFormatArgumentType::String: 29 | return FStringFormatArg(ArgumentValue); 30 | case ESiriusStringFormatArgumentType::Double: 31 | return FStringFormatArg(ArgumentValueDouble); 32 | default: 33 | break; 34 | } 35 | return FStringFormatArg(TEXT("")); 36 | } 37 | 38 | void operator<<(FStructuredArchive::FSlot Slot, FSiriusStringFormatArgument& Value) 39 | { 40 | FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive(); 41 | FStructuredArchive::FRecord Record = Slot.EnterRecord(); 42 | 43 | UnderlyingArchive.UsingCustomVersion(FEditorObjectVersion::GUID); 44 | 45 | Record << SA_VALUE(TEXT("ArgumentName"), Value.ArgumentName); 46 | 47 | uint8 TypeAsByte = static_cast(Value.ArgumentValueType); 48 | if (UnderlyingArchive.IsLoading()) 49 | { 50 | Value.ResetValue(); 51 | } 52 | Record << SA_VALUE(TEXT("Type"), TypeAsByte); 53 | 54 | Value.ArgumentValueType = static_cast(TypeAsByte); 55 | switch (Value.ArgumentValueType) 56 | { 57 | case ESiriusStringFormatArgumentType::Int: 58 | Record << SA_VALUE(TEXT("Value"), Value.ArgumentValueInt); 59 | break; 60 | case ESiriusStringFormatArgumentType::Int64: 61 | Record << SA_VALUE(TEXT("Value"), Value.ArgumentValueInt64); 62 | break; 63 | case ESiriusStringFormatArgumentType::Float: 64 | Record << SA_VALUE(TEXT("Value"), Value.ArgumentValueFloat); 65 | break; 66 | case ESiriusStringFormatArgumentType::String: 67 | Record << SA_VALUE(TEXT("Value"), Value.ArgumentValue); 68 | break; 69 | case ESiriusStringFormatArgumentType::Double: 70 | Record << SA_VALUE(TEXT("Value"), Value.ArgumentValueDouble); 71 | default: 72 | break; 73 | } 74 | } 75 | 76 | FString USiriusStringLibrary::Format(const FString& InPattern, const TArray& InArgs) 77 | { 78 | TMap Args; 79 | for (const FSiriusStringFormatArgument& Arg : InArgs) 80 | { 81 | Args.Emplace(Arg.ArgumentName, Arg.ToEngineFormatArg()); 82 | } 83 | return FString::Format(*InPattern, Args); 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sirius Utility Nodes for Unreal Engine 2 | 3 | ![Sirius Utility Nodes Logo](Resources/Icon128.png) 4 | 5 | A collection of utility nodes to boost your blueprint productivity with Unreal Engine projects. 6 | 7 | [Marketplace](https://www.unrealengine.com/marketplace/en-US/product/d5e0fa8a4b174b0a85db3697c1d53b98) • [Suggestions / Issues](https://github.com/JasperDeLaat94/sirius-utility-nodes/issues) • [Contact](https://twitter.com/jasper_de_laat) 8 | 9 | ## Features 10 | 11 | - Easy setup - Simply install and enable, no configuration required. 12 | - Format String - The power of Format Text for non-localized strings. 13 | - Print String Formatted - Print debug strings with built-in formatting of parameters. 14 | 15 | ### Format String 16 | 17 | ![Format String node](Resources/FormatString.png) 18 | 19 | The **Format String** node is a string-based equivalent to Unreal's built-in **Format Text** node. 20 | It is intended to be used in cases where the flexibility of Format Text is desired without the burden of having to use FText, such as for debug logging. 21 | Advantages include: 22 | 23 | - The same powerful "`{}`" argument syntax. 24 | - **No casting** from Text to String. 25 | - `Format` string is not localized, so you **won't forget** to turn it off. 26 | - Supports **Enum** arguments without having to cast to String. 27 | - Supports **Integer64** arguments. 28 | 29 | ### Print String 30 | 31 | ![Print String Formatted node](Resources/PrintStringFormatted.png) 32 | 33 | **Print String Formatted** offers a convenient experience for printing debug strings with values. 34 | It is essentially a **Format String** and **Print String** node in one. 35 | Simply use the same "`{}`" syntax to add argument pins. 36 | 37 | ## Installation 38 | 39 | ### Unreal Marketplace (UE4.25+) 40 | 41 | 1. Use the **"Free"** button to obtain the plugin. 42 | 2. Install the plugin using the **Epic Games Launcher**: 43 | 1. Go to the **Unreal Engine** tab. 44 | 2. Open the **Library** tab and scroll down to the **Vault** section. 45 | 3. Find the plugin and click **"Install to Engine"** (restart the launcher if it hasn't appeared yet). 46 | 3. After installation, open your project and enable the plugin: 47 | 1. **Edit > Plugins** 48 | 2. **Installed > Sirius** 49 | 3. Check the **"Enabled"** checkbox of the **Sirius Utility Nodes** plugin. 50 | 4. Restart the editor. 51 | 52 | ### Source 53 | 54 | 1. Create a **"Plugins"** folder in your project. 55 | 2. Clone this repo in your Plugins folder. 56 | 3. Enable the plugin found under **Project > Sirius**. 57 | 58 | ## Contributing 59 | 60 | I appreciate all contributions. Examples of how you can help: 61 | 62 | - If you have a bug or idea, please open an 'issue' in the [issues tab](https://github.com/JasperDeLaat94/sirius-utility-nodes/issues). 63 | - If you want to contribute to the code, feel free to fork the project and create a pull request. 64 | - Feel free to contact me via [Twitter](https://twitter.com/jasper_de_laat) or open an issue if you want to discuss things first. 65 | 66 | ## License 67 | 68 | The source code of this plugin is licensed under the standard [MIT License](https://github.com/JasperDeLaat94/sirius-utility-nodes/blob/main/LICENSE). 69 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/Public/K2Node_SiriusFormatString.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "EdGraph/EdGraphPin.h" 7 | #include "K2Node.h" 8 | #include "K2Node_SiriusFormatString.generated.h" 9 | 10 | class FBlueprintActionDatabaseRegistrar; 11 | class UEdGraph; 12 | 13 | UCLASS(MinimalAPI) 14 | class UK2Node_SiriusFormatString : public UK2Node 15 | { 16 | GENERATED_BODY() 17 | 18 | public: 19 | explicit UK2Node_SiriusFormatString(const FObjectInitializer& ObjectInitializer); 20 | 21 | //~ Begin UObject Interface 22 | virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; 23 | //~ End UObject Interface 24 | 25 | //~ Begin UEdGraphNode Interface. 26 | virtual void AllocateDefaultPins() override; 27 | virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; 28 | virtual bool ShouldShowNodeProperties() const override { return true; } 29 | virtual void PinConnectionListChanged(UEdGraphPin* Pin) override; 30 | virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override; 31 | virtual void PinTypeChanged(UEdGraphPin* Pin) override; 32 | virtual FText GetTooltipText() const override; 33 | virtual FText GetPinDisplayName(const UEdGraphPin* Pin) const override; 34 | virtual TSharedPtr CreateVisualWidget() override; 35 | //~ End UEdGraphNode Interface. 36 | 37 | //~ Begin UK2Node Interface. 38 | virtual bool IsNodePure() const override { return true; } 39 | virtual bool NodeCausesStructuralBlueprintChange() const override { return true; } 40 | virtual void PostReconstructNode() override; 41 | virtual void ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; 42 | virtual ERedirectType DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const override; 43 | virtual bool IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const override; 44 | virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; 45 | virtual FText GetMenuCategory() const override; 46 | //~ End UK2Node Interface. 47 | 48 | /** Returns Format pin */ 49 | SIRIUSUTILITYNODESEDITOR_API UEdGraphPin* GetFormatPin() const; 50 | 51 | /** Returns Result pin */ 52 | SIRIUSUTILITYNODESEDITOR_API UEdGraphPin* GetResultPin() const; 53 | 54 | /** 55 | * Finds an argument pin by name, checking strings in a strict, case sensitive fashion 56 | * 57 | * @param InPinName The pin name to check for 58 | * @return NULL if the pin was not found, otherwise the found pin. 59 | */ 60 | SIRIUSUTILITYNODESEDITOR_API UEdGraphPin* FindArgumentPin(const FName InPinName) const; 61 | 62 | /** 63 | * Add a new pin to the node. Intended for editor-time modification (e.g. details panel, custom slate button) */ 64 | SIRIUSUTILITYNODESEDITOR_API void AddArgumentPin(); 65 | 66 | /** 67 | * Add a new wildcard argument to the node and create the associated pin. Intended for programmatic usage (e.g. K2Node expansion). 68 | * 69 | * @param InPinName The pin name to add. 70 | * @return The newly created pin. 71 | */ 72 | SIRIUSUTILITYNODESEDITOR_API UEdGraphPin* AddArgumentPin(const FName InPinName); 73 | 74 | /** Synchronize the type of the given argument pin with the type its connected to, or reset it to a wildcard pin if there's no connection */ 75 | SIRIUSUTILITYNODESEDITOR_API void SynchronizeArgumentPinType(UEdGraphPin* Pin) const; 76 | 77 | bool CanEditArguments() const { return GetFormatPin()->LinkedTo.Num() > 0; } 78 | 79 | /** Returns the number of arguments currently available in the node */ 80 | int32 GetArgumentCount() const { return PinNames.Num(); } 81 | 82 | /** 83 | * Returns argument name based on argument index 84 | * 85 | * @param InIndex The argument's index to find the name for 86 | * @return Returns the argument's name if available 87 | */ 88 | FText GetArgumentName(int32 InIndex) const; 89 | 90 | /** Removes the argument at a given index */ 91 | void RemoveArgument(int32 InIndex); 92 | 93 | /** 94 | * Sets an argument name 95 | * 96 | * @param InIndex The argument's index to find the name for 97 | * @param InName Name to set the argument to 98 | */ 99 | void SetArgumentName(int32 InIndex, FName InName); 100 | 101 | /** Swaps two arguments by index */ 102 | void SwapArguments(int32 InIndexA, int32 InIndexB); 103 | 104 | private: 105 | /** Returns a unique pin name to use for a pin */ 106 | FName GetUniquePinName() const; 107 | 108 | static const FName FormatPinName; 109 | static const FName ResultPinName; 110 | 111 | /** When adding arguments to the node, their names are placed here and are generated as pins during construction */ 112 | UPROPERTY() 113 | TArray PinNames; 114 | 115 | /** The "Format" input pin, always available on the node */ 116 | UEdGraphPin* CachedFormatPin; 117 | 118 | /** Tooltip text for this node. */ 119 | FText NodeTooltip; 120 | }; 121 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/Private/Details/FormatStringDetails.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Input/Reply.h" 7 | #include "IDetailCustomization.h" 8 | #include "IDetailCustomNodeBuilder.h" 9 | 10 | class FDetailWidgetRow; 11 | class IDetailChildrenBuilder; 12 | class IDetailLayoutBuilder; 13 | class SEditableTextBox; 14 | class UK2Node_SiriusFormatString; 15 | 16 | /** Custom struct for each group of arguments in the function editing details */ 17 | class FFormatStringArgumentLayout final : public IDetailCustomNodeBuilder, public TSharedFromThis 18 | { 19 | public: 20 | FFormatStringArgumentLayout(UK2Node_SiriusFormatString* InTargetNode, int32 InArgumentIndex) 21 | : TargetNode(InTargetNode), 22 | ArgumentIndex(InArgumentIndex), 23 | bCausedChange(false) 24 | { 25 | } 26 | 27 | bool CausedChange() const { return bCausedChange; } 28 | 29 | private: 30 | /** IDetailCustomNodeBuilder Interface*/ 31 | virtual void SetOnRebuildChildren(FSimpleDelegate InOnRegenerateChildren) override 32 | { 33 | }; 34 | virtual void GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) override; 35 | 36 | virtual void GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) override 37 | { 38 | }; 39 | 40 | virtual void Tick(float DeltaTime) override 41 | { 42 | } 43 | 44 | virtual bool RequiresTick() const override { return false; } 45 | virtual FName GetName() const override { return NAME_None; } 46 | virtual bool InitiallyCollapsed() const override { return false; } 47 | 48 | /** Retrieves the argument's name */ 49 | FText GetArgumentName() const; 50 | 51 | /** Moves the argument up in the list */ 52 | FReply OnMoveArgumentUp() const; 53 | 54 | /** Moves the argument down in the list */ 55 | FReply OnMoveArgumentDown() const; 56 | 57 | /** Deletes the argument */ 58 | void OnArgumentRemove() const; 59 | 60 | /** Callback when the argument's name is committed */ 61 | void OnArgumentNameCommitted(const FText& NewText, ETextCommit::Type InTextCommit); 62 | 63 | /** Callback when changing the argument's name to verify the name */ 64 | void OnArgumentNameChanged(const FText& NewText) const; 65 | 66 | /** 67 | * Helper function to validate the argument's name 68 | * 69 | * @param InNewText The name to validate 70 | * 71 | * @return Returns true if the name is valid 72 | */ 73 | bool IsValidArgumentName(const FText& InNewText) const; 74 | 75 | bool CanEditArguments() const; 76 | 77 | /** The target node that this argument is on */ 78 | UK2Node_SiriusFormatString* TargetNode; 79 | 80 | /** Index of argument */ 81 | int32 ArgumentIndex; 82 | 83 | /** The argument's name widget, used for setting a argument's name */ 84 | TWeakPtr ArgumentNameWidget; 85 | 86 | bool bCausedChange; 87 | }; 88 | 89 | /** Custom struct for each group of arguments in the function editing details */ 90 | class FFormatStringLayout final : public IDetailCustomNodeBuilder, public TSharedFromThis 91 | { 92 | public: 93 | explicit FFormatStringLayout(UK2Node_SiriusFormatString* InTargetNode) 94 | : TargetNode(InTargetNode) 95 | { 96 | } 97 | 98 | void Refresh() const 99 | { 100 | // ReSharper disable once CppExpressionWithoutSideEffects 101 | OnRebuildChildren.ExecuteIfBound(); 102 | } 103 | 104 | bool CausedChange() const; 105 | 106 | private: 107 | /** IDetailCustomNodeBuilder Interface*/ 108 | virtual void SetOnRebuildChildren(FSimpleDelegate InOnRegenerateChildren) override { OnRebuildChildren = InOnRegenerateChildren; } 109 | 110 | virtual void GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) override 111 | { 112 | } 113 | 114 | virtual void GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) override; 115 | 116 | virtual void Tick(float DeltaTime) override 117 | { 118 | } 119 | 120 | virtual bool RequiresTick() const override { return false; } 121 | virtual FName GetName() const override { return NAME_None; } 122 | virtual bool InitiallyCollapsed() const override { return false; } 123 | 124 | FSimpleDelegate OnRebuildChildren; 125 | 126 | /** The target node that this argument is on */ 127 | UK2Node_SiriusFormatString* TargetNode; 128 | 129 | TArray> Children; 130 | }; 131 | 132 | /** Details customization for the "Format String" node */ 133 | class FFormatStringDetails final : public IDetailCustomization 134 | { 135 | public: 136 | /** Makes a new instance of this detail layout class for a specific detail view requesting it */ 137 | static TSharedRef MakeInstance() 138 | { 139 | return MakeShareable(new FFormatStringDetails); 140 | } 141 | 142 | FFormatStringDetails() : TargetNode(nullptr) 143 | { 144 | } 145 | 146 | virtual ~FFormatStringDetails() override; 147 | 148 | /** IDetailCustomization interface */ 149 | virtual void CustomizeDetails(IDetailLayoutBuilder& DetailLayout) override; 150 | 151 | /** Forces a refresh on the details customization */ 152 | void OnForceRefresh() const; 153 | 154 | private: 155 | /** Handles new argument request */ 156 | FReply OnAddNewArgument() const; 157 | 158 | /** Callback whenever a package is marked dirty, will refresh the node being represented by this details customization */ 159 | void OnEditorPackageModified(UPackage* Package) const; 160 | 161 | bool CanEditArguments() const; 162 | 163 | TSharedPtr Layout; 164 | /** The target node that this argument is on */ 165 | UK2Node_SiriusFormatString* TargetNode; 166 | }; 167 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/Private/Details/FormatStringDetails.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #include "FormatStringDetails.h" 4 | #include "Runtime/Launch/Resources/Version.h" 5 | #include "Widgets/SWidget.h" 6 | #include "Widgets/DeclarativeSyntaxSupport.h" 7 | #include "Widgets/SBoxPanel.h" 8 | #include "UObject/Package.h" 9 | #if ENGINE_MAJOR_VERSION < 5 || ENGINE_MINOR_VERSION < 1 // 5.0 and earlier 10 | #include "EditorStyleSet.h" 11 | #endif 12 | #include "PropertyHandle.h" 13 | #include "IDetailChildrenBuilder.h" 14 | #include "Widgets/Images/SImage.h" 15 | #include "Widgets/Layout/SBox.h" 16 | #include "Widgets/Input/SEditableTextBox.h" 17 | #include "Widgets/Input/SButton.h" 18 | #include "K2Node_SiriusFormatString.h" 19 | #include "DetailLayoutBuilder.h" 20 | #include "DetailWidgetRow.h" 21 | #include "DetailCategoryBuilder.h" 22 | #include "PropertyCustomizationHelpers.h" 23 | 24 | #define LOCTEXT_NAMESPACE "FormatStringDetails" 25 | 26 | void FFormatStringDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) 27 | { 28 | const TArray>& Objects = DetailLayout.GetSelectedObjects(); 29 | check(Objects.Num() > 0); 30 | 31 | if (Objects.Num() == 1) 32 | { 33 | TargetNode = CastChecked(Objects[0].Get()); 34 | TSharedRef PropertyHandle = DetailLayout.GetProperty(FName("PinNames"), UK2Node_SiriusFormatString::StaticClass()); 35 | 36 | IDetailCategoryBuilder& InputsCategory = DetailLayout.EditCategory("Arguments", LOCTEXT("DetailsArguments", "Arguments")); 37 | 38 | InputsCategory.AddCustomRow(LOCTEXT("FunctionNewInputArg", "New")) 39 | [ 40 | SNew(SBox) 41 | .HAlign(HAlign_Right) 42 | [ 43 | SNew(SButton) 44 | .Text(LOCTEXT("FunctionNewInputArg", "New")) 45 | .OnClicked(this, &FFormatStringDetails::OnAddNewArgument) 46 | .IsEnabled(this, &FFormatStringDetails::CanEditArguments) 47 | ] 48 | ]; 49 | 50 | Layout = MakeShareable(new FFormatStringLayout(TargetNode)); 51 | InputsCategory.AddCustomBuilder(Layout.ToSharedRef()); 52 | } 53 | 54 | UPackage::PackageDirtyStateChangedEvent.AddSP(this, &FFormatStringDetails::OnEditorPackageModified); 55 | } 56 | 57 | FFormatStringDetails::~FFormatStringDetails() 58 | { 59 | UPackage::PackageDirtyStateChangedEvent.RemoveAll(this); 60 | } 61 | 62 | void FFormatStringDetails::OnForceRefresh() const 63 | { 64 | Layout->Refresh(); 65 | } 66 | 67 | FReply FFormatStringDetails::OnAddNewArgument() const 68 | { 69 | TargetNode->AddArgumentPin(); 70 | OnForceRefresh(); 71 | return FReply::Handled(); 72 | } 73 | 74 | // ReSharper disable once CppParameterMayBeConstPtrOrRef 75 | void FFormatStringDetails::OnEditorPackageModified(UPackage* Package) const 76 | { 77 | if (TargetNode && 78 | Package && 79 | Package->IsDirty() && 80 | Package == TargetNode->GetOutermost() && 81 | (!Layout.IsValid() || !Layout->CausedChange())) 82 | { 83 | OnForceRefresh(); 84 | } 85 | } 86 | 87 | bool FFormatStringDetails::CanEditArguments() const 88 | { 89 | return TargetNode->CanEditArguments(); 90 | } 91 | 92 | void FFormatStringLayout::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) 93 | { 94 | Children.Empty(); 95 | for (int32 ArgIdx = 0; ArgIdx < TargetNode->GetArgumentCount(); ++ArgIdx) 96 | { 97 | TSharedRef ArgumentIndexLayout = MakeShareable(new FFormatStringArgumentLayout(TargetNode, ArgIdx)); 98 | ChildrenBuilder.AddCustomBuilder(ArgumentIndexLayout); 99 | Children.Add(ArgumentIndexLayout); 100 | } 101 | } 102 | 103 | bool FFormatStringLayout::CausedChange() const 104 | { 105 | for (const auto Child : Children) 106 | { 107 | if (Child.IsValid() && Child.Pin()->CausedChange()) 108 | { 109 | return true; 110 | } 111 | } 112 | return false; 113 | } 114 | 115 | void FFormatStringArgumentLayout::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) 116 | { 117 | const int32 ArgumentCount = TargetNode->GetArgumentCount(); 118 | const bool bIsMoveUpEnabled = ArgumentCount != 1 && ArgumentIndex != 0; 119 | const bool bIsMoveDownEnabled = ArgumentCount != 1 && ArgumentIndex < ArgumentCount - 1; 120 | 121 | const TSharedRef ClearButton = PropertyCustomizationHelpers::MakeClearButton(FSimpleDelegate::CreateSP(this, &FFormatStringArgumentLayout::OnArgumentRemove)); 122 | 123 | NodeRow 124 | .WholeRowWidget 125 | [ 126 | SNew(SHorizontalBox) 127 | .IsEnabled(this, &FFormatStringArgumentLayout::CanEditArguments) 128 | 129 | + SHorizontalBox::Slot() 130 | [ 131 | SAssignNew(ArgumentNameWidget, SEditableTextBox) 132 | .OnTextCommitted(this, &FFormatStringArgumentLayout::OnArgumentNameCommitted) 133 | .OnTextChanged(this, &FFormatStringArgumentLayout::OnArgumentNameChanged) 134 | .Text(this, &FFormatStringArgumentLayout::GetArgumentName) 135 | ] 136 | 137 | + SHorizontalBox::Slot() 138 | .AutoWidth() 139 | .Padding(2, 0) 140 | [ 141 | SNew(SButton) 142 | .ContentPadding(0) 143 | .OnClicked(this, &FFormatStringArgumentLayout::OnMoveArgumentUp) 144 | .IsEnabled(bIsMoveUpEnabled) 145 | [ 146 | SNew(SImage) 147 | #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1 148 | .Image(FAppStyle::GetBrush("Icons.ChevronUp")) 149 | #else 150 | .Image(FEditorStyle::GetBrush("Icons.ChevronUp")) 151 | #endif 152 | ] 153 | ] 154 | + SHorizontalBox::Slot() 155 | .AutoWidth() 156 | .Padding(2, 0) 157 | [ 158 | SNew(SButton) 159 | .ContentPadding(0) 160 | .OnClicked(this, &FFormatStringArgumentLayout::OnMoveArgumentDown) 161 | .IsEnabled(bIsMoveDownEnabled) 162 | [ 163 | SNew(SImage) 164 | #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1 165 | .Image(FAppStyle::GetBrush("Icons.ChevronDown")) 166 | #else 167 | .Image(FEditorStyle::GetBrush("Icons.ChevronDown")) 168 | #endif 169 | ] 170 | ] 171 | 172 | + SHorizontalBox::Slot() 173 | .AutoWidth() 174 | .Padding(2, 0) 175 | [ 176 | ClearButton 177 | ] 178 | ]; 179 | } 180 | 181 | FText FFormatStringArgumentLayout::GetArgumentName() const 182 | { 183 | return TargetNode->GetArgumentName(ArgumentIndex); 184 | } 185 | 186 | void FFormatStringArgumentLayout::OnArgumentRemove() const 187 | { 188 | TargetNode->RemoveArgument(ArgumentIndex); 189 | } 190 | 191 | FReply FFormatStringArgumentLayout::OnMoveArgumentUp() const 192 | { 193 | TargetNode->SwapArguments(ArgumentIndex, ArgumentIndex - 1); 194 | return FReply::Handled(); 195 | } 196 | 197 | FReply FFormatStringArgumentLayout::OnMoveArgumentDown() const 198 | { 199 | TargetNode->SwapArguments(ArgumentIndex, ArgumentIndex + 1); 200 | return FReply::Handled(); 201 | } 202 | 203 | bool FFormatStringArgumentLayout::CanEditArguments() const 204 | { 205 | return TargetNode->CanEditArguments(); 206 | } 207 | 208 | struct FScopeTrue 209 | { 210 | bool& bRef; 211 | 212 | explicit FScopeTrue(bool& bInRef) : bRef(bInRef) 213 | { 214 | ensure(!bRef); 215 | bRef = true; 216 | } 217 | 218 | ~FScopeTrue() 219 | { 220 | ensure(bRef); 221 | bRef = false; 222 | } 223 | }; 224 | 225 | void FFormatStringArgumentLayout::OnArgumentNameCommitted(const FText& NewText, ETextCommit::Type /*InTextCommit*/) 226 | { 227 | if (IsValidArgumentName(NewText)) 228 | { 229 | FScopeTrue ScopeTrue(bCausedChange); 230 | TargetNode->SetArgumentName(ArgumentIndex, *NewText.ToString()); 231 | } 232 | ArgumentNameWidget.Pin()->SetError(FString()); 233 | } 234 | 235 | void FFormatStringArgumentLayout::OnArgumentNameChanged(const FText& NewText) const 236 | { 237 | // ReSharper disable once CppExpressionWithoutSideEffects 238 | IsValidArgumentName(NewText); 239 | } 240 | 241 | bool FFormatStringArgumentLayout::IsValidArgumentName(const FText& InNewText) const 242 | { 243 | if (TargetNode->FindArgumentPin(*InNewText.ToString())) 244 | { 245 | ArgumentNameWidget.Pin()->SetError(LOCTEXT("UniqueName_Error", "Name must be unique.")); 246 | return false; 247 | } 248 | ArgumentNameWidget.Pin()->SetError(FString()); 249 | return true; 250 | } 251 | 252 | #undef LOCTEXT_NAMESPACE 253 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/Private/K2Node_SiriusPrintStringFormatted.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #include "K2Node_SiriusPrintStringFormatted.h" 4 | 5 | #include "BlueprintActionDatabaseRegistrar.h" 6 | #include "BlueprintNodeSpawner.h" 7 | #include "EditorCategoryUtils.h" 8 | #include "K2Node_CallFunction.h" 9 | #include "K2Node_SiriusFormatString.h" 10 | #include "KismetCompiler.h" 11 | #include "Kismet/KismetSystemLibrary.h" 12 | #include "Kismet2/BlueprintEditorUtils.h" 13 | 14 | #define LOCTEXT_NAMESPACE "K2Node_SiriusPrintStringFormatted" 15 | 16 | const FName UK2Node_SiriusPrintStringFormatted::ExecutePinName = UEdGraphSchema_K2::PN_Execute; 17 | const FName UK2Node_SiriusPrintStringFormatted::ThenPinName = UEdGraphSchema_K2::PN_Then; 18 | const FName UK2Node_SiriusPrintStringFormatted::FormatPinName = TEXT("In String"); 19 | const FName UK2Node_SiriusPrintStringFormatted::PrintScreenPinName = TEXT("Print to Screen"); 20 | const FName UK2Node_SiriusPrintStringFormatted::PrintLogPinName = TEXT("Print to Log"); 21 | const FName UK2Node_SiriusPrintStringFormatted::TextColorPinName = TEXT("Text Color"); 22 | const FName UK2Node_SiriusPrintStringFormatted::DurationPinName = TEXT("Duration"); 23 | 24 | UK2Node_SiriusPrintStringFormatted::UK2Node_SiriusPrintStringFormatted() 25 | { 26 | NodeTooltip = LOCTEXT("NodeTooltip", "Prints a formatted string to the log, and optionally, to the screen.\n If Print To Log is true, it will be visible in the Output Log window. Otherwise it will be logged only as 'Verbose', so it generally won't show up."); 27 | 28 | // Show the development only banner to warn the user they're not going to get the benefits of this node in a shipping build 29 | SetEnabledState(ENodeEnabledState::DevelopmentOnly, false); 30 | } 31 | 32 | void UK2Node_SiriusPrintStringFormatted::AllocateDefaultPins() 33 | { 34 | Super::AllocateDefaultPins(); 35 | 36 | // AdvancedPinDisplay is serialized. Any other value than NoPins might be from user input, don't overwrite those. 37 | if (AdvancedPinDisplay == ENodeAdvancedPins::NoPins) 38 | { 39 | AdvancedPinDisplay = ENodeAdvancedPins::Hidden; 40 | } 41 | 42 | const UEdGraphSchema_K2* DefaultSchema = GetDefault(); 43 | 44 | // Execution pins 45 | CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, ExecutePinName); 46 | CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, ThenPinName); 47 | 48 | // Format pins 49 | UEdGraphPin* FormatPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FormatPinName); 50 | DefaultSchema->SetPinAutogeneratedDefaultValue(FormatPin, TEXT("Hello")); 51 | for (const FName& PinName : PinNames) 52 | { 53 | CachedArgumentPins.Emplace(CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PinName)); 54 | } 55 | 56 | UEdGraphPin* PrintScreenPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Boolean, PrintScreenPinName); 57 | PrintScreenPin->bAdvancedView = true; 58 | DefaultSchema->SetPinAutogeneratedDefaultValue(PrintScreenPin, TEXT("true")); 59 | 60 | UEdGraphPin* PrintLogPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Boolean, PrintLogPinName); 61 | PrintLogPin->bAdvancedView = true; 62 | DefaultSchema->SetPinAutogeneratedDefaultValue(PrintLogPin, TEXT("true")); 63 | 64 | static UPackage* CoreUObjectPkg = FindObjectChecked(nullptr, TEXT("/Script/CoreUObject")); 65 | UScriptStruct* LinearColorScriptStruct = FindObjectChecked(CoreUObjectPkg, TEXT("LinearColor")); 66 | UEdGraphPin* TextColorPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Struct, LinearColorScriptStruct, TextColorPinName); 67 | TextColorPin->bAdvancedView = true; 68 | DefaultSchema->SetPinAutogeneratedDefaultValue(TextColorPin, FLinearColor(0.0f, 0.66f, 1.0f).ToString()); 69 | 70 | UEdGraphPin* DurationPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Real, UEdGraphSchema_K2::PC_Float, DurationPinName); 71 | DurationPin->bAdvancedView = true; 72 | DefaultSchema->SetPinAutogeneratedDefaultValue(DurationPin, TEXT("2.0")); 73 | } 74 | 75 | FText UK2Node_SiriusPrintStringFormatted::GetNodeTitle(ENodeTitleType::Type TitleType) const 76 | { 77 | return LOCTEXT("NodeTitle", "Print String Formatted (Sirius)"); 78 | } 79 | 80 | FText UK2Node_SiriusPrintStringFormatted::GetPinDisplayName(const UEdGraphPin* Pin) const 81 | { 82 | // Don't show the names of the execution pins. 83 | if (Pin != GetExecutePin() && Pin != GetThenPin()) 84 | { 85 | return FText::FromName(Pin->PinName); 86 | } 87 | 88 | return FText::GetEmpty(); 89 | } 90 | 91 | FText UK2Node_SiriusPrintStringFormatted::GetTooltipText() const 92 | { 93 | return NodeTooltip; 94 | } 95 | 96 | void UK2Node_SiriusPrintStringFormatted::PinConnectionListChanged(UEdGraphPin* Pin) 97 | { 98 | Modify(); 99 | 100 | // Potentially update an argument pin type 101 | SynchronizeArgumentPinType(Pin); 102 | } 103 | 104 | void UK2Node_SiriusPrintStringFormatted::PinDefaultValueChanged(UEdGraphPin* Pin) 105 | { 106 | // Detect if the format pin has changed. 107 | const UEdGraphPin* FormatPin = GetFormatPin(); 108 | if (Pin == FormatPin && FormatPin->LinkedTo.Num() == 0) 109 | { 110 | TArray ArgumentParams; 111 | FTextFormat::FromString(FormatPin->DefaultValue).GetFormatArgumentNames(ArgumentParams); 112 | 113 | PinNames.Reset(); 114 | 115 | // Create argument pins if new arguments were created. 116 | for (const FString& Param : ArgumentParams) 117 | { 118 | const FName ParamName(*Param); 119 | if (!FindArgumentPin(ParamName)) 120 | { 121 | // Insert the newly created argument pin(s) after the format pin and before the advanced option pins. 122 | FCreatePinParams CreatePinParams; 123 | CreatePinParams.Index = Pins.IndexOfByKey(FormatPin) + 1 + CachedArgumentPins.Num(); 124 | CachedArgumentPins.Emplace(CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, ParamName, CreatePinParams)); 125 | } 126 | PinNames.Add(ParamName); 127 | } 128 | 129 | // Destroy argument pins whose arguments were destroyed. 130 | for (auto It = Pins.CreateIterator(); It; ++It) 131 | { 132 | UEdGraphPin* CheckPin = *It; 133 | if (FindArgumentPin(CheckPin->PinName)) 134 | { 135 | const bool bIsValidArgPin = ArgumentParams.ContainsByPredicate([&CheckPin](const FString& InPinName) 136 | { 137 | return InPinName.Equals(CheckPin->PinName.ToString(), ESearchCase::CaseSensitive); 138 | }); 139 | 140 | if (!bIsValidArgPin) 141 | { 142 | CheckPin->MarkAsGarbage(); 143 | It.RemoveCurrent(); 144 | CachedArgumentPins.Remove(CheckPin); 145 | } 146 | } 147 | } 148 | 149 | // Notify graph that something changed. 150 | GetGraph()->NotifyGraphChanged(); 151 | } 152 | } 153 | 154 | void UK2Node_SiriusPrintStringFormatted::PinTypeChanged(UEdGraphPin* Pin) 155 | { 156 | // Potentially update an argument pin type 157 | SynchronizeArgumentPinType(Pin); 158 | 159 | Super::PinTypeChanged(Pin); 160 | } 161 | 162 | void UK2Node_SiriusPrintStringFormatted::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) 163 | { 164 | Super::ExpandNode(CompilerContext, SourceGraph); 165 | 166 | // Create a "FormatString" node to do the heavy lifting regarding the format string. 167 | UK2Node_SiriusFormatString* FormatStringNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); 168 | FormatStringNode->AllocateDefaultPins(); 169 | CompilerContext.MessageLog.NotifyIntermediateObjectCreation(FormatStringNode, this); 170 | 171 | // Move the format and argument pins to the format string node. 172 | CompilerContext.MovePinLinksToIntermediate(*GetFormatPin(), *FormatStringNode->GetFormatPin()); 173 | for (int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx) 174 | { 175 | const FName& PinName = PinNames[ArgIdx]; 176 | UEdGraphPin* ArgumentPin = FindArgumentPin(PinName); 177 | UEdGraphPin* TargetPin = FormatStringNode->AddArgumentPin(PinName); 178 | CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *TargetPin); 179 | FormatStringNode->SynchronizeArgumentPinType(TargetPin); 180 | } 181 | 182 | // Create a "PrintString" function node. 183 | UK2Node_CallFunction* PrintStringNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); 184 | const UFunction* Function = UKismetSystemLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, PrintString)); 185 | PrintStringNode->SetFromFunction(Function); 186 | PrintStringNode->AllocateDefaultPins(); 187 | CompilerContext.MessageLog.NotifyIntermediateObjectCreation(PrintStringNode, this); 188 | 189 | // Link pins with print string function node. 190 | CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *PrintStringNode->GetExecPin()); 191 | FormatStringNode->GetResultPin()->MakeLinkTo(PrintStringNode->FindPinChecked(TEXT("InString"))); 192 | CompilerContext.MovePinLinksToIntermediate(*GetPrintScreenPin(), *PrintStringNode->FindPinChecked(TEXT("bPrintToScreen"))); 193 | CompilerContext.MovePinLinksToIntermediate(*GetPrintLogPin(), *PrintStringNode->FindPinChecked(TEXT("bPrintToLog"))); 194 | CompilerContext.MovePinLinksToIntermediate(*GetTextColorPin(), *PrintStringNode->FindPinChecked(TEXT("TextColor"))); 195 | CompilerContext.MovePinLinksToIntermediate(*GetDurationPin(), *PrintStringNode->FindPinChecked(TEXT("Duration"))); 196 | CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *PrintStringNode->GetThenPin()); 197 | 198 | // Final step, break all links to this node as we've finished expanding it. 199 | BreakAllNodeLinks(); 200 | } 201 | 202 | void UK2Node_SiriusPrintStringFormatted::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const 203 | { 204 | const UClass* ActionKey = GetClass(); 205 | if (ActionRegistrar.IsOpenForRegistration(ActionKey)) 206 | { 207 | UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); 208 | check(NodeSpawner != nullptr); 209 | ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); 210 | } 211 | } 212 | 213 | FText UK2Node_SiriusPrintStringFormatted::GetMenuCategory() const 214 | { 215 | return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::String); 216 | } 217 | 218 | bool UK2Node_SiriusPrintStringFormatted::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const 219 | { 220 | if (FindArgumentPin(MyPin->PinName)) 221 | { 222 | const FName& OtherPinCategory = OtherPin->PinType.PinCategory; 223 | 224 | bool bIsValidType = false; 225 | if (OtherPinCategory == UEdGraphSchema_K2::PC_Int || 226 | OtherPinCategory == UEdGraphSchema_K2::PC_Int64 || 227 | OtherPinCategory == UEdGraphSchema_K2::PC_Real || 228 | OtherPinCategory == UEdGraphSchema_K2::PC_Text || 229 | OtherPinCategory == UEdGraphSchema_K2::PC_Byte || 230 | OtherPinCategory == UEdGraphSchema_K2::PC_Boolean || 231 | OtherPinCategory == UEdGraphSchema_K2::PC_String || 232 | OtherPinCategory == UEdGraphSchema_K2::PC_Name || 233 | OtherPinCategory == UEdGraphSchema_K2::PC_Object || 234 | OtherPinCategory == UEdGraphSchema_K2::PC_Wildcard) 235 | { 236 | bIsValidType = true; 237 | } 238 | 239 | if (!bIsValidType) 240 | { 241 | OutReason = LOCTEXT("Error_InvalidArgumentType", "Format arguments may only be Byte, Enum, Integer, Float, Text, String, Name, Boolean, Object or Wildcard.").ToString(); 242 | return true; 243 | } 244 | } 245 | 246 | return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); 247 | } 248 | 249 | void UK2Node_SiriusPrintStringFormatted::PostReconstructNode() 250 | { 251 | Super::PostReconstructNode(); 252 | 253 | if (!IsTemplate()) 254 | { 255 | // Make sure we're not dealing with a menu node 256 | if (GetSchema()) 257 | { 258 | for (UEdGraphPin* CurrentPin : Pins) 259 | { 260 | // Potentially update an argument pin type 261 | SynchronizeArgumentPinType(CurrentPin); 262 | } 263 | } 264 | } 265 | } 266 | 267 | UEdGraphPin* UK2Node_SiriusPrintStringFormatted::GetExecutePin() const 268 | { 269 | return FindPinChecked(ExecutePinName, EGPD_Input); 270 | } 271 | 272 | UEdGraphPin* UK2Node_SiriusPrintStringFormatted::GetThenPin() const 273 | { 274 | return FindPinChecked(ThenPinName, EGPD_Output); 275 | } 276 | 277 | UEdGraphPin* UK2Node_SiriusPrintStringFormatted::GetFormatPin() const 278 | { 279 | return FindPinChecked(FormatPinName, EGPD_Input); 280 | } 281 | 282 | UEdGraphPin* UK2Node_SiriusPrintStringFormatted::GetPrintScreenPin() const 283 | { 284 | return FindPinChecked(PrintScreenPinName, EGPD_Input); 285 | } 286 | 287 | UEdGraphPin* UK2Node_SiriusPrintStringFormatted::GetPrintLogPin() const 288 | { 289 | return FindPinChecked(PrintLogPinName, EGPD_Input); 290 | } 291 | 292 | UEdGraphPin* UK2Node_SiriusPrintStringFormatted::GetTextColorPin() const 293 | { 294 | return FindPinChecked(TextColorPinName, EGPD_Input); 295 | } 296 | 297 | UEdGraphPin* UK2Node_SiriusPrintStringFormatted::GetDurationPin() const 298 | { 299 | return FindPinChecked(DurationPinName, EGPD_Input); 300 | } 301 | 302 | UEdGraphPin* UK2Node_SiriusPrintStringFormatted::FindArgumentPin(const FName PinName) const 303 | { 304 | // Check if cache is out-of-date. 305 | if (CachedArgumentPins.Num() != PinNames.Num()) 306 | { 307 | const_cast(this)->CachedArgumentPins.Reset(); 308 | 309 | const TArray IgnorePins = {GetExecutePin(), GetThenPin(), GetFormatPin(), GetPrintScreenPin(), GetPrintLogPin(), GetTextColorPin(), GetDurationPin()}; 310 | for (UEdGraphPin* const Pin : Pins) 311 | { 312 | if (!IgnorePins.Contains(Pin)) 313 | { 314 | const_cast(this)->CachedArgumentPins.Emplace(Pin); 315 | } 316 | } 317 | } 318 | 319 | UEdGraphPin* const* Find = CachedArgumentPins.FindByPredicate([&](const UEdGraphPin* const ArgumentPin) 320 | { 321 | return ArgumentPin->PinName.ToString().Equals(PinName.ToString(), ESearchCase::CaseSensitive); 322 | }); 323 | return Find ? *Find : nullptr; 324 | } 325 | 326 | void UK2Node_SiriusPrintStringFormatted::SynchronizeArgumentPinType(UEdGraphPin* Pin) const 327 | { 328 | if (FindArgumentPin(Pin->PinName)) 329 | { 330 | bool bPinTypeChanged = false; 331 | if (Pin->LinkedTo.Num() == 0) 332 | { 333 | static const FEdGraphPinType WildcardPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Wildcard, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType()); 334 | 335 | // Ensure wildcard 336 | if (Pin->PinType != WildcardPinType) 337 | { 338 | Pin->PinType = WildcardPinType; 339 | bPinTypeChanged = true; 340 | } 341 | } 342 | else 343 | { 344 | const UEdGraphPin* ArgumentSourcePin = Pin->LinkedTo[0]; 345 | 346 | // Take the type of the connected pin 347 | if (Pin->PinType != ArgumentSourcePin->PinType) 348 | { 349 | Pin->PinType = ArgumentSourcePin->PinType; 350 | bPinTypeChanged = true; 351 | } 352 | } 353 | 354 | if (bPinTypeChanged) 355 | { 356 | // Let the graph know to refresh 357 | GetGraph()->NotifyGraphChanged(); 358 | 359 | UBlueprint* Blueprint = GetBlueprint(); 360 | if (!Blueprint->bBeingCompiled) 361 | { 362 | FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); 363 | Blueprint->BroadcastChanged(); 364 | } 365 | } 366 | } 367 | } 368 | 369 | #undef LOCTEXT_NAMESPACE 370 | -------------------------------------------------------------------------------- /Source/SiriusUtilityNodesEditor/Private/K2Node_SiriusFormatString.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2022 Jasper de Laat. All Rights Reserved. 2 | 3 | #include "K2Node_SiriusFormatString.h" 4 | 5 | #include "BlueprintActionDatabaseRegistrar.h" 6 | #include "BlueprintNodeSpawner.h" 7 | #include "EdGraphSchema_K2.h" 8 | #include "EditorCategoryUtils.h" 9 | #include "K2Node_CallFunction.h" 10 | #include "K2Node_MakeArray.h" 11 | #include "K2Node_MakeStruct.h" 12 | #include "KismetCompiler.h" 13 | #include "ScopedTransaction.h" 14 | #include "Slate/SGraphNodeFormatString.h" 15 | #include "SiriusStringLibrary.h" 16 | #include "Kismet/KismetMathLibrary.h" 17 | #include "Kismet/KismetNodeHelperLibrary.h" 18 | #include "Kismet/KismetStringLibrary.h" 19 | #include "Kismet/KismetTextLibrary.h" 20 | #include "Kismet2/BlueprintEditorUtils.h" 21 | 22 | #define LOCTEXT_NAMESPACE "K2Node_SiriusFormatString" 23 | 24 | const FName UK2Node_SiriusFormatString::FormatPinName = TEXT("Format"); 25 | const FName UK2Node_SiriusFormatString::ResultPinName = TEXT("Result"); 26 | 27 | UK2Node_SiriusFormatString::UK2Node_SiriusFormatString(const FObjectInitializer& ObjectInitializer) 28 | : Super(ObjectInitializer), 29 | CachedFormatPin(nullptr) 30 | { 31 | NodeTooltip = LOCTEXT("NodeTooltip", "Builds a formatted string using available format argument values.\n \u2022 Use {} to denote format arguments.\n \u2022 Argument types may be Byte, Enum, Integer, Integer64, Float, Double, Text, String, Name, Boolean or Object."); 32 | } 33 | 34 | void UK2Node_SiriusFormatString::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) 35 | { 36 | if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UK2Node_SiriusFormatString, PinNames)) 37 | { 38 | ReconstructNode(); 39 | GetGraph()->NotifyGraphChanged(); 40 | } 41 | 42 | Super::PostEditChangeProperty(PropertyChangedEvent); 43 | } 44 | 45 | void UK2Node_SiriusFormatString::AllocateDefaultPins() 46 | { 47 | Super::AllocateDefaultPins(); 48 | 49 | CachedFormatPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FormatPinName); 50 | CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_String, ResultPinName); 51 | 52 | for (const FName& PinName : PinNames) 53 | { 54 | CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PinName); 55 | } 56 | } 57 | 58 | FText UK2Node_SiriusFormatString::GetNodeTitle(ENodeTitleType::Type TitleType) const 59 | { 60 | return LOCTEXT("NodeTitle", "Format String (Sirius)"); 61 | } 62 | 63 | void UK2Node_SiriusFormatString::PinConnectionListChanged(UEdGraphPin* Pin) 64 | { 65 | UEdGraphPin* FormatPin = GetFormatPin(); 66 | 67 | Modify(); 68 | 69 | // Clear all pins. 70 | if (Pin == FormatPin && !FormatPin->DefaultValue.IsEmpty()) 71 | { 72 | PinNames.Empty(); 73 | GetSchema()->TrySetDefaultValue(*FormatPin, TEXT("")); 74 | 75 | for (auto It = Pins.CreateConstIterator(); It; ++It) 76 | { 77 | UEdGraphPin* CheckPin = *It; 78 | if (CheckPin != FormatPin && CheckPin->Direction == EGPD_Input) 79 | { 80 | CheckPin->Modify(); 81 | CheckPin->MarkAsGarbage(); 82 | Pins.Remove(CheckPin); 83 | --It; 84 | } 85 | } 86 | 87 | FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); 88 | } 89 | 90 | // Potentially update an argument pin type 91 | SynchronizeArgumentPinType(Pin); 92 | } 93 | 94 | void UK2Node_SiriusFormatString::PinDefaultValueChanged(UEdGraphPin* Pin) 95 | { 96 | const UEdGraphPin* FormatPin = GetFormatPin(); 97 | if (Pin == FormatPin && FormatPin->LinkedTo.Num() == 0) 98 | { 99 | TArray ArgumentParams; 100 | FTextFormat::FromString(FormatPin->DefaultValue).GetFormatArgumentNames(ArgumentParams); 101 | 102 | PinNames.Reset(); 103 | 104 | for (const FString& Param : ArgumentParams) 105 | { 106 | const FName ParamName(*Param); 107 | if (!FindArgumentPin(ParamName)) 108 | { 109 | CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, ParamName); 110 | } 111 | PinNames.Add(ParamName); 112 | } 113 | 114 | for (auto It = Pins.CreateIterator(); It; ++It) 115 | { 116 | UEdGraphPin* CheckPin = *It; 117 | if (CheckPin != FormatPin && CheckPin->Direction == EGPD_Input) 118 | { 119 | const bool bIsValidArgPin = ArgumentParams.ContainsByPredicate([&CheckPin](const FString& InPinName) 120 | { 121 | return InPinName.Equals(CheckPin->PinName.ToString(), ESearchCase::CaseSensitive); 122 | }); 123 | 124 | if (!bIsValidArgPin) 125 | { 126 | CheckPin->MarkAsGarbage(); 127 | It.RemoveCurrent(); 128 | } 129 | } 130 | } 131 | 132 | GetGraph()->NotifyGraphChanged(); 133 | } 134 | } 135 | 136 | void UK2Node_SiriusFormatString::PinTypeChanged(UEdGraphPin* Pin) 137 | { 138 | // Potentially update an argument pin type 139 | SynchronizeArgumentPinType(Pin); 140 | 141 | Super::PinTypeChanged(Pin); 142 | } 143 | 144 | FText UK2Node_SiriusFormatString::GetTooltipText() const 145 | { 146 | return NodeTooltip; 147 | } 148 | 149 | FText UK2Node_SiriusFormatString::GetPinDisplayName(const UEdGraphPin* Pin) const 150 | { 151 | return FText::FromName(Pin->PinName); 152 | } 153 | 154 | TSharedPtr UK2Node_SiriusFormatString::CreateVisualWidget() 155 | { 156 | return SNew(SGraphNodeFormatString, this); 157 | } 158 | 159 | void UK2Node_SiriusFormatString::PostReconstructNode() 160 | { 161 | Super::PostReconstructNode(); 162 | 163 | if (!IsTemplate()) 164 | { 165 | // Make sure we're not dealing with a menu node 166 | if (GetSchema()) 167 | { 168 | for (UEdGraphPin* CurrentPin : Pins) 169 | { 170 | // Potentially update an argument pin type 171 | SynchronizeArgumentPinType(CurrentPin); 172 | } 173 | } 174 | } 175 | } 176 | 177 | void UK2Node_SiriusFormatString::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) 178 | { 179 | Super::ExpandNode(CompilerContext, SourceGraph); 180 | 181 | /** 182 | At the end of this, the UK2Node_SiriusFormatString will not be a part of the Blueprint, it merely handles connecting 183 | the other nodes into the Blueprint. 184 | */ 185 | 186 | // Create a "Make Array" node to compile the list of arguments into an array for the Format function being called 187 | UK2Node_MakeArray* MakeArrayNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); 188 | MakeArrayNode->AllocateDefaultPins(); 189 | CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this); 190 | 191 | UEdGraphPin* ArrayOut = MakeArrayNode->GetOutputPin(); 192 | 193 | // This is the node that does all the Format work. 194 | UK2Node_CallFunction* CallFormatFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); 195 | CallFormatFunction->SetFromFunction(USiriusStringLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(USiriusStringLibrary, Format))); 196 | CallFormatFunction->AllocateDefaultPins(); 197 | CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFormatFunction, this); 198 | 199 | // Connect the output of the "Make Array" pin to the function's "InArgs" pin 200 | ArrayOut->MakeLinkTo(CallFormatFunction->FindPinChecked(TEXT("InArgs"))); 201 | 202 | // This will set the "Make Array" node's type, only works if one pin is connected. 203 | MakeArrayNode->PinConnectionListChanged(ArrayOut); 204 | 205 | // For each argument, we will need to add in a "Make Struct" node. 206 | for (int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx) 207 | { 208 | UEdGraphPin* ArgumentPin = FindArgumentPin(PinNames[ArgIdx]); 209 | 210 | static UScriptStruct* FormatArgumentDataStruct = FindObjectChecked(FindObjectChecked(nullptr, TEXT("/Script/SiriusUtilityNodes"), true), TEXT("SiriusStringFormatArgument"), true); 211 | 212 | // Spawn a "Make Struct" node to create the struct needed for formatting the text. 213 | UK2Node_MakeStruct* MakeFormatArgumentDataStruct = CompilerContext.SpawnIntermediateNode(this, SourceGraph); 214 | MakeFormatArgumentDataStruct->StructType = FormatArgumentDataStruct; 215 | MakeFormatArgumentDataStruct->AllocateDefaultPins(); 216 | MakeFormatArgumentDataStruct->bMadeAfterOverridePinRemoval = true; 217 | CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeFormatArgumentDataStruct, this); 218 | 219 | // Set the struct's "ArgumentName" pin literal to be the argument pin's name. 220 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_CHECKED(FSiriusStringFormatArgument, ArgumentName)), ArgumentPin->PinName.ToString()); 221 | 222 | UEdGraphPin* ArgumentTypePin = MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_CHECKED(FSiriusStringFormatArgument, ArgumentValueType)); 223 | 224 | // Move the connection of the argument pin to the correct argument value pin, and also set the correct argument type based on the pin that was hooked up. 225 | if (ArgumentPin->LinkedTo.Num() > 0) 226 | { 227 | const FName& ArgumentPinCategory = ArgumentPin->PinType.PinCategory; 228 | 229 | // Adds an implicit conversion node to this argument based on its function and pin name 230 | auto AddConversionNode = [&](const UFunction* ConversionFunction, const TCHAR* PinName) 231 | { 232 | // Set the default value if there was something passed in, or default to "String" 233 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("String")); 234 | 235 | // Spawn conversion node based on the given function name 236 | UK2Node_CallFunction* ToTextFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); 237 | ToTextFunction->SetFromFunction(ConversionFunction); 238 | ToTextFunction->AllocateDefaultPins(); 239 | CompilerContext.MessageLog.NotifyIntermediateObjectCreation(ToTextFunction, this); 240 | 241 | CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *ToTextFunction->FindPinChecked(PinName)); 242 | 243 | ToTextFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)->MakeLinkTo(MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_CHECKED(FSiriusStringFormatArgument, ArgumentValue))); 244 | }; 245 | 246 | if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Int) 247 | { 248 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Int")); 249 | CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_CHECKED(FSiriusStringFormatArgument, ArgumentValueInt))); 250 | } 251 | else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Int64) 252 | { 253 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Int64")); 254 | CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_CHECKED(FSiriusStringFormatArgument, ArgumentValueInt64))); 255 | } 256 | else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Real) 257 | { 258 | if (ArgumentPin->PinType.PinSubCategory == UEdGraphSchema_K2::PC_Float) 259 | { 260 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Float")); 261 | CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FSiriusStringFormatArgument, ArgumentValueFloat))); 262 | } 263 | else if (ArgumentPin->PinType.PinSubCategory == UEdGraphSchema_K2::PC_Double) 264 | { 265 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Double")); 266 | CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FSiriusStringFormatArgument, ArgumentValueDouble))); 267 | } 268 | else 269 | { 270 | check(false); 271 | } 272 | } 273 | else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_String) 274 | { 275 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("String")); 276 | CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_CHECKED(FSiriusStringFormatArgument, ArgumentValue))); 277 | } 278 | else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Byte) 279 | { 280 | if (ArgumentPin->PinType.PinSubCategoryObject.IsValid()) 281 | { 282 | UEnum* Enum = Cast(ArgumentPin->PinType.PinSubCategoryObject.Get()); 283 | if (!Enum) 284 | { 285 | CompilerContext.MessageLog.Error(*LOCTEXT("Error_MustHaveValidEnum", "@@ must have a valid enum defined").ToString(), this); 286 | return; 287 | } 288 | 289 | const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); 290 | 291 | // Convert the enum to a friendly display string. 292 | UK2Node_CallFunction* CallEnumToStringFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); 293 | CallEnumToStringFunction->SetFromFunction(UKismetNodeHelperLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetNodeHelperLibrary, GetEnumeratorUserFriendlyName))); 294 | CallEnumToStringFunction->AllocateDefaultPins(); 295 | check(CallEnumToStringFunction->IsNodePure()); 296 | CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallEnumToStringFunction, this); 297 | 298 | // Set the enum pin to the enum type we're converting. 299 | UEdGraphPin* EnumPin = CallEnumToStringFunction->FindPinChecked(TEXT("Enum")); 300 | Schema->TrySetDefaultObject(*EnumPin, Enum); 301 | check(EnumPin->DefaultObject == Enum); 302 | 303 | // Set the enum value pin next. 304 | UEdGraphPin* IndexPin = CallEnumToStringFunction->FindPinChecked(TEXT("EnumeratorValue")); 305 | check(EGPD_Input == IndexPin->Direction && UEdGraphSchema_K2::PC_Byte == IndexPin->PinType.PinCategory); 306 | CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *IndexPin); 307 | 308 | // Connect the string output pin to the argument value pin. 309 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("String")); 310 | CallEnumToStringFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)->MakeLinkTo(MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_CHECKED(FSiriusStringFormatArgument, ArgumentValue))); 311 | } 312 | else 313 | { 314 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Int")); 315 | 316 | // Need a manual cast from byte -> int 317 | UK2Node_CallFunction* CallByteToIntFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); 318 | CallByteToIntFunction->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetMathLibrary, Conv_ByteToInt))); 319 | CallByteToIntFunction->AllocateDefaultPins(); 320 | CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallByteToIntFunction, this); 321 | 322 | // Move the byte output pin to the input pin of the conversion node 323 | CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *CallByteToIntFunction->FindPinChecked(TEXT("InByte"))); 324 | 325 | // Connect the int output pin to the argument value 326 | CallByteToIntFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)->MakeLinkTo(MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_CHECKED(FSiriusStringFormatArgument, ArgumentValueInt))); 327 | } 328 | } 329 | else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Boolean) 330 | { 331 | AddConversionNode(UKismetStringLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED_OneParam(UKismetStringLibrary, Conv_BoolToString, bool)), TEXT("InBool")); 332 | } 333 | else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Name) 334 | { 335 | AddConversionNode(UKismetStringLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED_OneParam(UKismetStringLibrary, Conv_NameToString, FName)), TEXT("InName")); 336 | } 337 | else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Text) 338 | { 339 | AddConversionNode(UKismetTextLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED_OneParam(UKismetTextLibrary, Conv_TextToString, FText)), TEXT("InText")); 340 | } 341 | else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Object) 342 | { 343 | AddConversionNode(UKismetStringLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED_OneParam(UKismetStringLibrary, Conv_ObjectToString, UObject*)), TEXT("InObj")); 344 | } 345 | else 346 | { 347 | // Unexpected pin type! 348 | CompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("Error_UnexpectedPinType", "Pin '{0}' has an unexpected type: {1}"), FText::FromName(PinNames[ArgIdx]), FText::FromName(ArgumentPinCategory)).ToString()); 349 | } 350 | } 351 | else 352 | { 353 | // No connected pin - just default to an empty string 354 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("String")); 355 | MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultText(*MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_CHECKED(FSiriusStringFormatArgument, ArgumentValue)), FText::GetEmpty()); 356 | } 357 | 358 | // The "Make Array" node already has one pin available, so don't create one for ArgIdx == 0 359 | if (ArgIdx > 0) 360 | { 361 | MakeArrayNode->AddInputPin(); 362 | } 363 | 364 | // Find the input pin on the "Make Array" node by index. 365 | const FString PinName = FString::Printf(TEXT("[%d]"), ArgIdx); 366 | UEdGraphPin* InputPin = MakeArrayNode->FindPinChecked(PinName); 367 | 368 | // Find the output for the pin's "Make Struct" node and link it to the corresponding pin on the "Make Array" node. 369 | for (UEdGraphPin* Pin : MakeFormatArgumentDataStruct->Pins) 370 | { 371 | if (Pin && Pin->Direction == EGPD_Output) 372 | { 373 | Pin->MakeLinkTo(InputPin); 374 | break; 375 | } 376 | } 377 | } 378 | 379 | // Move connection of FormatText's "Result" pin to the call function's return value pin. 380 | CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(TEXT("Result")), *CallFormatFunction->GetReturnValuePin()); 381 | // Move connection of FormatText's "Format" pin to the call function's "InPattern" pin 382 | CompilerContext.MovePinLinksToIntermediate(*GetFormatPin(), *CallFormatFunction->FindPinChecked(TEXT("InPattern"))); 383 | 384 | BreakAllNodeLinks(); 385 | } 386 | 387 | UK2Node::ERedirectType UK2Node_SiriusFormatString::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const 388 | { 389 | ERedirectType RedirectType = ERedirectType_None; 390 | 391 | // if the pin names do match 392 | if (NewPin->PinName.ToString().Equals(OldPin->PinName.ToString(), ESearchCase::CaseSensitive)) 393 | { 394 | // Make sure we're not dealing with a menu node 395 | if (const UEdGraphSchema* Schema = GetSchema()) 396 | { 397 | const UEdGraphSchema_K2* K2Schema = Cast(Schema); 398 | if (!K2Schema || K2Schema->IsSelfPin(*NewPin) || K2Schema->ArePinTypesCompatible(OldPin->PinType, NewPin->PinType)) 399 | { 400 | RedirectType = ERedirectType_Name; 401 | } 402 | else 403 | { 404 | RedirectType = ERedirectType_None; 405 | } 406 | } 407 | } 408 | else 409 | { 410 | // try looking for a redirect if it's a K2 node 411 | if (const UK2Node* Node = Cast(NewPin->GetOwningNode())) 412 | { 413 | // if you don't have matching pin, now check if there is any redirect param set 414 | TArray OldPinNames; 415 | GetRedirectPinNames(*OldPin, OldPinNames); 416 | 417 | FName NewPinName; 418 | RedirectType = ShouldRedirectParam(OldPinNames, /*out*/ NewPinName, Node); 419 | 420 | // make sure they match 421 | if (RedirectType != ERedirectType_None && !NewPin->PinName.ToString().Equals(NewPinName.ToString(), ESearchCase::CaseSensitive)) 422 | { 423 | RedirectType = ERedirectType_None; 424 | } 425 | } 426 | } 427 | 428 | return RedirectType; 429 | } 430 | 431 | bool UK2Node_SiriusFormatString::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const 432 | { 433 | const UEdGraphPin* FormatPin = GetFormatPin(); 434 | if (MyPin != FormatPin && MyPin->Direction == EGPD_Input) 435 | { 436 | const FName& OtherPinCategory = OtherPin->PinType.PinCategory; 437 | 438 | bool bIsValidType = false; 439 | if (OtherPinCategory == UEdGraphSchema_K2::PC_Int || 440 | OtherPinCategory == UEdGraphSchema_K2::PC_Int64 || 441 | OtherPinCategory == UEdGraphSchema_K2::PC_Real || 442 | OtherPinCategory == UEdGraphSchema_K2::PC_Text || 443 | OtherPinCategory == UEdGraphSchema_K2::PC_Byte || 444 | OtherPinCategory == UEdGraphSchema_K2::PC_Boolean || 445 | OtherPinCategory == UEdGraphSchema_K2::PC_String || 446 | OtherPinCategory == UEdGraphSchema_K2::PC_Name || 447 | OtherPinCategory == UEdGraphSchema_K2::PC_Object || 448 | OtherPinCategory == UEdGraphSchema_K2::PC_Wildcard) 449 | { 450 | bIsValidType = true; 451 | } 452 | 453 | if (!bIsValidType) 454 | { 455 | OutReason = LOCTEXT("Error_InvalidArgumentType", "Format arguments may only be Byte, Enum, Integer, Integer64, Float, Double, Text, String, Name, Boolean, Object or Wildcard.").ToString(); 456 | return true; 457 | } 458 | } 459 | 460 | return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); 461 | } 462 | 463 | void UK2Node_SiriusFormatString::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const 464 | { 465 | const UClass* ActionKey = GetClass(); 466 | 467 | if (ActionRegistrar.IsOpenForRegistration(ActionKey)) 468 | { 469 | UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); 470 | check(NodeSpawner != nullptr); 471 | 472 | ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); 473 | } 474 | } 475 | 476 | FText UK2Node_SiriusFormatString::GetMenuCategory() const 477 | { 478 | return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::String); 479 | } 480 | 481 | UEdGraphPin* UK2Node_SiriusFormatString::GetFormatPin() const 482 | { 483 | if (!CachedFormatPin) 484 | { 485 | const_cast(this)->CachedFormatPin = FindPinChecked(FormatPinName, EGPD_Input); 486 | } 487 | return CachedFormatPin; 488 | } 489 | 490 | UEdGraphPin* UK2Node_SiriusFormatString::GetResultPin() const 491 | { 492 | return FindPinChecked(ResultPinName, EGPD_Output); 493 | } 494 | 495 | UEdGraphPin* UK2Node_SiriusFormatString::FindArgumentPin(const FName InPinName) const 496 | { 497 | const UEdGraphPin* FormatPin = GetFormatPin(); 498 | for (UEdGraphPin* Pin : Pins) 499 | { 500 | if (Pin != FormatPin && Pin->Direction == EGPD_Input && Pin->PinName.ToString().Equals(InPinName.ToString(), ESearchCase::CaseSensitive)) 501 | { 502 | return Pin; 503 | } 504 | } 505 | 506 | return nullptr; 507 | } 508 | 509 | void UK2Node_SiriusFormatString::AddArgumentPin() 510 | { 511 | const FScopedTransaction Transaction(NSLOCTEXT("Kismet", "AddArgumentPin", "Add Argument Pin")); 512 | Modify(); 513 | 514 | const FName PinName(GetUniquePinName()); 515 | CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PinName); 516 | PinNames.Add(PinName); 517 | 518 | FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); 519 | GetGraph()->NotifyGraphChanged(); 520 | } 521 | 522 | UEdGraphPin* UK2Node_SiriusFormatString::AddArgumentPin(const FName InPinName) 523 | { 524 | PinNames.Add(InPinName); 525 | return CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, InPinName); 526 | } 527 | 528 | void UK2Node_SiriusFormatString::SynchronizeArgumentPinType(UEdGraphPin* Pin) const 529 | { 530 | const UEdGraphPin* FormatPin = GetFormatPin(); 531 | if (Pin != FormatPin && Pin->Direction == EGPD_Input) 532 | { 533 | bool bPinTypeChanged = false; 534 | if (Pin->LinkedTo.Num() == 0) 535 | { 536 | static const FEdGraphPinType WildcardPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Wildcard, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType()); 537 | 538 | // Ensure wildcard 539 | if (Pin->PinType != WildcardPinType) 540 | { 541 | Pin->PinType = WildcardPinType; 542 | bPinTypeChanged = true; 543 | } 544 | } 545 | else 546 | { 547 | const UEdGraphPin* ArgumentSourcePin = Pin->LinkedTo[0]; 548 | 549 | // Take the type of the connected pin 550 | if (Pin->PinType != ArgumentSourcePin->PinType) 551 | { 552 | Pin->PinType = ArgumentSourcePin->PinType; 553 | bPinTypeChanged = true; 554 | } 555 | } 556 | 557 | if (bPinTypeChanged) 558 | { 559 | // Let the graph know to refresh 560 | GetGraph()->NotifyGraphChanged(); 561 | 562 | UBlueprint* Blueprint = GetBlueprint(); 563 | if (!Blueprint->bBeingCompiled) 564 | { 565 | FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); 566 | Blueprint->BroadcastChanged(); 567 | } 568 | } 569 | } 570 | } 571 | 572 | FText UK2Node_SiriusFormatString::GetArgumentName(const int32 InIndex) const 573 | { 574 | if (InIndex < PinNames.Num()) 575 | { 576 | return FText::FromName(PinNames[InIndex]); 577 | } 578 | return FText::GetEmpty(); 579 | } 580 | 581 | void UK2Node_SiriusFormatString::RemoveArgument(const int32 InIndex) 582 | { 583 | const FScopedTransaction Transaction(NSLOCTEXT("Kismet", "RemoveArgumentPin", "Remove Argument Pin")); 584 | Modify(); 585 | 586 | if (UEdGraphPin* ArgumentPin = FindArgumentPin(PinNames[InIndex])) 587 | { 588 | Pins.Remove(ArgumentPin); 589 | ArgumentPin->MarkAsGarbage(); 590 | } 591 | PinNames.RemoveAt(InIndex); 592 | 593 | FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); 594 | GetGraph()->NotifyGraphChanged(); 595 | } 596 | 597 | void UK2Node_SiriusFormatString::SetArgumentName(const int32 InIndex, const FName InName) 598 | { 599 | PinNames[InIndex] = InName; 600 | ReconstructNode(); 601 | FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprint()); 602 | } 603 | 604 | void UK2Node_SiriusFormatString::SwapArguments(const int32 InIndexA, const int32 InIndexB) 605 | { 606 | check(InIndexA < PinNames.Num()); 607 | check(InIndexB < PinNames.Num()); 608 | PinNames.Swap(InIndexA, InIndexB); 609 | 610 | ReconstructNode(); 611 | GetGraph()->NotifyGraphChanged(); 612 | 613 | FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprint()); 614 | } 615 | 616 | FName UK2Node_SiriusFormatString::GetUniquePinName() const 617 | { 618 | FName NewPinName; 619 | int32 i = 0; 620 | while (true) 621 | { 622 | NewPinName = *FString::FromInt(i++); 623 | if (!FindPin(NewPinName)) 624 | { 625 | break; 626 | } 627 | } 628 | return NewPinName; 629 | } 630 | 631 | #undef LOCTEXT_NAMESPACE 632 | --------------------------------------------------------------------------------