You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change3152124on 2016/10/05 by Jamie.Dale Fixed SOutputLog filter not handling OnTextCommitted Change 3152255 on 2016/10/05 by Michael.Dupuis #jira UE-28173 Support \" properly in FName Change 3152273 on 2016/10/05 by Nick.Darnell Core - The module manager is now thread safer, we had a critical section around the internal module list - but we were incrementing/decrementing shared pointers to module data shared pointers that were not thread safe outside of the critical section. Ran into a crash working on some heavily threaded code in automation. Change 3152314 on 2016/10/05 by Nick.Darnell Automation - Continued work to rough out the automation workflow for screenshot. Still lots of work remaining, but it appears the basic of approving images might be working as of this CL. Change 3152316 on 2016/10/05 by Michael.Dupuis #jira UE-30346 Update selection when in tree view mode Change 3152317 on 2016/10/05 by Nick.Darnell Automation - Adding some test shots to compare against to EngineTest for screenshot approval. Change 3152319 on 2016/10/05 by Michael.Dupuis #jira UE-29817 StringAssetReference will now only open an Asset picker (not actor picker) as the goal is to reference an asset Change 3152521 on 2016/10/05 by Nick.Darnell Automation - Fixing some issues with where it reads the screenshot compare rules. Change 3152536 on 2016/10/05 by Alexis.Matte Fix FBX automation test. - Make sure the fbx test can avoid automatic detection of the mesh type - Avoid to log the warning when the importer set the material usage after creating a material for skeletalmesh. Change 3152572 on 2016/10/05 by Nick.Darnell Automation - The GameProjectAutomationTests now do some pre-run house cleaning to make sure the project doesn't already exist, and tries to remove it if it was created previously but not deleted. Change 3152591 on 2016/10/05 by Nick.Darnell Automation - Changing the game project errors to be errors. Change 3153115 on 2016/10/06 by Jamie.Dale Removed superflous padding when SPropertyEditorAsset had no buttons Change 3153215 on 2016/10/06 by Michael.Dupuis Fixed build warning Change 3153248 on 2016/10/06 by Nick.Darnell Automation - Working on solving projects not being generated, suspect UBT isn't built or isn't available. Change 3153255 on 2016/10/06 by Nick.Darnell PR #2835: Fix TestEqual AddError Message in FAutomationTestBase (Contributed by dorgonman) #jira UE-36922 Change 3153300 on 2016/10/06 by Nick.Darnell Automation - Enabled verbose logging to automation build farm. Change 3153343 on 2016/10/06 by Matt.Kuhlenschmidt PR #2825: More project launcher progress improvements (Contributed by projectgheist) Change 3153506 on 2016/10/06 by Gareth.Martin Fixed crash trying to edit landscape with r.LightPropagationVolume=1 enabled #jira UE-36933 Change 3153752 on 2016/10/06 by tim.gautier Add toggle button to UMG_Behavior. Set Level Blueprint for TM-UMG to AllWidget Change 3153763 on 2016/10/06 by Nick.Darnell Automation - Disable verbose logging. Change 3153778 on 2016/10/06 by Nick.Darnell PR #2837: Fixed engine compilation with defined LOG_SLATE_EVENTS (Contributed by Pierdek) #jira UE-36940 Change 3153943 on 2016/10/06 by Nick.Darnell Automation - Disabling some broken tests. Change 3154035 on 2016/10/06 by Nick.Darnell Automation - Fixing re-runs for tests that want them. Previously this wasn't working for any test that was run using the Reprostring method of being executed. Change 3154039 on 2016/10/06 by Nick.Darnell Automation - Updating some test assets in the EngineTest project. Change 3154476 on 2016/10/07 by Richard.TalbotWatkin Fix to regression where vertex color view in Mesh Paint Mode no longer worked correctly. We now allow selected static meshes to go down the dynamic path if VertexColors show mode is active. #jira UE-36772 - Selecting a channel in Vertex Paint mode does not show the channel color Change 3154650 on 2016/10/07 by Alexis.Matte Add new front axis facing X option to fbx importer Change 3154785 on 2016/10/07 by Nick.Darnell Automation - Continued work on automation, ripping out the message bus from the screenshot manager, that's causing the screenshot manager to recieve shots from every machine ever running tests. The Automation Controller is now in charge, and will tell the screenshot manager whatever it needs. Change 3155131 on 2016/10/07 by Michael.Dupuis #jira UE-36509 Do not disabled inverse filter when doing a sync to asset Change 3155141 on 2016/10/07 by Michael.Dupuis #jira UE-36056 Do not open the Actor Picker if we're working on an archetype object Change 3155262 on 2016/10/07 by Michael.Dupuis #jira UE-19737 reset ctrl key when resetting state to None Change 3156326 on 2016/10/09 by Matt.Kuhlenschmidt Fixed crash when asset picker is used without a property editor (usually a heavily customized property). Change 3156473 on 2016/10/10 by Richard.TalbotWatkin Attempt to make geometry render / rebuild more robust in the hope of catching UE-36265. #jira UE-36265 - [CrashReport] UE4Editor_Engine!FModelSceneProxy::HasSelectedSurfaces() [modelrender.cpp:538] Change 3156479 on 2016/10/10 by Richard.TalbotWatkin Fixed non-editor build. Change 3156579 on 2016/10/10 by Alexis.Matte Add a check to make sure curve pointer is valid. #jira UE-36177 Change 3156585 on 2016/10/10 by Ben.Marsh Fix line endings for screenshot settings. Change 3156617 on 2016/10/10 by Matt.Kuhlenschmidt Disable per-pixel blending of menus by default. Causes artifacts on windows versions and we are not using it. Change 3156674 on 2016/10/10 by Nick.Darnell Automation - Continued work on the automation workflow. Now screenshot functional test actors actually have the screenshot processed for differences during the test back on the test controller machine, and then waits for the results of the comparison to be performed. Change 3156709 on 2016/10/10 by Alexis.Matte #jira UE-16337 Make sure the base mesh import data transform is used when we import a LOD. Change 3156714 on 2016/10/10 by Nick.Darnell Automation - Fixing -game crash due to TestName being null in functional test. Change 3156721 on 2016/10/10 by Nick.Darnell Automation - FunctionalAITest now implements IsReady to check if the navigation mesh has finished being built. Change 3156748 on 2016/10/10 by Nick.Darnell Autopmation - Fixing a warning. Change 3156943 on 2016/10/10 by Alex.Delesky Fixed an issue where closing out the "Add Code" window with an active toast stating that an IDE was downloading would prevent the toast from clearing correctly. #jira none Change 3156946 on 2016/10/10 by Alex.Delesky #jira UE-36414 - Adding support for the P4Changelist command line argument to the P4 operations that were missing it. Change 3158215 on 2016/10/11 by Nick.Darnell Automation - Continued work on the screenshot comparison, fixed a crash, the system now reports if the screenshot was new, as well as similar, so that the script can react and warn when new screenshots are produced. Manually fired screenshots now properly wait until they've been compared before the test moves forward. Change 3158322 on 2016/10/11 by Michael.Dupuis #jira UE-36063 If the collection is not shown when trying to save one, force show them so the user understand what is going on Change 3158333 on 2016/10/11 by Alex.Delesky #jira UE-36829 - Rebuilt SVN 1.9.4 libs for Windows with CyrusSASL 2.1.26 and SWIG 3.0.10 support. Change 3158399 on 2016/10/11 by Nick.Darnell Automation - TTF Font log statements that were not warnings are no longer warnings. Change 3158406 on 2016/10/11 by Nick.Darnell Automation - Updating some automation scripts in the engnine that were using file paths to now use FStringAssetReferences, that cleaned up a lot of nasty convert filepath to asset reference code in the tests. Also introducing some maps, and setting up the configs to use them to try and appease some of the existing tests that were expecting them. Change 3158419 on 2016/10/11 by Alex.Delesky #jira UE-36829 - SVN 1.9.4 libraries, but also with Java 8u101 support. Change 3158537 on 2016/10/11 by Nick.Darnell Automation - Updating some automation scripts in the engnine that were using file paths to now use FStringAssetReferences, that cleaned up a lot of nasty convert filepath to asset reference code in the tests. Also introducing some maps, and setting up the configs to use them to try and appease some of the existing tests that were expecting them. Adding some missing files. Change 3158726 on 2016/10/11 by Michael.Dupuis #jira UE-37001 Perform manual migration of UICurve to proper config category Change 3158728 on 2016/10/11 by Nick.Darnell Automation - Fixing some warnings. Adding more testing to the Domino map to serve as a better example. Change 3158753 on 2016/10/11 by Michael.Dupuis #jira UE-26261 change it's by its Change 3158984 on 2016/10/11 by Alexis.Matte Fix D&D folder import in content browser. We have to expand the root directory to have the correct path. #jira UE-32155 Change 3159640 on 2016/10/12 by Jamie.Dale Split localized package redirection out of FCoreDelegates::PackageNameResolvers They're different enough in behavior that the delegate resolution was breaking the localized package resolution by resolving in too many places and causing the localized package to be loaded with its real localized name as well as the fake non-localized name. #jira UE-37119 Change 3159741 on 2016/10/12 by Nick.Darnell Slate - Fixing the SGraphPanel's mouse offset calculations so that it works with HDPI mode. Change 3159762 on 2016/10/12 by Nick.Darnell Automation - Adding a way to take a screenshot with the UI, not great yet - it doesn't really obey the rules of resolution, because of the method it uses. Change 3160210 on 2016/10/12 by Gareth.Martin Fixed crash when changing Landscape LOD while using "Grass use Landscape Lightmap" Change 3160216 on 2016/10/12 by Gareth.Martin Removed unused FLandscapeComponentSceneProxy::LayerNames and made LayerColors editor-only Fixed negative LODBias on landscape components to actually do anything Change 3160239 on 2016/10/12 by Gareth.Martin Removed an unused variable Change 3160455 on 2016/10/12 by Jamie.Dale Fixed FText properties exported to asset tags displaying in their NSLOCTEXT form in the asset tooltips Change 3160457 on 2016/10/12 by Jamie.Dale Localization automation now groups everything into a single CL and reverts PO files without significant changes Change 3160554 on 2016/10/12 by Nick.Darnell UMG - Fixing some panning logic to work with HDPI mode in the designer. Change 3161712 on 2016/10/13 by Jamie.Dale Fixed TSharedMapView using hard-coded types Change 3163044 on 2016/10/14 by Jamie.Dale Fixed line-break iterators incorrectly breaking words in CJK Change 3163046 on 2016/10/14 by Jamie.Dale Text layout no longer creates break candidates when wrapping is disabled Change 3163217 on 2016/10/14 by Jamie.Dale Fixed ensure caused by FMediaTextureResource::GetResourceSizeEx Change 3163641 on 2016/10/14 by Alex.Delesky #jira UE-36829 - Updated Mac SVN libraries, along with a more accurate changelog for Windows SVN libs Change 3164428 on 2016/10/17 by Nick.Darnell Slate - Making the FReply for SetMousePos easier to debug, since that option is potentially very frustrating to debug when someone is changing it. Change 3164833 on 2016/10/17 by Jamie.Dale Fixed ParseNumber consuming strings with multiple sequential dots as valid numbers, eg) "10..." Change 3164868 on 2016/10/17 by Alexis.Matte Remove re-import material and LOD import material #jira UE-36640 Change 3164874 on 2016/10/17 by Alexis.Matte Fix fbx scene re-import of staticmesh loosing there materials #jira UE-37032 Change 3165080 on 2016/10/17 by Alexis.Matte Remove skinxx workflow for static mesh #jira UE-37262 Change 3165232 on 2016/10/17 by Nick.Darnell Automation - Adding some sub-level testing. Change 3165822 on 2016/10/18 by Nick.Darnell Slate - Add a counter to track how much time we spend drawing custom verts each frame. Change 3165934 on 2016/10/18 by Nick.Darnell Slate - Addding cycle counters to SInvalidationPanel's Tick and Paint. Change 3165947 on 2016/10/18 by Nick.Darnell Slate - Addding very verbose slate stats behind a compiler flag to avoid the large overhead of these stats. To enable them you'll need to set WITH_VERY_VERBOSE_SLATE_STATS to 1, added a guide on debugging slate performance to the SlateGlobals.h // HOW TO GET AN IN-DEPTH PERFORMANCE ANALYSIS OF SLATE // // Step 1) // Set WITH_VERY_VERBOSE_SLATE_STATS to 1. // // Step 2) // When running the game (outside of the editor), run these commandline options // in order and you'll get a large dump of where all the time is going in Slate. // // stat group enable slateverbose // stat group enable slateveryverbose // stat dumpave -root=stat_slate -num=120 -ms=0 Change 3165962 on 2016/10/18 by Nick.Darnell UMG - Play first frame of sequence in UMG immediately when told to play an animation. Change 3165981 on 2016/10/18 by Nick.Darnell Core - GetDisplayNameText now stores the FName for DisplayName in a static instead of using TEXT("DisplayName"). Change 3166000 on 2016/10/18 by Jamie.Dale Removed bulk-data from fonts The main complaints about composite fonts have always been: 1) They use too much memory at runtime. 2) They bloat if you use the same font face twice. 3) They often break when used outside the game thread. This change aims to address all of these issues by removing bulk-data from fonts (which was the cause of the memory bloat and threading issues), and introduces UFontFace assets (which allow you to re-use the same font face in different entries within a composite font). No existing font data is lost during this transition, however you must manually update your UFont assets to reference external UFontFace assets before you're able to edit the existing data (which is automatically upgraded to UFontFace assets *within* the UFont package). This upgrade can be done via the composite font editor. During cook these UFontFace assets write out the actual TTF/OTF font data as a .ufont file, which is what FreeType actually loads at runtime (the internals of the Slate font cache are now completely independent of UObjects and their lifetimes and loading concerns). Since we're now always loading files from disk, we can also give the user an option of whether to "pre-load" (which will load the entire font into memory, like bulk-data always used to), or "stream" the font from disk (which has minimal memory overhead, but needs decent I/O performance). Change 3166001 on 2016/10/18 by Jamie.Dale Updated the Launcher to no longer use bulk-data for fonts Change3166003on 2016/10/18 by Jamie.Dale Updated the Engine fonts to use UFontFace assets Change 3166028 on 2016/10/18 by Alex.Delesky #jira UE-37021 - The scrollbar will now remain at the bottom of the output log after specifying a filter. Change 3166071 on 2016/10/18 by Nick.Darnell Slate - Fixing a warning about hiding an inherited member. Change 3166213 on 2016/10/18 by Jamie.Dale Fixing crash caused by accessing a zeroed FText Change 3166222 on 2016/10/18 by Nick.Darnell Automation - Adding some code to end the sub level test when it starts. Change 3166231 on 2016/10/18 by Nick.Darnell Editor - Fixing the build warning, SOutputLog.cpp:748:4: warning: field 'TextLayout' will be initialized after field 'CachedNumMessages' Change 3166717 on 2016/10/18 by Nick.Darnell Automation - Removing references to old options that are no longer file paths, and are now StringAssetReferences these options were not being used by anyone as far as I can tell. #jira UE-37482 Change 3167279 on 2016/10/19 by Jamie.Dale Fixed text render component regression with custom MIDs #jira UE-37305 Change 3167356 on 2016/10/19 by Alexis.Matte Make sure the old asset are build correctly #jira UE-37461 Change 3167359 on 2016/10/19 by Alexis.Matte Fix re-import of mesh material assignment regression #jira UE-37479 [CL 3168049 by Matt Kuhlenschmidt in Main branch]
1722 lines
56 KiB
C++
1722 lines
56 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "GraphEditorCommon.h"
|
|
#include "NodeFactory.h"
|
|
|
|
#include "Editor/UnrealEd/Public/DragAndDrop/ActorDragDropGraphEdOp.h"
|
|
#include "Editor/UnrealEd/Public/DragAndDrop/AssetDragDropOp.h"
|
|
#include "Editor/UnrealEd/Public/DragAndDrop/LevelDragDropOp.h"
|
|
|
|
#include "GraphEditorActions.h"
|
|
#include "UICommandInfo.h"
|
|
#include "InputChord.h"
|
|
|
|
#include "ConnectionDrawingPolicy.h"
|
|
|
|
#include "AssetSelection.h"
|
|
#include "ComponentAssetBroker.h"
|
|
|
|
#include "KismetNodes/KismetNodeInfoContext.h"
|
|
#include "GraphDiffControl.h"
|
|
|
|
#include "AnimationGraphSchema.h"
|
|
#include "AnimationStateMachineSchema.h"
|
|
|
|
// Blueprint Profiler
|
|
#include "Editor/Kismet/Public/Profiler/BlueprintProfilerSettings.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogGraphPanel, Log, All);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FGraphPinHandle
|
|
|
|
FGraphPinHandle::FGraphPinHandle(UEdGraphPin* InPin)
|
|
{
|
|
if (InPin != nullptr)
|
|
{
|
|
if (UEdGraphNode* Node = InPin->GetOwningNodeUnchecked())
|
|
{
|
|
NodeGuid = Node->NodeGuid;
|
|
PinId = InPin->PinId;
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SGraphPin> FGraphPinHandle::FindInGraphPanel(const SGraphPanel& InPanel) const
|
|
{
|
|
// First off, find the node
|
|
if (NodeGuid.IsValid())
|
|
{
|
|
TSharedPtr<SGraphNode> GraphNode = InPanel.GetNodeWidgetFromGuid(NodeGuid);
|
|
if (GraphNode.IsValid())
|
|
{
|
|
UEdGraphNode* Node = GraphNode->GetNodeObj();
|
|
|
|
if (UEdGraphPin* Pin = Node->FindPinById(PinId))
|
|
{
|
|
return GraphNode->FindWidgetForPin(Pin);
|
|
}
|
|
}
|
|
}
|
|
|
|
return TSharedPtr<SGraphPin>();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// SGraphPanel
|
|
|
|
void SGraphPanel::Construct( const SGraphPanel::FArguments& InArgs )
|
|
{
|
|
SNodePanel::Construct();
|
|
|
|
this->OnGetContextMenuFor = InArgs._OnGetContextMenuFor;
|
|
this->GraphObj = InArgs._GraphObj;
|
|
this->GraphObjToDiff = InArgs._GraphObjToDiff;
|
|
this->SelectionManager.OnSelectionChanged = InArgs._OnSelectionChanged;
|
|
this->IsEditable = InArgs._IsEditable;
|
|
this->DisplayAsReadOnly = InArgs._DisplayAsReadOnly;
|
|
this->OnNodeDoubleClicked = InArgs._OnNodeDoubleClicked;
|
|
this->OnDropActor = InArgs._OnDropActor;
|
|
this->OnDropStreamingLevel = InArgs._OnDropStreamingLevel;
|
|
this->OnVerifyTextCommit = InArgs._OnVerifyTextCommit;
|
|
this->OnTextCommitted = InArgs._OnTextCommitted;
|
|
this->OnSpawnNodeByShortcut = InArgs._OnSpawnNodeByShortcut;
|
|
this->OnUpdateGraphPanel = InArgs._OnUpdateGraphPanel;
|
|
this->OnDisallowedPinConnection = InArgs._OnDisallowedPinConnection;
|
|
|
|
this->bPreservePinPreviewConnection = false;
|
|
this->PinVisibility = SGraphEditor::Pin_Show;
|
|
|
|
CachedAllottedGeometryScaledSize = FVector2D(160, 120);
|
|
if (InArgs._InitialZoomToFit)
|
|
{
|
|
ZoomToFit(/*bOnlySelection=*/ false);
|
|
bTeleportInsteadOfScrollingWhenZoomingToFit = true;
|
|
}
|
|
|
|
BounceCurve.AddCurve(0.0f, 1.0f);
|
|
|
|
FEditorDelegates::BeginPIE.AddRaw( this, &SGraphPanel::OnBeginPIE );
|
|
FEditorDelegates::EndPIE.AddRaw( this, &SGraphPanel::OnEndPIE );
|
|
|
|
// Register for notifications
|
|
MyRegisteredGraphChangedDelegate = FOnGraphChanged::FDelegate::CreateSP(this, &SGraphPanel::OnGraphChanged);
|
|
MyRegisteredGraphChangedDelegateHandle = this->GraphObj->AddOnGraphChangedHandler(MyRegisteredGraphChangedDelegate);
|
|
|
|
ShowGraphStateOverlay = InArgs._ShowGraphStateOverlay;
|
|
|
|
SavedMousePosForOnPaintEventLocalSpace = FVector2D::ZeroVector;
|
|
PreviousFrameSavedMousePosForSplineOverlap = FVector2D::ZeroVector;
|
|
|
|
TimeLeftToInvalidatePerTick = 0.0f;
|
|
}
|
|
|
|
SGraphPanel::~SGraphPanel()
|
|
{
|
|
FEditorDelegates::BeginPIE.RemoveAll( this );
|
|
FEditorDelegates::EndPIE.RemoveAll( this );
|
|
|
|
this->GraphObj->RemoveOnGraphChangedHandler(MyRegisteredGraphChangedDelegateHandle);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
CachedAllottedGeometryScaledSize = AllottedGeometry.Size * AllottedGeometry.Scale;
|
|
|
|
//Style used for objects that are the same between revisions
|
|
FWidgetStyle FadedStyle = InWidgetStyle;
|
|
FadedStyle.BlendColorAndOpacityTint(FLinearColor(0.45f,0.45f,0.45f,0.45f));
|
|
|
|
// First paint the background
|
|
const UEditorExperimentalSettings& Options = *GetDefault<UEditorExperimentalSettings>();
|
|
|
|
const FSlateBrush* BackgroundImage = FEditorStyle::GetBrush(TEXT("Graph.Panel.SolidBackground"));
|
|
PaintBackgroundAsLines(BackgroundImage, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId);
|
|
|
|
const float ZoomFactor = AllottedGeometry.Scale * GetZoomAmount();
|
|
|
|
FArrangedChildren ArrangedChildren(EVisibility::Visible);
|
|
ArrangeChildNodes(AllottedGeometry, ArrangedChildren);
|
|
|
|
// Determine some 'global' settings based on current LOD
|
|
const bool bDrawShadowsThisFrame = GetCurrentLOD() > EGraphRenderingLOD::LowestDetail;
|
|
|
|
// Enable the profiler heatmap displays.
|
|
const bool bDisplayProfilerHeatmap = GetDefault<UBlueprintProfilerSettings>()->GraphNodeHeatMapDisplayMode != EBlueprintProfilerHeatMapDisplayMode::None;
|
|
|
|
// Because we paint multiple children, we must track the maximum layer id that they produced in case one of our parents
|
|
// wants to an overlay for all of its contents.
|
|
|
|
// Save LayerId for comment boxes to ensure they always appear below nodes & wires
|
|
const int32 CommentNodeShadowLayerId = LayerId++;
|
|
const int32 CommentNodeLayerId = LayerId++;
|
|
|
|
// Save a LayerId for wires, which appear below nodes but above comments
|
|
// We will draw them later, along with the arrows which appear above nodes.
|
|
const int32 WireLayerId = LayerId++;
|
|
|
|
const int32 NodeShadowsLayerId = LayerId;
|
|
const int32 NodeLayerId = NodeShadowsLayerId + 1;
|
|
int32 MaxLayerId = NodeLayerId;
|
|
|
|
const FPaintArgs NewArgs = Args.WithNewParent(this);
|
|
|
|
const FVector2D NodeShadowSize = GetDefault<UGraphEditorSettings>()->GetShadowDeltaSize();
|
|
const UEdGraphSchema* Schema = GraphObj->GetSchema();
|
|
|
|
// Draw the child nodes
|
|
{
|
|
// When drawing a marquee, need a preview of what the selection will be.
|
|
const FGraphPanelSelectionSet* SelectionToVisualize = &(SelectionManager.SelectedNodes);
|
|
FGraphPanelSelectionSet SelectionPreview;
|
|
if ( Marquee.IsValid() )
|
|
{
|
|
ApplyMarqueeSelection(Marquee, SelectionManager.SelectedNodes, SelectionPreview);
|
|
SelectionToVisualize = &SelectionPreview;
|
|
}
|
|
|
|
// Context for rendering node infos
|
|
FKismetNodeInfoContext Context(GraphObj);
|
|
|
|
TArray<FGraphDiffControl::FNodeMatch> NodeMatches;
|
|
for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex)
|
|
{
|
|
FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex];
|
|
TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(CurWidget.Widget);
|
|
|
|
// Examine node to see what layers we should be drawing in
|
|
int32 ShadowLayerId = NodeShadowsLayerId;
|
|
int32 ChildLayerId = NodeLayerId;
|
|
|
|
// If a comment node, draw in the dedicated comment slots
|
|
{
|
|
UObject* NodeObj = ChildNode->GetObjectBeingDisplayed();
|
|
if (NodeObj && NodeObj->IsA(UEdGraphNode_Comment::StaticClass()))
|
|
{
|
|
ShadowLayerId = CommentNodeShadowLayerId;
|
|
ChildLayerId = CommentNodeLayerId;
|
|
}
|
|
}
|
|
|
|
|
|
const bool bNodeIsVisible = FSlateRect::DoRectanglesIntersect( CurWidget.Geometry.GetClippingRect(), MyClippingRect );
|
|
|
|
if (bNodeIsVisible)
|
|
{
|
|
const bool bSelected = SelectionToVisualize->Contains( StaticCastSharedRef<SNodePanel::SNode>(CurWidget.Widget)->GetObjectBeingDisplayed() );
|
|
|
|
// Handle Node renaming once the node is visible
|
|
if( bSelected && ChildNode->IsRenamePending() )
|
|
{
|
|
// Only open a rename when the window has focus
|
|
TSharedPtr<SWindow> OwnerWindow = FSlateApplication::Get().FindWidgetWindow(AsShared());
|
|
if (!OwnerWindow.IsValid() || FSlateApplication::Get().HasFocusedDescendants(OwnerWindow.ToSharedRef()))
|
|
{
|
|
ChildNode->ApplyRename();
|
|
}
|
|
}
|
|
|
|
// Draw the profiler heatmap if active
|
|
if (bDisplayProfilerHeatmap)
|
|
{
|
|
const FSlateBrush* ProfilerBrush = ChildNode->GetProfilerHeatmapBrush();
|
|
const FLinearColor ProfilerHeatIntensity = ChildNode->GetProfilerHeatmapIntensity();
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
ShadowLayerId,
|
|
CurWidget.Geometry.ToInflatedPaintGeometry(NodeShadowSize),
|
|
ProfilerBrush,
|
|
MyClippingRect,
|
|
ESlateDrawEffect::None,
|
|
ProfilerHeatIntensity
|
|
);
|
|
}
|
|
|
|
// Draw the node's shadow.
|
|
if (bDrawShadowsThisFrame || bSelected)
|
|
{
|
|
const FSlateBrush* ShadowBrush = ChildNode->GetShadowBrush(bSelected);
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
ShadowLayerId,
|
|
CurWidget.Geometry.ToInflatedPaintGeometry(NodeShadowSize),
|
|
ShadowBrush,
|
|
MyClippingRect
|
|
);
|
|
}
|
|
|
|
// Draw the comments and information popups for this node, if it has any.
|
|
{
|
|
const SNodePanel::SNode::FNodeSlot* CommentSlot = ChildNode->GetSlot( ENodeZone::TopCenter );
|
|
float CommentBubbleY = CommentSlot ? -CommentSlot->Offset.Get().Y : 0.f;
|
|
Context.bSelected = bSelected;
|
|
TArray<FGraphInformationPopupInfo> Popups;
|
|
|
|
{
|
|
ChildNode->GetNodeInfoPopups(&Context, /*out*/ Popups);
|
|
}
|
|
|
|
for (int32 PopupIndex = 0; PopupIndex < Popups.Num(); ++PopupIndex)
|
|
{
|
|
FGraphInformationPopupInfo& Popup = Popups[PopupIndex];
|
|
PaintComment(Popup.Message, CurWidget.Geometry, MyClippingRect, OutDrawElements, ChildLayerId, Popup.BackgroundColor, /*inout*/ CommentBubbleY, InWidgetStyle);
|
|
}
|
|
}
|
|
|
|
int32 CurWidgetsMaxLayerId;
|
|
{
|
|
UEdGraphNode* NodeObj = Cast<UEdGraphNode>(ChildNode->GetObjectBeingDisplayed());
|
|
|
|
/** When diffing nodes, nodes that are different between revisions are opaque, nodes that have not changed are faded */
|
|
FGraphDiffControl::FNodeMatch NodeMatch = FGraphDiffControl::FindNodeMatch(GraphObjToDiff, NodeObj, NodeMatches);
|
|
if (NodeMatch.IsValid())
|
|
{
|
|
NodeMatches.Add(NodeMatch);
|
|
}
|
|
const bool bNodeIsDifferent = (!GraphObjToDiff || NodeMatch.Diff(FGraphDiffControl::FNodeDiffContext()));
|
|
|
|
/* When dragging off a pin, we want to duck the alpha of some nodes */
|
|
TSharedPtr< SGraphPin > OnlyStartPin = (1 == PreviewConnectorFromPins.Num()) ? PreviewConnectorFromPins[0].FindInGraphPanel(*this) : TSharedPtr< SGraphPin >();
|
|
const bool bNodeIsNotUsableInCurrentContext = Schema->FadeNodeWhenDraggingOffPin(NodeObj, OnlyStartPin.IsValid() ? OnlyStartPin.Get()->GetPinObj() : nullptr);
|
|
const FWidgetStyle& NodeStyleToUse = (bNodeIsDifferent && !bNodeIsNotUsableInCurrentContext)? InWidgetStyle : FadedStyle;
|
|
|
|
// Draw the node.O
|
|
CurWidgetsMaxLayerId = CurWidget.Widget->Paint(NewArgs, CurWidget.Geometry, MyClippingRect, OutDrawElements, ChildLayerId, NodeStyleToUse, !DisplayAsReadOnly.Get() && ShouldBeEnabled( bParentEnabled ) );
|
|
}
|
|
|
|
// Draw the node's overlay, if it has one.
|
|
{
|
|
// Get its size
|
|
const FVector2D WidgetSize = CurWidget.Geometry.Size;
|
|
|
|
{
|
|
TArray<FOverlayBrushInfo> OverlayBrushes;
|
|
ChildNode->GetOverlayBrushes(bSelected, WidgetSize, /*out*/ OverlayBrushes);
|
|
|
|
for (int32 BrushIndex = 0; BrushIndex < OverlayBrushes.Num(); ++BrushIndex)
|
|
{
|
|
FOverlayBrushInfo& OverlayInfo = OverlayBrushes[BrushIndex];
|
|
const FSlateBrush* OverlayBrush = OverlayInfo.Brush;
|
|
if (OverlayBrush != nullptr)
|
|
{
|
|
FPaintGeometry BouncedGeometry = CurWidget.Geometry.ToPaintGeometry(OverlayInfo.OverlayOffset, OverlayBrush->ImageSize, 1.f);
|
|
|
|
// Handle bouncing during PIE
|
|
const float BounceValue = FMath::Sin(2.0f * PI * BounceCurve.GetLerp());
|
|
BouncedGeometry.DrawPosition += (OverlayInfo.AnimationEnvelope * BounceValue * ZoomFactor);
|
|
|
|
CurWidgetsMaxLayerId++;
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
CurWidgetsMaxLayerId,
|
|
BouncedGeometry,
|
|
OverlayBrush,
|
|
MyClippingRect
|
|
);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
{
|
|
TArray<FOverlayWidgetInfo> OverlayWidgets = ChildNode->GetOverlayWidgets(bSelected, WidgetSize);
|
|
|
|
for (int32 WidgetIndex = 0; WidgetIndex < OverlayWidgets.Num(); ++WidgetIndex)
|
|
{
|
|
FOverlayWidgetInfo& OverlayInfo = OverlayWidgets[WidgetIndex];
|
|
if(OverlayInfo.Widget->GetVisibility() == EVisibility::Visible)
|
|
{
|
|
// call SlatePrepass as these widgets are not in the 'normal' child hierarchy
|
|
OverlayInfo.Widget->SlatePrepass(AllottedGeometry.GetAccumulatedLayoutTransform().GetScale());
|
|
|
|
const FGeometry WidgetGeometry = CurWidget.Geometry.MakeChild(OverlayInfo.OverlayOffset, OverlayInfo.Widget->GetDesiredSize());
|
|
|
|
OverlayInfo.Widget->Paint(NewArgs, WidgetGeometry, MyClippingRect, OutDrawElements, CurWidgetsMaxLayerId, InWidgetStyle, bParentEnabled);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MaxLayerId = FMath::Max( MaxLayerId, CurWidgetsMaxLayerId + 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
MaxLayerId += 1;
|
|
|
|
|
|
// Draw connections between pins
|
|
if (Children.Num() > 0 )
|
|
{
|
|
FConnectionDrawingPolicy* ConnectionDrawingPolicy = FNodeFactory::CreateConnectionPolicy(Schema, WireLayerId, MaxLayerId, ZoomFactor, MyClippingRect, OutDrawElements, GraphObj);
|
|
|
|
TArray<TSharedPtr<SGraphPin>> OverridePins;
|
|
for (const FGraphPinHandle& Handle : PreviewConnectorFromPins)
|
|
{
|
|
TSharedPtr<SGraphPin> Pin = Handle.FindInGraphPanel(*this);
|
|
if (Pin.IsValid())
|
|
{
|
|
OverridePins.Add(Pin);
|
|
}
|
|
}
|
|
ConnectionDrawingPolicy->SetHoveredPins(CurrentHoveredPins, OverridePins, TimeWhenMouseEnteredPin);
|
|
ConnectionDrawingPolicy->SetMarkedPin(MarkedPin);
|
|
ConnectionDrawingPolicy->SetMousePosition(AllottedGeometry.LocalToAbsolute(SavedMousePosForOnPaintEventLocalSpace));
|
|
|
|
// Get the set of pins for all children and synthesize geometry for culled out pins so lines can be drawn to them.
|
|
TMap<TSharedRef<SWidget>, FArrangedWidget> PinGeometries;
|
|
TSet< TSharedRef<SWidget> > VisiblePins;
|
|
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
|
|
{
|
|
TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(Children[ChildIndex]);
|
|
|
|
// If this is a culled node, approximate the pin geometry to the corner of the node it is within
|
|
if (IsNodeCulled(ChildNode, AllottedGeometry))
|
|
{
|
|
TArray< TSharedRef<SWidget> > NodePins;
|
|
ChildNode->GetPins(NodePins);
|
|
|
|
const FVector2D NodeLoc = ChildNode->GetPosition();
|
|
const FGeometry SynthesizedNodeGeometry(GraphCoordToPanelCoord(NodeLoc), AllottedGeometry.AbsolutePosition, FVector2D::ZeroVector, 1.f);
|
|
|
|
for (TArray< TSharedRef<SWidget> >::TConstIterator NodePinIterator(NodePins); NodePinIterator; ++NodePinIterator)
|
|
{
|
|
const SGraphPin& PinWidget = static_cast<const SGraphPin&>((*NodePinIterator).Get());
|
|
FVector2D PinLoc = NodeLoc + PinWidget.GetNodeOffset();
|
|
|
|
const FGeometry SynthesizedPinGeometry(GraphCoordToPanelCoord(PinLoc), AllottedGeometry.AbsolutePosition, FVector2D::ZeroVector, 1.f);
|
|
PinGeometries.Add(*NodePinIterator, FArrangedWidget(*NodePinIterator, SynthesizedPinGeometry));
|
|
}
|
|
|
|
// Also add synthesized geometries for culled nodes
|
|
ArrangedChildren.AddWidget( FArrangedWidget(ChildNode, SynthesizedNodeGeometry) );
|
|
}
|
|
else
|
|
{
|
|
ChildNode->GetPins(VisiblePins);
|
|
}
|
|
}
|
|
|
|
// Now get the pin geometry for all visible children and append it to the PinGeometries map
|
|
TMap<TSharedRef<SWidget>, FArrangedWidget> VisiblePinGeometries;
|
|
{
|
|
this->FindChildGeometries(AllottedGeometry, VisiblePins, VisiblePinGeometries);
|
|
PinGeometries.Append(VisiblePinGeometries);
|
|
}
|
|
|
|
// Draw preview connections (only connected on one end)
|
|
if (PreviewConnectorFromPins.Num() > 0)
|
|
{
|
|
for (const FGraphPinHandle& Handle : PreviewConnectorFromPins)
|
|
{
|
|
TSharedPtr< SGraphPin > CurrentStartPin = Handle.FindInGraphPanel(*this);
|
|
if (!CurrentStartPin.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
const FArrangedWidget* PinGeometry = PinGeometries.Find( CurrentStartPin.ToSharedRef() );
|
|
|
|
if (PinGeometry != nullptr)
|
|
{
|
|
FVector2D StartPoint;
|
|
FVector2D EndPoint;
|
|
|
|
if (CurrentStartPin->GetDirection() == EGPD_Input)
|
|
{
|
|
StartPoint = AllottedGeometry.LocalToAbsolute(PreviewConnectorEndpoint);
|
|
EndPoint = FGeometryHelper::VerticalMiddleLeftOf( PinGeometry->Geometry ) - FVector2D(ConnectionDrawingPolicy->ArrowRadius.X, 0);
|
|
}
|
|
else
|
|
{
|
|
StartPoint = FGeometryHelper::VerticalMiddleRightOf( PinGeometry->Geometry );
|
|
EndPoint = AllottedGeometry.LocalToAbsolute(PreviewConnectorEndpoint);
|
|
}
|
|
|
|
ConnectionDrawingPolicy->DrawPreviewConnector(PinGeometry->Geometry, StartPoint, EndPoint, CurrentStartPin.Get()->GetPinObj());
|
|
}
|
|
|
|
//@TODO: Re-evaluate this incompatible mojo; it's mutating every pin state every frame to accomplish a visual effect
|
|
ConnectionDrawingPolicy->SetIncompatiblePinDrawState(CurrentStartPin, VisiblePins);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//@TODO: Re-evaluate this incompatible mojo; it's mutating every pin state every frame to accomplish a visual effect
|
|
ConnectionDrawingPolicy->ResetIncompatiblePinDrawState(VisiblePins);
|
|
}
|
|
|
|
// Draw all regular connections
|
|
ConnectionDrawingPolicy->Draw(PinGeometries, ArrangedChildren);
|
|
|
|
// Pull back data from the drawing policy
|
|
{
|
|
FGraphSplineOverlapResult OverlapData = ConnectionDrawingPolicy->SplineOverlapResult;
|
|
|
|
if (OverlapData.IsValid())
|
|
{
|
|
OverlapData.ComputeBestPin();
|
|
|
|
// Only allow spline overlaps when there is no node under the cursor (unless it is a comment box)
|
|
const FVector2D PaintAbsoluteSpaceMousePos = AllottedGeometry.LocalToAbsolute(SavedMousePosForOnPaintEventLocalSpace);
|
|
const int32 HoveredNodeIndex = SWidget::FindChildUnderPosition(ArrangedChildren, PaintAbsoluteSpaceMousePos);
|
|
if (HoveredNodeIndex != INDEX_NONE)
|
|
{
|
|
TSharedRef<SGraphNode> HoveredNode = StaticCastSharedRef<SGraphNode>(ArrangedChildren[HoveredNodeIndex].Widget);
|
|
UEdGraphNode_Comment* CommentNode = Cast<UEdGraphNode_Comment>(HoveredNode->GetObjectBeingDisplayed());
|
|
if (CommentNode == nullptr)
|
|
{
|
|
// Wasn't a comment node, disallow the spline interaction
|
|
OverlapData = FGraphSplineOverlapResult();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the spline hover state
|
|
const_cast<SGraphPanel*>(this)->OnSplineHoverStateChanged(OverlapData);
|
|
}
|
|
|
|
delete ConnectionDrawingPolicy;
|
|
}
|
|
|
|
// Draw a shadow overlay around the edges of the graph
|
|
++MaxLayerId;
|
|
PaintSurroundSunkenShadow(FEditorStyle::GetBrush(TEXT("Graph.Shadow")), AllottedGeometry, MyClippingRect, OutDrawElements, MaxLayerId);
|
|
|
|
if (ShowGraphStateOverlay.Get())
|
|
{
|
|
const FSlateBrush* BorderBrush = nullptr;
|
|
if ((GEditor->bIsSimulatingInEditor || GEditor->PlayWorld != nullptr))
|
|
{
|
|
// Draw a surrounding indicator when PIE is active, to make it clear that the graph is read-only, etc...
|
|
BorderBrush = FEditorStyle::GetBrush(TEXT("Graph.PlayInEditor"));
|
|
}
|
|
else if (!IsEditable.Get())
|
|
{
|
|
// Draw a different border when we're not simulating but the graph is read-only
|
|
BorderBrush = FEditorStyle::GetBrush(TEXT("Graph.ReadOnlyBorder"));
|
|
}
|
|
|
|
if (BorderBrush != nullptr)
|
|
{
|
|
// Actually draw the border
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
MaxLayerId,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
BorderBrush,
|
|
MyClippingRect
|
|
);
|
|
}
|
|
}
|
|
|
|
// Draw the marquee selection rectangle
|
|
PaintMarquee(AllottedGeometry, MyClippingRect, OutDrawElements, MaxLayerId);
|
|
|
|
// Draw the software cursor
|
|
++MaxLayerId;
|
|
PaintSoftwareCursor(AllottedGeometry, MyClippingRect, OutDrawElements, MaxLayerId);
|
|
|
|
return MaxLayerId;
|
|
}
|
|
|
|
void SGraphPanel::OnSplineHoverStateChanged(const FGraphSplineOverlapResult& NewSplineHoverState)
|
|
{
|
|
TSharedPtr<SGraphPin> OldPinWidget = PreviousFrameSplineOverlap.GetBestPinWidget(*this);
|
|
PreviousFrameSplineOverlap = NewSplineHoverState;
|
|
TSharedPtr<SGraphPin> NewPinWidget = PreviousFrameSplineOverlap.GetBestPinWidget(*this);
|
|
|
|
PreviousFrameSavedMousePosForSplineOverlap = SavedMousePosForOnPaintEventLocalSpace;
|
|
|
|
// Handle mouse enter/leaves on the associated pin
|
|
if (OldPinWidget != NewPinWidget)
|
|
{
|
|
if (OldPinWidget.IsValid())
|
|
{
|
|
OldPinWidget->OnMouseLeave(LastPointerEvent);
|
|
}
|
|
|
|
if (NewPinWidget.IsValid())
|
|
{
|
|
NewPinWidget->OnMouseEnter(LastPointerGeometry, LastPointerEvent);
|
|
|
|
// Get the pin/wire glowing quicker, since it's a direct selection (this time was already set to 'now' as part of entering the pin)
|
|
//@TODO: Source this parameter from the graph rendering settings once it is there (see code in ApplyHoverDeemphasis)
|
|
TimeWhenMouseEnteredPin -= 0.75f;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SGraphPanel::SupportsKeyboardFocus() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void SGraphPanel::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const
|
|
{
|
|
SNodePanel::OnArrangeChildren(AllottedGeometry, ArrangedChildren);
|
|
|
|
FArrangedChildren MyArrangedChildren(ArrangedChildren.GetFilter());
|
|
for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex)
|
|
{
|
|
FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex];
|
|
TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(CurWidget.Widget);
|
|
|
|
TArray<FOverlayWidgetInfo> OverlayWidgets = ChildNode->GetOverlayWidgets(false, CurWidget.Geometry.Size);
|
|
|
|
for (int32 WidgetIndex = 0; WidgetIndex < OverlayWidgets.Num(); ++WidgetIndex)
|
|
{
|
|
FOverlayWidgetInfo& OverlayInfo = OverlayWidgets[WidgetIndex];
|
|
|
|
MyArrangedChildren.AddWidget(AllottedGeometry.MakeChild( OverlayInfo.Widget.ToSharedRef(), CurWidget.Geometry.Position + OverlayInfo.OverlayOffset, OverlayInfo.Widget->GetDesiredSize(), GetZoomAmount() ));
|
|
}
|
|
}
|
|
|
|
ArrangedChildren.Append(MyArrangedChildren);
|
|
}
|
|
|
|
TSharedPtr<IToolTip> SGraphPanel::GetToolTip()
|
|
{
|
|
if (SGraphPin* BestPinFromHoveredSpline = GetBestPinFromHoveredSpline())
|
|
{
|
|
return BestPinFromHoveredSpline->GetToolTip();
|
|
}
|
|
|
|
return SNodePanel::GetToolTip();
|
|
}
|
|
|
|
void SGraphPanel::UpdateSelectedNodesPositions(FVector2D PositionIncrement)
|
|
{
|
|
for (FGraphPanelSelectionSet::TIterator NodeIt(SelectionManager.SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
TSharedRef<SNode>* pWidget = NodeToWidgetLookup.Find(*NodeIt);
|
|
if (pWidget != nullptr)
|
|
{
|
|
SNode& Widget = pWidget->Get();
|
|
SNode::FNodeSet NodeFilter;
|
|
Widget.MoveTo(Widget.GetPosition() + PositionIncrement, NodeFilter);
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply SGraphPanel::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
|
|
{
|
|
if( IsEditable.Get() )
|
|
{
|
|
const bool bIsModifierActive = InKeyEvent.IsCommandDown() || InKeyEvent.IsAltDown() || InKeyEvent.IsShiftDown() || InKeyEvent.IsControlDown();
|
|
if (!bIsModifierActive)
|
|
{
|
|
if( InKeyEvent.GetKey() == EKeys::Up ||InKeyEvent.GetKey() == EKeys::NumPadEight )
|
|
{
|
|
UpdateSelectedNodesPositions(FVector2D(0.0f,-GetSnapGridSize()));
|
|
return FReply::Handled();
|
|
}
|
|
if( InKeyEvent.GetKey() == EKeys::Down || InKeyEvent.GetKey() == EKeys::NumPadTwo )
|
|
{
|
|
UpdateSelectedNodesPositions(FVector2D(0.0f,GetSnapGridSize()));
|
|
return FReply::Handled();
|
|
}
|
|
if( InKeyEvent.GetKey() == EKeys::Right || InKeyEvent.GetKey() == EKeys::NumPadSix )
|
|
{
|
|
UpdateSelectedNodesPositions(FVector2D(GetSnapGridSize(),0.0f));
|
|
return FReply::Handled();
|
|
}
|
|
if( InKeyEvent.GetKey() == EKeys::Left || InKeyEvent.GetKey() == EKeys::NumPadFour )
|
|
{
|
|
UpdateSelectedNodesPositions(FVector2D(-GetSnapGridSize(),0.0f));
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
|
|
if(InKeyEvent.GetKey() == FGraphEditorCommands::Get().ZoomOut->GetActiveChord()->Key)
|
|
{
|
|
ChangeZoomLevel(-1, CachedAllottedGeometryScaledSize / 2.f, InKeyEvent.IsControlDown());
|
|
return FReply::Handled();
|
|
}
|
|
if(InKeyEvent.GetKey() == FGraphEditorCommands::Get().ZoomIn->GetActiveChord()->Key)
|
|
{
|
|
ChangeZoomLevel(+1, CachedAllottedGeometryScaledSize / 2.f, InKeyEvent.IsControlDown());
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
|
|
return SNodePanel::OnKeyDown(MyGeometry, InKeyEvent);
|
|
}
|
|
|
|
FReply SGraphPanel::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
if ((MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) && (MouseEvent.IsAltDown() || MouseEvent.IsControlDown()))
|
|
{
|
|
if (SGraphPin* BestPinFromHoveredSpline = GetBestPinFromHoveredSpline())
|
|
{
|
|
return BestPinFromHoveredSpline->OnPinMouseDown(MyGeometry, MouseEvent);
|
|
}
|
|
}
|
|
|
|
return SNodePanel::OnMouseButtonDown(MyGeometry, MouseEvent);
|
|
}
|
|
|
|
FReply SGraphPanel::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
if ((MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) && (MouseEvent.IsShiftDown()))
|
|
{
|
|
if (SGraphPin* BestPinFromHoveredSpline = GetBestPinFromHoveredSpline())
|
|
{
|
|
return BestPinFromHoveredSpline->OnMouseButtonUp(MyGeometry, MouseEvent);
|
|
}
|
|
}
|
|
|
|
return SNodePanel::OnMouseButtonUp(MyGeometry, MouseEvent);
|
|
}
|
|
|
|
FReply SGraphPanel::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
UEdGraphPin* Pin1;
|
|
UEdGraphPin* Pin2;
|
|
if (PreviousFrameSplineOverlap.GetPins(*this, /*out*/ Pin1, /*out*/ Pin2))
|
|
{
|
|
// Give the schema a chance to do something interesting with a double click on a proper spline (both ends are attached to a pin, i.e., not a preview/drag one)
|
|
const FVector2D DoubleClickPositionInGraphSpace = PanelCoordToGraphCoord(MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()));
|
|
|
|
const UEdGraphSchema* Schema = GraphObj->GetSchema();
|
|
Schema->OnPinConnectionDoubleCicked(Pin1, Pin2, DoubleClickPositionInGraphSpace);
|
|
}
|
|
|
|
return SNodePanel::OnMouseButtonDoubleClick(MyGeometry, MouseEvent);
|
|
}
|
|
|
|
class SGraphPin* SGraphPanel::GetBestPinFromHoveredSpline() const
|
|
{
|
|
TSharedPtr<SGraphPin> BestPinWidget = PreviousFrameSplineOverlap.GetBestPinWidget(*this);
|
|
return BestPinWidget.Get();
|
|
}
|
|
|
|
void SGraphPanel::GetAllPins(TSet< TSharedRef<SWidget> >& AllPins)
|
|
{
|
|
// Get the set of pins for all children
|
|
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
|
|
{
|
|
TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(Children[ChildIndex]);
|
|
ChildNode->GetPins(AllPins);
|
|
}
|
|
}
|
|
|
|
void SGraphPanel::AddPinToHoverSet(UEdGraphPin* HoveredPin)
|
|
{
|
|
CurrentHoveredPins.Add(HoveredPin);
|
|
TimeWhenMouseEnteredPin = FSlateApplication::Get().GetCurrentTime();
|
|
|
|
// About covers the fade in time when highlighting pins or splines.
|
|
TimeLeftToInvalidatePerTick += 1.5f;
|
|
|
|
// This handle should always be for this function
|
|
if (!ActiveTimerHandleInvalidatePerTick.IsValid())
|
|
{
|
|
ActiveTimerHandleInvalidatePerTick = RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SGraphPanel::InvalidatePerTick));
|
|
}
|
|
}
|
|
|
|
void SGraphPanel::RemovePinFromHoverSet(UEdGraphPin* UnhoveredPin)
|
|
{
|
|
CurrentHoveredPins.Remove(UnhoveredPin);
|
|
TimeWhenMouseLeftPin = FSlateApplication::Get().GetCurrentTime();
|
|
}
|
|
|
|
void SGraphPanel::ArrangeChildrenForContextMenuSummon(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const
|
|
{
|
|
// First pass nodes
|
|
for (int32 ChildIndex = 0; ChildIndex < VisibleChildren.Num(); ++ChildIndex)
|
|
{
|
|
const TSharedRef<SNode>& SomeChild = VisibleChildren[ChildIndex];
|
|
if (!SomeChild->RequiresSecondPassLayout())
|
|
{
|
|
ArrangedChildren.AddWidget(AllottedGeometry.MakeChild(SomeChild, SomeChild->GetPosition() - ViewOffset, SomeChild->GetDesiredSizeForMarquee(), GetZoomAmount()));
|
|
}
|
|
}
|
|
|
|
// Second pass nodes
|
|
for (int32 ChildIndex = 0; ChildIndex < VisibleChildren.Num(); ++ChildIndex)
|
|
{
|
|
const TSharedRef<SNode>& SomeChild = VisibleChildren[ChildIndex];
|
|
if (SomeChild->RequiresSecondPassLayout())
|
|
{
|
|
SomeChild->PerformSecondPassLayout(NodeToWidgetLookup);
|
|
ArrangedChildren.AddWidget(AllottedGeometry.MakeChild(SomeChild, SomeChild->GetPosition() - ViewOffset, SomeChild->GetDesiredSizeForMarquee(), GetZoomAmount()));
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SWidget> SGraphPanel::OnSummonContextMenu(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
//Editability is up to the user to consider for menu options
|
|
{
|
|
// If we didn't drag very far, summon a context menu.
|
|
// Figure out what's under the mouse: Node, Pin or just the Panel, and summon the context menu for that.
|
|
UEdGraphNode* NodeUnderCursor = nullptr;
|
|
UEdGraphPin* PinUnderCursor = nullptr;
|
|
{
|
|
FArrangedChildren ArrangedNodes(EVisibility::Visible);
|
|
this->ArrangeChildrenForContextMenuSummon(MyGeometry, ArrangedNodes);
|
|
const int32 HoveredNodeIndex = SWidget::FindChildUnderMouse( ArrangedNodes, MouseEvent );
|
|
if (HoveredNodeIndex != INDEX_NONE)
|
|
{
|
|
const FArrangedWidget& HoveredNode = ArrangedNodes[HoveredNodeIndex];
|
|
TSharedRef<SGraphNode> GraphNode = StaticCastSharedRef<SGraphNode>(HoveredNode.Widget);
|
|
TSharedPtr<SGraphNode> GraphSubNode = GraphNode->GetNodeUnderMouse(HoveredNode.Geometry, MouseEvent);
|
|
GraphNode = GraphSubNode.IsValid() ? GraphSubNode.ToSharedRef() : GraphNode;
|
|
NodeUnderCursor = GraphNode->GetNodeObj();
|
|
|
|
// Selection should switch to this code if it isn't already selected.
|
|
// When multiple nodes are selected, we do nothing, provided that the
|
|
// node for which the context menu is being created is in the selection set.
|
|
if (!SelectionManager.IsNodeSelected(GraphNode->GetObjectBeingDisplayed()))
|
|
{
|
|
SelectionManager.SelectSingleNode(GraphNode->GetObjectBeingDisplayed());
|
|
}
|
|
|
|
const TSharedPtr<SGraphPin> HoveredPin = GraphNode->GetHoveredPin( HoveredNode.Geometry, MouseEvent );
|
|
if (HoveredPin.IsValid())
|
|
{
|
|
PinUnderCursor = HoveredPin->GetPinObj();
|
|
}
|
|
}
|
|
}
|
|
|
|
const FVector2D NodeAddPosition = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()) );
|
|
TArray<UEdGraphPin*> NoSourcePins;
|
|
|
|
return SummonContextMenu(MouseEvent.GetScreenSpacePosition(), NodeAddPosition, NodeUnderCursor, PinUnderCursor, NoSourcePins, MouseEvent.IsShiftDown());
|
|
}
|
|
|
|
return TSharedPtr<SWidget>();
|
|
}
|
|
|
|
|
|
bool SGraphPanel::OnHandleLeftMouseRelease(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
TSharedPtr<SGraphPin> PreviewConnectionPin = PreviewConnectorFromPins.Num() > 0 ? PreviewConnectorFromPins[0].FindInGraphPanel(*this) : nullptr;
|
|
if (PreviewConnectionPin.IsValid() && IsEditable.Get())
|
|
{
|
|
TSet< TSharedRef<SWidget> > AllConnectors;
|
|
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
|
|
{
|
|
//@FINDME:
|
|
TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(Children[ChildIndex]);
|
|
ChildNode->GetPins(AllConnectors);
|
|
}
|
|
|
|
TMap<TSharedRef<SWidget>, FArrangedWidget> PinGeometries;
|
|
this->FindChildGeometries(MyGeometry, AllConnectors, PinGeometries);
|
|
|
|
bool bHandledDrop = false;
|
|
TSet<UEdGraphNode*> NodeList;
|
|
for ( TMap<TSharedRef<SWidget>, FArrangedWidget>::TIterator SomePinIt(PinGeometries); !bHandledDrop && SomePinIt; ++SomePinIt )
|
|
{
|
|
FArrangedWidget& PinWidgetGeometry = SomePinIt.Value();
|
|
if( PinWidgetGeometry.Geometry.IsUnderLocation( MouseEvent.GetScreenSpacePosition() ) )
|
|
{
|
|
SGraphPin& TargetPin = static_cast<SGraphPin&>( PinWidgetGeometry.Widget.Get() );
|
|
|
|
if (PreviewConnectionPin->TryHandlePinConnection(TargetPin))
|
|
{
|
|
NodeList.Add(TargetPin.GetPinObj()->GetOwningNode());
|
|
NodeList.Add(PreviewConnectionPin->GetPinObj()->GetOwningNode());
|
|
}
|
|
bHandledDrop = true;
|
|
}
|
|
}
|
|
|
|
// No longer make a connection for a pin; we just connected or failed to connect.
|
|
OnStopMakingConnection(/*bForceStop=*/ true);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FReply SGraphPanel::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
LastPointerEvent = MouseEvent;
|
|
LastPointerGeometry = MyGeometry;
|
|
|
|
// Save the mouse position to use in OnPaint for spline hit detection
|
|
SavedMousePosForOnPaintEventLocalSpace = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
|
|
|
// Invalidate the spline results if we moved very far
|
|
const FVector2D MouseDelta = SavedMousePosForOnPaintEventLocalSpace - PreviousFrameSavedMousePosForSplineOverlap;
|
|
const float MouseDeltaLengthSquared = MouseDelta.SizeSquared();
|
|
const bool bCursorInDeadZone = MouseDeltaLengthSquared <= FMath::Square(FSlateApplication::Get().GetDragTriggerDistance());
|
|
|
|
if (!bCursorInDeadZone)
|
|
{
|
|
//@TODO: Should we do this or just rely on the next OnPaint?
|
|
// Our frame-latent approximation is going to be totally junk if the mouse is moving quickly
|
|
OnSplineHoverStateChanged(FGraphSplineOverlapResult());
|
|
}
|
|
|
|
return SNodePanel::OnMouseMove(MyGeometry, MouseEvent);
|
|
}
|
|
|
|
void SGraphPanel::OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
TSharedPtr<FGraphEditorDragDropAction> DragConnectionOp = DragDropEvent.GetOperationAs<FGraphEditorDragDropAction>();
|
|
if (DragConnectionOp.IsValid())
|
|
{
|
|
DragConnectionOp->SetHoveredGraph( SharedThis(this) );
|
|
}
|
|
}
|
|
|
|
void SGraphPanel::OnDragLeave( const FDragDropEvent& DragDropEvent )
|
|
{
|
|
TSharedPtr<FGraphEditorDragDropAction> Operation = DragDropEvent.GetOperationAs<FGraphEditorDragDropAction>();
|
|
if( Operation.IsValid() )
|
|
{
|
|
Operation->SetHoveredGraph(TSharedPtr<SGraphPanel>(nullptr));
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<FDecoratedDragDropOp> AssetOp = DragDropEvent.GetOperationAs<FDecoratedDragDropOp>();
|
|
if( AssetOp.IsValid() )
|
|
{
|
|
AssetOp->ResetToDefaultToolTip();
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply SGraphPanel::OnDragOver( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
|
|
if (!Operation.IsValid())
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
// Handle Read only graphs
|
|
if (!IsEditable.Get())
|
|
{
|
|
TSharedPtr<FGraphEditorDragDropAction> GraphDragDropOp = DragDropEvent.GetOperationAs<FGraphEditorDragDropAction>();
|
|
|
|
if (GraphDragDropOp.IsValid())
|
|
{
|
|
GraphDragDropOp->SetDropTargetValid(false);
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<FDecoratedDragDropOp> AssetOp = DragDropEvent.GetOperationAs<FDecoratedDragDropOp>();
|
|
if (AssetOp.IsValid())
|
|
{
|
|
FText Tooltip = AssetOp->GetHoverText();
|
|
if (Tooltip.IsEmpty())
|
|
{
|
|
Tooltip = NSLOCTEXT( "GraphPanel", "DragDropOperation", "Graph is Read-Only" );
|
|
}
|
|
AssetOp->SetToolTip(Tooltip, FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")));
|
|
}
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
if (Operation->IsOfType<FGraphEditorDragDropAction>())
|
|
{
|
|
PreviewConnectorEndpoint = MyGeometry.AbsoluteToLocal( DragDropEvent.GetScreenSpacePosition() );
|
|
return FReply::Handled();
|
|
}
|
|
else if (Operation->IsOfType<FExternalDragOperation>())
|
|
{
|
|
return AssetUtil::CanHandleAssetDrag(DragDropEvent);
|
|
}
|
|
else if (Operation->IsOfType<FAssetDragDropOp>())
|
|
{
|
|
if ((GraphObj != nullptr) && (GraphObj->GetSchema() != nullptr))
|
|
{
|
|
TSharedPtr<FAssetDragDropOp> AssetOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
|
|
bool bOkIcon = false;
|
|
FString TooltipText;
|
|
GraphObj->GetSchema()->GetAssetsGraphHoverMessage(AssetOp->AssetData, GraphObj, TooltipText, 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();
|
|
}
|
|
else
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
FReply SGraphPanel::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
const FVector2D NodeAddPosition = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( DragDropEvent.GetScreenSpacePosition() ) );
|
|
|
|
FSlateApplication::Get().SetKeyboardFocus(AsShared(), EFocusCause::SetDirectly);
|
|
|
|
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
|
|
if (!Operation.IsValid() || !IsEditable.Get())
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
if (Operation->IsOfType<FGraphEditorDragDropAction>())
|
|
{
|
|
check(GraphObj);
|
|
TSharedPtr<FGraphEditorDragDropAction> DragConn = StaticCastSharedPtr<FGraphEditorDragDropAction>(Operation);
|
|
if (DragConn.IsValid() && DragConn->IsSupportedBySchema(GraphObj->GetSchema()))
|
|
{
|
|
return DragConn->DroppedOnPanel(SharedThis(this), DragDropEvent.GetScreenSpacePosition(), NodeAddPosition, *GraphObj);
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
else if (Operation->IsOfType<FActorDragDropGraphEdOp>())
|
|
{
|
|
TSharedPtr<FActorDragDropGraphEdOp> ActorOp = StaticCastSharedPtr<FActorDragDropGraphEdOp>(Operation);
|
|
OnDropActor.ExecuteIfBound(ActorOp->Actors, GraphObj, NodeAddPosition);
|
|
return FReply::Handled();
|
|
}
|
|
|
|
else if (Operation->IsOfType<FLevelDragDropOp>())
|
|
{
|
|
TSharedPtr<FLevelDragDropOp> LevelOp = StaticCastSharedPtr<FLevelDragDropOp>(Operation);
|
|
OnDropStreamingLevel.ExecuteIfBound(LevelOp->StreamingLevelsToDrop, GraphObj, NodeAddPosition);
|
|
return FReply::Handled();
|
|
}
|
|
else
|
|
{
|
|
if ((GraphObj != nullptr) && (GraphObj->GetSchema() != nullptr))
|
|
{
|
|
TArray< FAssetData > DroppedAssetData = AssetUtil::ExtractAssetDataFromDrag( DragDropEvent );
|
|
|
|
if ( DroppedAssetData.Num() > 0 )
|
|
{
|
|
GraphObj->GetSchema()->DroppedAssetsOnGraph( DroppedAssetData, NodeAddPosition, GraphObj );
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
void SGraphPanel::OnBeginMakingConnection(UEdGraphPin* InOriginatingPin)
|
|
{
|
|
if (InOriginatingPin != nullptr)
|
|
{
|
|
PreviewConnectorFromPins.Add(InOriginatingPin);
|
|
}
|
|
}
|
|
|
|
void SGraphPanel::OnStopMakingConnection(bool bForceStop)
|
|
{
|
|
if (bForceStop || !bPreservePinPreviewConnection)
|
|
{
|
|
PreviewConnectorFromPins.Reset();
|
|
bPreservePinPreviewConnection = false;
|
|
}
|
|
}
|
|
|
|
void SGraphPanel::PreservePinPreviewUntilForced()
|
|
{
|
|
bPreservePinPreviewConnection = true;
|
|
}
|
|
|
|
/** Add a slot to the CanvasPanel dynamically */
|
|
void SGraphPanel::AddGraphNode( const TSharedRef<SNodePanel::SNode>& NodeToAdd )
|
|
{
|
|
TSharedRef<SGraphNode> GraphNode = StaticCastSharedRef<SGraphNode>(NodeToAdd);
|
|
GraphNode->SetOwner( SharedThis(this) );
|
|
|
|
const UEdGraphNode* Node = GraphNode->GetNodeObj();
|
|
if (Node)
|
|
{
|
|
NodeGuidMap.Add(Node->NodeGuid, GraphNode);
|
|
}
|
|
|
|
SNodePanel::AddGraphNode(NodeToAdd);
|
|
}
|
|
|
|
void SGraphPanel::RemoveAllNodes()
|
|
{
|
|
NodeGuidMap.Empty();
|
|
CurrentHoveredPins.Empty();
|
|
SNodePanel::RemoveAllNodes();
|
|
}
|
|
|
|
TSharedPtr<SWidget> SGraphPanel::SummonContextMenu(const FVector2D& WhereToSummon, const FVector2D& WhereToAddNode, UEdGraphNode* ForNode, UEdGraphPin* ForPin, const TArray<UEdGraphPin*>& DragFromPins, bool bShiftOperation)
|
|
{
|
|
if (OnGetContextMenuFor.IsBound())
|
|
{
|
|
FGraphContextMenuArguments SpawnInfo;
|
|
SpawnInfo.NodeAddPosition = WhereToAddNode;
|
|
SpawnInfo.GraphNode = ForNode;
|
|
SpawnInfo.GraphPin = ForPin;
|
|
SpawnInfo.DragFromPins = DragFromPins;
|
|
SpawnInfo.bShiftOperation = bShiftOperation;
|
|
|
|
FActionMenuContent FocusedContent = OnGetContextMenuFor.Execute(SpawnInfo);
|
|
|
|
TSharedRef<SWidget> MenuContent = FocusedContent.Content;
|
|
|
|
TSharedPtr<IMenu> Menu = FSlateApplication::Get().PushMenu(
|
|
AsShared(),
|
|
FWidgetPath(),
|
|
MenuContent,
|
|
WhereToSummon,
|
|
FPopupTransitionEffect( FPopupTransitionEffect::ContextMenu )
|
|
);
|
|
|
|
if (Menu.IsValid() && Menu->GetOwnedWindow().IsValid())
|
|
{
|
|
Menu->GetOwnedWindow()->SetWidgetToFocusOnActivate(FocusedContent.WidgetToFocus);
|
|
}
|
|
|
|
return FocusedContent.WidgetToFocus;
|
|
}
|
|
|
|
return TSharedPtr<SWidget>();
|
|
}
|
|
|
|
void SGraphPanel::AttachGraphEvents(TSharedPtr<SGraphNode> CreatedSubNode)
|
|
{
|
|
check(CreatedSubNode.IsValid());
|
|
CreatedSubNode->SetIsEditable(IsEditable);
|
|
CreatedSubNode->SetDoubleClickEvent(OnNodeDoubleClicked);
|
|
CreatedSubNode->SetVerifyTextCommitEvent(OnVerifyTextCommit);
|
|
CreatedSubNode->SetTextCommittedEvent(OnTextCommitted);
|
|
}
|
|
|
|
bool SGraphPanel::GetBoundsForNode(const UObject* InNode, FVector2D& MinCorner, FVector2D& MaxCorner, float Padding) const
|
|
{
|
|
return SNodePanel::GetBoundsForNode(InNode, MinCorner, MaxCorner, Padding);
|
|
}
|
|
|
|
class FConnectionAligner
|
|
{
|
|
public:
|
|
void DefineConnection(UEdGraphNode* SourceNode, const TSharedPtr<SGraphPin>& SourcePin, UEdGraphNode* DestinationNode, const TSharedPtr<SGraphPin>& DestinationPin)
|
|
{
|
|
auto& Dependencies = Connections.FindOrAdd(SourceNode);
|
|
if (SourcePin->GetPinObj()->Direction == EEdGraphPinDirection::EGPD_Output)
|
|
{
|
|
Dependencies.Outputs.FindOrAdd(DestinationNode).Add(FPinPair{ SourcePin, DestinationPin });
|
|
}
|
|
else
|
|
{
|
|
Dependencies.Inputs.FindOrAdd(DestinationNode).Add(FPinPair{ SourcePin, DestinationPin });
|
|
}
|
|
}
|
|
|
|
/** Align all the connections */
|
|
void Process()
|
|
{
|
|
struct FRankedNode
|
|
{
|
|
UEdGraphNode* Node;
|
|
uint32 Rank;
|
|
};
|
|
|
|
TArray<FRankedNode> RankedNodes;
|
|
RankedNodes.Reserve(Connections.Num());
|
|
|
|
TMap<UEdGraphNode*, uint32> LongestChainCache;
|
|
LongestChainCache.Reserve(Connections.Num());
|
|
|
|
for (auto& Pair : Connections)
|
|
{
|
|
RankedNodes.Add(FRankedNode{ Pair.Key, CalculateNodeRank(Pair.Key, LongestChainCache) });
|
|
}
|
|
|
|
// Sort the nodes based on dependencies - highest is processed first
|
|
RankedNodes.Sort([](const FRankedNode& A, const FRankedNode& B){
|
|
return A.Rank > B.Rank;
|
|
});
|
|
|
|
TSet<UEdGraphNode*> VistedNodes;
|
|
for (FRankedNode& RankedNode : RankedNodes)
|
|
{
|
|
StraightenConnectionsForNode(RankedNode.Node, VistedNodes, EEdGraphPinDirection::EGPD_Output);
|
|
if (VistedNodes.Num() == RankedNodes.Num())
|
|
{
|
|
return;
|
|
}
|
|
|
|
StraightenConnectionsForNode(RankedNode.Node, VistedNodes, EEdGraphPinDirection::EGPD_Input);
|
|
if (VistedNodes.Num() == RankedNodes.Num())
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
void StraightenConnectionsForNode(UEdGraphNode* Node, TSet<UEdGraphNode*>& VisitedNodes, EEdGraphPinDirection Direction)
|
|
{
|
|
FDependencyInfo* Info = Connections.Find(Node);
|
|
if (!Info)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (auto& NodeToPins : Info->GetDirection(Direction))
|
|
{
|
|
if (NodeToPins.Value.Num() == 0 || VisitedNodes.Contains(NodeToPins.Key))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Align the averages of all the pins
|
|
float AlignmentDelta = 0.f;
|
|
for (const FPinPair& Pins : NodeToPins.Value)
|
|
{
|
|
AlignmentDelta += (Node->NodePosY + Pins.SrcPin->GetNodeOffset().Y) - (NodeToPins.Key->NodePosY + Pins.DstPin->GetNodeOffset().Y);
|
|
}
|
|
|
|
NodeToPins.Key->Modify();
|
|
NodeToPins.Key->NodePosY += AlignmentDelta / NodeToPins.Value.Num();
|
|
|
|
VisitedNodes.Add(Node);
|
|
VisitedNodes.Add(NodeToPins.Key);
|
|
|
|
StraightenConnectionsForNode(NodeToPins.Key, VisitedNodes, Direction);
|
|
}
|
|
}
|
|
|
|
/** Find the longest chain of single-connection nodes connected to the specified node */
|
|
uint32 FindLongestUniqueChain(UEdGraphNode* Node, TMap<UEdGraphNode*, uint32>& LongestChainCache, EEdGraphPinDirection Direction)
|
|
{
|
|
if (uint32* Length = LongestChainCache.Find(Node))
|
|
{
|
|
// Already set, or circular dependency - ignore
|
|
return *Length;
|
|
}
|
|
|
|
// Prevent reentrancy
|
|
LongestChainCache.Add(Node, 0);
|
|
|
|
uint32 ThisLength = 0;
|
|
|
|
if (FDependencyInfo* Dependencies = Connections.Find(Node))
|
|
{
|
|
auto& ConnectedNodes = Dependencies->GetDirection(Direction);
|
|
|
|
// We only follow unique (1-1) connections
|
|
if (ConnectedNodes.Num() == 1)
|
|
{
|
|
for (auto& NodeToPins : ConnectedNodes)
|
|
{
|
|
ThisLength = FindLongestUniqueChain(NodeToPins.Key, LongestChainCache, Direction) + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
LongestChainCache[Node] = ThisLength;
|
|
return ThisLength;
|
|
};
|
|
|
|
/** Calculate the depth of dependencies for the specified node */
|
|
uint32 CalculateNodeRank(UEdGraphNode* Node, TMap<UEdGraphNode*, uint32>& LongestChainCache)
|
|
{
|
|
uint32 Rank = 0;
|
|
if (FDependencyInfo* PinMap = Connections.Find(Node))
|
|
{
|
|
for (auto& NodeToPins : PinMap->Outputs)
|
|
{
|
|
Rank += FindLongestUniqueChain(NodeToPins.Key, LongestChainCache, EEdGraphPinDirection::EGPD_Output) + 1;
|
|
}
|
|
for (auto& NodeToPins : PinMap->Inputs)
|
|
{
|
|
Rank += FindLongestUniqueChain(NodeToPins.Key, LongestChainCache, EEdGraphPinDirection::EGPD_Input) + 1;
|
|
}
|
|
}
|
|
return Rank;
|
|
}
|
|
|
|
private:
|
|
|
|
/** A pair of pins */
|
|
struct FPinPair
|
|
{
|
|
TSharedPtr<SGraphPin> SrcPin, DstPin;
|
|
};
|
|
|
|
/** Map of nodes and pins that are connected to the owning pin */
|
|
struct FDependencyInfo
|
|
{
|
|
TMap<UEdGraphNode*, TArray<FPinPair>> Outputs;
|
|
TMap<UEdGraphNode*, TArray<FPinPair>> Inputs;
|
|
uint32 Rank;
|
|
|
|
TMap<UEdGraphNode*, TArray<FPinPair>>& GetDirection(EEdGraphPinDirection Direction)
|
|
{
|
|
return Direction == EEdGraphPinDirection::EGPD_Output ? Outputs : Inputs;
|
|
}
|
|
};
|
|
typedef TMap<UEdGraphNode*, FDependencyInfo> FConnections;
|
|
|
|
FConnections Connections;
|
|
};
|
|
|
|
void SGraphPanel::StraightenConnections()
|
|
{
|
|
FConnectionAligner Aligner;
|
|
for (auto* It : SelectionManager.SelectedNodes)
|
|
{
|
|
UEdGraphNode* SourceNode = Cast<UEdGraphNode>(It);
|
|
if (!SourceNode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedRef<SNode>* ThisNodePtr = NodeToWidgetLookup.Find(SourceNode);
|
|
if (!ThisNodePtr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (UEdGraphPin* SourcePin : SourceNode->Pins)
|
|
{
|
|
for (UEdGraphPin* LinkedTo : SourcePin->LinkedTo)
|
|
{
|
|
UEdGraphNode* DestNode = LinkedTo ? LinkedTo->GetOwningNode() : nullptr;
|
|
if (DestNode && SelectionManager.SelectedNodes.Contains(DestNode))
|
|
{
|
|
TSharedRef<SNode>* DestGraphNodePtr = NodeToWidgetLookup.Find(DestNode);
|
|
if (!DestGraphNodePtr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedPtr<SGraphPin> PinWidget = StaticCastSharedRef<SGraphNode>(*ThisNodePtr)->FindWidgetForPin(SourcePin);
|
|
TSharedPtr<SGraphPin> LinkedPinWidget = StaticCastSharedRef<SGraphNode>(*DestGraphNodePtr)->FindWidgetForPin(LinkedTo);
|
|
|
|
if (PinWidget.IsValid() && LinkedPinWidget.IsValid())
|
|
{
|
|
Aligner.DefineConnection(SourceNode, PinWidget, DestNode, LinkedPinWidget);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Aligner.Process();
|
|
}
|
|
|
|
void SGraphPanel::StraightenConnections(UEdGraphPin* SourcePin, UEdGraphPin* PinToAlign)
|
|
{
|
|
UEdGraphNode* OwningNode = SourcePin->GetOwningNode();
|
|
|
|
TSharedRef<SNode>* OwningNodeWidgetPtr = NodeToWidgetLookup.Find(OwningNode);
|
|
if (!OwningNodeWidgetPtr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedRef<SGraphNode> SourceGraphNode = StaticCastSharedRef<SGraphNode>(*OwningNodeWidgetPtr);
|
|
|
|
FConnectionAligner Aligner;
|
|
|
|
auto AddConnectedPin = [&](UEdGraphPin* ConnectedPin){
|
|
UEdGraphNode* ConnectedNode = ConnectedPin ? ConnectedPin->GetOwningNode() : nullptr;
|
|
if (!ConnectedNode)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedRef<SNode>* DestGraphNodePtr = NodeToWidgetLookup.Find(ConnectedNode);
|
|
if (!DestGraphNodePtr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedPtr<SGraphPin> PinWidget = SourceGraphNode->FindWidgetForPin(SourcePin);
|
|
TSharedPtr<SGraphPin> LinkedPinWidget = StaticCastSharedRef<SGraphNode>(*DestGraphNodePtr)->FindWidgetForPin(ConnectedPin);
|
|
|
|
if (PinWidget.IsValid() && LinkedPinWidget.IsValid())
|
|
{
|
|
Aligner.DefineConnection(OwningNode, PinWidget, ConnectedNode, LinkedPinWidget);
|
|
}
|
|
};
|
|
|
|
if (PinToAlign)
|
|
{
|
|
// If we're only aligning a specific pin, do that
|
|
AddConnectedPin(PinToAlign);
|
|
}
|
|
// Else add all the connected pins
|
|
else for (UEdGraphPin* ConnectedPin : SourcePin->LinkedTo)
|
|
{
|
|
AddConnectedPin(ConnectedPin);
|
|
}
|
|
|
|
Aligner.Process();
|
|
}
|
|
|
|
const TSharedRef<SGraphNode> SGraphPanel::GetChild(int32 ChildIndex)
|
|
{
|
|
return StaticCastSharedRef<SGraphNode>(Children[ChildIndex]);
|
|
}
|
|
|
|
void SGraphPanel::AddNode(UEdGraphNode* Node, AddNodeBehavior Behavior)
|
|
{
|
|
TSharedPtr<SGraphNode> NewNode = FNodeFactory::CreateNodeWidget(Node);
|
|
check(NewNode.IsValid());
|
|
|
|
const bool bWasUserAdded =
|
|
Behavior == WasUserAdded ? true :
|
|
Behavior == NotUserAdded ? false :
|
|
(UserAddedNodes.Find(Node) != nullptr);
|
|
|
|
NewNode->SetIsEditable(IsEditable);
|
|
NewNode->SetDoubleClickEvent(OnNodeDoubleClicked);
|
|
NewNode->SetVerifyTextCommitEvent(OnVerifyTextCommit);
|
|
NewNode->SetTextCommittedEvent(OnTextCommitted);
|
|
NewNode->SetDisallowedPinConnectionEvent(OnDisallowedPinConnection);
|
|
|
|
this->AddGraphNode
|
|
(
|
|
NewNode.ToSharedRef()
|
|
);
|
|
|
|
if (bWasUserAdded)
|
|
{
|
|
// Add the node to visible children, this allows focus to occur on sub-widgets for naming purposes.
|
|
VisibleChildren.Add(NewNode.ToSharedRef());
|
|
|
|
NewNode->PlaySpawnEffect();
|
|
NewNode->RequestRenameOnSpawn();
|
|
}
|
|
}
|
|
|
|
void SGraphPanel::RemoveNode(const UEdGraphNode* Node)
|
|
{
|
|
for (int32 Iter = 0; Iter != Children.Num(); ++Iter)
|
|
{
|
|
TSharedRef<SGraphNode> Child = GetChild(Iter);
|
|
if (Child->GetNodeObj() == Node)
|
|
{
|
|
Children.RemoveAt(Iter);
|
|
break;
|
|
}
|
|
}
|
|
for (int32 Iter = 0; Iter != VisibleChildren.Num(); ++Iter)
|
|
{
|
|
TSharedRef<SGraphNode> Child = StaticCastSharedRef<SGraphNode>(VisibleChildren[Iter]);
|
|
if (Child->GetNodeObj() == Node)
|
|
{
|
|
VisibleChildren.RemoveAt(Iter);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SGraphNode> SGraphPanel::GetNodeWidgetFromGuid(FGuid Guid) const
|
|
{
|
|
return NodeGuidMap.FindRef(Guid).Pin();
|
|
}
|
|
|
|
void SGraphPanel::Update()
|
|
{
|
|
static bool bIsUpdating = false;
|
|
if (bIsUpdating)
|
|
{
|
|
return;
|
|
}
|
|
TGuardValue<bool> ReentrancyGuard(bIsUpdating, true);
|
|
|
|
// Add widgets for all the nodes that don't have one.
|
|
if (GraphObj != nullptr)
|
|
{
|
|
// Scan for all missing nodes
|
|
for (int32 NodeIndex = 0; NodeIndex < GraphObj->Nodes.Num(); ++NodeIndex)
|
|
{
|
|
UEdGraphNode* Node = GraphObj->Nodes[NodeIndex];
|
|
if (Node)
|
|
{
|
|
// Helps detect cases of UE-26998 without causing a crash. Prevents the node from being rendered altogether and provides info on the state of the graph vs the node.
|
|
// Because the editor won't crash, a GLEO can be expected if the node's outer is in the transient package.
|
|
if (ensureMsgf(Node->GetOuter() == GraphObj, TEXT("Found %s ('%s') that does not belong to %s. Node Outer: %s, Node Outer Type: %s, Graph Outer: %s, Graph Outer Type: %s"),
|
|
*Node->GetName(), *Node->GetClass()->GetName(),
|
|
*GraphObj->GetName(),
|
|
*Node->GetOuter()->GetName(), *Node->GetOuter()->GetClass()->GetName(),
|
|
*GraphObj->GetOuter()->GetName(), *GraphObj->GetOuter()->GetClass()->GetName()
|
|
))
|
|
{
|
|
AddNode(Node, CheckUserAddedNodesList);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogGraphPanel, Error, TEXT("Found %s ('%s') that does not belong to %s. Node Outer: %s, Node Outer Type: %s, Graph Outer: %s, Graph Outer Type: %s"),
|
|
*Node->GetName(), *Node->GetClass()->GetName(),
|
|
*GraphObj->GetName(),
|
|
*Node->GetOuter()->GetName(), *Node->GetOuter()->GetClass()->GetName(),
|
|
*GraphObj->GetOuter()->GetName(), *GraphObj->GetOuter()->GetClass()->GetName()
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogGraphPanel, Warning, TEXT("Found NULL Node in GraphObj array of a graph in asset '%s'. A node type has been deleted without creating an ActiveClassRedirector to K2Node_DeadClass."), *GraphObj->GetOutermost()->GetName());
|
|
}
|
|
}
|
|
|
|
// find the last selection action, and execute it
|
|
for (int32 ActionIndex = UserActions.Num() - 1; ActionIndex >= 0; --ActionIndex)
|
|
{
|
|
if (UserActions[ActionIndex].Action & GRAPHACTION_SelectNode)
|
|
{
|
|
DeferredSelectionTargetObjects.Empty();
|
|
for (const UEdGraphNode* Node : UserActions[ActionIndex].Nodes)
|
|
{
|
|
DeferredSelectionTargetObjects.Add(Node);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RemoveAllNodes();
|
|
}
|
|
|
|
// Clean out set of added nodes
|
|
UserAddedNodes.Empty();
|
|
UserActions.Empty();
|
|
|
|
// Invoke any delegate methods
|
|
OnUpdateGraphPanel.ExecuteIfBound();
|
|
}
|
|
|
|
// Purges the existing visual representation (typically followed by an Update call in the next tick)
|
|
void SGraphPanel::PurgeVisualRepresentation()
|
|
{
|
|
// No need to call OnSplineHoverStateChanged since we're about to destroy all the nodes and pins
|
|
PreviousFrameSplineOverlap = FGraphSplineOverlapResult();
|
|
|
|
// Clear all of the nodes and pins
|
|
RemoveAllNodes();
|
|
}
|
|
|
|
bool SGraphPanel::IsNodeTitleVisible(const class UEdGraphNode* Node, bool bRequestRename)
|
|
{
|
|
bool bTitleVisible = false;
|
|
TSharedRef<SNode>* pWidget = NodeToWidgetLookup.Find(Node);
|
|
|
|
if (pWidget != nullptr)
|
|
{
|
|
TWeakPtr<SGraphNode> GraphNode = StaticCastSharedRef<SGraphNode>(*pWidget);
|
|
if(GraphNode.IsValid() && !HasMouseCapture())
|
|
{
|
|
FSlateRect TitleRect = GraphNode.Pin()->GetTitleRect();
|
|
const FVector2D TopLeft = FVector2D( TitleRect.Left, TitleRect.Top );
|
|
const FVector2D BottomRight = FVector2D( TitleRect.Right, TitleRect.Bottom );
|
|
|
|
if( IsRectVisible( TopLeft, BottomRight ))
|
|
{
|
|
bTitleVisible = true;
|
|
}
|
|
else if( bRequestRename )
|
|
{
|
|
bTitleVisible = JumpToRect( TopLeft, BottomRight );
|
|
}
|
|
|
|
if( bTitleVisible && bRequestRename )
|
|
{
|
|
GraphNode.Pin()->RequestRename();
|
|
SelectAndCenterObject(Node, false);
|
|
}
|
|
}
|
|
}
|
|
return bTitleVisible;
|
|
}
|
|
|
|
bool SGraphPanel::IsRectVisible(const FVector2D &TopLeft, const FVector2D &BottomRight)
|
|
{
|
|
return TopLeft >= PanelCoordToGraphCoord( FVector2D::ZeroVector ) && BottomRight <= PanelCoordToGraphCoord( CachedAllottedGeometryScaledSize );
|
|
}
|
|
|
|
bool SGraphPanel::JumpToRect(const FVector2D &TopLeft, const FVector2D &BottomRight)
|
|
{
|
|
ZoomToTarget(TopLeft, BottomRight);
|
|
|
|
return true;
|
|
}
|
|
|
|
void SGraphPanel::JumpToNode(const UEdGraphNode* JumpToMe, bool bRequestRename, bool bSelectNode)
|
|
{
|
|
if (JumpToMe != nullptr)
|
|
{
|
|
if (bRequestRename)
|
|
{
|
|
TSharedRef<SNode>* pWidget = NodeToWidgetLookup.Find(JumpToMe);
|
|
if (pWidget != nullptr)
|
|
{
|
|
TSharedRef<SGraphNode> GraphNode = StaticCastSharedRef<SGraphNode>(*pWidget);
|
|
GraphNode->RequestRename();
|
|
}
|
|
}
|
|
|
|
if (bSelectNode)
|
|
{
|
|
// Select this node, and request that we jump to it.
|
|
SelectAndCenterObject(JumpToMe, true);
|
|
}
|
|
else
|
|
{
|
|
// Jump to the node
|
|
CenterObject(JumpToMe);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SGraphPanel::JumpToPin(const UEdGraphPin* JumpToMe)
|
|
{
|
|
if (JumpToMe != nullptr)
|
|
{
|
|
JumpToNode(JumpToMe->GetOwningNode(), false, true);
|
|
}
|
|
}
|
|
|
|
void SGraphPanel::OnBeginPIE( const bool bIsSimulating )
|
|
{
|
|
// Play the bounce curve on a continuous loop during PIE
|
|
BounceCurve.Play( this->AsShared(), true );
|
|
}
|
|
|
|
void SGraphPanel::OnEndPIE( const bool bIsSimulating )
|
|
{
|
|
// Stop the bounce curve
|
|
BounceCurve.JumpToEnd();
|
|
}
|
|
|
|
void SGraphPanel::OnGraphChanged(const FEdGraphEditAction& EditAction)
|
|
{
|
|
const bool bWillPurge = GraphObj->GetSchema()->ShouldAlwaysPurgeOnModification();
|
|
if (bWillPurge)
|
|
{
|
|
if ((EditAction.Graph == GraphObj) &&
|
|
(EditAction.Nodes.Num() > 0) &&
|
|
EditAction.bUserInvoked)
|
|
{
|
|
int32 ActionIndex = UserActions.Num();
|
|
if (EditAction.Action & GRAPHACTION_AddNode)
|
|
{
|
|
for (const UEdGraphNode* Node : EditAction.Nodes)
|
|
{
|
|
UserAddedNodes.Add(Node, ActionIndex);
|
|
}
|
|
}
|
|
UserActions.Add(EditAction);
|
|
}
|
|
}
|
|
else if ((EditAction.Graph == GraphObj) && (EditAction.Nodes.Num() > 0) )
|
|
{
|
|
// Remove action handled immediately by SGraphPanel::OnGraphChanged
|
|
const bool bWasAddAction = (EditAction.Action & GRAPHACTION_AddNode) != 0;
|
|
const bool bWasSelectAction = (EditAction.Action & GRAPHACTION_SelectNode) != 0;
|
|
const bool bWasRemoveAction = (EditAction.Action & GRAPHACTION_RemoveNode) != 0;
|
|
|
|
// The *only* reason we defer these actions is because code higher up the call stack
|
|
// assumes that the node is created later (for example, GenerateBlueprintAPIUtils::AddNodeToGraph
|
|
// calls AddNode (which calls this function) before calling AllocateDefaultPins, so if we create
|
|
// the widget immediately it won't be able to create its pins). There are lots of other examples,
|
|
// and I can't be sure that I've found them all....
|
|
|
|
// Minor note, the ugly little lambdas are just to deal with the time values and return values
|
|
// that the timer system requires (and we don't leverage):
|
|
if (bWasRemoveAction)
|
|
{
|
|
const auto RemoveNodeDelegateWrapper = [](double, float, SGraphPanel* Parent, TWeakObjectPtr<UEdGraphNode> NodePtr) -> EActiveTimerReturnType
|
|
{
|
|
if (NodePtr.IsValid())
|
|
{
|
|
UEdGraphNode* Node = NodePtr.Get();
|
|
Parent->RemoveNode(Node);
|
|
}
|
|
return EActiveTimerReturnType::Stop;
|
|
};
|
|
|
|
for (const UEdGraphNode* Node : EditAction.Nodes)
|
|
{
|
|
TWeakObjectPtr<UEdGraphNode> NodePtr = Node;
|
|
RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateStatic(RemoveNodeDelegateWrapper, this, NodePtr));
|
|
}
|
|
}
|
|
if (bWasAddAction)
|
|
{
|
|
const auto AddNodeDelegateWrapper = [](double, float, SGraphPanel* Parent, TWeakObjectPtr<UEdGraphNode> NodePtr, bool bForceUserAdded) -> EActiveTimerReturnType
|
|
{
|
|
if (NodePtr.IsValid())
|
|
{
|
|
UEdGraphNode* Node = NodePtr.Get();
|
|
Parent->RemoveNode(Node);
|
|
Parent->AddNode(Node, bForceUserAdded ? WasUserAdded : NotUserAdded);
|
|
}
|
|
return EActiveTimerReturnType::Stop;
|
|
};
|
|
|
|
for (const UEdGraphNode* Node : EditAction.Nodes)
|
|
{
|
|
TWeakObjectPtr<UEdGraphNode> NodePtr = Node;
|
|
RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateStatic(AddNodeDelegateWrapper, this, NodePtr, EditAction.bUserInvoked));
|
|
}
|
|
}
|
|
if (bWasSelectAction)
|
|
{
|
|
const auto SelectNodeDelegateWrapper = [](double, float, SGraphPanel* Parent, TSet< TWeakObjectPtr<UEdGraphNode> > NodePtrs) -> EActiveTimerReturnType
|
|
{
|
|
Parent->DeferredSelectionTargetObjects.Empty();
|
|
for (TWeakObjectPtr<UEdGraphNode>& NodePtr : NodePtrs)
|
|
{
|
|
if (NodePtr.IsValid())
|
|
{
|
|
UEdGraphNode* Node = NodePtr.Get();
|
|
Parent->DeferredSelectionTargetObjects.Add(Node);
|
|
}
|
|
}
|
|
return EActiveTimerReturnType::Stop;
|
|
};
|
|
|
|
TSet< TWeakObjectPtr<UEdGraphNode> > NodePtrSet;
|
|
for (const UEdGraphNode* Node : EditAction.Nodes)
|
|
{
|
|
NodePtrSet.Add(Node);
|
|
}
|
|
|
|
RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateStatic(SelectNodeDelegateWrapper, this, NodePtrSet));
|
|
}
|
|
}
|
|
}
|
|
|
|
void SGraphPanel::NotifyGraphChanged(const FEdGraphEditAction& EditAction)
|
|
{
|
|
// Forward call
|
|
OnGraphChanged(EditAction);
|
|
}
|
|
|
|
void SGraphPanel::AddReferencedObjects(FReferenceCollector& Collector)
|
|
{
|
|
Collector.AddReferencedObject( GraphObj );
|
|
Collector.AddReferencedObject( GraphObjToDiff );
|
|
}
|
|
|
|
EActiveTimerReturnType SGraphPanel::InvalidatePerTick(double InCurrentTime, float InDeltaTime)
|
|
{
|
|
// Invalidate the layout so it will redraw.
|
|
Invalidate(EInvalidateWidget::Layout);
|
|
|
|
TimeLeftToInvalidatePerTick -= InDeltaTime;
|
|
|
|
// When the time is done, stop the invalidation per tick because the UI will be static once more.
|
|
if (TimeLeftToInvalidatePerTick <= 0.0f)
|
|
{
|
|
TimeLeftToInvalidatePerTick = 0.0f;
|
|
return EActiveTimerReturnType::Stop;
|
|
}
|
|
return EActiveTimerReturnType::Continue;
|
|
}
|