Files
UnrealEngineUWP/Engine/Source/Editor/GraphEditor/Private/SGraphNode.cpp
Marc Audy eaccf4135c Copying //UE4/Dev-Framework to //UE4/Dev-Main (Source: //UE4/Dev-Framework @ 3582324)
#lockdown Nick.Penwarden
#rb none
#rnx

============================
  MAJOR FEATURES & CHANGES
============================

Change 3431439 by Marc.Audy

	Editor only subobjects shouldn't exist in PIE world
	#jira UE-43186

Change 3457323 by Marc.Audy

	Undo CL# 3431439 and once again allow (incorrectly) for editor only objects to exist in a PIE world

	#jira UE-45087

Change 3499927 by Dan.Oconnor

	UField::Serialize no longer serialize's its next ptr,  UStruct::Serialize serializes all Children properties instead. This resolves a hard circular dependency between function libraries that EDL detected. It was resolved in an ad hoc way by the old linker

	#jira UE-43458

Change 3502939 by Michael.Noland

	Back out changelist 3499927

Change 3522783 by Zak.Middleton

	#ue4 - Imported new simple collision for Engine/Content/BasicShaps/Cylinder.uasset which is a single convex shape (rather than being 4 shapes as before).

Change 3544641 by Dan.Oconnor

	Remove conditional that is no longer needed, replace with ensure. It is unsafe to change CDO names

	#jira OR-38176

Change 3544645 by Dan.Oconnor

	In addition to marking nodes as not transient, FBlueprintEditor::ExpandNode needs to mark them as transactional
	#jira UE-45248

Change 3545023 by Marc.Audy

	Properly encapsulate FPinDeletionQueue
	Fix ensure during deletion of split pins when not clearing links
	Fix split pins able to end up in delete queue twice during undo/redo

Change 3545025 by Marc.Audy

	Properly allow changing the pin type from a struct that is split on the node
	#jira UE-47328

Change 3545455 by Ben.Zeigler

	Fix issue where combined streamable handles could be freed before their complete callback is called if nothing external referenced them
	Copy of CL#3544474

Change 3545456 by Ben.Zeigler

	Allow PrimaryAssets to update their AssetData based on in-memory changes when launching 'Standalone Game' and 'Mobile Preview' from the editor. As it was, changes could be detected and propagated through UPrimaryDataAsset::PostLoad, but the changes would always reapply whatever already exists in the AssetRegistry. The primary use-case for this: making AssetBundle tag changes and allowing them to propagate without resaving/recooking all affected assets.
	Copy of CL #3544374

Change 3545547 by Ben.Zeigler

	CIS Fix

Change 3545568 by Michael.Noland

	PR #3758: Fixing a comment typo from Transistion to Transition (Contributed by gsfreema)

	#jira UE-46845

Change 3545582 by Michael.Noland

	Blueprints: Prevent duplicate messages from being added to the compiler results log (fixes a crash when resizing the results log while a math expression node has an error)
	Blueprints: Fixed the tooltip of math expression nodes not showing up, and error messages getting cleared on subsequent compiles
	[Duplicating fixes for UE-47491 and UE-47512 from 4.17 to Dev-Framework]

Change 3546528 by Ben.Zeigler

	#jira UE-47548
	Fix crash when a map's key type has changed but value has not, it was passing the UStruct defaults in when serialize was expecting the default instance, so pass null instead as we don't have the instance

Change 3546544 by Marc.Audy

	Fix split pin restoration logic to deal with wildcards and variations in const/refness

Change 3546551 by Marc.Audy

	Don't crash if the struct type is missing for whatever reason

Change 3547152 by Marc.Audy

	Fix array exporting so you don't end up getting none instead of defaults
	#jira UE-47320

Change 3547438 by Marc.Audy

	Fix split pins on class defaults
	Don't cause a structural change when reapplying a split pin as part of node reconstruction
	#jira UE-46935

Change 3547501 by Ben.Zeigler

	Fix ensure, it's valid to pass a null path for a dynamic asset

Change 3551185 by Ben.Zeigler

	#jira UE-42835 Fix it so SoftObjectPaths work correctly when inside levels loaded for the first time from PIE. Added code to do in-place PIE fixup for levels that are loaded instead of duplicated, and changed the fixup logic to fix all level references, not just ones being explicitly duplicated

Change 3551723 by Ben.Zeigler

	Improve UI for displaying actor soft references. Add an error/warning icon if the cross level reference is broken or unloaded, and fix various display and copy/paste behaviors

Change 3553216 by Phillip.Kavan

	#jira UE-39303, UE-46268, UE-47519
	- Nativized UDS now support external asset dependencies and will construct their own linker import tables on load.

	Change summary:
	- Modified FCompactBlueprintDependencyData and FFakeImportTableHelper to be more relevant to UStruct and not just UClass-derivative types.
	- Modified FBlueprintDependencyData to accept a single FCompactBlueprintDependencyData struct rather than its individual fields.
	- Modified FBlueprintCompilerCppBackendBase::GenerateCodeFromStruct() to emit dependency assignment and static type registration functions for nativized UStruct types.
	- Modified FBlueprintNativeCodeGenModule::FStatePerPlatform to include an array for tracking UDS assets that need to be converted during the nativization pass at cook time.
	- Modified FBlueprintNativeCodeGenModule::GenerateFullyConvertedClasses() to generate nativized code for UDS assets. This ensures that UDS conversion falls under the same scope as BPGC conversion, so that they both feed into the same NativizationSummary context for asset dependency tracking (i.e. since we only have a single global dependency table in the codegen). Also modified InitializeForRerunDebugOnly() to do the same.
	- Modified FBlueprintNativeCodeGenModule::Convert() to defer UDS conversion so that it's done at the same time as BPGC conversion (see note above).
	- Modified FEmitDefaultValueHelper::AddStaticFunctionsForDependencies() to include support for UStruct types and to conform to changes made to FCompactBlueprintDependencyData.
	- Modified FEmitDefaultValueHelper::AddRegisterHelper() to include support for UStruct types.
	- Modified FBlueprintNativeCodeGenModule::FindReplacedClassForObject() to ensure that converted User-Defined Enum types are switched to a UEnumProperty at package save time so that any import tables contain the proper class. This is necessary because we nativize User-Defined Enum types as 'enum class' types, and UHT will generate code for those as a UEnumProperty with an "underlying" property. However, non-nativized User-Defined Enum types are referenced as a UByteProperty with a UEnum reference, so we have to fix up all the import tables before saving. Otherwise, EDL will assert on load (see UE-47519).

Change 3553301 by Ben.Zeigler

	Fix ensure when passing literal None to SoftObjectPath, it now treats them as empty instead

Change 3553631 by Dan.Oconnor

	UField::Serialize no longer serialize's its next ptr,  UStruct::Serialize serializes all Children properties instead. This resolves a hard circular dependency between function libraries that EDL detected. It was resolved in an ad hoc way by the old linker. This change was originally submitted in 3499927, but it was incorrectly clearing the UField::Next pointer in UField::Serialize.

	#jira UE-43458

Change 3553799 by Ben.Zeigler

	Fix issue where calling WaitUntilComplete on a combined handle with Stalled children wouldn't work properly. It now forces all stalled children to start immediately. I also added a warning log when this happens and an ensure if somehow the force didn't work
	Copy of CL #3553781

Change 3553896 by Michael.Noland

	Blueprints: Allow the autowiring logic to better break and replace existing connections when made (e.g., when dragging a variable onto a compatible pin with an existing connection, break the old connection to allow the new connection to be made)
	#jira UE-31031

Change 3553897 by Michael.Noland

	Blueprints: Adjust search query when doing 'Find References' on variables from My Blueprints so that bound event nodes show up for components and widgets
	#jira UE-37862

Change 3553898 by Michael.Noland

	Blueprints: Add the name of the variable directly in the get/set menu options (when dragging from My Blueprints into the graph)

Change 3553909 by Michael.Noland

	Blueprints: Added the full name of the type, container type (and value type for maps) to the tooltips for the type picker elements, so long names can be read in full
	#jira UE-19710

Change 3554517 by Michael.Noland

	Blueprints: Added an option to disable the comment bubble on comment boxes that appears when zoomed out
	#jira UE-21810

Change 3554664 by Michael.Noland

	Editor: Renamed "Find in CB" command to "Browse" and renamed "Search" (in BP) to "Find", so terminology is consistent and keyboard shortcuts make sense (Ctrl+B for Browse, Ctrl+F for find, not using the term Search anywhere)

	#jira UE-27121

Change 3554831 by Dan.Oconnor

	Non editor build fix

Change 3554834 by Dan.Oconnor

	Actor bound event related warnings now show up in blueprint status when opening level blueprint for first time, improved warning message. Removed unused delegate and return value from FixLevelScriptActorBindings. Can now pass raw strings to blueprint results log (no need for Printf, although padding is not great), UClasses in compiler results log will open the generated blueprint when clicked on

	#jira UE-40438

Change 3556157 by Ben.Zeigler

	Convert LevelSequenceBindingReference to use FSoftObjectPath so it works properly with redirectors and fixups

Change 3557775 by Michael.Noland

	Blueprints: Fixed swapped transaction messages when converting a cast node between pure and impure
	#jira UE-36090

Change 3557777 by Michael.Noland

	Blueprints: Allow 'Goto Definition' and 'Find References' to be used while stopped at a breakpoint
	PR #3774: Expose GotoFunctionDefinition during BP debugging (Contributed by projectgheist)
	#jira UE-47024

Change 3560510 by Michael.Noland

	Blueprints: Add support for 'goto definition' on Create Event nodes when the Object pin is not hooked up
	#jira UE-38912

Change 3560563 by Michael.Noland

	Blueprints: Disallow converting a variable get node to impure/back when debugging (no graph mutating operations should be allowed)

Change 3561443 by Ben.Zeigler

	Restore code to support gc.DumpPoolStats, was accidentally removed when FGCArrayPool was moved to a header.
	Clean up comments around Cleanup function, the functionality to trim the memory pools was integrated into ClearWeakReferences on a prior change

Change 3561658 by Michael.Noland

	Blueprints: Refactored 'Goto Definition' so there is no per-class logic in the Blueprint editor or schema any more; any node can opt in individually
	- Added a key binding for Goto Definition (Alt+G)
	- Added a key binding for Find References (Shift+Alt+F)
	- Collapsed 'Goto Code Definition' for variables and functions into the same path, so there aren't separate menu items and commands
	- Added new methods CanJumpToDefinition and JumpToDefinition to UEdGraphNode, the default behavior for UK2Node subclasses is to call GetJumpTargetForDoubleClick and call into FKismetEditorUtilities::BringKismetToFocusAttentionOnObject
	- Going to a native function now goes thru a better code path that will actually put the source code editor on the function definition, rather than just opening the file containing the definition

Change 3562291 by Ben.Zeigler

	Fix issue where calling FSoftObjectPtr::Get during a package save would result in that ptr being forever marked broken, because the ResolveObject fails during save. It's too risky to change that behavior, so change it so the TagAtLastTest doesn't get updated in that case

Change 3562292 by Ben.Zeigler

	#jira UE-39042 When renaming or moving actors between levels it now attempts to fix any soft object references from blueprints or sequencer
	When deleting actors that are soft referenced by actor/sequencer it will now warn the same way it does for level script. Added IAssetTools::FindSoftReferencesToObject to perform this search
	Change it so saving a non-primary world does not result it being dirtied due to the temporary physics scene fixup
	Fix issue where the actor name was shown incorrectly in the SSCS tree for actor instances, which meant that if you renamed it you would end up renaming it to the BP's name

Change 3564814 by Ben.Zeigler

	#jira UE-47843 Don't set InputKey output pins to AnyKey if empty, this was causing blueprints with key events to constantly dirty themselves

Change 3566707 by Dan.Oconnor

	Remove unused code, UClassGenerateCDODuplicatesForHotReload was attempting to create a CDO with a special name, which triggered an ensure. The Duplicated CDO was never used (callign code removed in 3289276 as it was a waste of cycles)

	#jira None

Change 3566717 by Michael.Noland

	Core: Remove all defintions that contain "_API" from the command line when compiling .rc files (they do not support repsonse files and a too-long command line will fail the compile)

Change 3566771 by Michael.Noland

	Editor: Fixing deprecation warning

	#jira UE-47922

Change 3567023 by Michael.Noland

	Blueprints: Change various TArray<> uses to TSet<> throughout name validation and related code to enable it to scale better to high component or variable counts
	Adapted from PR #3708: Fast construction of bp (Contributed by gildor2)
	#jira UE-46473

Change 3567304 by Ben.Zeigler

	Add bCheckRecursive option to IsEditorOnlyObject that is enabled by default and will check outer/archetype/class.
	This is needed for places that call this function from outside of SavePackage.cpp when the editor only mark is set, such as the automation test code

Change 3567398 by Ben.Zeigler

	Fix crash when spawning a widget that has no editor WidgetTree, but does have a cooked widget tree template due to tree inheritance

Change 3567729 by Michael.Noland

	Blueprints: Clarified message about "{VariableName} is not blueprint visible" to define what that means "(BlueprintReadOnly or BlueprintReadWrite)"

Change 3567739 by Ben.Zeigler

	Don't crash if PropertyStruct cannot find it's struct. The function half handled this before, but Preload crashes with a null parameter

Change 3567741 by Ben.Zeigler

	Disable optimization for a path test that was crashing in VC2015 in a monolithic build

Change 3568332 by Mieszko.Zielinski

	Prevented UAIPerceptionSystem::GetCurrent causing a BP error when WorldContextObject is null #UE4

	#jira UE-47948

Change 3568676 by Michael.Noland

	Blueprints: Allow editing the tooltip of each enum value in a user defined enum

	#jira UE-20036

	Known issue: Undo/redo is not currently possible on the tooltip as it is directly stored in package metadata

Change 3569128 by Michael.Noland

	Blueprints: Removing the experimental profiler as we won't be returning to it any time soon
	#jira UE-46852

Change 3569207 by Michael.Noland

	Blueprints: Allow drag-dropping component Blueprint assets into the graph to create Add Component nodes and improved the error message when dragging something that cannot be dropped into an actor Blueprint
	#jira UE-8708

Change 3569208 by Michael.Noland

	Blueprints: Allow specifying a description for user defined enums (shown in the content browser)
	#jira UE-20036

Change 3569209 by Michael.Noland

	Editor: Allow adjusting the font size for each individual comment box node in Blueprints and Materials
	#jira UE-16085

Change 3570177 by Michael.Noland

	Blueprints: Fixed discrepancy between the Structure tab name and the menu option for the tab in the user defined structure editor (now both say Structure Editor)

	#jira UE-47962

Change 3570179 by Michael.Noland

	Blueprints: Fixed the tooltip of a user defined structure not updating immediately in the content browser after being edited

Change 3570192 by Michael.Noland

	Blueprints: Added "(selected)" after the label (in the 'debug filter' dropdown in the Blueprint editor) for actors that are selected in the level editor, which should make it easier to choose the specific actor you want to debug
	#jira UE-20709

Change 3571203 by Michael.Noland

	Blueprints: Cleanup and refactoring to prepare for turning commented out nodes into an official feature
	- Made EnabledState and bUserSetEnabledState private on UEdGraphNode and added new getters/setters
	- Introduced IsAutomaticallyPlacedGhostNode() and MakeAutomaticallyPlacedGhostNode() to start decoupling the concept from commented out nodes
	- Updated a couple of places that used a hardcoded UberGraphPages[0] into instead editing the most recently interacted with event graph if possible
	- Updated 'is data only blueprint' logic to allow multiple ubergraph pages, as long as they're all empty of non-disabled nodes

Change 3571224 by Michael.Noland

	Blueprints: Draw banners on development-only and user disabled nodes (excluding 'ghost' nodes like autogenerated event entries in new BPs)
	Adapted from PR #2701: Differentiate development nodes in BP (Contributed by projectgheist)

	#jira UE-29848
	#jira UE-34698

Change 3571279 by Michael.Noland

	Blueprints: Changed UK2Node::GetPassThroughPin so that only the execution wire on nodes with exactly one input and one output exec wire will have a corresponding pass-through pin (when there is ambiguity in which exec would be used, e.g., with a branch or sequence or timeline node, the subsequent nodes are now effectively disabled as well)

Change 3571282 by Michael.Noland

	Blueprints: Fixed the tooltip for dragging a variable onto an inherited category not showing the 'move to category' hint

Change 3571284 by Michael.Noland

	Blueprints: Made wires into/out of a user-disabled node draw verly dimly (other than the passthrough exec if it exists)

Change 3571311 by Ben.Zeigler

	Add ActorIteratorFlags which allows overriding which types of actors/levels are iterated by ActorIterator, to allow iterating levels that are not visible.
	All of the iteration logic is now in the base and the children just set different flags, which fixes it so TActorIterator does the same level collection check as FActorIterator

Change 3571313 by Ben.Zeigler

	Several fixes to automation framework to allow it to work better with Cooked builds.
	Change it so the automation test list is a single  message. There is no guarantee on order of message packets, so several tests were being missed each time.

Change 3571485 by mason.seay

	Test map for Make Set bug

Change 3571501 by Ben.Zeigler

	Accidentally undid the UHT fixup for TAssetPtr during my bulk rename

Change 3571531 by Ben.Zeigler

	Fix warning messages

Change 3571591 by Michael.Noland

	Blueprints: Made drag-dropping assets into a graph to create a component transactional (allowing the action to be undone)
	#jira UE-48024

Change 3572938 by Michael.Noland

	Blueprints: Fixed a typo in a set function comment

	#jira UE-48036

Change 3572941 by Michael.Noland

	Blueprints: Made the compact node title for cross and dot product the words cross and dot rather than hard to see . and x symbols
	#jira UE-38624

Change 3574816 by mason.seay

	Renamed asset to better reflect name of object reference

Change 3574985 by mason.seay

	Updated comments and string outputs to list Soft Object Reference

Change 3575740 by Ben.Zeigler

	#jira UE-48061 Change it so Editor builds work like cooked builds and always try to reuse existing packages when loading them instead of recreating them in place. Recreating in place does not work well for levels and blueprints, and blueprints already had a hack that was causing this behavior to not activate

Change 3575795 by Ben.Zeigler

	#jira UE-48118 Call into the AssetManager as part of the DistillPackages commandlet. This makes sure that ShooterGame and EngineTest end up with the correct content in launcher builds

Change 3576374 by mason.seay

	Forgot to submit the deleting of a redirector

Change 3576966 by Ben.Zeigler

	#jira UE-48153 Fix issue where actors in streaming levels weren't properly replicating in PIE. It now does the remap path on both send and receive for the manual PC level streaming commands

Change 3577002 by Marc.Audy

	Prevent wildcard pins from being connected to exec pins
	#jira UE-48148

Change 3577232 by Phillip.Kavan

	#jira UE-48034 - Fix EDL assert on load caused by a native reference to a nativized BP class that also references a nativized UDS asset.

	Change summary:
	- Modified FNativeClassHeaderGenerator::ExportGeneratedStructBodyMacros() to emit the 'ReplaceConverted' package name for the FCompiledInDeferStruct global associated with the nativized UDS asset in the UHT codegen. This brings nativized UDS to parity with nativized BP class assets (it was likely just an oversight initially).

Change 3577710 by Dan.Oconnor

	Mirror of 3576977:
	Fix for crash when loading cooked uassets that reference functions that are not present
	#jira UE-47644

Change 3577723 by Dan.Oconnor

	Prevent deferring of classes that are needed to load subobjects

	#jira UE-47726

Change 3577741 by Dan.Oconnor

	Back out changelist 3577723 - causes crash when starting QAGame in Dev-Framework - not in Release-4.17

Change 3578938 by Ben.Zeigler

	#jira UE-27124 Fix issue where renaming a map with legacy map build data would end up with a half-loaded redirector package, becuase the old map build data was still in use. It's possible the call to HandleLegacyMapBuildData should just be in World PostLoad instead of piecemeal in several other places but I am unsure.
	Fix it so the redirector cleanup code on map change will not be stopped by non-standalone top level objects, which could be left behind by issues in other systems

Change 3578947 by Marc.Audy

	(4.17) Properly expose members of DialogueContext to blueprints
	#jira UE-48175

Change 3578952 by Ben.Zeigler

	Fix ensure where ParentHandles on a StreamableHandle could be modified while iterating

Change 3579315 by mason.seay

	Test map for Make Container nodes

Change 3579600 by Ben.Zeigler

	Disable window test on non-desktop platforms as they cannot be resized post launch

Change 3579601 by Ben.Zeigler

	#jira UE-48236 Disable optimizations on PS4 for certain math tests pending fixing of platform issue

Change 3579713 by Dan.Oconnor

	Prevent crashes when bluepints implement an interface that was deleted
	#jira UE-48223

Change 3579719 by Dan.Oconnor

	Fix two compilation manager issues: Make sure CDOs are not renamed under a UClass, because they will be considered as possible subobjects, which has bad side effects and make sure that we update references even on empty functions, so that empty UFunctions are not left with references to REINST data

	#jira UE-48240

Change 3579745 by Michael.Noland

	Blueprints: Improve categorization and reordering support in 'My Blueprints'
	- Allow drag-dropping functions, macros, delegates, etc... to reorder them within a category or to change categories (bringing them to parity with variables)
	- Make category ordering on all categories sticky so you can reorder categories (the relative ordering will be the same within different sections like variables and functions)
	- Added hover cues when drag dropping some items that were missing them (e.g., event dispatchers)
	- Added support for renaming categories using F2

	Known issues (none are regressions):
	- Timelines cannot be moved to other categories or reordered
	- Renaming a nested category will result in it becoming a top level category (discarding the parent category chain)
	- Some actions do not support undo

	#jira UE-31557

Change 3579795 by Michael.Noland

	PR #3867: Fix for not releasing Local Notification Delegate. (Contributed by enginevividgames)
	#jira UE-48105

Change 3580463 by Marc.Audy

	(4.17) Don't crash if calling PostEditUndo on an Actor in the transient package
	#jira UE-47523

Change 3581073 by Marc.Audy

	Make UK2Node_SpawnActorFromClass inherit from K2Node_ConstructObjectFromClass and eliminate duplicate code.
	Correctly reconnect split pins when changing class on create widget, construct object, and spawn actor nodes

Change 3581156 by Ben.Zeigler

	#jira UE-48161 Fix issue where split pins would not be restored if a Struct type was changed due to refactoring of parent variables/functions. For structs we want to copy the pins, if they're invalid due to other changes they will be individual orphaned
	Also fix bug where the category of parent pins was being set incorrectly while changing variable type, we only want to override type for wildcard pins

Change 3581473 by Ben.Zeigler

	Properly turn off optimization for PS4 test

Change 3582094 by Marc.Audy

	Fix anim nodes not navigating to their graph on double click
	#jira UE-48333

Change 3582157 by Marc.Audy

	Fix double-clicking on animation asset nodes not opening the asset editors

Change 3582289 by Marc.Audy

	(4.17) Don't crash when adding a streaming level that's already in the level
	#jira UE-48928

Change 3545435 by Ben.Zeigler

	#jira UE-47509 Rename and refactor internals StringAssetReferences and AssetPtrs to become SoftObjectPath/Ptr. This is to prepare them for use for subobjects like actors. Here is the rename table:
	FStringAssetReference -> FSoftObjectPath
	FStringClassReference -> FSoftClassPath
	TAssetPtr -> TSoftObjectPtr
	TAssetSubclassOf -> TSoftClassPtr
	The old headers are deprecated, and FSoftClassPath is now in the same header has FSoftObjectPath.
	This change increments the UE4 version because FSoftObjectPaths are now stored as a name + substring instead of one giant name, which in practice will improve performance and memory while manipulating them. Also the package table of soft referenced packages is now stored as FNames instead of FStrings as these packages names will already be in the name table due to the AssetRegistry
	Remove automatic support for loading Objectpaths starting with engine-ini:, as it was only partially supported and is very outdated. ResolveIniObjectsReference can still be called manually
	Changed TPersistentObjectPtr and TLazyObjectPtr to be structs instead of classes
	Change UnrealHeaderTool to read configuration such as StructsWithNoPrefix out of inis instead of using a hardcoded list. Add support for TypeRedirects, which is used to make the old type names automatically remap to the new ones
	Clean up FRedirectCollector to remove some of the functionality that is no longer used by the cooker, and disable tracking of redirects in standalone -game builds

Change 3567760 by Ben.Zeigler

	Fix it so EngineTest can be cooked by moving some utility functions to EditorTestsUtilityLibrary and adding an EditorFunctionalTest. The core EngineTest module is safely runtime-only now and can run it's tests locally in windows cooked
	Merge FuncTestManager into FunctionalTestModule to avoid confusion with FunctionalTestingManager UObject
	Add EngineTestAssetManager and override the cook function to cook all maps with runtime functional tests
	Change actor merging tests to be editor only, this stops them from cooking
	Several individual tests crash on cooked builds, I started threads with the owners of those

Change 3575737 by Ben.Zeigler

	#jira UE-48042 Change it so playing via PIE Standalone, multiprocess networked PIE and external cook launch on does not save temporary levels to UEDPC and instead prompts the user to save. This is how launch on works by default already, and this fixes cross level references/sequencer. The UEDPC code has been removed entirely.
	As part of this change, connecting a -game client to a PIE server and vice versa is much more likely to work. You may still have game-side problems, look at UEditorEngine::NetworkRemapPath for an example of how to do the PIE prefix conversion
	Remove advanced CreateTemporaryCopiesOfLevels option from sequencer capture, it has not been tested in multiple years and does not work with newer sequencer features
	#jira UE-27124 Fix several possible crashes with changing levels while in PIE

Change 3578806 by Marc.Audy

	Fix Construct Object not working correctly with split pins.
	Add Construct Object test cases to functional tests.
	Added split pin expose on spawn test cases.
	#jira UE-33924

[CL 3582334 by Marc Audy in Main branch]
2017-08-11 12:43:42 -04:00

1560 lines
45 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "SGraphNode.h"
#include "EdGraph/EdGraph.h"
#include "Widgets/SBoxPanel.h"
#include "SlateOptMacros.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "GraphEditorSettings.h"
#include "SCommentBubble.h"
#include "SGraphPin.h"
#include "GraphEditorDragDropAction.h"
#include "EdGraphSchema_K2.h"
#include "K2Node_Literal.h"
#include "NodeFactory.h"
#include "Logging/TokenizedMessage.h"
#include "DragAndDrop/ActorDragDropGraphEdOp.h"
#include "DragAndDrop/AssetDragDropOp.h"
#include "Editor/Persona/Public/BoneDragDropOp.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "SLevelOfDetailBranchNode.h"
#include "Widgets/SToolTip.h"
#include "IDocumentation.h"
#include "TutorialMetaData.h"
#include "SGraphPanel.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "BlueprintEditorSettings.h"
/////////////////////////////////////////////////////
// SNodeTitle
void SNodeTitle::Construct(const FArguments& InArgs, UEdGraphNode* InNode)
{
GraphNode = InNode;
ExtraLineStyle = InArgs._ExtraLineStyle;
CachedSize = FVector2D::ZeroVector;
// If the user set the text, use it, otherwise use the node title by default
if(InArgs._Text.IsSet())
{
TitleText = InArgs._Text;
}
else
{
TitleText = TAttribute<FText>(this, &SNodeTitle::GetNodeTitle);
}
NodeTitleCache.SetCachedText(TitleText.Get(), GraphNode);
RebuildWidget();
}
void SNodeTitle::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
CachedSize = AllottedGeometry.GetLocalSize();
// Checks to see if the cached string is valid, and if not, updates it.
if (NodeTitleCache.IsOutOfDate(GraphNode))
{
NodeTitleCache.SetCachedText(TitleText.Get(), GraphNode);
RebuildWidget();
}
}
FText SNodeTitle::GetNodeTitle() const
{
if (GetDefault<UBlueprintEditorSettings>()->bBlueprintNodeUniqueNames && GraphNode)
{
return FText::FromName(GraphNode->GetFName());
}
else
{
return (GraphNode != NULL)
? GraphNode->GetNodeTitle(ENodeTitleType::FullTitle)
: NSLOCTEXT("GraphEditor", "NullNode", "Null Node");
}
}
FText SNodeTitle::GetHeadTitle() const
{
return (GraphNode && GraphNode->bCanRenameNode) ? GraphNode->GetNodeTitle(ENodeTitleType::EditableTitle) : CachedHeadTitle;
}
FVector2D SNodeTitle::GetTitleSize() const
{
return CachedSize;
}
void SNodeTitle::RebuildWidget()
{
// Create the box to contain the lines
TSharedPtr<SVerticalBox> VerticalBox;
this->ChildSlot
[
SAssignNew(VerticalBox, SVerticalBox)
];
// Break the title into lines
TArray<FString> Lines;
const FString CachedTitleString = NodeTitleCache.GetCachedText().ToString().Replace(TEXT("\r"), TEXT(""));
CachedTitleString.ParseIntoArray(Lines, TEXT("\n"), false);
if (Lines.Num())
{
CachedHeadTitle = FText::FromString(Lines[0]);
}
// Pad the height of multi-line node titles to be a multiple of the graph snap grid taller than
// single-line nodes, so the pins will still line up if you place the node N cell snaps above
if (Lines.Num() > 1)
{
// Note: This code a little fragile, and will need to be updated if the font or padding of titles
// changes in the future, but the failure mode is just a slight misalignment.
const int32 EstimatedExtraHeight = (Lines.Num() - 1) * 13;
const int32 SnapSize = (int32)SNodePanel::GetSnapGridSize();
const int32 PadSize = SnapSize - (EstimatedExtraHeight % SnapSize);
if (PadSize < SnapSize)
{
VerticalBox->AddSlot()
[
SNew(SSpacer)
.Size(FVector2D(1.0f, PadSize))
];
}
}
// Make a separate widget for each line, using a less obvious style for subsequent lines
for (int32 Index = 1; Index < Lines.Num(); ++Index)
{
VerticalBox->AddSlot()
.AutoHeight()
[
SNew(STextBlock)
.TextStyle( FEditorStyle::Get(), ExtraLineStyle )
.Text(FText::FromString(Lines[Index]))
];
}
}
/////////////////////////////////////////////////////
// SGraphNode
// Check whether drag and drop functionality is permitted on the given node
bool SGraphNode::CanAllowInteractionUsingDragDropOp( const UEdGraphNode* GraphNodePtr, const TSharedPtr<FActorDragDropOp>& DragDropOp )
{
bool bReturn = false;
//Allow interaction only if this node is a literal type object.
//Only change actor reference if a single actor reference is dragged from the outliner.
if( GraphNodePtr->IsA( UK2Node_Literal::StaticClass() ) && DragDropOp->Actors.Num() == 1 )
{
bReturn = true;
}
return bReturn;
}
void SGraphNode::SetIsEditable(TAttribute<bool> InIsEditable)
{
IsEditable = InIsEditable;
}
bool SGraphNode::IsNodeEditable() const
{
bool bIsEditable = OwnerGraphPanelPtr.IsValid() ? OwnerGraphPanelPtr.Pin()->IsGraphEditable() : true;
return IsEditable.Get() && bIsEditable;
}
/** Set event when node is double clicked */
void SGraphNode::SetDoubleClickEvent(FSingleNodeEvent InDoubleClickEvent)
{
OnDoubleClick = InDoubleClickEvent;
}
void SGraphNode::SetVerifyTextCommitEvent(FOnNodeVerifyTextCommit InOnVerifyTextCommit)
{
OnVerifyTextCommit = InOnVerifyTextCommit;
}
void SGraphNode::SetTextCommittedEvent(FOnNodeTextCommitted InOnTextCommitted)
{
OnTextCommitted = InOnTextCommitted;
}
void SGraphNode::OnCommentTextCommitted(const FText& NewComment, ETextCommit::Type CommitInfo)
{
GetNodeObj()->OnUpdateCommentText(NewComment.ToString());
}
void SGraphNode::OnCommentBubbleToggled(bool bInCommentBubbleVisible)
{
GetNodeObj()->OnCommentBubbleToggled(bInCommentBubbleVisible);
}
void SGraphNode::SetDisallowedPinConnectionEvent(SGraphEditor::FOnDisallowedPinConnection InOnDisallowedPinConnection)
{
OnDisallowedPinConnection = InOnDisallowedPinConnection;
}
void SGraphNode::OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return;
}
// Is someone dragging a connection?
if (Operation->IsOfType<FGraphEditorDragDropAction>())
{
// Inform the Drag and Drop operation that we are hovering over this pin.
TSharedPtr<FGraphEditorDragDropAction> DragConnectionOp = StaticCastSharedPtr<FGraphEditorDragDropAction>(Operation);
DragConnectionOp->SetHoveredNode( SharedThis(this) );
}
else if (Operation->IsOfType<FActorDragDropGraphEdOp>())
{
TSharedPtr<FActorDragDropGraphEdOp> DragConnectionOp = StaticCastSharedPtr<FActorDragDropGraphEdOp>(Operation);
if( GraphNode->IsA( UK2Node_Literal::StaticClass() ) )
{
//Show tool tip only if a single actor is dragged
if( DragConnectionOp->Actors.Num() == 1 )
{
UK2Node_Literal* LiteralNode = CastChecked< UK2Node_Literal > ( GraphNode );
//Check whether this node is already referencing the same actor dragged from outliner
if( LiteralNode->GetObjectRef() != DragConnectionOp->Actors[0].Get() )
{
DragConnectionOp->SetToolTip(FActorDragDropGraphEdOp::ToolTip_Compatible);
}
}
else
{
//For more that one actor dragged on to a literal node, show tooltip as incompatible
DragConnectionOp->SetToolTip(FActorDragDropGraphEdOp::ToolTip_MultipleSelection_Incompatible);
}
}
else
{
DragConnectionOp->SetToolTip( (DragConnectionOp->Actors.Num() == 1) ? FActorDragDropGraphEdOp::ToolTip_Incompatible : FActorDragDropGraphEdOp::ToolTip_MultipleSelection_Incompatible);
}
}
else if (Operation->IsOfType<FBoneDragDropOp>())
{
//@TODO: A2REMOVAL: No support for A3 nodes handling this drag-drop op yet!
}
}
void SGraphNode::OnDragLeave( const FDragDropEvent& DragDropEvent )
{
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return;
}
// Is someone dragging a connection?
if (Operation->IsOfType<FGraphEditorDragDropAction>())
{
// Inform the Drag and Drop operation that we are not hovering any pins
TSharedPtr<FGraphEditorDragDropAction> DragConnectionOp = StaticCastSharedPtr<FGraphEditorDragDropAction>(Operation);
DragConnectionOp->SetHoveredNode( TSharedPtr<SGraphNode>(NULL) );
}
else if (Operation->IsOfType<FActorDragDropGraphEdOp>())
{
//Default tool tip
TSharedPtr<FActorDragDropGraphEdOp> DragConnectionOp = StaticCastSharedPtr<FActorDragDropGraphEdOp>(Operation);
DragConnectionOp->ResetToDefaultToolTip();
}
else if (Operation->IsOfType<FAssetDragDropOp>())
{
TSharedPtr<FAssetDragDropOp> AssetOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
AssetOp->ResetToDefaultToolTip();
}
else if (Operation->IsOfType<FBoneDragDropOp>())
{
//@TODO: A2REMOVAL: No support for A3 nodes handling this drag-drop op yet!
}
}
FReply SGraphNode::OnDragOver( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
TSharedPtr<FAssetDragDropOp> AssetOp = DragDropEvent.GetOperationAs<FAssetDragDropOp>();
if (AssetOp.IsValid())
{
if(GraphNode != NULL && GraphNode->GetSchema() != NULL)
{
bool bOkIcon = false;
FString TooltipText;
if (AssetOp->HasAssets())
{
GraphNode->GetSchema()->GetAssetsNodeHoverMessage(AssetOp->GetAssets(), GraphNode, TooltipText, bOkIcon);
}
bool bReadOnly = OwnerGraphPanelPtr.IsValid() ? !OwnerGraphPanelPtr.Pin()->IsGraphEditable() : false;
bOkIcon = bReadOnly ? false : bOkIcon;
const FSlateBrush* TooltipIcon = bOkIcon ? FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")) : FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"));;
AssetOp->SetToolTip(FText::FromString(TooltipText), TooltipIcon);
}
return FReply::Handled();
}
return FReply::Unhandled();
}
/** Given a coordinate in SGraphPanel space (i.e. panel widget space), return the same coordinate in graph space while taking zoom and panning into account */
FVector2D SGraphNode::NodeCoordToGraphCoord( const FVector2D& NodeSpaceCoordinate ) const
{
TSharedPtr<SGraphPanel> OwnerCanvas = OwnerGraphPanelPtr.Pin();
if (OwnerCanvas.IsValid())
{
//@TODO: NodeSpaceCoordinate != PanelCoordinate
FVector2D PanelSpaceCoordinate = NodeSpaceCoordinate;
return OwnerCanvas->PanelCoordToGraphCoord( PanelSpaceCoordinate );
}
else
{
return FVector2D::ZeroVector;
}
}
FReply SGraphNode::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
bool bReadOnly = OwnerGraphPanelPtr.IsValid() ? !OwnerGraphPanelPtr.Pin()->IsGraphEditable() : false;
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid() || bReadOnly)
{
return FReply::Unhandled();
}
// Is someone dropping a connection onto this node?
if (Operation->IsOfType<FGraphEditorDragDropAction>())
{
TSharedPtr<FGraphEditorDragDropAction> DragConnectionOp = StaticCastSharedPtr<FGraphEditorDragDropAction>(Operation);
const FVector2D NodeAddPosition = NodeCoordToGraphCoord( MyGeometry.AbsoluteToLocal( DragDropEvent.GetScreenSpacePosition() ) );
FReply Result = DragConnectionOp->DroppedOnNode(DragDropEvent.GetScreenSpacePosition(), NodeAddPosition);
if (Result.IsEventHandled() && (GraphNode != NULL))
{
GraphNode->GetGraph()->NotifyGraphChanged();
}
return Result;
}
else if (Operation->IsOfType<FActorDragDropGraphEdOp>())
{
TSharedPtr<FActorDragDropGraphEdOp> DragConnectionOp = StaticCastSharedPtr<FActorDragDropGraphEdOp>(Operation);
if( CanAllowInteractionUsingDragDropOp( GraphNode, DragConnectionOp ) )
{
UK2Node_Literal* LiteralNode = CastChecked< UK2Node_Literal > ( GraphNode );
//Check whether this node is already referencing the same actor
if( LiteralNode->GetObjectRef() != DragConnectionOp->Actors[0].Get() )
{
//Replace literal node's object reference
LiteralNode->SetObjectRef( DragConnectionOp->Actors[ 0 ].Get() );
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(CastChecked<UEdGraph>(GraphNode->GetOuter()));
if (Blueprint != nullptr)
{
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
return FReply::Handled();
}
else if (Operation->IsOfType<FAssetDragDropOp>())
{
UEdGraphNode* Node = GetNodeObj();
if(Node != NULL && Node->GetSchema() != NULL)
{
TSharedPtr<FAssetDragDropOp> AssetOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
if (AssetOp->HasAssets())
{
Node->GetSchema()->DroppedAssetsOnNode(AssetOp->GetAssets(), DragDropEvent.GetScreenSpacePosition(), Node);
}
}
return FReply::Handled();
}
else if (Operation->IsOfType<FBoneDragDropOp>())
{
//@TODO: A2REMOVAL: No support for A3 nodes handling this drag-drop op yet!
}
return FReply::Unhandled();
}
/**
* The system calls this method to notify the widget that a mouse button was pressed within it. This event is bubbled.
*
* @param MyGeometry The Geometry of the widget receiving the event
* @param MouseEvent Information about the input event
*
* @return Whether the event was handled along with possible requests for the system to take action.
*/
FReply SGraphNode::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
return FReply::Unhandled();
}
// The system calls this method to notify the widget that a mouse button was release within it. This event is bubbled.
FReply SGraphNode::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
return FReply::Unhandled();
}
// Called when a mouse button is double clicked. Override this in derived classes
FReply SGraphNode::OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent )
{
if(InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
OnDoubleClick.ExecuteIfBound(GraphNode);
return FReply::Handled();
}
return FReply::Unhandled();
}
TSharedPtr<IToolTip> SGraphNode::GetToolTip()
{
TSharedPtr<IToolTip> CurrentTooltip = SWidget::GetToolTip();
if (!CurrentTooltip.IsValid())
{
TSharedPtr<SToolTip> ComplexTooltip = GetComplexTooltip();
if (ComplexTooltip.IsValid())
{
SetToolTip(ComplexTooltip);
bProvidedComplexTooltip = true;
}
}
return SWidget::GetToolTip();
}
void SGraphNode::OnToolTipClosing()
{
if (bProvidedComplexTooltip)
{
SetToolTip(NULL);
bProvidedComplexTooltip = false;
}
}
void SGraphNode::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
CachedUnscaledPosition = AllottedGeometry.AbsolutePosition/AllottedGeometry.Scale;
SNodePanel::SNode::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
const bool bNeedToUpdateCommentBubble = GetNodeObj()->ShouldMakeCommentBubbleVisible();
if (IsHovered() || bNeedToUpdateCommentBubble)
{
if (FNodeSlot* CommentSlot = GetSlot(ENodeZone::TopCenter))
{
TSharedPtr<SCommentBubble> CommentBubble = StaticCastSharedRef<SCommentBubble>(CommentSlot->GetWidget());
if (CommentBubble.IsValid())
{
if (bNeedToUpdateCommentBubble)
{
CommentBubble->SetCommentBubbleVisibility(true);
GetNodeObj()->SetMakeCommentBubbleVisible(false);
}
else
{
CommentBubble->TickVisibility(InCurrentTime, InDeltaTime);
}
}
}
}
}
bool SGraphNode::IsSelectedExclusively() const
{
TSharedPtr<SGraphPanel> OwnerPanel = OwnerGraphPanelPtr.Pin();
if (!OwnerPanel->HasKeyboardFocus() || OwnerPanel->SelectionManager.GetSelectedNodes().Num() > 1)
{
return false;
}
return OwnerPanel->SelectionManager.IsNodeSelected(GraphNode);
}
/** @param OwnerPanel The GraphPanel that this node belongs to */
void SGraphNode::SetOwner( const TSharedRef<SGraphPanel>& OwnerPanel )
{
check( !OwnerGraphPanelPtr.IsValid() );
SetParentPanel(OwnerPanel);
OwnerGraphPanelPtr = OwnerPanel;
GraphNode->DEPRECATED_NodeWidget = SharedThis(this);
/*Once we have an owner, and if hide Unused pins is enabled, we need to remake our pins to drop the hidden ones*/
if(OwnerGraphPanelPtr.Pin()->GetPinVisibility() != SGraphEditor::Pin_Show
&& LeftNodeBox.IsValid()
&& RightNodeBox.IsValid())
{
this->LeftNodeBox->ClearChildren();
this->RightNodeBox->ClearChildren();
CreatePinWidgets();
}
}
/** @param NewPosition The Node should be relocated to this position in the graph panel */
void SGraphNode::MoveTo( const FVector2D& NewPosition, FNodeSet& NodeFilter )
{
if ( !NodeFilter.Find( SharedThis( this )))
{
if (GraphNode && !RequiresSecondPassLayout())
{
NodeFilter.Add( SharedThis( this ) );
GraphNode->Modify();
GraphNode->NodePosX = NewPosition.X;
GraphNode->NodePosY = NewPosition.Y;
}
}
}
/** @return the Node's position within the graph */
FVector2D SGraphNode::GetPosition() const
{
return FVector2D( GraphNode->NodePosX, GraphNode->NodePosY );
}
FString SGraphNode::GetEditableNodeTitle() const
{
if (GraphNode != NULL)
{
// Trying to catch a non-reproducible crash in this function
check(GraphNode->IsValidLowLevel());
}
if(GraphNode)
{
return GraphNode->GetNodeTitle(ENodeTitleType::EditableTitle).ToString();
}
return NSLOCTEXT("GraphEditor", "NullNode", "Null Node").ToString();
// Get the portion of the node that is actually editable text (may be a subsection of the title, or something else entirely)
return (GraphNode != NULL)
? GraphNode->GetNodeTitle(ENodeTitleType::EditableTitle).ToString()
: NSLOCTEXT("GraphEditor", "NullNode", "Null Node").ToString();
}
FText SGraphNode::GetEditableNodeTitleAsText() const
{
FString NewString = GetEditableNodeTitle();
return FText::FromString(NewString);
}
FString SGraphNode::GetNodeComment() const
{
return GetNodeObj()->NodeComment;
}
UObject* SGraphNode::GetObjectBeingDisplayed() const
{
return GetNodeObj();
}
FSlateColor SGraphNode::GetNodeTitleColor() const
{
FLinearColor ReturnTitleColor = GraphNode->IsDeprecated() ? FLinearColor::Red : GetNodeObj()->GetNodeTitleColor();
if(!GraphNode->IsNodeEnabled())
{
ReturnTitleColor *= FLinearColor(0.5f, 0.5f, 0.5f, 0.4f);
}
else
{
ReturnTitleColor.A = FadeCurve.GetLerp();
}
return ReturnTitleColor;
}
FSlateColor SGraphNode::GetNodeBodyColor() const
{
FLinearColor ReturnBodyColor = FLinearColor::White;
if(!GraphNode->IsNodeEnabled())
{
ReturnBodyColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.5f);
}
return ReturnBodyColor;
}
FSlateColor SGraphNode::GetNodeTitleIconColor() const
{
FLinearColor ReturnIconColor = IconColor;
if(!GraphNode->IsNodeEnabled())
{
ReturnIconColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.3f);
}
return ReturnIconColor;
}
FLinearColor SGraphNode::GetNodeTitleTextColor() const
{
FLinearColor ReturnTextColor = FLinearColor::White;
if(!GraphNode->IsNodeEnabled())
{
ReturnTextColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.3f);
}
return ReturnTextColor;
}
FSlateColor SGraphNode::GetNodeCommentColor() const
{
return GetNodeObj()->GetNodeCommentColor();
}
/** @return the tooltip to display when over the node */
FText SGraphNode::GetNodeTooltip() const
{
if (GraphNode != NULL)
{
// Display the native title of the node when alt is held
if(FSlateApplication::Get().GetModifierKeys().IsAltDown())
{
return FText::FromString(GraphNode->GetNodeTitle(ENodeTitleType::ListView).BuildSourceString());
}
FText TooltipText = GraphNode->GetTooltipText();
if (UEdGraph* Graph = GraphNode->GetGraph())
{
// If the node resides in an intermediate graph, show the UObject name for debug purposes
if (Graph->HasAnyFlags(RF_Transient))
{
FFormatNamedArguments Args;
Args.Add(TEXT("NodeName"), FText::FromString(GraphNode->GetName()));
Args.Add(TEXT("TooltipText"), TooltipText);
TooltipText = FText::Format(NSLOCTEXT("GraphEditor", "GraphNodeTooltip", "{NodeName}\n\n{TooltipText}"), Args);
}
}
if (TooltipText.IsEmpty())
{
TooltipText = GraphNode->GetNodeTitle(ENodeTitleType::FullTitle);
}
return TooltipText;
}
else
{
return NSLOCTEXT("GraphEditor", "InvalidGraphNode", "<Invalid graph node>");
}
}
/** @return the node being observed by this widget*/
UEdGraphNode* SGraphNode::GetNodeObj() const
{
return GraphNode;
}
TSharedRef<SGraphNode> SGraphNode::GetNodeUnderMouse(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
return StaticCastSharedRef<SGraphNode>(AsShared());
}
TSharedPtr<SGraphPanel> SGraphNode::GetOwnerPanel() const
{
return OwnerGraphPanelPtr.Pin();
}
void SGraphNode::UpdateErrorInfo()
{
//Check for node errors/warnings
if (GraphNode->bHasCompilerMessage)
{
if (GraphNode->ErrorType <= EMessageSeverity::Error)
{
ErrorMsg = FString( TEXT("ERROR!") );
ErrorColor = FEditorStyle::GetColor("ErrorReporting.BackgroundColor");
}
else if (GraphNode->ErrorType <= EMessageSeverity::Warning)
{
ErrorMsg = FString( TEXT("WARNING!") );
ErrorColor = FEditorStyle::GetColor("ErrorReporting.WarningBackgroundColor");
}
else
{
ErrorMsg = FString( TEXT("NOTE") );
ErrorColor = FEditorStyle::GetColor("InfoReporting.BackgroundColor");
}
}
else if (!GraphNode->NodeUpgradeMessage.IsEmpty())
{
ErrorMsg = FString(TEXT("UPGRADE NOTE"));
ErrorColor = FEditorStyle::GetColor("InfoReporting.BackgroundColor");
}
else
{
ErrorColor = FLinearColor(0,0,0);
ErrorMsg.Empty();
}
}
void SGraphNode::SetupErrorReporting()
{
UpdateErrorInfo();
if( !ErrorReporting.IsValid() )
{
TSharedPtr<SErrorText> ErrorTextWidget;
// generate widget
SAssignNew(ErrorTextWidget, SErrorText)
.BackgroundColor( this, &SGraphNode::GetErrorColor )
.ToolTipText( this, &SGraphNode::GetErrorMsgToolTip );
ErrorReporting = ErrorTextWidget;
}
ErrorReporting->SetError(ErrorMsg);
}
TSharedRef<SWidget> SGraphNode::CreateTitleWidget(TSharedPtr<SNodeTitle> NodeTitle)
{
SAssignNew(InlineEditableText, SInlineEditableTextBlock)
.Style(FEditorStyle::Get(), "Graph.Node.NodeTitleInlineEditableText")
.Text(NodeTitle.Get(), &SNodeTitle::GetHeadTitle)
.OnVerifyTextChanged(this, &SGraphNode::OnVerifyNameTextChanged)
.OnTextCommitted(this, &SGraphNode::OnNameTextCommited)
.IsReadOnly(this, &SGraphNode::IsNameReadOnly)
.IsSelected(this, &SGraphNode::IsSelectedExclusively);
InlineEditableText->SetColorAndOpacity(TAttribute<FLinearColor>::Create(TAttribute<FLinearColor>::FGetter::CreateSP(this, &SGraphNode::GetNodeTitleTextColor)));
return InlineEditableText.ToSharedRef();
}
/**
* Update this GraphNode to match the data that it is observing
*/
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SGraphNode::UpdateGraphNode()
{
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();
//
// ______________________
// | TITLE AREA |
// +-------+------+-------+
// | (>) L | | R (>) |
// | (>) E | | I (>) |
// | (>) F | | G (>) |
// | (>) T | | H (>) |
// | | | T (>) |
// |_______|______|_______|
//
TSharedPtr<SVerticalBox> MainVerticalBox;
SetupErrorReporting();
TSharedPtr<SNodeTitle> NodeTitle = SNew(SNodeTitle, GraphNode);
// Get node icon
IconColor = FLinearColor::White;
const FSlateBrush* IconBrush = NULL;
if (GraphNode != NULL && GraphNode->ShowPaletteIconOnNode())
{
IconBrush = GraphNode->GetIconAndTint(IconColor).GetOptionalIcon();
}
TSharedRef<SOverlay> DefaultTitleAreaWidget =
SNew(SOverlay)
+SOverlay::Slot()
[
SNew(SImage)
.Image( FEditorStyle::GetBrush("Graph.Node.TitleGloss") )
.ColorAndOpacity( this, &SGraphNode::GetNodeTitleIconColor )
]
+SOverlay::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(SBorder)
.BorderImage( FEditorStyle::GetBrush("Graph.Node.ColorSpill") )
// The extra margin on the right
// is for making the color spill stretch well past the node title
.Padding( FMargin(10,5,30,3) )
.BorderBackgroundColor( this, &SGraphNode::GetNodeTitleColor )
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Top)
.Padding(FMargin(0.f, 0.f, 4.f, 0.f))
.AutoWidth()
[
SNew(SImage)
.Image(IconBrush)
.ColorAndOpacity(this, &SGraphNode::GetNodeTitleIconColor)
]
+ SHorizontalBox::Slot()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
CreateTitleWidget(NodeTitle)
]
+ SVerticalBox::Slot()
.AutoHeight()
[
NodeTitle.ToSharedRef()
]
]
]
]
+SOverlay::Slot()
.VAlign(VAlign_Top)
[
SNew(SBorder)
.Visibility(EVisibility::HitTestInvisible)
.BorderImage( FEditorStyle::GetBrush( "Graph.Node.TitleHighlight" ) )
.BorderBackgroundColor( this, &SGraphNode::GetNodeTitleIconColor )
[
SNew(SSpacer)
.Size(FVector2D(20,20))
]
];
SetDefaultTitleAreaWidget(DefaultTitleAreaWidget);
TSharedRef<SWidget> TitleAreaWidget =
SNew(SLevelOfDetailBranchNode)
.UseLowDetailSlot(this, &SGraphNode::UseLowDetailNodeTitles)
.LowDetail()
[
SNew(SBorder)
.BorderImage( FEditorStyle::GetBrush("Graph.Node.ColorSpill") )
.Padding( FMargin(75.0f, 22.0f) ) // Saving enough space for a 'typical' title so the transition isn't quite so abrupt
.BorderBackgroundColor( this, &SGraphNode::GetNodeTitleColor )
]
.HighDetail()
[
DefaultTitleAreaWidget
];
if (!SWidget::GetToolTip().IsValid())
{
TSharedRef<SToolTip> DefaultToolTip = IDocumentation::Get()->CreateToolTip( TAttribute< FText >( this, &SGraphNode::GetNodeTooltip ), NULL, GraphNode->GetDocumentationLink(), GraphNode->GetDocumentationExcerptName() );
SetToolTip(DefaultToolTip);
}
// Setup a meta tag for this node
FGraphNodeMetaData TagMeta(TEXT("Graphnode"));
PopulateMetaTag(&TagMeta);
TSharedPtr<SVerticalBox> InnerVerticalBox;
this->ContentScale.Bind( this, &SGraphNode::GetContentScale );
InnerVerticalBox = SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
.Padding(Settings->GetNonPinNodeBodyPadding())
[
TitleAreaWidget
]
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
[
CreateNodeContentArea()
];
if ((GraphNode->GetDesiredEnabledState() != ENodeEnabledState::Enabled) && !GraphNode->IsAutomaticallyPlacedGhostNode())
{
const bool bDevelopmentOnly = GraphNode->GetDesiredEnabledState() == ENodeEnabledState::DevelopmentOnly;
const FText StatusMessage = bDevelopmentOnly ? NSLOCTEXT("SGraphNode", "DevelopmentOnly", "Development Only") : NSLOCTEXT("SGraphNode", "DisabledNode", "Disabled");
const FText StatusMessageTooltip = bDevelopmentOnly ?
NSLOCTEXT("SGraphNode", "DevelopmentOnlyTooltip", "This node will only be executed in the editor and in Development builds in a packaged game (it will be treated as disabled in Shipping or Test builds cooked from a commandlet)") :
NSLOCTEXT("SGraphNode", "DisabledNodeTooltip", "This node is currently disabled and will not be executed");
InnerVerticalBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
.Padding(FMargin(2, 0))
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush(bDevelopmentOnly ? "Graph.Node.DevelopmentBanner" : "Graph.Node.DisabledBanner"))
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(STextBlock)
.Text(StatusMessage)
.ToolTipText(StatusMessageTooltip)
.Justification(ETextJustify::Center)
.ColorAndOpacity(FLinearColor::White)
.ShadowOffset(FVector2D::UnitVector)
.Visibility(EVisibility::Visible)
]
];
}
InnerVerticalBox->AddSlot()
.AutoHeight()
.Padding(Settings->GetNonPinNodeBodyPadding())
[
ErrorReporting->AsWidget()
];
this->GetOrAddSlot( ENodeZone::Center )
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SAssignNew(MainVerticalBox, SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SOverlay)
.AddMetaData<FGraphNodeMetaData>(TagMeta)
+SOverlay::Slot()
.Padding(Settings->GetNonPinNodeBodyPadding())
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("Graph.Node.Body"))
.ColorAndOpacity(this, &SGraphNode::GetNodeBodyColor)
]
+SOverlay::Slot()
[
InnerVerticalBox.ToSharedRef()
]
]
];
// Create comment bubble
TSharedPtr<SCommentBubble> CommentBubble;
const FSlateColor CommentColor = GetDefault<UGraphEditorSettings>()->DefaultCommentNodeTitleColor;
SAssignNew( CommentBubble, SCommentBubble )
.GraphNode( GraphNode )
.Text( this, &SGraphNode::GetNodeComment )
.OnTextCommitted( this, &SGraphNode::OnCommentTextCommitted )
.OnToggled( this, &SGraphNode::OnCommentBubbleToggled )
.ColorAndOpacity( CommentColor )
.AllowPinning( true )
.EnableTitleBarBubble( true )
.EnableBubbleCtrls( true )
.GraphLOD( this, &SGraphNode::GetCurrentLOD )
.IsGraphNodeHovered( this, &SGraphNode::IsHovered );
GetOrAddSlot( ENodeZone::TopCenter )
.SlotOffset( TAttribute<FVector2D>( CommentBubble.Get(), &SCommentBubble::GetOffset ))
.SlotSize( TAttribute<FVector2D>( CommentBubble.Get(), &SCommentBubble::GetSize ))
.AllowScaling( TAttribute<bool>( CommentBubble.Get(), &SCommentBubble::IsScalingAllowed ))
.VAlign( VAlign_Top )
[
CommentBubble.ToSharedRef()
];
CreateBelowWidgetControls(MainVerticalBox);
CreatePinWidgets();
CreateInputSideAddButton(LeftNodeBox);
CreateOutputSideAddButton(RightNodeBox);
CreateBelowPinControls(InnerVerticalBox);
CreateAdvancedViewArrow(InnerVerticalBox);
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedRef<SWidget> SGraphNode::CreateNodeContentArea()
{
// NODE CONTENT AREA
return SNew(SBorder)
.BorderImage( FEditorStyle::GetBrush("NoBorder") )
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding( FMargin(0,3) )
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.HAlign(HAlign_Left)
.FillWidth(1.0f)
[
// LEFT
SAssignNew(LeftNodeBox, SVerticalBox)
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Right)
[
// RIGHT
SAssignNew(RightNodeBox, SVerticalBox)
]
];
}
/** Returns visibility of AdvancedViewButton */
EVisibility SGraphNode::AdvancedViewArrowVisibility() const
{
const bool bShowAdvancedViewArrow = GraphNode && (ENodeAdvancedPins::NoPins != GraphNode->AdvancedPinDisplay);
return bShowAdvancedViewArrow ? EVisibility::Visible : EVisibility::Collapsed;
}
void SGraphNode::OnAdvancedViewChanged( const ECheckBoxState NewCheckedState )
{
if(GraphNode && (ENodeAdvancedPins::NoPins != GraphNode->AdvancedPinDisplay))
{
const bool bAdvancedPinsHidden = (NewCheckedState != ECheckBoxState::Checked);
GraphNode->AdvancedPinDisplay = bAdvancedPinsHidden ? ENodeAdvancedPins::Hidden : ENodeAdvancedPins::Shown;
}
}
ECheckBoxState SGraphNode::IsAdvancedViewChecked() const
{
const bool bAdvancedPinsHidden = GraphNode && (ENodeAdvancedPins::Hidden == GraphNode->AdvancedPinDisplay);
return bAdvancedPinsHidden ? ECheckBoxState::Unchecked : ECheckBoxState::Checked;
}
const FSlateBrush* SGraphNode::GetAdvancedViewArrow() const
{
const bool bAdvancedPinsHidden = GraphNode && (ENodeAdvancedPins::Hidden == GraphNode->AdvancedPinDisplay);
return FEditorStyle::GetBrush(bAdvancedPinsHidden ? TEXT("Kismet.TitleBarEditor.ArrowDown") : TEXT("Kismet.TitleBarEditor.ArrowUp"));
}
/** Create widget to show/hide advanced pins */
void SGraphNode::CreateAdvancedViewArrow(TSharedPtr<SVerticalBox> MainBox)
{
const bool bHidePins = OwnerGraphPanelPtr.IsValid() && (OwnerGraphPanelPtr.Pin()->GetPinVisibility() != SGraphEditor::Pin_Show);
const bool bAnyAdvancedPin = GraphNode && (ENodeAdvancedPins::NoPins != GraphNode->AdvancedPinDisplay);
if(!bHidePins && GraphNode && MainBox.IsValid())
{
MainBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
.Padding(3, 0, 3, 3)
[
SNew(SCheckBox)
.Visibility(this, &SGraphNode::AdvancedViewArrowVisibility)
.OnCheckStateChanged( this, &SGraphNode::OnAdvancedViewChanged )
.IsChecked( this, &SGraphNode::IsAdvancedViewChecked )
.Cursor(EMouseCursor::Default)
.Style(FEditorStyle::Get(), "Graph.Node.AdvancedView")
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
SNew(SImage)
. Image(this, &SGraphNode::GetAdvancedViewArrow)
]
]
];
}
}
bool SGraphNode::ShouldPinBeHidden(const UEdGraphPin* InPin) const
{
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GraphNode->GetSchema());
bool bHideNoConnectionPins = false;
bool bHideNoConnectionNoDefaultPins = false;
// Not allowed to hide exec pins
const bool bCanHidePin = (K2Schema && (InPin->PinType.PinCategory != K2Schema->PC_Exec));
if (OwnerGraphPanelPtr.IsValid() && bCanHidePin)
{
bHideNoConnectionPins = OwnerGraphPanelPtr.Pin()->GetPinVisibility() == SGraphEditor::Pin_HideNoConnection;
bHideNoConnectionNoDefaultPins = OwnerGraphPanelPtr.Pin()->GetPinVisibility() == SGraphEditor::Pin_HideNoConnectionNoDefault;
}
const bool bIsOutputPin = InPin->Direction == EGPD_Output;
const bool bPinHasDefaultValue = !InPin->DefaultValue.IsEmpty() || (InPin->DefaultObject != NULL);
const bool bIsSelfTarget = K2Schema && (InPin->PinType.PinCategory == K2Schema->PC_Object) && (InPin->PinName == K2Schema->PN_Self);
const bool bPinHasValidDefault = !bIsOutputPin && (bPinHasDefaultValue || bIsSelfTarget);
const bool bPinHasConections = InPin->LinkedTo.Num() > 0;
const bool bPinDesiresToBeHidden = InPin->bHidden || (bHideNoConnectionPins && !bPinHasConections) || (bHideNoConnectionNoDefaultPins && !bPinHasConections && !bPinHasValidDefault);
// No matter how strong the desire, a pin with connections can never be hidden!
const bool bShowPin = !bPinDesiresToBeHidden || bPinHasConections;
return bShowPin;
}
void SGraphNode::CreateStandardPinWidget(UEdGraphPin* CurPin)
{
const bool bShowPin = ShouldPinBeHidden(CurPin);
if (bShowPin)
{
TSharedPtr<SGraphPin> NewPin = CreatePinWidget(CurPin);
check(NewPin.IsValid());
this->AddPin(NewPin.ToSharedRef());
}
}
void SGraphNode::CreatePinWidgets()
{
// Create Pin widgets for each of the pins.
for (int32 PinIndex = 0; PinIndex < GraphNode->Pins.Num(); ++PinIndex)
{
UEdGraphPin* CurPin = GraphNode->Pins[PinIndex];
if ( !ensureMsgf(CurPin->GetOuter() == GraphNode
, TEXT("Graph node ('%s' - %s) has an invalid %s pin: '%s'; (with a bad %s outer: '%s'); skiping creation of a widget for this pin.")
, *GraphNode->GetNodeTitle(ENodeTitleType::ListView).ToString()
, *GraphNode->GetPathName()
, (CurPin->Direction == EEdGraphPinDirection::EGPD_Input) ? TEXT("input") : TEXT("output")
, CurPin->PinFriendlyName.IsEmpty() ? *CurPin->PinName : *CurPin->PinFriendlyName.ToString()
, CurPin->GetOuter() ? *CurPin->GetOuter()->GetClass()->GetName() : TEXT("UNKNOWN")
, CurPin->GetOuter() ? *CurPin->GetOuter()->GetPathName() : TEXT("NULL")) )
{
continue;
}
CreateStandardPinWidget(CurPin);
}
}
TSharedPtr<SGraphPin> SGraphNode::CreatePinWidget(UEdGraphPin* Pin) const
{
return FNodeFactory::CreatePinWidget(Pin);
}
void SGraphNode::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
PinToAdd->SetOwner(SharedThis(this));
const UEdGraphPin* PinObj = PinToAdd->GetPinObj();
const bool bAdvancedParameter = (PinObj != nullptr) && PinObj->bAdvancedView;
if (bAdvancedParameter)
{
PinToAdd->SetVisibility( TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced) );
}
if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Input)
{
LeftNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(Settings->GetInputPinPadding())
[
PinToAdd
];
InputPins.Add(PinToAdd);
}
else // Direction == EEdGraphPinDirection::EGPD_Output
{
RightNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(Settings->GetOutputPinPadding())
[
PinToAdd
];
OutputPins.Add(PinToAdd);
}
}
/**
* Get all the pins found on this node.
*
* @param AllPins The set of pins found on this node.
*/
void SGraphNode::GetPins( TSet< TSharedRef<SWidget> >& AllPins ) const
{
for( int32 PinIndex=0; PinIndex < this->InputPins.Num(); ++PinIndex )
{
AllPins.Add(InputPins[PinIndex]);
}
for( int32 PinIndex=0; PinIndex < this->OutputPins.Num(); ++PinIndex )
{
AllPins.Add(OutputPins[PinIndex]);
}
}
void SGraphNode::GetPins( TArray< TSharedRef<SWidget> >& AllPins ) const
{
for( int32 PinIndex=0; PinIndex < this->InputPins.Num(); ++PinIndex )
{
AllPins.Add(InputPins[PinIndex]);
}
for( int32 PinIndex=0; PinIndex < this->OutputPins.Num(); ++PinIndex )
{
AllPins.Add(OutputPins[PinIndex]);
}
}
TSharedPtr<SGraphPin> SGraphNode::GetHoveredPin( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) const
{
// We just need to find the one WidgetToFind among our descendants.
TSet< TSharedRef<SWidget> > MyPins;
{
GetPins( MyPins );
}
TMap<TSharedRef<SWidget>, FArrangedWidget> Result;
FindChildGeometries(MyGeometry, MyPins, Result);
if ( Result.Num() > 0 )
{
FArrangedChildren ArrangedPins(EVisibility::Visible);
Result.GenerateValueArray( ArrangedPins.GetInternalArray() );
int32 HoveredPinIndex = SWidget::FindChildUnderMouse( ArrangedPins, MouseEvent );
if ( HoveredPinIndex != INDEX_NONE )
{
return StaticCastSharedRef<SGraphPin>(ArrangedPins[HoveredPinIndex].Widget);
}
}
return TSharedPtr<SGraphPin>();
}
TSharedPtr<SGraphPin> SGraphNode::FindWidgetForPin( UEdGraphPin* ThePin ) const
{
// Search input or output pins?
const TArray< TSharedRef<SGraphPin> > &PinsToSearch = (ThePin->Direction == EGPD_Input) ? InputPins : OutputPins;
// Actually search for the widget
for( int32 PinIndex=0; PinIndex < PinsToSearch.Num(); ++PinIndex )
{
if ( PinsToSearch[PinIndex]->GetPinObj() == ThePin )
{
return PinsToSearch[PinIndex];
}
}
return TSharedPtr<SGraphPin>(NULL);
}
void SGraphNode::PlaySpawnEffect()
{
SpawnAnim.Play( this->AsShared() );
}
FVector2D SGraphNode::GetContentScale() const
{
const float CurZoomValue = ZoomCurve.GetLerp();
return FVector2D( CurZoomValue, CurZoomValue );
}
FLinearColor SGraphNode::GetColorAndOpacity() const
{
return FLinearColor(1,1,1,FadeCurve.GetLerp());
}
FLinearColor SGraphNode::GetPinLabelColorAndOpacity() const
{
return FLinearColor(0,0,0,FadeCurve.GetLerp());
}
SGraphNode::SGraphNode()
: IsEditable(true)
, bProvidedComplexTooltip(false)
, bRenameIsPending( false )
, ErrorColor( FLinearColor::White )
, CachedUnscaledPosition( FVector2D::ZeroVector )
, Settings( GetDefault<UGraphEditorSettings>() )
{
// Set up animation
{
ZoomCurve = SpawnAnim.AddCurve(0, 0.1f);
FadeCurve = SpawnAnim.AddCurve(0.15f, 0.15f);
SpawnAnim.JumpToEnd();
}
}
void SGraphNode::PositionThisNodeBetweenOtherNodes(const TMap< UObject*, TSharedRef<SNode> >& NodeToWidgetLookup, UEdGraphNode* PreviousNode, UEdGraphNode* NextNode, float HeightAboveWire) const
{
if ((PreviousNode != NULL) && (NextNode != NULL))
{
TSet<UEdGraphNode*> PrevNodes;
PrevNodes.Add(PreviousNode);
TSet<UEdGraphNode*> NextNodes;
NextNodes.Add(NextNode);
PositionThisNodeBetweenOtherNodes(NodeToWidgetLookup, PrevNodes, NextNodes, HeightAboveWire);
}
}
void SGraphNode::PositionThisNodeBetweenOtherNodes(const TMap< UObject*, TSharedRef<SNode> >& NodeToWidgetLookup, TSet<UEdGraphNode*>& PreviousNodes, TSet<UEdGraphNode*>& NextNodes, float HeightAboveWire) const
{
// Find the previous position centroid
FVector2D PrevPos(0.0f, 0.0f);
for (auto NodeIt = PreviousNodes.CreateConstIterator(); NodeIt; ++NodeIt)
{
UEdGraphNode* PreviousNode = *NodeIt;
const FVector2D CornerPos(PreviousNode->NodePosX, PreviousNode->NodePosY);
PrevPos += CornerPos + NodeToWidgetLookup.FindChecked(PreviousNode)->GetDesiredSize() * 0.5f;
}
// Find the next position centroid
FVector2D NextPos(0.0f, 0.0f);
for (auto NodeIt = NextNodes.CreateConstIterator(); NodeIt; ++NodeIt)
{
UEdGraphNode* NextNode = *NodeIt;
const FVector2D CornerPos(NextNode->NodePosX, NextNode->NodePosY);
NextPos += CornerPos + NodeToWidgetLookup.FindChecked(NextNode)->GetDesiredSize() * 0.5f;
}
PositionThisNodeBetweenOtherNodes(PrevPos, NextPos, HeightAboveWire);
}
void SGraphNode::PositionThisNodeBetweenOtherNodes(const FVector2D& PrevPos, const FVector2D& NextPos, float HeightAboveWire) const
{
const FVector2D DesiredNodeSize = GetDesiredSize();
FVector2D DeltaPos(NextPos - PrevPos);
if (DeltaPos.IsNearlyZero())
{
DeltaPos = FVector2D(10.0f, 0.0f);
}
const FVector2D Normal = FVector2D(DeltaPos.Y, -DeltaPos.X).GetSafeNormal();
const FVector2D SlidingCapsuleBias = FVector2D::ZeroVector;//(0.5f * FMath::Sin(Normal.X * (float)HALF_PI) * DesiredNodeSize.X, 0.0f);
const FVector2D NewCenter = PrevPos + (0.5f * DeltaPos) + (HeightAboveWire * Normal) + SlidingCapsuleBias;
// Now we need to adjust the new center by the node size and zoom factor
const FVector2D NewCorner = NewCenter - (0.5f * DesiredNodeSize);
GraphNode->NodePosX = NewCorner.X;
GraphNode->NodePosY = NewCorner.Y;
}
FText SGraphNode::GetErrorMsgToolTip( ) const
{
FText Result;
// Append the node's upgrade message, if any.
if (!GraphNode->NodeUpgradeMessage.IsEmpty())
{
if (Result.IsEmpty())
{
Result = GraphNode->NodeUpgradeMessage;
}
else
{
Result = FText::Format(FText::FromString(TEXT("{0}\n\n{1}")), Result, GraphNode->NodeUpgradeMessage);
}
}
else
{
Result = FText::FromString(GraphNode->ErrorMsg);
}
return Result;
}
bool SGraphNode::IsNameReadOnly() const
{
return (!GraphNode->bCanRenameNode || !IsNodeEditable());
}
bool SGraphNode::OnVerifyNameTextChanged(const FText& InText, FText& OutErrorMessage)
{
bool bValid(true);
if ((GetEditableNodeTitle() != InText.ToString()) && OnVerifyTextCommit.IsBound())
{
bValid = OnVerifyTextCommit.Execute(InText, GraphNode, OutErrorMessage);
}
if( OutErrorMessage.IsEmpty() )
{
OutErrorMessage = FText::FromString(TEXT("Error"));
}
//UpdateErrorInfo();
//ErrorReporting->SetError(ErrorMsg);
return bValid;
}
void SGraphNode::OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo)
{
OnTextCommitted.ExecuteIfBound(InText, CommitInfo, GraphNode);
UpdateErrorInfo();
if (ErrorReporting.IsValid())
{
ErrorReporting->SetError(ErrorMsg);
}
}
void SGraphNode::RequestRename()
{
if ((GraphNode != NULL) && GraphNode->bCanRenameNode)
{
bRenameIsPending = true;
}
}
void SGraphNode::ApplyRename()
{
if (bRenameIsPending)
{
bRenameIsPending = false;
InlineEditableText->EnterEditingMode();
}
}
FSlateRect SGraphNode::GetTitleRect() const
{
const FVector2D NodePosition = GetPosition();
const FVector2D NodeSize = GraphNode ? InlineEditableText->GetDesiredSize() : GetDesiredSize();
return FSlateRect( NodePosition.X, NodePosition.Y + NodeSize.Y, NodePosition.X + NodeSize.X, NodePosition.Y );
}
void SGraphNode::NotifyDisallowedPinConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const
{
OnDisallowedPinConnection.ExecuteIfBound(PinA, PinB);
}
bool SGraphNode::UseLowDetailNodeTitles() const
{
if (const SGraphPanel* MyOwnerPanel = GetOwnerPanel().Get())
{
return (MyOwnerPanel->GetCurrentLOD() <= EGraphRenderingLOD::LowestDetail) && !InlineEditableText->IsInEditMode();
}
else
{
return false;
}
}
TSharedRef<SWidget> SGraphNode::AddPinButtonContent(FText PinText, FText PinTooltipText, bool bRightSide, FString DocumentationExcerpt, TSharedPtr<SToolTip> CustomTooltip)
{
TSharedPtr<SWidget> ButtonContent;
if(bRightSide)
{
SAssignNew(ButtonContent, SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(PinText)
.ColorAndOpacity(FLinearColor::White)
]
+SHorizontalBox::Slot()
.AutoWidth()
. VAlign(VAlign_Center)
. Padding( 7,0,0,0 )
[
SNew(SImage)
.Image(FEditorStyle::GetBrush(TEXT("PropertyWindow.Button_AddToArray")))
];
}
else
{
SAssignNew(ButtonContent, SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
. VAlign(VAlign_Center)
. Padding( 0,0,7,0 )
[
SNew(SImage)
.Image(FEditorStyle::GetBrush(TEXT("PropertyWindow.Button_AddToArray")))
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(PinText)
.ColorAndOpacity(FLinearColor::White)
];
}
TSharedPtr<SToolTip> Tooltip;
if (CustomTooltip.IsValid())
{
Tooltip = CustomTooltip;
}
else if (!DocumentationExcerpt.IsEmpty())
{
Tooltip = IDocumentation::Get()->CreateToolTip( PinTooltipText, NULL, GraphNode->GetDocumentationLink(), DocumentationExcerpt );
}
TSharedRef<SButton> AddPinButton = SNew(SButton)
.ContentPadding(0.0f)
.ButtonStyle( FEditorStyle::Get(), "NoBorder" )
.OnClicked( this, &SGraphNode::OnAddPin )
.IsEnabled( this, &SGraphNode::IsNodeEditable )
.ToolTipText(PinTooltipText)
.ToolTip(Tooltip)
.Visibility(this, &SGraphNode::IsAddPinButtonVisible)
[
ButtonContent.ToSharedRef()
];
AddPinButton->SetCursor( EMouseCursor::Hand );
return AddPinButton;
}
EVisibility SGraphNode::IsAddPinButtonVisible() const
{
bool bIsHidden = false;
auto OwnerGraphPanel = OwnerGraphPanelPtr.Pin();
if(OwnerGraphPanel.IsValid())
{
bIsHidden |= (SGraphEditor::EPinVisibility::Pin_Show != OwnerGraphPanel->GetPinVisibility());
bIsHidden |= (OwnerGraphPanel->GetCurrentLOD() <= EGraphRenderingLOD::LowDetail);
}
return bIsHidden ? EVisibility::Collapsed : EVisibility::Visible;
}
void SGraphNode::PopulateMetaTag(FGraphNodeMetaData* TagMeta) const
{
if (GraphNode != nullptr)
{
// We want the name of the blueprint as our name - we can find the node from the GUID
UObject* Package = GraphNode->GetOutermost();
UObject* LastOuter = GraphNode->GetOuter();
while (LastOuter->GetOuter() != Package)
{
LastOuter = LastOuter->GetOuter();
}
TagMeta->Tag = FName(*FString::Printf(TEXT("GraphNode_%s_%s"), *LastOuter->GetFullName(), *GraphNode->NodeGuid.ToString()));
TagMeta->OuterName = LastOuter->GetFullName();
TagMeta->GUID = GraphNode->NodeGuid;
TagMeta->FriendlyName = FString::Printf(TEXT("%s in %s"), *GraphNode->GetNodeTitle(ENodeTitleType::ListView).ToString(), *TagMeta->OuterName);
}
}
EGraphRenderingLOD::Type SGraphNode::GetCurrentLOD() const
{
return OwnerGraphPanelPtr.IsValid() ? OwnerGraphPanelPtr.Pin()->GetCurrentLOD() : EGraphRenderingLOD::DefaultDetail;
}
void SGraphNode::RefreshErrorInfo()
{
SetupErrorReporting();
}