Files
UnrealEngineUWP/Engine/Source/Editor/GraphEditor/Private/SGraphActionMenu.cpp
shaun kime c39e5304a8 #ROBOMERGE-AUTHOR: shaun.kime
Catching up 4.20 to Dev-Niagara as of CL 4111104
#rb none
#tests PC and PS4 auto-tests pass

Change 4075849 by Wyeth.Johnson

	Metadata on location and velocity modules, new DI

Change 4076028 by Frank.Fella

	Niagara - Fix an issue where the list of relevant scripts in the shared script view model could get out of sync when changing properties on an emitter such as interpolated spawning and gpu simulation.  This could result in an emitter recompiling forever if it started as GPU and was then switched to CPU and then a force compile was requested since it would include the GPU script when determing the compile status, but it would never compile it.

	#jira UE-59220

Change 4076925 by Frank.Fella

	Niagara - Adding and removing pins from an assignment node wasn't correctly invalidating the graph which I broke with my crash fix 4058428 since I thought the refresh call would do that.

	#jira UE-59249

Change 4076971 by Frank.Fella

	Niagara - Made few changes to stack issue handling while fixing an issue where the stack error wouldn't change when the compile error changed.
	+ Changed the unique identifier for stack issues to be automatically generated from a hash of the combined stack editor data key and the long description of the error.
	+ Changed the stack issue unique identifier from an FName to an FString to avoid poluting the name table with lots of generated hash strings.
	+ Encapsulated all of the stack issue data to validate the required inputs.

	#jira UE-59251

Change 4076974 by Frank.Fella

	Niagara - Minor change missed in last checkin.

Change 4076990 by Frank.Fella

	Niagara - Fix the assignment node so that it uses a "Begin Defaults" node instead of a regular input node when it's hooking up linked defaults.

	#jira UE-59224

Change 4077392 by jonathan.lindquist

	Changing pin order

Change 4077426 by Wyeth.Johnson

	transform position DI

Change 4077636 by Frank.Fella

	Niagara - Fix an issue where the stack function input collection wasn't generating errors correctly due to data being cached between refreshes which became stale.

	#jira UE-59269

Change 4078004 by jonathan.lindquist

	Submitting progress on a module

Change 4078009 by jonathan.lindquist

	changing a variable name in rotate around point

Change 4078043 by Frank.Fella

	Niagara - Fix the stack function input so that it cleans up properly when removing pins from assignment nodes, also fix undo for the remove operation.

	#jira UE-59271

Change 4078063 by Shaun.Kime

	Fixing debug particle data texture usage

	#tests n/a

Change 4079110 by jonathan.lindquist

	Submitting a cone mask function

Change 4079161 by jonathan.lindquist

	Adding a new cone mask module

Change 4079164 by jonathan.lindquist

	Adding a description to the cone mask function

Change 4079166 by jonathan.lindquist

	Submitting a new cone mask dynamic input

Change 4079988 by Yannick.Lange

	Set persistend guid for if node input pins on creating a new output pin.

Change 4080531 by jonathan.lindquist

	New cone based mask for curl noise contributions. Additional meta data descriptions for other inputs.

Change 4080541 by jonathan.lindquist

	Exposing the cone axis variable

Change 4080544 by jonathan.lindquist

	One more meta data tweak :D

Change 4081107 by Shaun.Kime

	Fixing underlying GPU collision system after Rendering refactored to use the FSceneTexturesUniformParameters instead of individual textures. Note that GPU collision only works with the primary back buffer. We will need more work to support split-screen or PIP.

	#tests Collsion test GPU now is functional, but we still get a few nondeterministic strays in different directions keeping me from turning it on at the moment

Change 4081111 by Shaun.Kime

	Updating the compile GUID because the previous change adjusted generated code

	#tests n/a

Change 4081231 by Shaun.Kime

	Allowing several descriptions to be multiline, accessible by Shift + Enter.

	#tests created descriptions for both module fields and modules themselves that were multi-line. confirmed UI was correct.

Change 4081552 by Jonathan.Lindquist

	Additional tooltips/documentation

Change 4081566 by Jonathan.Lindquist

	Changing split linear color's pin order

Change 4081646 by Shaun.Kime

	Added tooltips to the parameter map get and set nodes that should grealy improve understanding.

	#tests n/a

Change 4082769 by Yannick.Lange

	Pins and parameters unique name on creation

Change 4082792 by Yannick.Lange

	Fix: Adding a property pin to a Niagara Module Script map node creates a duplicate of that property in the Properties menu

	#jira UE-58823

Change 4082851 by jonathan.lindquist

	Ensuring that the latest version of this content is available for Simon

Change 4082875 by Yannick.Lange

	Parameter, source and dest pins of a parameter map node have a subcategories. Only pins with the parameter subcategory will be found by the graph.

	#jira UE-57692

Change 4083076 by Wyeth.Johnson

	Gnomon asset for example content

Change 4083783 by Frank.Fella

	Niagara - Fix issues with drag/drop
	+ Don't allow the user to drop a module if the usage flags of the target script aren't supported.
	+ Allow dragging to different scripts event if they are in different graphs, or different emitters.
	+ Transfer rapid iteration paramters correctly when moving modules between scripts.
	+ Fix undo for rapid iteration paramters when undoing a move.

	#jira UE-59340
	#jira UE-59401

Change 4083999 by Bradut.Palas

	Improved functionality of module dependencies: intercategory module dependencies now work, module order is fixed.

	#tests none
	#jira UE-58200

Change 4084002 by Shaun.Kime

	Validating modules reads and writes.
	You cannot read/write from particles namespace in system and emitter scripts
	You cannot write to user or NPC namespaces ever
	You cannot write to system/emitter namespaces in particle scripts

	#tests auto-tests pass

Change 4084419 by jonathan.lindquist

	Changing default texture assignments to work with the new project directory.

Change 4084595 by jonathan.lindquist

	Submitting a new material that will generate a 3d sphere on a sprite using world position offset and pixel depth offset.

Change 4084603 by Jonathan.Lindquist

	New thumbnail

Change 4084607 by jonathan.lindquist

	Submitting final variable settings for the skeletal mesh reproduction particle system

Change 4084649 by jonathan.lindquist

	Finalizing sampling mesh code after exploring multiple approaches.

Change 4084746 by Frank.Fella

	Niagara - When creating the render state in the niagara component, also send the dynamic data the same frame since the emitter might not actually tick the next frame.

	#jira UE-57696

	#tests engine tests.

Change 4085536 by Yannick.Lange

	Fix assert attempting to add a Niagara emitter parameter to a system before tracking an emitter.
	Also passes all graphs to the add button, to avoid any use of Graphs[0] in SNiagaraParameterMapView.

	#jira UE-58832

Change 4085757 by Yannick.Lange

	Prevent circular connections when trying to connect pins

	#jira UE-55541

Change 4086086 by Bradut.Palas

	Fixing static code analysis issues by moving the RefreshIssues call inside the FunctionCallNode nullcheck

	#tests none

Change 4086155 by jonathan.lindquist

	Updating meta data etc.

Change 4086965 by Olaf.Piesche

	Fixing uniform buffer alignment and padding to 16 bytes for all vector types; bumping vec2 and vec3 uniforms to vec4, and adding component mask to code chunk for accesses to uniform chunks according to their initial type
	OpenGL requires this since because adherence to the std140 memory layout is shaky at best when it comes to sub-16-byte vector types

Change 4086968 by Olaf.Piesche

	Making division by 0xFFFFFFFF explicitly unsigned, because OpenGL otherwise assumes it's a signed int, just dividing by -1

Change 4086975 by Frank.Fella

	Niagara - Renderer update fixes.
	+ Trigger data object changed when adding, removing, and changing the enabled state of renderers so that the simulation updates.
	+ Fix undo for changing the enabled state on renderers.

	#jira UE-57696
	#jira UE-59390

Change 4087008 by Frank.Fella

	Niagara - When refreshing the sequencer tracks in the emitter/system editor don't set sequencer the time to 0.  This fixes an issue where modifying data in the timeline and undo would reset the time to 0 when paused rather than resimulating.

	#jira UE-59463

Change 4087030 by Shaun.Kime

	Fixing when you can create certain pin types to prevent invalid types from appearing in the list.

	#tests autotests pass on PC

Change 4087271 by jonathan.lindquist

	Adding an option to clamp particles.velocity's magnitude.

Change 4087279 by Wyeth.Johnson

	Comments and dependencies

Change 4087333 by Wyeth.Johnson

	Bitmask useage flags on forces to adhere to standards, plus dependencies

Change 4087636 by Wyeth.Johnson

	Age related dependencies on update modules

Change 4087702 by Shaun.Kime

	Getting translation set up for Frank's rapid iteration parameter rework in support for default dynamic inputs

	#tests n/a

Change 4087992 by jonathan.lindquist

	Adding a limit force module

Change 4088872 by Yannick.Lange

	Fix renaming variables will not work if the user is only changing capitalization.
	#jira UE-59119

Change 4088891 by Yannick.Lange

	Fix adding a new attribute makes it hidden in the attribute spreadsheet. Now shows the added attribute when doing a new capture.
	#jira UE-57167

Change 4089072 by Yannick.Lange

	Reorder parameter list categories

Change 4089164 by jonathan.lindquist

	Adding a velocity clamp feature and an acceleration clamp

Change 4089953 by Bradut.Palas

	Disabled modules no longer display errors.
	Also, enabling/disabling modules is now registered with the Undo system
	Also fixed the GUID generation for all issues, now issues are properly differentiated from each other on refresh.

	#tests none

Change 4090194 by Shaun.Kime

	Fixing auto tests after acceleration force defaulted to world instead of local

	#tests all pass

Change 4090195 by Shaun.Kime

	Cleaning up UI for code view

	#tests n/a

Change 4090198 by jonathan.lindquist

	Setting the fallback vector to 0,0,0

Change 4090430 by jonathan.lindquist

	Removing a reciprocal operation from the node. Now we use a single divide. Also, I added another length calculation to provide the proper length of the input fallback vector. This is important for cases in which the user specifies that the fallback vector should be 0,0,0 or another unnormalized value. Previously, the fallback vector length always returned 1.

Change 4090512 by Shaun.Kime

	Fix for crash during Jonathan's deletion of the Set node in SolveForcesInVelocity.

	#tests n/a

Change 4090534 by jonathan.lindquist

	New acceleration limit

Change 4090676 by Olaf.Piesche

	GPU Spawning auto test

Change 4090770 by Shaun.Kime

	Curl noise bug test case

Change 4090796 by Olaf.Piesche

	Added missing abs for GPU sim

Change 4091368 by Bradut.Palas

	Also removing issues from disabled input collections and renderer items

	#tests none

Change 4091417 by Simon.Tovey

	Making emitter local space a constant embeded directly into emitter and particle scripts.
	Allows a lot of optimization and exposes the value to emitter scripts properly.

Change 4091727 by jonathan.lindquist

	Exposing delta time as an advanced input and organizing the graph

Change 4091788 by Bradut.Palas

	#jira UE-54678 fIxing issues with refresh of skeletal mesh details
	#tests none

Change 4092040 by Frank.Fella

	Niagara - Fix some issues with modify, transactions, and change ids which was causing assets to be dirty or modified on load, or were allowing internal operation to be undone.
	+ Move some transactions from public utility functions into private functions called by menu items in the UNiagaraNodeWithDynamicPins.
	+ Prevent a few modify calls in UNiagaraEmitter from marking the package dirty since they're sometimes called as a result of compiling and in the other cases earlier modifies would have already marked the package dirty.
	+ In the system view model, don't create transactions when adding an emitter if the system view model is in emitter asset mode since the user should be able to undo it.
	+ In the system toolkit when opening an emitter asset initialize, clean up, and propagate the rapid iteration parameters before copying the emitter to prevent the change ids from getting out of sync after the compile completes.
	+ In the system toolkit when trying to see if an emitter has changed using the change ids, use the last synced id from the copied emitter instead of the original emitter since duplicating the emitter can change the id, and there's not way to set it externally.

	#jira UE-59517
	#jira UE-59566

Change 4092700 by jonathan.lindquist

	Removed param groups. We're now using inline bools to enable or disable limits on velocity and acceleration

Change 4093032 by Shaun.Kime

	Fixing display of errors

	#tests now errors in compilation properly display

Change 4093172 by Shaun.Kime

	Curl noise cpu/gpu test map

	#tests added last known good

Change 4094156 by Damien.Pernuit

	Fixed crash in the editor when opening a Niagara Emitter/Script containing outdated script functions.
	Fixed incorrect type cast, FNiagaraFloat instead of FNiagaraInt32.

Change 4094515 by Tim.Gautier

	Enabled Niagara + Niagara Extras in QAGame

Change 4094674 by jonathan.lindquist

	submitting an example of variable defaults not working as intended

Change 4094712 by Damien.Pernuit

	Niagara - Houdini:

	Houdini Niagara Data Interface:
	- Removed the GetCSVFloatValueByString function as String aren't currently supported by Niagara.
	- Particles in the CSV file can now be updated over time (not just spawned)
	- Added GetParticlePositionAtTime, GetParticleValueAtTime, GetParticleVectorValueAtTime returning a linearly interpolated value for a given particle at a given time.
	- Added GetParticleIndexesAtTime for getting the previous/next row indexes and weight to access the values for a given particle at a given time and handle the interpolation of the values.
	- Added GetCSVVectorValue for accessing a Vector value at a given row/col.

	Houdini CSV Assets now looks for the following attributes in the CSV "Title" line:
	- pos for position.
	- id and # for particle ID.
	- alive and life for calculating a particles LifeTime.

Change 4094932 by Frank.Fella

	Niagara - Fix a few more issues where asset editors would open with their assets modified.
	+ Fix rapid iteration parameter preparation so that it doesn't modify the parameter store if it doesn't change after syncing with the graphs and propagating from dependencies.  This fixes the emitter editor allowing changes to be applied on open.
	+ Refactor the change notification for the script tool kit so that it uses the graph change and property change messages to determine if any changes have been made and can be applied.

	#jira UE-59517
	#tests auto tests

Change 4094978 by Damien.Pernuit

	Niagara - Houdini:

	Houdini Niagara Data Interface:
	- Since we can now update particles over time, renamed/modified most of the functions to make a clear distinction between row indexes (row) and particle ids (N)
	- Replaced GetNumberOfPointsInCSV by GetNumberOfRowsInCSV and GetNumberOfParticlesInCSV
	- Renamed GetParticleIndexesAtTime to GetRowIndexesForParticleAtTime and GetLastParticleIndexAtTime to GetLastRowIndexAtTime
	- Fixed some DI Functions that were using floats for input parameter instead of using integers.

Change 4095428 by Damien.Pernuit

	Niagara - Houdini:

	Houdini Niagara Data Interface:
	Fixed incorrect behavior of the GetLastRowIndexAtTime and GetParticleIndexesToSpawnAtTime functions due to supporting particle update over time.

	Houdini CSV Asset:
	Fixed missing UPropery for SpawnTimes and LifeValues array.

Change 4096355 by Damien.Pernuit

	Houdini Niagara:

	Fixed performance warning for UHoudiniCSV::GetParticleLifeAtTime()

Change 4096419 by Damien.Pernuit

	Niagara - Houdini:

	Houdini Niagara Data Interface:
	Added GetParticleLifeAtTime for accessing a given particle's life at a given time value.
	Fixed GetParticleVectorValueAtTime not bound properly.
	Fixed GetRowIndexesForParticleAtTime returning incorrect values when the time value was past the particle's last update.

Change 4096466 by Damien.Pernuit

	Niagara - Houdini:
	- Added GetNumberOfColumnsInCSV to the Houdini Data Interface
	- Added descriptions to the functions exposed by the DI

Change 4096528 by Damien.Pernuit

	Niagara - Houdini:

	Houdini CSV Asset:
	- As the DI expects the values to be sorted by time, if it's not the case, the CSV importer will sort them on import.
	- As the DI spawning functions relies on the particle IDs starting at zero and increment, the CSV importer will fix the particles IDs on import if it's not the case.

Change 4096838 by Yannick.Lange

	Fix focus search box on add parameter menu

	#jira UE-59502

Change 4097205 by Bradut.Palas

	Fixes for metadata details in script toolkit (now the apply and compile buttons refresh and sort the metadata collection). The metadata functionality is fixed. Delete, add and modify work just as before, but the sorting isn't applied because refreshing the whole collection is skipped for internal changes.
	#jira UE-58745
	#jira UE-59589

	#tests none

Change 4097593 by Shaun.Kime

	Now generating compiler debug info for VM shaders just like the rest of Materials using the r.DumpShaderDebugInfo

	#tests now properly generate data in a VM folder sibling to other generated debug shader data

Change 4097721 by Frank.Fella

	Niagara - Make the lifetime of stack entries well defined so that we can safely remove delegate bindings and clear out pointers.

Change 4097962 by Bradut.Palas

	Stack issues now update fix delegates on each refresh, even if the fix GUIDs don't change, to account for other possible changes in the graph. Had to introduce unique identifiers for fixes too, now the issue entry is using the same recycle mechanism for fixes that the base stack entry uses for issues.

	#tests none

Change 4098063 by Frank.Fella

	Niagara - Fix input initialization for drag/drop with a "Set Variables" node.

	#jira UE-57699

Change 4098192 by Damien.Pernuit

	Niagara - Houdini:

	Houdini CSV Asset:
	When importing the CSV file, the importer creates a list of the row indexes updating each particle.
	This greatly improves performance when accessing data in large files with a lot of particles updating over time.

Change 4098406 by Damien.Pernuit

	Niagara - Houdini:

	Houdini Niagara Data Interface:
	Added helper functions for accessing Color and Velocity values in the CSV file.

	Houdini CSV Asset:
	The importer now looks for the Color (Cd, color), Alpha (A, Alpha) and velocity (V) attributes.

Change 4099945 by Frank.Fella

	Niagara - Fix op description tool tip and keyword searches in the graph add menu, fix and standardize tool tip handling for script objects in menus, and add support for keyword searches for user defined scripts to match the built in ops.

	#jira UE-59402

Change 4100451 by Shaun.Kime

	Fixing wyeth's torus error, which was caused by us not properly initializing defaults. We now initialize defaults in three waves in spawn scripts. Wave 1 are any straight up constants at the top of the spawn function. Wave 2 is inlined in spawn just before the function that needs them is called. Wave 3 is at the bottom of spawn in a section called HandleMissingDefaultValues.
	Also updated the error and warning messages to be much clearer text.

	#jira UE-59723, UE-59762

	#tests auto-tests pass

Change 4100568 by Shaun.Kime

	Removing the old compile debug file generation and now unified with the existing shader compiler workflow for the future.
	If r.DumpShaderDebugInfo=1, make sure that we generate the assembly, ush, and params files in the Saved\ShaderDebugInfo\VM\<SYSTEM_NAME>\<EMITTER_NAME>\<SCRIPT_NAME_AND_USAGE_ID_IF_NONZERO>

	#jira UE-59767
	#tests auto-tests pass

Change 4100913 by jonathan.lindquist

	changing the pin order

Change 4100932 by jonathan.lindquist

	setting the input pin order on a, b and alpha

Change 4101546 by jonathan.lindquist

	Submitting a dynamic input that returns the exec index as an int

Change 4101734 by Shaun.Kime

	Fixing static analysis errors

	#tests n/a

Change 4101736 by Shaun.Kime

	Creating new last known good for GPU Functional Test auto-test
	#tests n/a

Change 4102305 by Simon.Tovey

	Fix for VM Crash

	#codereivew Frank.Fella, Shaun.Kime, Olaf.Piesche

Change 4102552 by Yannick.Lange

	Tooltip variable types

	#jira UE-59520

Change 4102599 by Yannick.Lange

	New variables in maps or parameter view will get the name Namespace.NewVariable. This is not an actual fix for UE-59633, but gives the user the incentive to rename variables.

	#codereveiw Shaun.Kime

Change 4102752 by Yannick.Lange

	Fix auto expanding all the sections for the niagara parameters list view.

	#jira UE-59121

Change 4102779 by Yannick.Lange

	Fix auto expanding all the sections for the niagara parameters list view. Fix incorrect comment changelist: 4102752

	#jira UE-59121

Change 4103419 by Shaun.Kime

	Fixing build issues

	#tests n/a

Change 4103522 by Damien.Pernuit

	Houdini - Niagara:
	Big renaming pass on the  Houdini CSV Assets  and Data Interface to follow naming conventions:
	Replaced the GetCSVXXX functions by GetXXXX (GetCSVPosition is now GetPosition)
	Always use "row" instead of "line", "Point" instead of "Particle", "PointID" instead of "N" or "ID" etc.

	Houdini Data Interface:
	- Added the GetVectorValueEx and GetPointVectorValueAtTimeEx functions that allow the user to decide how the vector conversion from houdini to unreal's coordinate system is handled.
	- Replaced the GetParticleLifeAtTime function by GetPointLife, that returns the life of a particle at spawn time.
	- Added the GetPointType function returning the type of a given point.

	Houdini CSV Asset:
	- Added the editable SourceTitleRow UProperty.
	Editing this will trigger a reimport of the source CSV file and might be used to fix/modify column titles in the file.
	- Added support for "type" attributes.
	- Removed the unused StringValues buffer and GetCSVStringValues() functions.
	- Added assetTags so the Houdini CSV asset thumbnails show more infos on the CSV data.
	- Added the "FindSourceCSV" asset action to browse to the source CSV file.

Change 4104008 by Shaun.Kime

	Missing header in Monolithic builds

	Fixed indent issues, was using spaces vs tabs

	#jira UE-59705

Change 4105249 by Simon.Tovey

	Fixes in VMM backend and propagation visitors to ensure proper optimization for VM external function calls.

	also adding a visitor to strip empty stats scopes.

Change 4105250 by Simon.Tovey

	Updated windows binaries for hlslcc

Change 4105283 by Yannick.Lange

	Fix creating an input parameter node from an input pin.

	#jira UE-57362

Change 4105509 by Yannick.Lange

	Fix being able to drop parameters in the system view on incorrect execution categories.

Change 4105726 by Wyeth.Johnson

	Fix detection of valid toolchain directories with Visual Studio 2017 desktop (change by Ben.Marsh)

Change 4105727 by Shaun.Kime

	Fixing nightly build due to missing GetAssetTags definition due to mismatches in WITH_EDITORONLY_DATA

	#tests n/a

Change 4106034 by Damien.Pernuit

	Houdini-Niagara:
	Houdini CSV Asset:
	- Fixed build break due to GetAssetRegistryTags()
	- Replaced the different hardcoded ColumnIndexes member variables by an array.

Change 4106254 by Frank.Fella

	Niagara - Fix playback issues where completed systems wouldn't simulate again until you pressed play.

	#jira UE-58616
	#jira UE-58721

Change 4106617 by Frank.Fella

	Niagara - Prevent crash on shutdown.

	#jira UE-59516

Change 4106623 by Frank.Fella

	Niagara - Fix static analysis warning for posible null dereference in UNiagaraScriptItemGroup

Change 4106983 by Shaun.Kime

	Fix to prevent PS4 compiler warning during cook on ambiguous uses of AtomicAdd.

	#tests doesn't cause cook errors now

Change 4106988 by Shaun.Kime

	Resaved test assets with latest non-zero version

	#tests cooking no longer complains about file versions

Change 4106992 by Shaun.Kime

	Now when errors appear in a cook for Niagara GPU shaders, we see them in the same location as the cook log

	#tests n/a

Change 4108852 by Simon.Tovey

	Fix for transforms in emitter scripts.
	Param->Dataset bindings weren't handling structs correctly.

Change 4109260 by Wyeth.Johnson

	Normalize Vector dynamic input

Change 4109748 by Marcus.Wassmer

	olaf.piesche: Fresh build of hlslcc for Mac

Change 4110624 by Rolando.Caloca

	-fresh build of hlslcc for Linux
	-fixed a warning in NiagaraStackModuleItem.cpp

Change 4111103 by Shaun.Kime

	Fixing nightly build issues with redundant left and right side of &&
	CI Issue:
	d:\build\++ue4+dev-niagara+compile\sync\engine\plugins\fx\niagara\source\niagaraeditor\private\viewmodels\niagarasystemviewmodel.cpp(1425): warning V501: There are identical sub-expressions 'bStartedPlaying == false' to the left and to the right of the '&&' operator.

	#tests auto-tests pass

Change 4111104 by Shaun.Kime

	Fix for CI issue:
	d:\build\++ue4+dev-niagara+compile\sync\engine\plugins\fx\niagara\source\niagaraeditor\private\viewmodels\stack\niagarastackscriptitemgroup.cpp(553): warning V595: The 'SourceModuleItem' pointer was utilized before it was verified against nullptr. Check lines: 553, 554.

	#tests auto-tests pass

#ROBOMERGE-SOURCE: CL 4113881 in //UE4/Release-4.20/...
#ROBOMERGE-BOT: RELEASE (Release-4.20 -> Release-Staging-4.20)

[CL 4114750 by shaun kime in Staging-4.20 branch]
2018-06-05 20:16:37 -04:00

1383 lines
44 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "SGraphActionMenu.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Text/SRichTextBlock.h"
#include "Widgets/Layout/SScrollBorder.h"
#include "EditorStyleSet.h"
#include "Styling/CoreStyle.h"
#include "GraphEditorDragDropAction.h"
#include "EdGraphSchema_K2.h"
#include "K2Node.h"
#include "EdGraphSchema_K2_Actions.h"
#include "GraphActionNode.h"
#include "Widgets/SToolTip.h"
#include "IDocumentation.h"
#include "EditorCategoryUtils.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#define LOCTEXT_NAMESPACE "GraphActionMenu"
//////////////////////////////////////////////////////////////////////////
template<typename ItemType>
class SCategoryHeaderTableRow : public STableRow < ItemType >
{
public:
SLATE_BEGIN_ARGS(SCategoryHeaderTableRow)
{}
SLATE_DEFAULT_SLOT(typename SCategoryHeaderTableRow::FArguments, Content)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
STableRow<ItemType>::ChildSlot
.Padding(0.0f, 2.0f, 0.0f, 0.0f)
[
SAssignNew(ContentBorder, SBorder)
.BorderImage(this, &SCategoryHeaderTableRow::GetBackgroundImage)
.Padding(FMargin(0.0f, 3.0f))
.BorderBackgroundColor(FLinearColor(.6, .6, .6, 1.0f))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(2.0f, 2.0f, 2.0f, 2.0f)
.AutoWidth()
[
SNew(SExpanderArrow, STableRow< ItemType >::SharedThis(this))
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
InArgs._Content.Widget
]
]
];
STableRow < ItemType >::ConstructInternal(
typename STableRow< ItemType >::FArguments()
.Style(FEditorStyle::Get(), "DetailsView.TreeView.TableRow")
.ShowSelection(false),
InOwnerTableView
);
}
const FSlateBrush* GetBackgroundImage() const
{
if ( STableRow<ItemType>::IsHovered() )
{
return STableRow<ItemType>::IsItemExpanded() ? FEditorStyle::GetBrush("DetailsView.CategoryTop_Hovered") : FEditorStyle::GetBrush("DetailsView.CollapsedCategory_Hovered");
}
else
{
return STableRow<ItemType>::IsItemExpanded() ? FEditorStyle::GetBrush("DetailsView.CategoryTop") : FEditorStyle::GetBrush("DetailsView.CollapsedCategory");
}
}
virtual void SetContent(TSharedRef< SWidget > InContent) override
{
ContentBorder->SetContent(InContent);
}
virtual void SetRowContent(TSharedRef< SWidget > InContent) override
{
ContentBorder->SetContent(InContent);
}
private:
TSharedPtr<SBorder> ContentBorder;
};
//////////////////////////////////////////////////////////////////////////
namespace GraphActionMenuHelpers
{
bool ActionMatchesName(const FEdGraphSchemaAction* InGraphAction, const FName& ItemName)
{
bool bCheck = false;
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Var::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2Var*)InGraphAction)->GetVariableName() == ItemName);
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2LocalVar::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2LocalVar*)InGraphAction)->GetVariableName() == ItemName);
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2Graph*)InGraphAction)->EdGraph &&
((FEdGraphSchemaAction_K2Graph*)InGraphAction)->EdGraph->GetFName() == ItemName);
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Enum::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2Enum*)InGraphAction)->GetPathName() == ItemName);
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Struct::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2Struct*)InGraphAction)->GetPathName() == ItemName);
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Delegate::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2Delegate*)InGraphAction)->GetDelegateName() == ItemName);
const bool bIsTargetNodeSubclass = (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2TargetNode::StaticGetTypeId()) ||
(InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Event::StaticGetTypeId()) ||
(InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2InputAction::StaticGetTypeId());
bCheck |= (bIsTargetNodeSubclass &&
((FEdGraphSchemaAction_K2TargetNode*)InGraphAction)->NodeTemplate->GetNodeTitle(ENodeTitleType::EditableTitle).ToString() == ItemName.ToString());
return bCheck;
}
}
//////////////////////////////////////////////////////////////////////////
void SDefaultGraphActionWidget::Construct(const FArguments& InArgs, const FCreateWidgetForActionData* InCreateData)
{
ActionPtr = InCreateData->Action;
MouseButtonDownDelegate = InCreateData->MouseButtonDownDelegate;
this->ChildSlot
[
SNew(SHorizontalBox)
.ToolTipText(InCreateData->Action->GetTooltipDescription())
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9))
.Text(InCreateData->Action->GetMenuDescription())
.HighlightText(InArgs._HighlightText)
]
];
}
FReply SDefaultGraphActionWidget::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if( MouseButtonDownDelegate.Execute( ActionPtr ) )
{
return FReply::Handled();
}
return FReply::Unhandled();
}
//////////////////////////////////////////////////////////////////////////
class SGraphActionCategoryWidget : public SCompoundWidget
{
SLATE_BEGIN_ARGS( SGraphActionCategoryWidget )
{}
SLATE_ATTRIBUTE( FText, HighlightText )
SLATE_EVENT( FOnTextCommitted, OnTextCommitted )
SLATE_EVENT( FIsSelected, IsSelected )
SLATE_ATTRIBUTE( bool, IsReadOnly )
SLATE_END_ARGS()
TWeakPtr<FGraphActionNode> ActionNode;
TAttribute<bool> IsReadOnly;
public:
TWeakPtr<SInlineEditableTextBlock> InlineWidget;
void Construct( const FArguments& InArgs, TSharedPtr<FGraphActionNode> InActionNode )
{
ActionNode = InActionNode;
FText CategoryTooltip;
FString CategoryLink, CategoryExcerpt;
FEditorCategoryUtils::GetCategoryTooltipInfo(*InActionNode->GetDisplayName().ToString(), CategoryTooltip, CategoryLink, CategoryExcerpt);
TSharedRef<SToolTip> ToolTipWidget = IDocumentation::Get()->CreateToolTip(CategoryTooltip, NULL, CategoryLink, CategoryExcerpt);
IsReadOnly = InArgs._IsReadOnly;
this->ChildSlot
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SAssignNew(InlineWidget, SInlineEditableTextBlock)
.Font( FCoreStyle::GetDefaultFontStyle("Bold", 9) )
.Text( FEditorCategoryUtils::GetCategoryDisplayString(InActionNode->GetDisplayName()) )
.ToolTip( ToolTipWidget )
.HighlightText( InArgs._HighlightText )
.OnVerifyTextChanged( this, &SGraphActionCategoryWidget::OnVerifyTextChanged )
.OnTextCommitted( InArgs._OnTextCommitted )
.IsSelected( InArgs._IsSelected )
.IsReadOnly( InArgs._IsReadOnly )
]
];
}
// SWidget interface
virtual FReply OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent ) override
{
TSharedPtr<FGraphEditorDragDropAction> GraphDropOp = DragDropEvent.GetOperationAs<FGraphEditorDragDropAction>();
if (GraphDropOp.IsValid())
{
GraphDropOp->DroppedOnCategory( ActionNode.Pin()->GetCategoryPath() );
return FReply::Handled();
}
return FReply::Unhandled();
}
virtual void OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent ) override
{
TSharedPtr<FGraphEditorDragDropAction> GraphDropOp = DragDropEvent.GetOperationAs<FGraphEditorDragDropAction>();
if (GraphDropOp.IsValid())
{
GraphDropOp->SetHoveredCategoryName( ActionNode.Pin()->GetDisplayName() );
}
}
virtual void OnDragLeave( const FDragDropEvent& DragDropEvent ) override
{
TSharedPtr<FGraphEditorDragDropAction> GraphDropOp = DragDropEvent.GetOperationAs<FGraphEditorDragDropAction>();
if (GraphDropOp.IsValid())
{
GraphDropOp->SetHoveredCategoryName( FText::GetEmpty() );
}
}
// End of SWidget interface
/** Callback for the SInlineEditableTextBlock to verify the text before commit */
bool OnVerifyTextChanged(const FText& InText, FText& OutErrorMessage)
{
if(InText.ToString().Len() > NAME_SIZE)
{
OutErrorMessage = LOCTEXT("CategoryNameTooLong_Error", "Name too long!");
return false;
}
return true;
}
};
//////////////////////////////////////////////////////////////////////////
void SGraphActionMenu::Construct( const FArguments& InArgs, bool bIsReadOnly/* = true*/ )
{
this->SelectedSuggestion = INDEX_NONE;
this->bIgnoreUIUpdate = false;
this->bUseSectionStyling = InArgs._UseSectionStyling;
this->bAutoExpandActionMenu = InArgs._AutoExpandActionMenu;
this->bShowFilterTextBox = InArgs._ShowFilterTextBox;
this->bAlphaSortItems = InArgs._AlphaSortItems;
this->OnActionSelected = InArgs._OnActionSelected;
this->OnActionDoubleClicked = InArgs._OnActionDoubleClicked;
this->OnActionDragged = InArgs._OnActionDragged;
this->OnCategoryDragged = InArgs._OnCategoryDragged;
this->OnCreateWidgetForAction = InArgs._OnCreateWidgetForAction;
this->OnCreateCustomRowExpander = InArgs._OnCreateCustomRowExpander;
this->OnCollectAllActions = InArgs._OnCollectAllActions;
this->OnCollectStaticSections = InArgs._OnCollectStaticSections;
this->OnCategoryTextCommitted = InArgs._OnCategoryTextCommitted;
this->OnCanRenameSelectedAction = InArgs._OnCanRenameSelectedAction;
this->OnGetSectionTitle = InArgs._OnGetSectionTitle;
this->OnGetSectionToolTip = InArgs._OnGetSectionToolTip;
this->OnGetSectionWidget = InArgs._OnGetSectionWidget;
this->FilteredRootAction = FGraphActionNode::NewRootNode();
this->OnActionMatchesName = InArgs._OnActionMatchesName;
// If a delegate for filtering text is passed in, assign it so that it will be used instead of the built-in filter box
if(InArgs._OnGetFilterText.IsBound())
{
this->OnGetFilterText = InArgs._OnGetFilterText;
}
TreeView = SNew(STreeView< TSharedPtr<FGraphActionNode> >)
.ItemHeight(24)
.TreeItemsSource(&(this->FilteredRootAction->Children))
.OnGenerateRow(this, &SGraphActionMenu::MakeWidget, bIsReadOnly)
.OnSelectionChanged(this, &SGraphActionMenu::OnItemSelected)
.OnMouseButtonDoubleClick(this, &SGraphActionMenu::OnItemDoubleClicked)
.OnContextMenuOpening(InArgs._OnContextMenuOpening)
.OnGetChildren(this, &SGraphActionMenu::OnGetChildrenForCategory)
.SelectionMode(ESelectionMode::Single)
.OnItemScrolledIntoView(this, &SGraphActionMenu::OnItemScrolledIntoView)
.OnSetExpansionRecursive(this, &SGraphActionMenu::OnSetExpansionRecursive);
this->ChildSlot
[
SNew(SVerticalBox)
// FILTER BOX
+SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(FilterTextBox, SSearchBox)
// If there is an external filter delegate, do not display this filter box
.Visibility(InArgs._OnGetFilterText.IsBound()? EVisibility::Collapsed : EVisibility::Visible)
.OnTextChanged( this, &SGraphActionMenu::OnFilterTextChanged )
.OnTextCommitted( this, &SGraphActionMenu::OnFilterTextCommitted )
]
// ACTION LIST
+SVerticalBox::Slot()
.Padding(FMargin(0.0f, 2.0f, 0.0f, 0.0f))
.FillHeight(1.f)
[
SNew(SScrollBorder, TreeView.ToSharedRef())
[
TreeView.ToSharedRef()
]
]
];
// When the search box has focus, we want first chance handling of any key down events so we can handle the up/down and escape keys the way we want
FilterTextBox->SetOnKeyDownHandler(FOnKeyDown::CreateSP(this, &SGraphActionMenu::OnKeyDown));
if (!InArgs._ShowFilterTextBox)
{
FilterTextBox->SetVisibility(EVisibility::Collapsed);
}
// Get all actions.
RefreshAllActions(false);
}
void SGraphActionMenu::RefreshAllActions(bool bPreserveExpansion, bool bHandleOnSelectionEvent/*=true*/)
{
// Save Selection (of only the first selected thing)
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
TSharedPtr<FGraphActionNode> SelectedAction = SelectedNodes.Num() > 0 ? SelectedNodes[0] : nullptr;
AllActions.Empty();
OnCollectAllActions.ExecuteIfBound(AllActions);
GenerateFilteredItems(bPreserveExpansion);
// Re-apply selection #0 if possible
if (SelectedAction.IsValid())
{
// Clear the selection, we will be re-selecting the previous action
TreeView->ClearSelection();
if(bHandleOnSelectionEvent)
{
SelectItemByName(*SelectedAction->GetDisplayName().ToString(), ESelectInfo::OnMouseClick, SelectedAction->SectionID, SelectedNodes[0]->IsCategoryNode());
}
else
{
// If we do not want to handle the selection, set it directly so it will reselect the item but not handle the event.
SelectItemByName(*SelectedAction->GetDisplayName().ToString(), ESelectInfo::Direct, SelectedAction->SectionID, SelectedNodes[0]->IsCategoryNode());
}
}
}
void SGraphActionMenu::GetSectionExpansion(TMap<int32, bool>& SectionExpansion) const
{
}
void SGraphActionMenu::SetSectionExpansion(const TMap<int32, bool>& InSectionExpansion)
{
for ( auto& PossibleSection : FilteredRootAction->Children )
{
if ( PossibleSection->IsSectionHeadingNode() )
{
const bool* IsExpanded = InSectionExpansion.Find(PossibleSection->SectionID);
if ( IsExpanded != nullptr )
{
TreeView->SetItemExpansion(PossibleSection, *IsExpanded);
}
}
}
}
TSharedRef<SEditableTextBox> SGraphActionMenu::GetFilterTextBox()
{
return FilterTextBox.ToSharedRef();
}
void SGraphActionMenu::GetSelectedActions(TArray< TSharedPtr<FEdGraphSchemaAction> >& OutSelectedActions) const
{
OutSelectedActions.Empty();
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
if(SelectedNodes.Num() > 0)
{
for ( int32 NodeIndex = 0; NodeIndex < SelectedNodes.Num(); NodeIndex++ )
{
OutSelectedActions.Append( SelectedNodes[NodeIndex]->Actions );
}
}
}
void SGraphActionMenu::OnRequestRenameOnActionNode()
{
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
if(SelectedNodes.Num() > 0)
{
if (!SelectedNodes[0]->BroadcastRenameRequest())
{
TreeView->RequestScrollIntoView(SelectedNodes[0]);
}
}
}
bool SGraphActionMenu::CanRequestRenameOnActionNode() const
{
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
if(SelectedNodes.Num() == 1 && OnCanRenameSelectedAction.IsBound())
{
return OnCanRenameSelectedAction.Execute(SelectedNodes[0]);
}
return false;
}
FString SGraphActionMenu::GetSelectedCategoryName() const
{
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
return (SelectedNodes.Num() > 0) ? SelectedNodes[0]->GetDisplayName().ToString() : FString();
}
void SGraphActionMenu::GetSelectedCategorySubActions(TArray<TSharedPtr<FEdGraphSchemaAction>>& OutActions) const
{
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
for ( int32 SelectionIndex = 0; SelectionIndex < SelectedNodes.Num(); SelectionIndex++ )
{
if ( SelectedNodes[SelectionIndex].IsValid() )
{
GetCategorySubActions(SelectedNodes[SelectionIndex], OutActions);
}
}
}
void SGraphActionMenu::GetCategorySubActions(TWeakPtr<FGraphActionNode> InAction, TArray<TSharedPtr<FEdGraphSchemaAction>>& OutActions) const
{
if(InAction.IsValid())
{
TSharedPtr<FGraphActionNode> CategoryNode = InAction.Pin();
TArray<TSharedPtr<FGraphActionNode>> Children;
CategoryNode->GetLeafNodes(Children);
for (int32 i = 0; i < Children.Num(); ++i)
{
TSharedPtr<FGraphActionNode> CurrentChild = Children[i];
if (CurrentChild.IsValid() && CurrentChild->IsActionNode())
{
for ( int32 ActionIndex = 0; ActionIndex != CurrentChild->Actions.Num(); ActionIndex++ )
{
OutActions.Add(CurrentChild->Actions[ActionIndex]);
}
}
}
}
}
bool SGraphActionMenu::SelectItemByName(const FName& ItemName, ESelectInfo::Type SelectInfo, int32 SectionId/* = INDEX_NONE */, bool bIsCategory/* = false*/)
{
if (ItemName != NAME_None)
{
TSharedPtr<FGraphActionNode> SelectionNode;
TArray<TSharedPtr<FGraphActionNode>> GraphNodes;
FilteredRootAction->GetAllNodes(GraphNodes);
for (int32 i = 0; i < GraphNodes.Num() && !SelectionNode.IsValid(); ++i)
{
TSharedPtr<FGraphActionNode> CurrentGraphNode = GraphNodes[i];
FEdGraphSchemaAction* GraphAction = CurrentGraphNode->GetPrimaryAction().Get();
// If the user is attempting to select a category, make sure it's a category
if( CurrentGraphNode->IsCategoryNode() == bIsCategory )
{
if(SectionId == INDEX_NONE || CurrentGraphNode->SectionID == SectionId)
{
if (GraphAction)
{
if ((OnActionMatchesName.IsBound() && OnActionMatchesName.Execute(GraphAction, ItemName)) || GraphActionMenuHelpers::ActionMatchesName(GraphAction, ItemName))
{
SelectionNode = GraphNodes[i];
break;
}
}
if (CurrentGraphNode->GetDisplayName().ToString() == FName::NameToDisplayString(ItemName.ToString(), false))
{
SelectionNode = CurrentGraphNode;
break;
}
}
}
// One of the children may match
for(int32 ChildIdx = 0; ChildIdx < CurrentGraphNode->Children.Num() && !SelectionNode.IsValid(); ++ChildIdx)
{
TSharedPtr<FGraphActionNode> CurrentChildNode = CurrentGraphNode->Children[ChildIdx];
for ( int32 ActionIndex = 0; ActionIndex < CurrentChildNode->Actions.Num(); ActionIndex++ )
{
FEdGraphSchemaAction* ChildGraphAction = CurrentChildNode->Actions[ActionIndex].Get();
// If the user is attempting to select a category, make sure it's a category
if( CurrentChildNode->IsCategoryNode() == bIsCategory )
{
if(SectionId == INDEX_NONE || CurrentChildNode->SectionID == SectionId)
{
if(ChildGraphAction)
{
if ((OnActionMatchesName.IsBound() && OnActionMatchesName.Execute(ChildGraphAction, ItemName)) || GraphActionMenuHelpers::ActionMatchesName(ChildGraphAction, ItemName))
{
SelectionNode = GraphNodes[i]->Children[ChildIdx];
break;
}
}
else if (CurrentChildNode->GetDisplayName().ToString() == FName::NameToDisplayString(ItemName.ToString(), false))
{
SelectionNode = CurrentChildNode;
break;
}
}
}
}
}
}
if(SelectionNode.IsValid())
{
// Expand the parent nodes
for (TSharedPtr<FGraphActionNode> ParentAction = SelectionNode->GetParentNode().Pin(); ParentAction.IsValid(); ParentAction = ParentAction->GetParentNode().Pin())
{
TreeView->SetItemExpansion(ParentAction, true);
}
// Select the node
TreeView->SetSelection(SelectionNode,SelectInfo);
TreeView->RequestScrollIntoView(SelectionNode);
return true;
}
}
else
{
TreeView->ClearSelection();
return true;
}
return false;
}
void SGraphActionMenu::ExpandCategory(const FText& CategoryName)
{
if (!CategoryName.IsEmpty())
{
TArray<TSharedPtr<FGraphActionNode>> GraphNodes;
FilteredRootAction->GetAllNodes(GraphNodes);
for (int32 i = 0; i < GraphNodes.Num(); ++i)
{
if (GraphNodes[i]->GetDisplayName().EqualTo(CategoryName))
{
GraphNodes[i]->ExpandAllChildren(TreeView);
}
}
}
}
static bool CompareGraphActionNode(TSharedPtr<FGraphActionNode> A, TSharedPtr<FGraphActionNode> B)
{
check(A.IsValid());
check(B.IsValid());
// First check grouping is the same
if (A->GetDisplayName().ToString() != B->GetDisplayName().ToString())
{
return false;
}
if (A->SectionID != B->SectionID)
{
return false;
}
if (A->HasValidAction() && B->HasValidAction())
{
return A->GetPrimaryAction()->GetMenuDescription().CompareTo(B->GetPrimaryAction()->GetMenuDescription()) == 0;
}
else if(!A->HasValidAction() && !B->HasValidAction())
{
return true;
}
else
{
return false;
}
}
template<typename ItemType, typename ComparisonType>
void RestoreExpansionState(TSharedPtr< STreeView<ItemType> > InTree, const TArray<ItemType>& ItemSource, const TSet<ItemType>& OldExpansionState, ComparisonType ComparisonFunction)
{
check(InTree.IsValid());
// Iterate over new tree items
for(int32 ItemIdx=0; ItemIdx<ItemSource.Num(); ItemIdx++)
{
ItemType NewItem = ItemSource[ItemIdx];
// Look through old expansion state
for (typename TSet<ItemType>::TConstIterator OldExpansionIter(OldExpansionState); OldExpansionIter; ++OldExpansionIter)
{
const ItemType OldItem = *OldExpansionIter;
// See if this matches this new item
if(ComparisonFunction(OldItem, NewItem))
{
// It does, so expand it
InTree->SetItemExpansion(NewItem, true);
}
}
}
}
void SGraphActionMenu::GenerateFilteredItems(bool bPreserveExpansion)
{
// First, save off current expansion state
TSet< TSharedPtr<FGraphActionNode> > OldExpansionState;
if(bPreserveExpansion)
{
TreeView->GetExpandedItems(OldExpansionState);
}
// Clear the filtered root action
FilteredRootAction->ClearChildren();
// Collect the list of always visible sections if any, and force the creation of those sections.
if ( OnCollectStaticSections.IsBound() )
{
TArray<int32> StaticSectionIDs;
OnCollectStaticSections.Execute(StaticSectionIDs);
for ( int32 i = 0; i < StaticSectionIDs.Num(); i++ )
{
FilteredRootAction->AddSection(0, StaticSectionIDs[i]);
}
}
// Trim and sanitized the filter text (so that it more likely matches the action descriptions)
FString TrimmedFilterString = FText::TrimPrecedingAndTrailing(GetFilterText()).ToString();
// Tokenize the search box text into a set of terms; all of them must be present to pass the filter
TArray<FString> FilterTerms;
TrimmedFilterString.ParseIntoArray(FilterTerms, TEXT(" "), true);
for (auto& String : FilterTerms)
{
String = String.ToLower();
}
// Generate a list of sanitized versions of the strings
TArray<FString> SanitizedFilterTerms;
for (int32 iFilters = 0; iFilters < FilterTerms.Num() ; iFilters++)
{
FString EachString = FName::NameToDisplayString( FilterTerms[iFilters], false );
EachString = EachString.Replace( TEXT( " " ), TEXT( "" ) );
SanitizedFilterTerms.Add( EachString );
}
ensure( SanitizedFilterTerms.Num() == FilterTerms.Num() );// Both of these should match !
const bool bRequiresFiltering = FilterTerms.Num() > 0;
int32 BestMatchCount = 0;
int32 BestMatchIndex = INDEX_NONE;
for (int32 CurTypeIndex=0; CurTypeIndex < AllActions.GetNumActions(); ++CurTypeIndex)
{
FGraphActionListBuilderBase::ActionGroup& CurrentAction = AllActions.GetAction( CurTypeIndex );
// If we're filtering, search check to see if we need to show this action
bool bShowAction = true;
int32 EachWeight = 0;
if (bRequiresFiltering)
{
// Combine the actions string, separate with \n so terms don't run into each other, and remove the spaces (incase the user is searching for a variable)
// In the case of groups containing multiple actions, they will have been created and added at the same place in the code, using the same description
// and keywords, so we only need to use the first one for filtering.
const FString& SearchText = CurrentAction.GetSearchTextForFirstAction();
FString EachTermSanitized;
for (int32 FilterIndex = 0; (FilterIndex < FilterTerms.Num()) && bShowAction; ++FilterIndex)
{
const bool bMatchesTerm = (SearchText.Contains(FilterTerms[FilterIndex], ESearchCase::CaseSensitive) || (SearchText.Contains(SanitizedFilterTerms[FilterIndex], ESearchCase::CaseSensitive) == true));
bShowAction = bShowAction && bMatchesTerm;
}
// Only if we are going to show the action do we want to generate the weight of the filter text
if (bShowAction)
{
// Get the 'weight' of this in relation to the filter
EachWeight = GetActionFilteredWeight( CurrentAction, FilterTerms, SanitizedFilterTerms );
}
}
if (bShowAction)
{
// If this action has a greater relevance than others, cache its index.
if( EachWeight > BestMatchCount )
{
BestMatchCount = EachWeight;
BestMatchIndex = CurTypeIndex;
}
FilteredRootAction->AddChild(CurrentAction);
}
}
FilteredRootAction->SortChildren(bAlphaSortItems, /*bRecursive =*/true);
TreeView->RequestTreeRefresh();
// Update the filtered list (needs to be done in a separate pass because the list is sorted as items are inserted)
FilteredActionNodes.Empty();
FilteredRootAction->GetLeafNodes(FilteredActionNodes);
// Get _all_ new nodes (flattened tree basically)
TArray< TSharedPtr<FGraphActionNode> > AllNodes;
FilteredRootAction->GetAllNodes(AllNodes);
// If theres a BestMatchIndex find it in the actions nodes and select it (maybe this should check the current selected suggestion first ?)
if( BestMatchIndex != INDEX_NONE )
{
FGraphActionListBuilderBase::ActionGroup& FilterSelectAction = AllActions.GetAction( BestMatchIndex );
if( FilterSelectAction.Actions[0].IsValid() == true )
{
for (int32 iNode = 0; iNode < FilteredActionNodes.Num() ; iNode++)
{
if( FilteredActionNodes[ iNode ].Get()->GetPrimaryAction() == FilterSelectAction.Actions[ 0 ] )
{
SelectedSuggestion = iNode;
}
}
}
}
// Make sure the selected suggestion stays within the filtered list
if ((SelectedSuggestion >= 0) && (FilteredActionNodes.Num() > 0))
{
//@TODO: Should try to actually maintain the highlight on the same item if it survived the filtering
SelectedSuggestion = FMath::Clamp<int32>(SelectedSuggestion, 0, FilteredActionNodes.Num() - 1);
MarkActiveSuggestion();
}
else
{
SelectedSuggestion = INDEX_NONE;
}
if (ShouldExpandNodes())
{
// Expand all
FilteredRootAction->ExpandAllChildren(TreeView);
}
else
{
// Expand to match the old state
RestoreExpansionState< TSharedPtr<FGraphActionNode> >(TreeView, AllNodes, OldExpansionState, CompareGraphActionNode);
}
}
int32 SGraphActionMenu::GetActionFilteredWeight( const FGraphActionListBuilderBase::ActionGroup& InCurrentAction, const TArray<FString>& InFilterTerms, const TArray<FString>& InSanitizedFilterTerms )
{
// The overall 'weight'
int32 TotalWeight = 0;
// Some simple weight figures to help find the most appropriate match
const int32 WholeMatchWeightMultiplier = 2;
const int32 WholeMatchLocalizedWeightMultiplier = 3;
const int32 DescriptionWeight = 10;
const int32 CategoryWeight = 1;
const int32 NodeTitleWeight = 1;
const int32 KeywordWeight = 4;
// Helper array
struct FArrayWithWeight
{
FArrayWithWeight(const TArray< FString >* InArray, int32 InWeight)
: Array(InArray)
, Weight(InWeight)
{
}
const TArray< FString >* Array;
int32 Weight;
};
// Setup an array of arrays so we can do a weighted search
TArray< FArrayWithWeight > WeightedArrayList;
int32 Action = 0;
if( InCurrentAction.Actions[Action].IsValid() == true )
{
// Combine the actions string, separate with \n so terms don't run into each other, and remove the spaces (incase the user is searching for a variable)
// In the case of groups containing multiple actions, they will have been created and added at the same place in the code, using the same description
// and keywords, so we only need to use the first one for filtering.
const FString& SearchText = InCurrentAction.GetSearchTextForFirstAction();
// First the localized keywords
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetLocalizedSearchKeywordsArrayForFirstAction(), KeywordWeight));
// The localized description
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetLocalizedMenuDescriptionArrayForFirstAction(), DescriptionWeight));
// The node search localized title weight
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetLocalizedSearchTitleArrayForFirstAction(), NodeTitleWeight));
// The localized category
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetLocalizedSearchCategoryArrayForFirstAction(), CategoryWeight));
// First the keywords
int32 NonLocalizedFirstIndex = WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetSearchKeywordsArrayForFirstAction(), KeywordWeight));
// The description
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetMenuDescriptionArrayForFirstAction(), DescriptionWeight));
// The node search title weight
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetSearchTitleArrayForFirstAction(), NodeTitleWeight));
// The category
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetSearchCategoryArrayForFirstAction(), CategoryWeight));
// Now iterate through all the filter terms and calculate a 'weight' using the values and multipliers
const FString* EachTerm = nullptr;
const FString* EachTermSanitized = nullptr;
for (int32 FilterIndex = 0; FilterIndex < InFilterTerms.Num(); ++FilterIndex)
{
EachTerm = &InFilterTerms[FilterIndex];
EachTermSanitized = &InSanitizedFilterTerms[FilterIndex];
if( SearchText.Contains( *EachTerm, ESearchCase::CaseSensitive ) )
{
TotalWeight += 2;
}
else if (SearchText.Contains(*EachTermSanitized, ESearchCase::CaseSensitive))
{
TotalWeight++;
}
// Now check the weighted lists (We could further improve the hit weight by checking consecutive word matches)
for (int32 iFindCount = 0; iFindCount < WeightedArrayList.Num() ; iFindCount++)
{
int32 WeightPerList = 0;
const TArray<FString>& KeywordArray = *WeightedArrayList[iFindCount].Array;
int32 EachWeight = WeightedArrayList[iFindCount].Weight;
int32 WholeMatchCount = 0;
int32 WholeMatchMultiplier = (iFindCount < NonLocalizedFirstIndex) ? WholeMatchLocalizedWeightMultiplier : WholeMatchWeightMultiplier;
for (int32 iEachWord = 0; iEachWord < KeywordArray.Num() ; iEachWord++)
{
// If we get an exact match weight the find count to get exact matches higher priority
if (KeywordArray[iEachWord].StartsWith(*EachTerm, ESearchCase::CaseSensitive))
{
if (iEachWord == 0)
{
WeightPerList += EachWeight * WholeMatchMultiplier;
}
else
{
WeightPerList += EachWeight;
}
WholeMatchCount++;
}
else if (KeywordArray[iEachWord].Contains(*EachTerm, ESearchCase::CaseSensitive))
{
WeightPerList += EachWeight;
}
if (KeywordArray[iEachWord].StartsWith(*EachTermSanitized, ESearchCase::CaseSensitive))
{
if (iEachWord == 0)
{
WeightPerList += EachWeight * WholeMatchMultiplier;
}
else
{
WeightPerList += EachWeight;
}
WholeMatchCount++;
}
else if (KeywordArray[iEachWord].Contains(*EachTermSanitized, ESearchCase::CaseSensitive))
{
WeightPerList += EachWeight / 2;
}
}
// Increase the weight if theres a larger % of matches in the keyword list
if( WholeMatchCount != 0 )
{
int32 PercentAdjust = ( 100 / KeywordArray.Num() ) * WholeMatchCount;
WeightPerList *= PercentAdjust;
}
TotalWeight += WeightPerList;
}
}
}
return TotalWeight;
}
// Returns true if the tree should be autoexpanded
bool SGraphActionMenu::ShouldExpandNodes() const
{
// Expand all the categories that have filter results, or when there are only a few to show
const bool bFilterActive = !GetFilterText().IsEmpty();
const bool bOnlyAFewTotal = AllActions.GetNumActions() < 10;
return bFilterActive || bOnlyAFewTotal || bAutoExpandActionMenu;
}
bool SGraphActionMenu::CanRenameNode(TWeakPtr<FGraphActionNode> InNode) const
{
return !OnCanRenameSelectedAction.Execute(InNode);
}
void SGraphActionMenu::OnFilterTextChanged( const FText& InFilterText )
{
// Reset the selection if the string is empty
if( InFilterText.IsEmpty() == true )
{
SelectedSuggestion = INDEX_NONE;
}
GenerateFilteredItems(false);
}
void SGraphActionMenu::OnFilterTextCommitted(const FText& InText, ETextCommit::Type CommitInfo)
{
if (CommitInfo == ETextCommit::OnEnter)
{
TryToSpawnActiveSuggestion();
}
}
bool SGraphActionMenu::TryToSpawnActiveSuggestion()
{
TArray< TSharedPtr<FGraphActionNode> > SelectionList = TreeView->GetSelectedItems();
if (SelectionList.Num() == 1)
{
// This isnt really a keypress - its Direct, but its always called from a keypress function. (Maybe pass the selectinfo in ?)
OnItemSelected( SelectionList[0], ESelectInfo::OnKeyPress );
return true;
}
else if (FilteredActionNodes.Num() == 1)
{
OnItemSelected( FilteredActionNodes[0], ESelectInfo::OnKeyPress );
return true;
}
return false;
}
void SGraphActionMenu::OnGetChildrenForCategory( TSharedPtr<FGraphActionNode> InItem, TArray< TSharedPtr<FGraphActionNode> >& OutChildren )
{
if (InItem->Children.Num())
{
OutChildren = InItem->Children;
}
}
void SGraphActionMenu::OnNameTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit, TWeakPtr< FGraphActionNode > InAction )
{
if(OnCategoryTextCommitted.IsBound())
{
OnCategoryTextCommitted.Execute(NewText, InTextCommit, InAction);
}
}
void SGraphActionMenu::OnItemScrolledIntoView( TSharedPtr<FGraphActionNode> InActionNode, const TSharedPtr<ITableRow>& InWidget )
{
if (InActionNode->IsRenameRequestPending())
{
InActionNode->BroadcastRenameRequest();
}
}
TSharedRef<ITableRow> SGraphActionMenu::MakeWidget( TSharedPtr<FGraphActionNode> InItem, const TSharedRef<STableViewBase>& OwnerTable, bool bIsReadOnly )
{
TSharedPtr<IToolTip> SectionToolTip;
if ( InItem->IsSectionHeadingNode() )
{
if ( OnGetSectionToolTip.IsBound() )
{
SectionToolTip = OnGetSectionToolTip.Execute(InItem->SectionID);
}
}
// In the case of FGraphActionNodes that have multiple actions, all of the actions will
// have the same text as they will have been created at the same point - only the actual
// action itself will differ, which is why parts of this function only refer to InItem->Actions[0]
// rather than iterating over the array
// Create the widget but do not add any content, the widget is needed to pass the IsSelectedExclusively function down to the potential SInlineEditableTextBlock widget
TSharedPtr< STableRow< TSharedPtr<FGraphActionNode> > > TableRow;
if ( InItem->IsSectionHeadingNode() )
{
TableRow = SNew(SCategoryHeaderTableRow< TSharedPtr<FGraphActionNode> >, OwnerTable)
.ToolTip(SectionToolTip);
}
else
{
const FTableRowStyle* Style = bUseSectionStyling ? &FEditorStyle::Get().GetWidgetStyle<FTableRowStyle>("TableView.DarkRow") : &FCoreStyle::Get().GetWidgetStyle<FTableRowStyle>("TableView.Row");
TableRow = SNew(STableRow< TSharedPtr<FGraphActionNode> >, OwnerTable)
.Style(Style)
.OnDragDetected(this, &SGraphActionMenu::OnItemDragDetected)
.ShowSelection(!InItem->IsSeparator());
}
TSharedPtr<SHorizontalBox> RowContainer;
TableRow->SetRowContent
(
SAssignNew(RowContainer, SHorizontalBox)
);
TSharedPtr<SWidget> RowContent;
FMargin RowPadding = FMargin(0, 2);
if( InItem->IsActionNode() )
{
check(InItem->HasValidAction());
FCreateWidgetForActionData CreateData(&InItem->OnRenameRequest());
CreateData.Action = InItem->GetPrimaryAction();
CreateData.HighlightText = TAttribute<FText>(this, &SGraphActionMenu::GetFilterText);
CreateData.MouseButtonDownDelegate = FCreateWidgetMouseButtonDown::CreateSP( this, &SGraphActionMenu::OnMouseButtonDownEvent );
if(OnCreateWidgetForAction.IsBound())
{
CreateData.IsRowSelectedDelegate = FIsSelected::CreateSP( TableRow.Get(), &STableRow< TSharedPtr<FGraphActionNode> >::IsSelected );
CreateData.bIsReadOnly = bIsReadOnly;
CreateData.bHandleMouseButtonDown = false; //Default to NOT using the delegate. OnCreateWidgetForAction can set to true if we need it
RowContent = OnCreateWidgetForAction.Execute( &CreateData );
}
else
{
RowContent = SNew(SDefaultGraphActionWidget, &CreateData);
}
}
else if( InItem->IsCategoryNode() )
{
TWeakPtr< FGraphActionNode > WeakItem = InItem;
// Hook up the delegate for verifying the category action is read only or not
SGraphActionCategoryWidget::FArguments ReadOnlyArgument;
if(bIsReadOnly)
{
ReadOnlyArgument.IsReadOnly(bIsReadOnly);
}
else
{
ReadOnlyArgument.IsReadOnly(this, &SGraphActionMenu::CanRenameNode, WeakItem);
}
TSharedRef<SGraphActionCategoryWidget> CategoryWidget =
SNew(SGraphActionCategoryWidget, InItem)
.HighlightText(this, &SGraphActionMenu::GetFilterText)
.OnTextCommitted(this, &SGraphActionMenu::OnNameTextCommitted, TWeakPtr< FGraphActionNode >(InItem))
.IsSelected(TableRow.Get(), &STableRow< TSharedPtr<FGraphActionNode> >::IsSelectedExclusively)
.IsReadOnly(ReadOnlyArgument._IsReadOnly);
if(!bIsReadOnly)
{
InItem->OnRenameRequest().BindSP( CategoryWidget->InlineWidget.Pin().Get(), &SInlineEditableTextBlock::EnterEditingMode );
}
RowContent = CategoryWidget;
}
else if( InItem->IsSeparator() )
{
RowPadding = FMargin(0);
FText SectionTitle;
if( OnGetSectionTitle.IsBound() )
{
SectionTitle = OnGetSectionTitle.Execute(InItem->SectionID);
}
if( SectionTitle.IsEmpty() )
{
RowContent = SNew( SVerticalBox )
.Visibility(EVisibility::HitTestInvisible)
+ SVerticalBox::Slot()
.AutoHeight()
// Add some empty space before the line, and a tiny bit after it
.Padding( 0.0f, 5.f, 0.0f, 5.f )
[
SNew( SBorder )
// We'll use the border's padding to actually create the horizontal line
.Padding(FEditorStyle::GetMargin(TEXT("Menu.Separator.Padding")))
// Separator graphic
.BorderImage( FEditorStyle::GetBrush( TEXT( "Menu.Separator" ) ) )
];
}
else
{
RowContent = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SNew(SRichTextBlock)
.Text(SectionTitle)
.DecoratorStyleSet(&FEditorStyle::Get())
.TextStyle(FEditorStyle::Get(), "DetailsView.CategoryTextStyle")
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Right)
.Padding(FMargin(0,0,2,0))
[
OnGetSectionWidget.IsBound() ? OnGetSectionWidget.Execute(TableRow.ToSharedRef(), InItem->SectionID) : SNullWidget::NullWidget
];
}
}
TSharedPtr<SExpanderArrow> ExpanderWidget;
if (OnCreateCustomRowExpander.IsBound())
{
FCustomExpanderData CreateData;
CreateData.TableRow = TableRow;
CreateData.WidgetContainer = RowContainer;
if (InItem->IsActionNode())
{
check(InItem->HasValidAction());
CreateData.RowAction = InItem->GetPrimaryAction();
}
ExpanderWidget = OnCreateCustomRowExpander.Execute(CreateData);
}
else
{
ExpanderWidget =
SNew(SExpanderArrow, TableRow)
.BaseIndentLevel(1);
}
RowContainer->AddSlot()
.AutoWidth()
.VAlign(VAlign_Fill)
.HAlign(HAlign_Right)
[
ExpanderWidget.ToSharedRef()
];
RowContainer->AddSlot()
.FillWidth(1.0)
.Padding(RowPadding)
[
RowContent.ToSharedRef()
];
return TableRow.ToSharedRef();
}
FText SGraphActionMenu::GetFilterText() const
{
// If there is an external source for the filter, use that text instead
if(OnGetFilterText.IsBound())
{
return OnGetFilterText.Execute();
}
return FilterTextBox->GetText();;
}
void SGraphActionMenu::OnItemSelected( TSharedPtr< FGraphActionNode > InSelectedItem, ESelectInfo::Type SelectInfo )
{
if (!bIgnoreUIUpdate)
{
HandleSelection(InSelectedItem, SelectInfo);
}
}
void SGraphActionMenu::OnItemDoubleClicked( TSharedPtr< FGraphActionNode > InClickedItem )
{
if ( InClickedItem.IsValid() && !bIgnoreUIUpdate )
{
if ( InClickedItem->IsActionNode() )
{
OnActionDoubleClicked.ExecuteIfBound(InClickedItem->Actions);
}
else if (InClickedItem->Children.Num())
{
TreeView->SetItemExpansion(InClickedItem, !TreeView->IsItemExpanded(InClickedItem));
}
}
}
FReply SGraphActionMenu::OnItemDragDetected( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
// Start a function-call drag event for any entry that can be called by kismet
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
if(SelectedNodes.Num() > 0)
{
TSharedPtr<FGraphActionNode> Node = SelectedNodes[0];
// Dragging a ctaegory
if(Node.IsValid() && Node->IsCategoryNode())
{
if(OnCategoryDragged.IsBound())
{
return OnCategoryDragged.Execute(Node->GetCategoryPath(), MouseEvent);
}
}
// Dragging an action
else
{
if(OnActionDragged.IsBound())
{
TArray< TSharedPtr<FEdGraphSchemaAction> > Actions;
GetSelectedActions(Actions);
return OnActionDragged.Execute(Actions, MouseEvent);
}
}
}
}
return FReply::Unhandled();
}
bool SGraphActionMenu::OnMouseButtonDownEvent( TWeakPtr<FEdGraphSchemaAction> InAction )
{
bool bResult = false;
if( (!bIgnoreUIUpdate) && InAction.IsValid() )
{
TArray< TSharedPtr<FGraphActionNode> > SelectionList = TreeView->GetSelectedItems();
TSharedPtr<FGraphActionNode> SelectedNode;
if (SelectionList.Num() == 1)
{
SelectedNode = SelectionList[0];
}
else if (FilteredActionNodes.Num() == 1)
{
SelectedNode = FilteredActionNodes[0];
}
if (SelectedNode.IsValid() && SelectedNode->HasValidAction())
{
if( SelectedNode->GetPrimaryAction().Get() == InAction.Pin().Get() )
{
bResult = HandleSelection( SelectedNode, ESelectInfo::OnMouseClick );
}
}
}
return bResult;
}
FReply SGraphActionMenu::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& KeyEvent )
{
int32 SelectionDelta = 0;
// Escape dismisses the menu without placing a node
if (KeyEvent.GetKey() == EKeys::Escape)
{
FSlateApplication::Get().DismissAllMenus();
return FReply::Handled();
}
else if ((KeyEvent.GetKey() == EKeys::Enter) && !bIgnoreUIUpdate)
{
return TryToSpawnActiveSuggestion() ? FReply::Handled() : FReply::Unhandled();
}
else if (FilteredActionNodes.Num() > 0 && !FilterTextBox->GetText().IsEmpty())
{
// Up and down move thru the filtered node list
if (KeyEvent.GetKey() == EKeys::Up)
{
SelectionDelta = -1;
}
else if (KeyEvent.GetKey() == EKeys::Down)
{
SelectionDelta = +1;
}
if (SelectionDelta != 0)
{
// If we have no selected suggestion then we need to use the items in the root to set the selection and set the focus
if( SelectedSuggestion == INDEX_NONE )
{
SelectedSuggestion = (SelectedSuggestion + SelectionDelta + FilteredRootAction->Children.Num()) % FilteredRootAction->Children.Num();
MarkActiveSuggestion();
return FReply::Handled();
}
//Move up or down one, wrapping around
SelectedSuggestion = (SelectedSuggestion + SelectionDelta + FilteredActionNodes.Num()) % FilteredActionNodes.Num();
MarkActiveSuggestion();
return FReply::Handled();
}
}
else
{
// When all else fails, it means we haven't filtered the list and we want to handle it as if we were just scrolling through a normal tree view
return TreeView->OnKeyDown(FindChildGeometry(MyGeometry, TreeView.ToSharedRef()), KeyEvent);
}
return FReply::Unhandled();
}
void SGraphActionMenu::MarkActiveSuggestion()
{
TGuardValue<bool> PreventSelectionFromTriggeringCommit(bIgnoreUIUpdate, true);
if (SelectedSuggestion >= 0)
{
TSharedPtr<FGraphActionNode>& ActionToSelect = FilteredActionNodes[SelectedSuggestion];
TreeView->SetSelection(ActionToSelect);
TreeView->RequestScrollIntoView(ActionToSelect);
}
else
{
TreeView->ClearSelection();
}
}
void SGraphActionMenu::AddReferencedObjects( FReferenceCollector& Collector )
{
for (int32 CurTypeIndex=0; CurTypeIndex < AllActions.GetNumActions(); ++CurTypeIndex)
{
FGraphActionListBuilderBase::ActionGroup& Action = AllActions.GetAction( CurTypeIndex );
for ( int32 ActionIndex = 0; ActionIndex < Action.Actions.Num(); ActionIndex++ )
{
Action.Actions[ActionIndex]->AddReferencedObjects(Collector);
}
}
}
bool SGraphActionMenu::HandleSelection( TSharedPtr< FGraphActionNode > &InSelectedItem, ESelectInfo::Type InSelectionType )
{
bool bResult = false;
if( OnActionSelected.IsBound() )
{
if ( InSelectedItem.IsValid() && InSelectedItem->IsActionNode() )
{
OnActionSelected.Execute(InSelectedItem->Actions, InSelectionType);
bResult = true;
}
else
{
OnActionSelected.Execute(TArray< TSharedPtr<FEdGraphSchemaAction> >(), InSelectionType);
bResult = true;
}
}
return bResult;
}
void SGraphActionMenu::OnSetExpansionRecursive(TSharedPtr<FGraphActionNode> InTreeNode, bool bInIsItemExpanded)
{
if (InTreeNode.IsValid() && InTreeNode->Children.Num())
{
TreeView->SetItemExpansion(InTreeNode, bInIsItemExpanded);
for (TSharedPtr<FGraphActionNode> Child : InTreeNode->Children)
{
OnSetExpansionRecursive(Child, bInIsItemExpanded);
}
}
}
/////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE