Files
UnrealEngineUWP/Engine/Source/Editor/LevelEditor/Private/SLevelViewport.cpp
Matt Kuhlenschmidt 765a83175b Copying //UE4/Dev-Editor to //UE4/Dev-Main (Source: //UE4/Dev-Editor @ 3379190)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3342222 on 2017/03/10 by Nick.Darnell

	UMG - Adding a GetContent to the UContentWidget.

Change 3342228 on 2017/03/10 by Nick.Darnell

	Project Launcher - Always consume mouse wheel vertically so it stops scrolling to the right.

Change 3342310 on 2017/03/10 by Nick.Darnell

	UMG - Cleaning up some extra class references.

Change 3343382 on 2017/03/13 by Jamie.Dale

	Applying optimization to FChunkManifestGenerator::ContainsMap

Change 3343523 on 2017/03/13 by Mike.Fricker

	New details view option:  "Show Hidden Properties while Playing"
	- Enabling this allows you to see every property on selected objects that belong to a simulating world, even non-visible and non-editable properties.  Very useful for inspection and debugging.
	- Remember to change World Outliner to show you actors in the "Play World" if you want to select and inspect those objects first!
	- This setting is saved for your entire project, similar to "Show All Advanced"

Change 3343573 on 2017/03/13 by Mike.Fricker

	New details view option:  "Show Hidden Properties while Playing" (part 2)
	- Fixed missing include / unity issue

Change 3343709 on 2017/03/13 by Jamie.Dale

	Some fixes for gathering cached dependency data

	- We no longer load dependency data that doesn't have the correct package name.
	- We no longer populate the dependency results when bGatherDependsData is false.

Change 3343900 on 2017/03/13 by Alexis.Matte

	fix crash when creating too much LOD at import
	#jira UE-42785

Change 3344104 on 2017/03/13 by Alexis.Matte

	Add a boolean to the static mesh socket so we know if the socket was imported or created in UE4. This allow us to not impact editor socket when we re-import a fbx
	#jira UE-42736

Change 3344802 on 2017/03/14 by Michael.Dupuis

	#jira UE-42244 : added missing nullptr so render thread wont try to access global var when we're no longer in landscape mode
	Changed the sync method between graphic resource from render thread and game thread to prevent desync

Change 3346061 on 2017/03/14 by Jamie.Dale

	Adding const& and && overloads of FText::Format

Change 3346192 on 2017/03/14 by Arciel.Rekman

	Linux: fix VHACD to retain bincompat with the baseline (UE-42895).

	- It is now compiled against libc++ instead of libstdc++ in the toolchain.

Change 3347083 on 2017/03/15 by Andrew.Rodham

	Fixed crash when changing anchors on a background blur widget

Change 3347359 on 2017/03/15 by Michael.Dupuis

	#jira UE-38193:
	Added Rename, Delete, New Folder, Size Map, Show In Explorer for folder and asset in the path view and asset view

Change 3347382 on 2017/03/15 by Michael.Dupuis

	missing include incremental

Change 3347500 on 2017/03/15 by Alex.Delesky

	#jira UE-41231 - Selecting multiple text widgets in UMG will now allow you to set their value correctly, and the "Multiple Values" text will no longer be set in the widgets instead.

Change 3347920 on 2017/03/15 by Jamie.Dale

	Fixing some places passing tooltips as FString rather than FText

	#jira UE-42603

Change 3347925 on 2017/03/15 by Jamie.Dale

	Re-saving some assets so their tooltips can be gathered

	#jira UE-42603

Change 3348788 on 2017/03/15 by Jamie.Dale

	Updated the Windows platform to use the newer Vista+ style browser dialogs, rather than the older XP style dialogs

Change 3349187 on 2017/03/16 by Andrew.Rodham

	Sequencer: Added the ability to specify additional event receivers for level sequence actors
	  - Such actors will receive events from event tracks

Change 3349194 on 2017/03/16 by Andrew.Rodham

	Sequencer: Reset compiled templates on load in the editor, and ensure correct serialization of generation ledger
	  - Resetting on load means that we guarantee up-to-date templates, even if underlying compilation logic changes.

	#jira UE-42198
	#jira UE-40969

Change 3349210 on 2017/03/16 by Andrew.Rodham

	Sequencer: Event tracks can now be defined to trigger events at the start of evaluation, after objects are spawned, or at the end of evaluation

Change 3349211 on 2017/03/16 by Andrew.Rodham

	Sequencer: Add ability to retrieve bound objects from blueprint

Change 3349398 on 2017/03/16 by Nick.Darnell

	UMG - Fixing a flashing hierarchy view.  Looks like assets continuing to stream in causing the object change notification to continue to fire, and the widget designer refreshed any time it happened.  Now limit to only if widgets are changing.

Change 3349420 on 2017/03/16 by Alex.Delesky

	#jira UE-40720 - Multiline editable text boxes can now be set to Read-Only.

Change 3349548 on 2017/03/16 by Alexis.Matte

	Fbx importer, when importing a staticmesh with combine mesh option check and the fbx file contain some "MultiSub Material" the materialinstance are now always hook properly.

Change 3349818 on 2017/03/16 by Cody.Albert

	Fixed constructor for FNavigationMetaData

Change 3350047 on 2017/03/16 by Cody.Albert

	Removed unneeded check so that children actors are never orphaned when their parent is moved into a newly created folder in the world outliner

Change 3350072 on 2017/03/16 by Arciel.Rekman

	ShaderCompiler: make sure strings are at least 4-byte aligned.

	- Can crash wcscpy() under Linux otherwise (reported by a licensee).

Change 3350146 on 2017/03/16 by Arciel.Rekman

	Fix CodeLite project generation (UE-42921).

	- Reportedly causes a crash in CodeLite 10.x

Change 3350235 on 2017/03/16 by Arciel.Rekman

	Fix memory leak in address symbolication on Linux.

	- Makes MallocProfiler work again.
	- Also add progress update in MallocProfiler since symbolication is still slow.

	Merging CL 3338764 from Fortnite to Dev-Editor.

Change 3350382 on 2017/03/16 by Arciel.Rekman

	Linux: fix incorrect cast of rlimit in i686.

Change 3350471 on 2017/03/16 by Jamie.Dale

	Enabling loc dashboard by default for new projects

Change 3350516 on 2017/03/16 by Jamie.Dale

	Enabling content hot-reloading by default

Change 3350582 on 2017/03/16 by Cody.Albert

	Corrected Widget Interaction Component to use current impact point instead of last impact point

Change 3350945 on 2017/03/16 by Jamie.Dale

	Gave FConfigFile::FindOrAddSection API linkage

Change 3351441 on 2017/03/17 by Michael.Dupuis

	#jira UE-42843: Fixed Transaction begin/end order issue happening with min slider passing max slider value
	Add support for multiple selection value display

Change 3351558 on 2017/03/17 by Michael.Dupuis

	#jira UE-42845: Always refresh the detail panel to properly update for selection change, delete, etc.

Change 3351657 on 2017/03/17 by Matt.Kuhlenschmidt

	Adding USD Third Party dependencies

Change 3351665 on 2017/03/17 by Matt.Kuhlenschmidt

	Added experimental USD Importer Plugin
	This plugin supports basic static mesh importing and scene creation of actors using static meshes

Change 3351682 on 2017/03/17 by Matt.Kuhlenschmidt

	Enabling USD importer in engine test project for automation tests

Change 3351749 on 2017/03/17 by Alexis.Matte

	Make sure the selection proxy is off for the skeletal mesh component. UE4 use the selection outline instead

	#jira UE-41677

Change 3351831 on 2017/03/17 by Michael.Dupuis

	#jira UETOOL-1102:
	Added HSV controls to Color Grading
	Some look improvement for RGV/HSV
	Color Grading refactor
	Group Reset bug fix (relevant only to color grading)

Change 3352041 on 2017/03/17 by Matt.Kuhlenschmidt

	Updated USD plugin whitelisting

Change 3352093 on 2017/03/17 by Michael.Dupuis

	when FREEZERENDERING is called, stop the foliage culling too

Change 3352211 on 2017/03/17 by Alexis.Matte

	Fix the physic asset missing skeleton warning
	#jira UE-43006

Change 3352336 on 2017/03/17 by Alexis.Matte

	We now allow a negative W value of the ScreenPoint vector in the ScreenToPixel function. In this case we simply reverse the W value to kept the manipulator direction on the good side.
	#jira UE-37458

Change 3352947 on 2017/03/17 by Phillip.Kavan

	#jira UE-42510 - Instanced static mesh transform edits are now reflected in the Blueprint editor's preview scene.

	Change summary:
	- Added IPropertyHandle::GetValueBaseAddress() (interface).
	- Modified IPropertyHandle::NotifyPostChange() to include EPropertyChangeType as an optional input.
	- Added FPropertyHandleBase::GetValueBaseAddress() (implementation).
	- Modified FPropertyHandleBase::NotifyPostChange() to include the optional input arg in the property change event.
	- Modified FPropertyHandleBase::CreatePropertyNameWidget() to clear the override text after temporarily replacing display name/tooltip text for the creation of the SPropertyNameWidget. This was done to allow for transactions to be named according to the property that's being modified.
	- Modified FMathStructProxyCustomization::OnValueCommitted() to only apply the input value while not interactively editing via spinbox as well as when not post-processing an undo/redo (which can trigger a focus loss).
	- Modified the FMathStructProxyCustomization::OnEndSliderMovement() delegate to include property handle and proxy value input parameters, as well as to call FlushValues() as part of the implementation.
	- Modified FlushValues() for each of FMatrixStructCustomization, FTransformStructCustomization and FQuatStructCustomization to explicitly handle both propagation and transaction processing.
	- Modified UInstancedStaticMeshComponent::UpdateInstanceTransform() to call Modify() prior to applying changes (so that the previous state is recorded when inside a transaction context).
	- Modified FInstanceStaticMeshSCSEditorCustomization::HandleViewportDrag() to propagate changes to all instances of the ISMC archetype.

	Known issues:
	- Using the spinbox to edit instanced mesh transform values in the Blueprint editor will not apply the change to instances in the level editor until after you release the mouse button (i.e. - it will not be shown as a "live" update).

Change 3353678 on 2017/03/20 by Michael.Dupuis

	properly unfreeze the culling of foliage when toggling the freezerendering command

Change 3353747 on 2017/03/20 by Matt.Kuhlenschmidt

	PR #3372: Git plugin: fix update status on directories hotfix (still) slightly broken in master (UE4.16) (Contributed by SRombauts)

Change 3353749 on 2017/03/20 by Matt.Kuhlenschmidt

	PR #3373: Git Plugin: hotfix for regression off Visual Diffs with older version of Git in master (UE4.16) (Contributed by SRombauts)

Change 3353754 on 2017/03/20 by Matt.Kuhlenschmidt

	PR #3390: Allow OBJ imports to change if materials and textures are also imported (Contributed by mmdanggg2)

Change 3353909 on 2017/03/20 by Matt.Kuhlenschmidt

	Fixed actors showing thumbnails in details panel and made a few other tweeks to thumbnail displays in details panels
	- The color of the accepted type is now  shown properly
	- All object based properties now have thumbnails on by default.

Change 3353948 on 2017/03/20 by Nick.Darnell

	UMG - Updating the background blur widget's upgrade code to use the custom version, and handling older cases that were continuing to generate blur slots, even when already upgraded.

Change 3354335 on 2017/03/20 by Nick.Darnell

	Paragon - Excluding Archetype objects from reporting references, which causes crashes in the fast template mode.

Change 3354495 on 2017/03/20 by Nick.Darnell

	Core - Making it so order that outers are discovered does not matter, initializing the chain of outers if hasn't been created when instancing subobjects.

Change 3354578 on 2017/03/20 by Nick.Darnell

	Slate - There's now a console variable option, Slate.VerifyHitTestVisibility (off by default) which enables additional visibility checks for widgets.  Normally this isn't nessesary, but if you're changing the visibility of widgets during a frame, and several hit tests need to be performed that frame there's a chance that a button could be clicked twice in one frame.  Enabling this mode will make all hit testing more expensive, so for now it's off by default, but available for licensees that need the extra testing.

Change 3354737 on 2017/03/20 by Nick.Darnell

	Core - Adding a fix to Dev-Editor from that enables objects in the same package being requested to also be loaded.  This came about during async streaming callbacks alerting that a requested class was done loading, but there were still other assets in the package 'not loaded' but were available, just needed post load called on them.

Change 3355923 on 2017/03/21 by Yannick.Lange

	VR Editor: - Remove unnecessary cleanup functions.
	- Initialize with VR Mode and remove SetOwner function, since it shouldn't be possible to reset the VR Mode afterwards.

Change 3355959 on 2017/03/21 by Yannick.Lange

	VR Editor: - Rename VREditorWorldInteraction to VREditorPlacement, to avoid confusion with ViewportWorldInteraction. VREditorPlacement will only handle placing objects from content browser in the VR Mode.
	- Removed SnapSelectedActorsToGround to VREditorMode.

Change 3355965 on 2017/03/21 by Yannick.Lange

	VR Editor:  Forgot to add files to previous submit 3355959.

Change 3355977 on 2017/03/21 by Yannick.Lange

	VR Editor: Remove function to add a new extension with  TSubclassOf<UEditorWorldExtension>.

Change 3356017 on 2017/03/21 by Yannick.Lange

	VR Editor: - UI system check owner VRMode.
	- UI system fix check on VRMode on shutdown.

Change 3356028 on 2017/03/21 by Nick.Darnell

	Slate - SButton now correctly releases mouse capture even if it becomes disabled while pressed, but before 'click' has been fired.

	#jira UE-42777

Change 3356071 on 2017/03/21 by Yannick.Lange

	VR Editor: Copy of change 3353663.
	- Fix having to press once on the landscape to see the visuals for landscape editing.
	- Fix when sculpting/painting the position wouldn't update.
	- Fix inverted action for brushes while holding down shift or modifier on motioncontroller.
	- Cleanup FLandscapeToolInteractorPosition.

	- Change from 3353663: Use TStrokeClass::UseContinuousApply and TimeSinceLastInteractorMove to decide when to apply ToolStroke on tick.

Change 3356180 on 2017/03/21 by Michael.Dupuis

	Added ShowFlag Foliage Occlusion Bounds
	Fixed non initialized variable
	Expose changing Min Occlusion Bounds instead of assuming 6

	#rn none

Change 3356347 on 2017/03/21 by Nick.Darnell

	UMG - Introducing a faster CreateWidget.  When cooking, the widget compiler now generates a widget template/archetype that is stored in the same package as the generated blueprint class.  During compiling we generate a nearly fully initialized widget tree including all sub userwidgets and their trees, hookup all member variables, initialize named slots, setup any animations...etc.  This nearly fully constructed widget can be instanced using it as an archetype in the NewObject call, and does not have to use the correspondingly slow StaticDuplicateObject path.  There are restrictions on this method, part of the compiling step for widgets now inspects if the instancing would be successful, or if there would be GLEO references after instancing because a user forgot to setup Instanced on a subobject property.  Luckily that should be few and far between, all UVisuals (Widgets & Slots) are now DefaultToInstanced, which takes care of the overwhelming cases that demand the instanced flag.  Especially given the bulk of cases using BindWidget in native code.

	UMG - Removing a lot of deprecated functions from 4.8 on UUserWidget.

Change 3356357 on 2017/03/21 by Nick.Darnell

	Build - Fixing some IWYU issues on the incremental build.

Change 3356461 on 2017/03/21 by Nick.Darnell

	Build - Fixing linux build errors.

Change 3356468 on 2017/03/21 by Jamie.Dale

	STextPropertyEditableTextBox now handles empty texts correctly

Change 3356916 on 2017/03/21 by Matt.Kuhlenschmidt

	Fixed a crash when a material render proxy on a preview node is deleted when it is in flight on the render thread

	#jira UE-40556

Change 3357033 on 2017/03/21 by Alexis.Matte

	Fix crash when importing file with import commandlet
	Make sure path are combine properly to avoid crash
	Add some missing pointer check
	Make sure the asset are save when there is no source control
	#jira UE-42334

Change 3357176 on 2017/03/21 by Alex.Delesky

	#jira UE-42445 - TMaps now support editing the values of structs that act as map keys. TMaps with struct keys will now show the types of their elements in the details panel as well, and structs will now also display numbers next to set elements.

Change 3357197 on 2017/03/21 by Alex.Delesky

	#jira none - Fixing build issue for TMap key struct change.

Change 3357205 on 2017/03/21 by Michael.Dupuis

	Forgot to reset min granularity to 6 from testing

Change 3357340 on 2017/03/21 by Arciel.Rekman

	Mark FMallocAnsi (standard libc malloc) thread-safe on Linux.

Change 3357413 on 2017/03/21 by matt.kuhlenschmidt

	Added '/Game/Effects/Fort_Effects/Materials/Smoke/M_Main_Smoke_Puff.M_Main_Smoke_Puff' to collection 'MattKTest'

	Upgraded collection 'MattKTest' (was version 1, now version 2)

Change 3357505 on 2017/03/21 by Alexis.Matte

	Fix to avoid changing the CDO of FbxAssetImportData. The UI was saving the Config which was saving the CDO. But already serialized data will be reload badly if the CDO change since we serialize only the diff.

	#jira UE-42947

Change 3357825 on 2017/03/21 by Arciel.Rekman

	Clean up the large thread pool on exit.

	- Seems like the destruction was missed in the original CL 2785131 (12/1/15).
	- Fixes problems when threads were allocated in memory that is being cleaned up in another place on exit.

Change 3358086 on 2017/03/22 by Yannick.Lange

	VR Editor: - Fix gizmo scaling down when dragging the world.
	- Fix gizmo scaling down when dragging rotation handle.

Change 3358175 on 2017/03/22 by Andrew.Rodham

	Sequencer: Made ALevelSequenceActor::AdditionalEventReceivers advanced display

Change 3358367 on 2017/03/22 by tim.gautier

	Submitting resaved QAGame assets - Materials, Material Instances, Material Functions and Parameters

Change 3358457 on 2017/03/22 by Yannick.Lange

	VR Editor: Deleting unused UI assets.

Change 3358801 on 2017/03/22 by Matt.Kuhlenschmidt

	Guard against crash if the level editor is shut down when the object system has already been shut down

	#jira UE-35605

Change 3358897 on 2017/03/22 by matt.barnes

	Checking in WIP test content for UEQATC-1635 (UMG Navigation)

Change 3358976 on 2017/03/22 by Alex.Delesky

	#jira none - Fixing an issue where ItemPropertyNode could potentially dereference a null property

Change 3358987 on 2017/03/22 by Yannick.Lange

	VR Editor: Fix warning: Can't find file for asset '/Engine/VREditor/UI/VRButtonBackground' while loading ../../../Engine/Content/VREditor/Devices/Vive/VivePreControllerMaterial.uasset.

Change 3359067 on 2017/03/22 by Yannick.Lange

	VR Editor: Fix Radial Menu remains on controller after exiting VR Preview
	#jira UE-42885

Change 3359179 on 2017/03/22 by Matt.Kuhlenschmidt

	Fixed "Multiple Values" in Body Setup when single bone has multiple bodies

	#jira UE-41546

Change 3359626 on 2017/03/22 by Arciel.Rekman

	Linux: pool OS allocations.

	- Add a TMemoryPool and TMemoryPoolArray classes that can be used with any type of OS allocator functions.
	- Add ability to bypass CachedOSPageAllocator for given sizes. Also, corrected the condition on AllocImpl to match one on FreeImpl.
	- Switch Linux to pool mmap()/munmap() by default (helps 32-bit Linux and also speeds up 64-bit one), except 64-bit servers.
	- Add a test to TestPAL to check performance and thread safety.
	- Misc. fixes.

Change 3359989 on 2017/03/23 by Andrew.Rodham

	Sequencer: Binding overrides improvements
	  - Added the ability to override spawnable bindings
	  - Added the ability to override bindings in sub sequences
	  - Deprecated "Get Sequence Bindings" node in favor of "Get Sequence Binding", which is more robust, and provides a better UI/UX for selecting single bindings

	#jira UE-42470

Change 3360369 on 2017/03/23 by Alexis.Matte

	Fix the staticmesh conversion from UE4 4.13 to earlier UE4 versions
	#jira UE-42731

Change 3360556 on 2017/03/23 by Andrew.Rodham

	Sequencer: Added drag/drop support for binding overrides
	  - You can now drag and drop sequencer object binding nodes into blueprint graphs (to create 'Get Sequence Binding' nodes), and onto binding overrides specified on level sequence actors.

Change 3360618 on 2017/03/23 by Arciel.Rekman

	Make Binned2 work on Mac.

	- Game/server will use Binned2 by default.

Change 3360838 on 2017/03/23 by Nick.Darnell

	CommonUI - Making the SingleMaterialStyleMID property transient.  It had been serialized mistakenly onto several widgets when it appears the intent is to dynamically allocate it upon demand.

Change 3360841 on 2017/03/23 by Nick.Darnell

	UMG - Updating the editor to use DuplicateAndInitializeFromWidgetTree, so that Initialize is properly called when duplicating sub widget trees.

Change 3362561 on 2017/03/24 by Matt.Kuhlenschmidt

	Fixed text outlines being cropped at large sizes

	#jira UE-42647

Change 3362565 on 2017/03/24 by Matt.Kuhlenschmidt

	Added automation test for font outlines

Change 3362567 on 2017/03/24 by Matt.Kuhlenschmidt

	Resaved this file to fix 0 engine version warnings

Change 3362582 on 2017/03/24 by Yannick.Lange

	VR Editor: - Fix log warnings when teleporting.
	- Fix undo/redo when using teleport scaling.
	- Improved teleport scaling and push/pull input.
	#jira UE-43214

Change 3362631 on 2017/03/24 by Jamie.Dale

	Split the monolithic culture concept in UE4

	UE4 has historically only supported the concept of a single monolithic "culture" that applied to both text localization and internationalization, as well as all asset localization. Typically the "culture" was set to the "locale" of the OS, however that could be undesirable or incorrect on platforms (such as newer versions of Windows) that have a distinct concept of "language" (for localization) and "locale" (for internationalization).

	This change splits the concept of "culture" into "language" and "locale", and also adds the concept of "asset groups". The language is now used to work out which localization we should use, and the locale is used to control how numbers/dates/times/etc are formatted within our internationalization library.

	Asset groups expand on the language used by asset localization and allow you to create a group of asset classes that can be assigned a different culture than the main game language. A typical use-case of this would be creating an "audio" group that could, for example, be set to Japanese while the rest of the game runs in English.

	If your game doesn't care about the distinction between language and locale, and doesn't need to use asset groups, then you're able to continue to use "culture" as you always have. If, however, you do care about those things, then you'll likely want to avoid using the "culture" directly (as it's now a very aggressive setting that overrides all others), and instead favor using language/locale (games will typically treat these as the same) and asset groups as separate concepts (both in settings, and in your in-game UI).

	The language or locale for a game can be controlled by settings within the "Internationalization" section of your configs (this would typically be set in your GameUserSettings config, in the same way that "culture" works), eg)

	  [Internationalization]
	  language=fr
	  locale=fr

	The asset groups for a game can be controlled by settings within the "Internationalization.AssetGroupClasses" and "Internationalization.AssetGroupCultures" sections of your configs (the asset group class definition would typically be set in your DefaultGame config, and the cultures the groups use would typically be set in your GameUserSettings config), eg)

	  [Internationalization.AssetGroupClasses]
	  +Audio=SoundWave
	  +Audio=DialogueWave

	  [Internationalization.AssetGroupCultures]
	  +Audio=ja

	#jira UE-38418
	#jira UE-43014

Change 3362798 on 2017/03/24 by Nick.Darnell

	UMG - Putting the finishing touches on the hardware cursor system.  Can now load them from blueprints, and there are options for setting them up in the project settings.

	UMG - Deprecating the old properties for software widget cursors.  They've been moved into a map that can handle any of the mouse cursors as the enum key, which was always the intent/desire but maps couldn't be used as UProperties then.

Change 3362805 on 2017/03/24 by Jamie.Dale

	PR #3397: Allow empty source to override display string (Contributed by jorgenpt)

Change 3363039 on 2017/03/24 by Jamie.Dale

	Use the pre-scaled font height where possible to avoid an extra multiply

Change 3363188 on 2017/03/24 by Joe.Graf

	Added support for -iterate for content plugins that require path remapping during cook/packaging

	#CodeReview: matt.kuhlenschmidt
	#rb: matt.kuhlenschmidt

Change 3363355 on 2017/03/24 by Nick.Darnell

	UMG - Removing the CookAdditionalFiles function in UserInterfaceSettings.

Change 3363672 on 2017/03/24 by Matt.Kuhlenschmidt

	Material thumbnails now respect used particle system sprites flag and show a quad insead of a sphere by default. For this change I added the ability to have per asset type override for the default thumbnail shape and I added a way to reset thumbnails to default.  All existinging particle system materials that have not had a custom thumbnail will have to be reloaded and resaved for this to work

	#jira UE-42410

Change 3363699 on 2017/03/24 by Mike.Fricker

	VR Editor: Improved extensibility (for mesh editor)
	- This was merged from CL 3352612 and re-opened for edit before commit
	- All mesh editor changes were stripped before merging

Change 3363784 on 2017/03/24 by Matt.Barnes

	Adding content for tests following UEQATC-3548

Change 3363872 on 2017/03/24 by Arciel.Rekman

	Linux: require user to setup clang/clang++ for building hlslcc.

	- Earlier we tried to handle most common scenarios since libhlslcc needed to be built during the setup. Now that we supply a prebuilt version we don't need to be as user friendly, especially given that the attempts to second guess the compiler started to look complicated.

Change 3364089 on 2017/03/24 by Matt.Kuhlenschmidt

	Fix CIS

Change 3364381 on 2017/03/24 by JeanMichel.Dignard

	UV Packing optim
	- Use horizontal segments instead of checking texel by texel to fit source chart in layout.
	- Skip a couple of rasterize by flipping either the X texels or the Y texels when possible.
	- Keep the best chart raster so that we don't need to reraster when adding the chart to the layout.
	- Added a lightmap UV version in StaticMesh so that we don't invalidate the lighting cache. Only use the new lightmap UV generation when going through UStaticMesh::Build which invalidates the lighting.

Change 3364587 on 2017/03/24 by Arciel.Rekman

	Fix ordered comparison warning from clang 4.0.

Change 3364596 on 2017/03/24 by Arciel.Rekman

	Linux: fix editor being stuck (hack).

	- Rebuilt hlslcc in Debug.

Change 3364863 on 2017/03/25 by Max.Chen

	Sequencer: Fixed crash when deactivating a section in sequencer

	#jira UE-39880

Change 3364864 on 2017/03/25 by Max.Chen

	Sequencer: Integrating fix from licensee to ensure FVirtualTrackArea::HitTestSection checks the row of the section

Change 3364865 on 2017/03/25 by Max.Chen

	Cine Camera: Default post process depth of field method to CircleDOF and use that setting in UpdateCameraLens.

	#jira UE-40621

Change 3364866 on 2017/03/25 by Max.Chen

	GitHub #3183: Conversion to base class is inaccessible.

Change 3364869 on 2017/03/25 by Max.Chen

	Sequencer: Changed the time snapping interval in the toolbar ui so that it no longer additionally updates the sequencer setting. The setting is only used to initialize the time snapping interval of the level sequence. Added translate keys with ctrl and left-right arrows.

	#jira UE-41009
	#jira UE-41210

Change 3364870 on 2017/03/25 by Max.Chen

	Sequencer: Added translate keys with ctrl and left-right arrows.

	#jira UE-41210

Change 3364871 on 2017/03/25 by Max.Chen

	Sequencer: Add level sequence actor customization to open sequencer from the details panel. For matinee parity.

	#jira UE-41459

Change 3364879 on 2017/03/25 by Max.Chen

	Sequencer: Duplicate shot should put the duplicate on the next available row, keeping the start/end times the same.

	#jira UE-41289

Change 3364880 on 2017/03/25 by Max.Chen

	Sequencer: Opening the API for MovieSceneAudio-related classes along with some minor functionality additions:
	- Adding _API specifiers to MovieSceneAudioTrack, MovieSceneAudioSection, and FAudioTrackEditor so they can be subclassed in other modules.
	- Made GetSoundDuration function in MovieSceneAudioTrack.cpp a member function so it's functionaliy could be reused by subclasses.
	- Adding ability to specify delegates for OnQueueSubtitles, OnAudioFinished, and OnAudioPlaybackPercent in a MovieSceneAudioSection, and have them automatically assigned to any AudioComponents that are played by the MovieSceneAudioTemplate

Change 3364884 on 2017/03/25 by Max.Chen

	Sequencer fbx import
	- Removed the PostRotation compensation as it was setuped for 3ds max.
	- On import, add a rotation to camera and light animation keys like we do on export.
	- Merge the component local transform with the ActorNode transform when exporting only one component that isn't the root component in fbx since we're not creating child nodes in that case.

	#jira UE-34692

Change 3364885 on 2017/03/25 by Max.Chen

	Sequence Recorder: Fix crash when clearing properties to record.

	#jira UE-41873

Change 3364886 on 2017/03/25 by Max.Chen

	Sequencer: Add error when attempting to add a circularly dependent level sequence

	#jira UE-22358

Change 3364890 on 2017/03/26 by Max.Chen

	Sequencer: Added ability to specify a 'notify' function to property instance bindings
	  - When specified, the (parameterless) function will be called after a property is set

Change 3364891 on 2017/03/26 by Max.Chen

	Sequencer: Various fixes to thumbnails
	  - Fixed alpha blending being used when presenting the full screen quad for thumbnails

Change 3364892 on 2017/03/26 by Max.Chen

	Sequencer: PreRoll and PostRoll is now exposed at the section level, for all sections
	  - For the majority of sections this will be unimplemented, but it will allow for some tracks to set up their data ahead of time

Change 3364896 on 2017/03/26 by Max.Chen

	Sequencer: Add segment flags to equality operator for movie scene evaluation segments
	  - This prevents them from being accumulated into adjacent segments of the same index and forced time, but differing flags

Change 3364897 on 2017/03/26 by Max.Chen

	Sequencer: Fixed "Evaluate in preroll" and "Evaluate in Postroll" options
	  - Pre and postroll flags now come through on compiled segments, so the previous manual logic for sub sections is obsolete; we can just use the compiled segment data directly.

Change 3364898 on 2017/03/26 by Max.Chen

	Sequencer: Moved track options to be accessible on all nodes, and operate on all selected tracks

Change 3364902 on 2017/03/26 by Max.Chen

	Sequencer: Ensure evaluation flags are considered when compiling segments from external sequences
	  - This ensures that preroll regions in sub sequences are correctly evaluated when their parent section has preroll
	  - Changed high pass blending to always allow preroll

Change 3364903 on 2017/03/26 by Max.Chen

	Engine: Moved proxy mesh transform update out of camera view computation code
	  - GetCameraView can happen as part of end of frame updates, which will assert if any changes of transform happen during its processing

Change 3364908 on 2017/03/26 by Max.Chen

	Sequencer: Added visualization of pre and postroll on sections

Change 3364909 on 2017/03/26 by Max.Chen

	Sequencer: Prevent MovieSceneCompiler from removing preroll segments

Change 3364910 on 2017/03/26 by Max.Chen

	Sequencer: MediaPlayer PreRoll/PostRoll fix
	- Handle PreRoll/PostRoll on sub scenes that have a start offset

Change 3364922 on 2017/03/26 by Max.Chen

	Sequencer: Add check for valid property before dereferencing.

	#jira UE-40951

Change 3364923 on 2017/03/26 by Max.Chen

	Sequencer: Fix MovieScene preroll so that it seeks to the start correct frame before the preroll.

Change 3364924 on 2017/03/26 by Max.Chen

	Sequencer - change default behavior for pre/post roll evaluation
	- MovieSceneTracks are NOT evaluated by default

Change 3364925 on 2017/03/26 by Max.Chen

	Sequencer: Shot track rows now consider pre and post roll when being compiled

Change 3364926 on 2017/03/26 by Max.Chen

	Sequencer: Added the ability to define shared execution tokens, identifyable with a unique identifier, and sortable based on a sort order (<=0: before standard tokens, >0: after other tokens)

Change 3364927 on 2017/03/26 by Max.Chen

	Sequencer: Added the ability to selectively restore state for specific anim type IDs for a given object
	  - This allows us to specifically restore one particular type of animation for a given object (ie, transform, skeletal animation control, or motion blur)

Change 3364928 on 2017/03/26 by Max.Chen

	Sequencer: Fixed sub-sub tracks not being present in master sequences
	  - In order to correctly handle preroll in inner-inner sequences, we need to have access to those tracks when compiling intermediate sub sections. By caching off all the inner templates, we can have access to these tracks to check whether they want to be evaluated in pre/post roll in the master sequence

Change 3364937 on 2017/03/26 by Max.Chen

	Sequencer: Update cine camera component debug focus plane on tick, rather than in GetCameraView

	#jira UE-41332

Change 3364938 on 2017/03/26 by Max.Chen

	Sequencer: Fix crash inserting a level sequence with an invalid shot.

	#jira UE-41481

Change 3364940 on 2017/03/26 by Max.Chen

	Sequencer: Made handling of pre and post roll more consistent between explicit section pre/post roll and pre/post roll inherited from an outer sub section

Change 3364942 on 2017/03/26 by Max.Chen

	Movie Scene Capture: Move EDL generation to setup instead of close to ensure it gets written out when capturing as a separate process.

	#jira UE-41703

Change 3364943 on 2017/03/26 by Max.Chen

	Sequencer: Prevent capturing movies in editor while a PIE session is running

	#jira UE-41399

Change 3364944 on 2017/03/26 by Max.Chen

	CIS fixes

Change 3364951 on 2017/03/26 by Max.Chen

	Sequencer: Fix autokey not setting a keyframe for slate color with specified color.

	#jira UE-41645

Change 3364952 on 2017/03/26 by Max.Chen

	Sequencer: Level sequence frame snapshots now take account of fixed-frame interval offsets, and overlapping shot sections on the same row

	#jira UE-41684

Change 3364953 on 2017/03/26 by Max.Chen

	Sequencer: Fix edl so that it doesn't write out when a shot is out of range. Also fixed not writing the EDL with the correct frame rate when exporting from the track. Reworked the cmx EDL so that its encoded in the same edit time space, including a blank slug at the beginning of the edit.

	#jira UE-41925

Change 3364954 on 2017/03/26 by Max.Chen

	Sequencer - Allow animating parameters on cascade effect components which aren't owned by an AEmitter.

Change 3364955 on 2017/03/26 by Max.Chen

	Sequencer: Fixed sequencer anim instance not being used in the case where one was requested, but a different anim instance was already set

	This fixes an issue when rendering in seaprate process, animations that were set up to use the sequencer instance would be controlled using montage animation instead.

Change 3364963 on 2017/03/26 by Max.Chen

	Sequencer: Fix filtering to include child nodes.

	#jira UE-42068

Change 3364964 on 2017/03/26 by Max.Chen

	Sequencer: Enable UseCustomStartFrame and UseCustomEndFrame when rendering a single shot from the menu.

	#jira UE-42021

Change 3364965 on 2017/03/26 by Max.Chen

	Sequencer: Set the fade color in the track display

Change 3364966 on 2017/03/26 by Max.Chen

	Sequencer: Show actor attached to label in attach section.

Change 3364967 on 2017/03/26 by Max.Chen

	Sequencer: Fix static analysis warnings

Change 3364968 on 2017/03/26 by Max.Chen

	Sequencer: Fix crash on converting to spawnable.

	The previous implementation purported to allow null objects to set up spawnable defaults but it actually needed to compare the spawned object to the supported type.  This new mechanism now allows the spawner to indicate that it accepts null objects and doesn't crash.

	#jira UE-42069

Change 3364969 on 2017/03/26 by Max.Chen

	Sequencer: Fixed crash caused by holding onto stale properties through a raw ptr

	#jira UE-42072

Change 3364977 on 2017/03/26 by Max.Chen

	Sequencer: Convert FLinearColor to FColor for fade.

	#jira UE-41990

Change 3364978 on 2017/03/26 by Max.Chen

	Sequencer: Limit GetAllSections to the sections that actually correspond to the track

	#jira UE-42167

Change 3364979 on 2017/03/26 by Max.Chen

	Sequencer: Filter root nodes too

	#jira UE-42068

Change 3364980 on 2017/03/26 by Max.Chen

	Sequencer: Filter relevant material parameters

	#jira UE-40712

Change 3364982 on 2017/03/26 by Max.Chen

	Sequencer: Remove audio range bounds which clamps to the section bounds (needed for evaluating in pre and post roll)

Change 3364983 on 2017/03/26 by Max.Chen

	Sequencer: Add socket name to attach track section.

Change 3364984 on 2017/03/26 by Max.Chen

	Sequencer: Fix sub track node deletion so that all the sub tracks aren't deleted, only the row being requested.

	#jira UE-40955

Change 3364988 on 2017/03/26 by Max.Chen

	Sequencer: Invalidate expired objects when blueprints are compiled. Fix actor references now handles sections that need to have their guids updated (ie. attach tracks).

Change 3364994 on 2017/03/26 by Max.Chen

	Sequencer: Audio waveforms now show peak samples with smoothed RMS in the center
	  - Audio row heights are now also resizable by dragging on the bottom end of the track lane in the track area view

Change 3364995 on 2017/03/26 by Max.Chen

	UMG: Fix crash on undo

	#jira UE-42210

Change 3365000 on 2017/03/26 by Max.Chen

	Sequencer: Fix crash from GetCurrentValue.

Change 3365001 on 2017/03/26 by Max.Chen

	Sequencer: Split "Snap to the Dragged Key" option into two options, pressed key and dragged key.

	#jira UE-42382

Change 3365002 on 2017/03/26 by Max.Chen

	Sequencer: Downgraded check to ensure for FMovieSceneEvalTemplateBase::GetScriptStructImpl()

Change 3365003 on 2017/03/26 by Max.Chen

	Sequencer: Fixed section template script struct
	  - Because the cpp is not parsed by UHT, the empty template had its parent struct, rather than its own
	  - We now just return null, and handle empty segments correctly in the segment remapper as part of the track compilation

Change 3365013 on 2017/03/26 by Max.Chen

	Sequencer: Added data validation on compiled template loads, and extra guards against misuse of movie scene types

Change 3365014 on 2017/03/26 by Max.Chen

	Sequencer: Sequencer now re-evaluates when starting PIE or Simulate
	  - This can be disabled by disabling "Bind Sequencer to PIE" and "Bind Sequencer to Simulate" in PIE advanced settings

Change 3365015 on 2017/03/26 by Max.Chen

	Sequencer: Fix edl files so that they don't write out empty range shots

Change 3365017 on 2017/03/26 by Max.Chen

	Sequencer: Set max tick rate when in game.

	#jira UE-41078

Change 3365018 on 2017/03/26 by Max.Chen

	Sequencer: When finishing a scrub, playback status is now correctly set to stopped rather than stepping
	  - This fixes a hack that was previously in place from the old PostTickRenderFixup that caused it to run that step after scrubbing bad finished. This is no longer necessary, and actually breaks clicking to set the scrub position, as it now means that we step across the entire range between the previous and current time.

Change 3365022 on 2017/03/26 by Max.Chen

	Sequencer: Insert shot now creates a shot at the current time and puts it on the next available row.

	#jira UE-41480, UE-27699

Change 3365023 on 2017/03/26 by Max.Chen

	Sequencer: Add loop selection range. If there is no selection range, loop mode is restricted to loop or no loop.

	#jira UE-42285

Change 3365029 on 2017/03/26 by Max.Chen

	Sequencer: Add hotkeys to set the selection range to the next and previous shot (page up, page down). Also, added hotkey to set the playback range to all the shots (end)

Change 3365030 on 2017/03/26 by Max.Chen

	Sequencer: Fix particle system restore state so that it gets the proper initial active state of the particle system.

	#jira UE-42861, UE-42859

Change 3365031 on 2017/03/26 by Max.Chen

	Sequencer: Snap time when changing time snapping intervals.

	#jira UE-42590

Change 3365032 on 2017/03/26 by Max.Chen

	Sequencer: Add When Finished state to sections. By default, sections now restore state.

	#jira UE-41991, UE-31569

Change 3365033 on 2017/03/26 by Max.Chen

	#jira UE-42028 "DialogueWave playback calls OnQueueSubtitles multiple times"

	Only queue subtitles once per wave instance playback

Change 3365041 on 2017/03/26 by Max.Chen

	Sequencer: Subscene hierarchical bias

	Tracks can now be prioritized based on their subscene hierarhical bias value. Higher bias values take precedence.

	#jira UE-42078

Change 3365042 on 2017/03/26 by Max.Chen

	Sequencer: Generic paste menu for master (root) tracks.

Change 3365043 on 2017/03/26 by Max.Chen

	Sequencer: Hierarchical bias for level visibility track

	#jira UE-43024

Change 3365044 on 2017/03/26 by Max.Chen

	Sequencer: Prevent throttling on editing keys/sections.

Change 3365045 on 2017/03/26 by Max.Chen

	Sequencer: Set sequencer audio components bIsUISound to false so that they don't continue playing when the game is paused.

	#jira UE-39391

Change 3365046 on 2017/03/26 by Max.Chen

	Sequencer: Add missing BindLevelEditorCommands()

Change 3365049 on 2017/03/26 by Max.Chen

	Sequencer: Set tick prerequites for spawnables when they are spawned.

	#jira UE-43009

Change 3365050 on 2017/03/26 by Max.Chen

	Sequencer: Jump to Start and End of playback shortcuts.

	Rewind renamed to Jump to Start. Shortcut - up arrow.
	Jump to End Shortcut - ctrl up arrow.

	#jira UE-43224

Change 3365051 on 2017/03/26 by Max.Chen

	Sequencer: Add last range to playback

Change 3365057 on 2017/03/26 by Max.Chen

	Sequencer: Fix master sequence subscene generation times.

Change 3365058 on 2017/03/26 by Max.Chen

	Sequencer: Fix paste so that it doesn't paste both onto object nodes and master tracks.

Change 3365059 on 2017/03/26 by Max.Chen

	Sequencer: Fix crash pasting audio track.

Change 3365060 on 2017/03/26 by Max.Chen

	Sequencer: Cache player fade state so that restore state will return the values to the pre animated state.

	#jira UE-43313

Change 3365061 on 2017/03/26 by Max.Chen

	Sequencer: Filter hidden functions. This fixes a bug where the field of view property for a cinematic camera appears to be animatable. It should be hidden just like it is in the property editor.

	#jira UE-41461

Change 3365065 on 2017/03/26 by Max.Chen

	Sequencer: Support component hierarchies when drawing animation paths

	#jira UE-39500

Change 3365066 on 2017/03/26 by Max.Chen

	Sequencer: Refine pause behaviour in sequencer to always evaluate the next frame
	  - This ensures that we get a full frame's worth of evaluation so that the paused frame is of a good quality (and avoids us evaluating a tiny range)

Change 3365075 on 2017/03/26 by Max.Chen

	Sequencer: Fix add shot not setting next row.

Change 3365076 on 2017/03/26 by Max.Chen

	Sequencer: Export MovieSceneTrackEditor

	#jira UE-41641

Change 3365472 on 2017/03/27 by Yannick.Lange

	VR Editor landscape. Back out changelist 3356071 with new proper fixes.
	CL 3356071 introduced another bug and it wasn't correct because of removing FLandscapeToolInteractorPosition. This changelist fixes the same and additional bugs for VREditor Landscape mode.
	- Fix when sculpting/painting the position wouldn't update.
	- Fix inverted action for brushes while holding down shift or modifier on motioncontroller.
	- Fix VREditor Landscape Texture Painting does not paint continuously
	- Fix having to press once on the landscape to see the visuals for landscape editing.
	- Removed Interactor parameter from BeginTool.
	#jira UE-42780, UE-42779

Change 3365497 on 2017/03/27 by Matt.Kuhlenschmidt

	Fix texture importing when an FBX file incorrectly reports absolute path as relative.  First we try absolute, then we try fbx reported relative, then we try relative to parent FBX file.

Change 3365498 on 2017/03/27 by Matt.Kuhlenschmidt

	Fix attempting to load a package in FBX scene import when the import path is empty. This greatly reduces FBX scene import time

Change 3365504 on 2017/03/27 by Yannick.Lange

	VR Editor landscape fix ensure in when starting to paint/sculpt. Mousemove on tool should only be called when the tool is actually active, not when hovering.

Change 3365551 on 2017/03/27 by Matt.Kuhlenschmidt

	PR #3425: Added Scrollbar customization to SComboBox (Contributed by Altrue)

	#jira UE-43338

Change 3365580 on 2017/03/27 by Matt.Kuhlenschmidt

	PR #3409: Add support for per-Category filtering in Output Log (Contributed by thagberg)

Change 3365672 on 2017/03/27 by Andrew.Rodham

	Sequencer: Preanimated state producers can now produce null tokens
	  - Doing so implies no preanimated state should be saved

Change 3365791 on 2017/03/27 by Andrew.Rodham

	Sequencer: Added Material Parameter Collection track

Change 3365806 on 2017/03/27 by Max.Chen

	Sequencer: Add option to instance sub sequences.

	#jira UE-43307

Change 3365822 on 2017/03/27 by Matt.Kuhlenschmidt

	Subdue the output log font color a bit

Change 3365846 on 2017/03/27 by Jamie.Dale

	Added package redirection on load/find

Change 3365852 on 2017/03/27 by Jamie.Dale

	Adding a way to mark a package as no longer missing

Change 3365896 on 2017/03/27 by Jamie.Dale

	Adding GlobalNotification to Slate

	This is the guts of the GlobalEditorNotification, so it can be used by code that doesn't link to UnrealEd.

Change 3365900 on 2017/03/27 by Jamie.Dale

	Prevent the default cooked sandbox from trying to read non-cooked assets

Change 3366550 on 2017/03/27 by Max.Chen

	Sequencer: Fix case

Change 3367301 on 2017/03/28 by Andrew.Rodham

	Tests: Added test actor with a variety of properties for testing purposes

Change 3367303 on 2017/03/28 by Andrew.Rodham

	Tests: Enabled ActorSequenceEditor plugin in EngineTest project

Change 3367304 on 2017/03/28 by Andrew.Rodham

	Tests: Added several functional testing maps for sequencer
	  - SequencerTest_Properties - tests animating various property types
	  - SequencerTest_Events - tests basic event triggering functionality (including additional event receivers and event ordering)
	  - SequencerTest_BindingOverrides - tests overriding possessable and spawnable bindings, along with bindings in sub sequences
	  - SequencerTest_ActorSequence - tests basic actor sequence functionality

Change 3367465 on 2017/03/28 by Max.Chen

	Sequencer: Set Bind Sequencer to PIE off by default, Bind Sequencer to Simulate remains on by default.

Change 3367515 on 2017/03/28 by Matt.Kuhlenschmidt

	Guard against visual studio accessor crash

	#jira UE-43368

Change 3368118 on 2017/03/28 by Alexis.Matte

	Fix the staticmesh conversion from 4.13. There was a error in the LOD loop we where not remapping the LOD 0.
	#jira UE-42731

Change 3368485 on 2017/03/28 by Alex.Delesky

	#jira UE-42207 - Updated SVN Binaries for MacOSX 64-bit:
	- Subversion 1.9.4 -> 1.9.5
	- OpenSSL 1.0.2h -> 1.0.2k
	- BerkeleyDB 5.3.15 -> 6.2.23
	- Java 8u101 -> 8u121
	- Sqlite 3.13.0 -> 3.17.0
	- Serf 1.3.8 -> 1.3.9
	- Swig 3.0.10 -> 3.0.12
	- ZLib 1.2.9 -> 1.2.11

Change 3368495 on 2017/03/28 by Alex.Delesky

	#jira UE-42207 - Updated SVN Binaries for Windows 64-bit:
	- Subversion 1.9.4 -> 1.9.5
	- OpenSSL 1.0.2h -> 1.0.2k
	- BerkeleyDB 5.3.15 -> 6.2.23
	- Java 8u101 -> 8u121
	- Sqlite 3.13.0 -> 3.17.0
	- Serf 1.3.8 -> 1.3.9
	- Swig 3.0.10 -> 3.0.12
	- ZLib 1.2.9 -> 1.2.11

Change 3368501 on 2017/03/28 by Alex.Delesky

	#jira UE-42207 - SVN Build instructions for Windows and Mac 64-bit libraries, and license files for Mac libraries

Change 3368782 on 2017/03/28 by Nick.Darnell

	UMG - Improving some logging for fast widget creation.

Change 3368826 on 2017/03/28 by Nick.Darnell

	Slate - Slate Application now maintains seperate tracking for each pointer being utilized for drag drop, so now multiple fingers on multiple widgets can now simultaneously be attempting a drag, however once one of them becomes successful, we clear all state of all other tracking since only one Drag Drop operation is possible at a time.

	Slate - bFoldTick is now removed from the codebase, we haven't supported the other (non-folded) code path for awhile, so there was no point in maintaining the switch.

	Slate - Users have noticed issues where the cursor does not appear when changing visibility (through toggling the way the cursor appears).  This was rooted in how the OS requested cursor changes, WM_SETCURSOR on Windows only asks for new cursors when the mouse moves, but often cursors change just because mouse capture changes.  So now the path has been centralized in Slate Tick to only handle the cursor changes in one place, and several places that need to refresh the cursor state, now set a flag to handle it on next tick.

	#jira UE-40486

Change 3368917 on 2017/03/28 by Arciel.Rekman

	Linux: allow building with clang 4.0.

Change 3369074 on 2017/03/28 by Nick.Darnell

	UMG - Fixing some spelling on the hardware cursor tip.

	UMG - Changed some checks to ensure now that users can input the wrong data from the editor.  Adding some clamping to the editor interface so that users are not tempted to enter incorrect hotspot ranges for their cursors.

	#jira UE-43419
	#jira UE-43425

Change 3369137 on 2017/03/28 by Max.Chen

	Sequencer: Add given master track sets the outer to the movie scene.

Change 3369360 on 2017/03/29 by Andrew.Rodham

	Sequencer: Reconciled 3349194 and 3365041 with animphys merge

Change 3369410 on 2017/03/29 by Alexis.Matte

	Fix the select filename in the FileDialog "Desktop window platform"
	#jira UE-43319

Change 3369475 on 2017/03/29 by Nick.Darnell

	PR #3413: UE-37710: Proper scaling of WebBrowserViewport (Contributed by projectgheist)

	Modified - you can't use the clip rect to decide on how large you should be.

	#jira UE-37710

Change 3369775 on 2017/03/29 by Max.Chen

	ControlRig: Fix crash on exit.

	#jira UE-43411

Change 3370466 on 2017/03/29 by Nick.Darnell

	AsyncLoading - Adding USoundBase to the set of CDOs that have a particular fixed boot order.

	StreamableManager - Only showing the duplicate load error in debug builds, it's not a real error.

	#jira UE-43409

Change 3370570 on 2017/03/29 by Nick.Darnell

	Slate - Fixing a bug with ZOrder being discarded on the SOverlay Slot.

	#jira UE-43431

Change 3370644 on 2017/03/29 by Andrew.Rodham

	Temporarily disabling sequencer functional test "Event Position"

Change 3370713 on 2017/03/29 by Nick.Darnell

	PR #3399: UE-42831: Anchor text ignores scale (Contributed by projectgheist)

	#jira UE-43156
	#jira UE-42831

Change 3371243 on 2017/03/30 by Arciel.Rekman

	Linux: scale OS allocation pool to match memory size.

	- Number of distinct VMAs (contiguous virtual memory areas, i.e. mappings done via mmap()) is rather low (~64k)
	  and we can run out of VMAs earlier than we will run into available memory. Larger pool makes this less likely.

Change 3371262 on 2017/03/30 by Arciel.Rekman

	Linux: fix custom present.

	- PR #3383 contributed by yaakuro.

Change 3371301 on 2017/03/30 by Arciel.Rekman

	Linux: fix copying to a non-existent directory during Setup.

Change 3371307 on 2017/03/30 by Andrew.Rodham

	Editor: Added "Resave All" functionality to content browser folders

Change 3371364 on 2017/03/30 by Andrew.Rodham

	Sequencer: Level streaming improvements
	  - Tick prerequisites are now set up when any object binding is resolved, not at the start of the sequence. This unifies code between spawnables and possessables, and allows tick prerequisites to still be set up when levels are streamed in
	  - Actor references are no longer resolved when a PIEInstance is specified on the package, and it cannot be fixed up to a different ptr than the original. This stops us resolving actors from one world into another.
	  - Fixed level visibility request getting cleared when the cumulative total was 0 (it should only do this if there are no requests left)

	#jira UE-43225

Change 3371365 on 2017/03/30 by Andrew.Rodham

	Tests: Sequencer level streaming tests

Change 3371493 on 2017/03/30 by Nick.Darnell

	PR #3408: UE-19980: Added FCanExecuteAction to prevent keyboard shortcut. (Contributed by projectgheist)

Change 3371524 on 2017/03/30 by Nick.Darnell

	PR #2938: Minor UMG code fixups (Contributed by projectgheist), accepted most of the changes.

Change 3371545 on 2017/03/30 by Nick.Darnell

	UMG - Fixing some minor issues with WidgetComponents not properly limiting input depending on what is supported with reguard to hardware input.

Change 3371576 on 2017/03/30 by Matt.Kuhlenschmidt

	PR #3433: Fix for the Standalone D3D Slate Shader using the wrong value for the. (Contributed by megasjay)

Change 3371590 on 2017/03/30 by Nick.Darnell

	UMG - Fixing widget alignment in the viewport when using the widget component with screen space, with an aspect ratio lock on the player's camera.  The widgets should now show up in the right locations.

Change 3371625 on 2017/03/30 by Alexis.Matte

	Fix the merge tool material id assignment
	#jira UE-43246

Change 3371666 on 2017/03/30 by Nick.Darnell

	UMG - Reducing logging, don't need to tell everyone all the time we're using the fast widget path.

Change 3371687 on 2017/03/30 by Arciel.Rekman

	Linux: switch to new managed filehandles.

Change 3371778 on 2017/03/30 by Matt.Kuhlenschmidt

	Fixed the animation to play property on skeletal meshes being too small to read anything

	#jira UE-43327

Change 3372709 on 2017/03/30 by Matt.Kuhlenschmidt

	Made slate loading widget / movie play back more thread safe by eliminating Slate applicaiton or the main window from being ticked directly on another thread.  We now have a separate virtual window for ticking and painting the loading screen widgets in isolation

Change 3372757 on 2017/03/30 by Nick.Darnell

	Paragon - Fixing cases where people were using PostLoad() where really it should have done when the widget was constructed or created.  This is a side effect of the FastWidget creation path 'PostLoad()' is not called on newly constructed widgets, though it did before because part of duplicating the WidgetTree, required serialization, which would have called it.

Change 3372777 on 2017/03/30 by Nick.Darnell

	Fixing fast widget template cooking so that it does the same logic as Initialize did, centralizing the code to find the first widgetblueprintclass.

Change 3372949 on 2017/03/30 by Nick.Darnell

	UMG - Fixing some cooking crashes for the super class.

Change 3373139 on 2017/03/30 by Jeff.Farris

	Added TimingPolicy option to WidgetComponent, so widgets can optionally tick in game time rather than real time.

	(Copy of CL 3279699 from Robo Recall to Dev-Editor)

Change 3373235 on 2017/03/30 by Nick.Darnell

	Fixing a cooking issue, accidentally removed code that was properly loading some needed assets.

Change 3373266 on 2017/03/30 by Matt.Kuhlenschmidt

	Made GetMoviePlayer thread safe.  Simply accessing GetMoviePlayer is safe now as is checking IsLoadingFinished.  However, most of the functions on movie player are only safe from the game thread!

Change 3374026 on 2017/03/31 by Andrew.Rodham

	Sequencer: Moved evaluation group registration to IMovieSceneModule

	#jira UE-43420

Change 3374060 on 2017/03/31 by Yannick.Lange

	VR Editor: Collision on motion controllers in simulate.

Change 3374185 on 2017/03/31 by Nick.Darnell

	Attempting to fix the build.

Change 3374232 on 2017/03/31 by Max.Chen

	Sequencer: Fix audio not playing in editor

	#jira UE-43514

Change 3374322 on 2017/03/31 by Nick.Darnell

	UMG - SafeZone widget now has comments, and useful tips.  Using the debugging console commands now trigger the broadcast that will cause controls like the SSafeZone widget to resample the display metrics to learn the new safezone ratio.

Change 3374424 on 2017/03/31 by Max.Chen

	Updated test content so that the door animation is now set to "Keep State" for the When Finished property.

	#jira UE-43519

Change 3374447 on 2017/03/31 by Max.Chen

	Sequencer: Notify streaming system prior to camera cuts

	By default, this does nothing. Users will need to enable the preroll section of camera cuts for the streaming system to activate prior to cutting to cameras.

	#jira UE-42406

Change 3374571 on 2017/03/31 by Andrew.Rodham

	Sequencer: Unified global and object-bound pre animated state, added InitializeObjectForAnimation method to state producers

Change 3374578 on 2017/03/31 by Andrew.Rodham

	Sequencer: Added unit tests for pre-animated state

Change 3374592 on 2017/03/31 by Max.Chen

	Color Customization: Set curve color names.

	#jira UE-43405

Change 3374596 on 2017/03/31 by Andrew.Rodham

	Corrected documentation comment

Change 3374671 on 2017/03/31 by Matt.Kuhlenschmidt

	Fix movie scene audio track not compiling outside of editor

Change 3374689 on 2017/03/31 by Matt.Kuhlenschmidt

	Remove the slate thread masquerading as the game thread in IsInGameThread

Change 3374730 on 2017/03/31 by Max.Chen

	Sequencer: Add check for null loaded level.

Change 3374732 on 2017/03/31 by Max.Chen

	Sequencer: Remove null tracks on postload.

Change 3374737 on 2017/03/31 by tim.gautier

	- Updated UMG_Optimization: Adjusted Variable names to resolve compile errors due to Widget Components and Variables sharing names (cannot be done with new compile improvements)

	- Set Level Blueprint for TM-UMG back to AllPalettes

Change 3374987 on 2017/03/31 by Nick.Darnell

	UMG - Introducing a way to inform the widgets more information about the designer.  There's now a DesignerChanged event sent to all design time widgets letting them know things like the current screen size and DPI scale.

	UMG - The SafeZone widget will now show the correct safe zone amount if you use the safezone command line options, which are now documented in the comment for the USafeZone class.

Change 3375599 on 2017/03/31 by Max.Chen

	Cine Camera: Update camera debug plane when property changes, rather rely soley on tick. This fixes a bug where sliding the value on the details panel doesn't update the debug plane in the viewport simultaneously.

	#jira UE-43543

Change 3375601 on 2017/03/31 by Arciel.Rekman

	Linux: switch to v9 cross-toolchain.

Change 3375856 on 2017/04/01 by Andrew.Rodham

	Sequencer: Fixed 'formal parameter with requested alignment of 16 won't be aligned'

Change 3375870 on 2017/04/01 by Andrew.Rodham

	Sequencer: Fixed explicit template instantiation ocurring before the complete definition of type's members
	  - This resulted such members not being instantiated (and hence exported) when compiled with clang

Change 3376114 on 2017/04/02 by Arciel.Rekman

	Linux: make source code accessor aware of clang 3.9 and 4.0.

Change 3376138 on 2017/04/02 by Arciel.Rekman

	Linux: add clang to fedora deps (UE-42123).

	- PR #3273 submitted by cpyarger.

Change 3376159 on 2017/04/02 by Arciel.Rekman

	Linux: some support for building on Debian Sid or Stretch (UE-35841).

	- Basd on PR #2790 by haimat.

Change 3376163 on 2017/04/02 by Arciel.Rekman

	Linux: install latest clang on Arch (UE-42341).

	- This undoes PR #1905.
	- PR #2897 by SiebenCorgie.
	- PR #3302 by awesomeness872.
	- PR #3341 by patrickelectric.

Change 3376167 on 2017/04/02 by Arciel.Rekman

	Add FreeBSD mem info (courtesy support for the out of tree build) (UE-42994).

	- PR #3378 by mdcasey.

Change 3376168 on 2017/04/02 by Arciel.Rekman

	Linux: fixed VHACD Makefile on a case sensitive fs (UE-42905).

	- PR #3381 by slonopotamus.

Change 3376177 on 2017/04/02 by Arciel.Rekman

	SlateDlg: case-insensitive comparison of filter extensions (UE-39477).

	- PR #3019 by aknarts.

Change 3376178 on 2017/04/02 by Arciel.Rekman

	WebRTC: only x86_64 version exists for Linux.

Change 3376245 on 2017/04/03 by Andrew.Rodham

	Sequencer: Re-enabled event order test

Change 3376339 on 2017/04/03 by Matt.Kuhlenschmidt

	Fix crash during loading movie playback on DX12 due to not ever cleaning up old resources

	#jira UE-27026

Change 3376481 on 2017/04/03 by Alex.Delesky

	#jira UE-43495 - TMaps will now support customized key properties correctly.

Change 3376741 on 2017/04/03 by Matt.Kuhlenschmidt

	Fix crash flushing font cache when loading a movie.  This is no longer save on the slate movie thread

	#jira UE-43567

Change 3376763 on 2017/04/03 by Shaun.Kime

	Material Reroute nodes do not work for Texture Object Parameters as they return a base output type. Modified logic to now support this node type.
	#jira UE-43521

Change 3376836 on 2017/04/03 by Jamie.Dale

	Fixed text format history being clobbered by reference collection

	#jira UE-37513

Change 3376852 on 2017/04/03 by Nick.Darnell

	Paragon - Found a case where a user had marked a BindWidget property as Transient which prevents serializing the property binding now for widget fast mode.

	#jira UE-43564

Change 3377207 on 2017/04/03 by Jamie.Dale

	Desktop platform directory pickers are expected to return absolute paths

	File pickers return relative paths though, and we should make this consistent at some point.

	#jira UE-43588

Change 3377214 on 2017/04/03 by Matt.Kuhlenschmidt

	Fix movie player shutdown crash in non-editor builds

	#jira UE-43577

Change 3377299 on 2017/04/03 by Michael.Dupuis

	#jira UE-43586 : properties should be non transactional
	#jira UE-43559

Change 3378333 on 2017/04/04 by Michael.Dupuis

	#jira UE-43585
	#jira UE-43586
	Revert back to purple color

Change 3378633 on 2017/04/04 by Matt.Kuhlenschmidt

	Resaved this asset to avoid zero engine version warnings

Change 3378958 on 2017/04/04 by Nick.Darnell

	Automation - Fixing the race condition to finish compiling shaders on screenshots for UI.

[CL 3379345 by Matt Kuhlenschmidt in Main branch]
2017-04-04 15:35:21 -04:00

3957 lines
133 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "SLevelViewport.h"
#include "Materials/MaterialInterface.h"
#include "Engine/Selection.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandList.h"
#include "Widgets/Text/STextBlock.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/Docking/TabManager.h"
#include "EngineGlobals.h"
#include "ActorFactories/ActorFactory.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/FeedbackContext.h"
#include "Modules/ModuleManager.h"
#include "GameFramework/PlayerController.h"
#include "Application/ThrottleManager.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Views/SHeaderRow.h"
#include "Framework/Docking/LayoutService.h"
#include "EditorStyleSet.h"
#include "Editor/UnrealEdEngine.h"
#include "Exporters/ExportTextContainer.h"
#include "Camera/CameraActor.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/WorldSettings.h"
#include "LevelEditorViewport.h"
#include "UnrealEdMisc.h"
#include "UnrealEdGlobals.h"
#include "LevelEditor.h"
#include "SLevelViewportToolBar.h"
#include "LevelViewportActions.h"
#include "LevelEditorActions.h"
#include "Slate/SceneViewport.h"
#include "EditorShowFlags.h"
#include "SLevelEditor.h"
#include "AssetSelection.h"
#include "Kismet2/DebuggerCommands.h"
#include "Layers/ILayers.h"
#include "DragAndDrop/ClassDragDropOp.h"
#include "DragAndDrop/AssetDragDropOp.h"
#include "DragAndDrop/ExportTextDragDropOp.h"
#include "LevelUtils.h"
#include "DragAndDrop/BrushBuilderDragDropOp.h"
#include "ISceneOutlinerColumn.h"
#include "ActorTreeItem.h"
#include "ScopedTransaction.h"
#include "SCaptureRegionWidget.h"
#include "HighresScreenshotUI.h"
#include "ISettingsModule.h"
#include "BufferVisualizationData.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "SLevelViewportControlsPopup.h"
#include "SActorPilotViewportToolbar.h"
#include "Engine/LocalPlayer.h"
#include "Slate/SGameLayerManager.h"
#include "FoliageType.h"
static const FName LevelEditorName("LevelEditor");
#define LOCTEXT_NAMESPACE "LevelViewport"
// @todo Slate Hack: Disallow game UI to be used in play in viewport until GWorld problem is fixed
// Currently Slate has no knowledge of a world and cannot switch it before input events,etc
#define ALLOW_PLAY_IN_VIEWPORT_GAMEUI 1
namespace SLevelViewportPIEAnimation
{
float const MouseControlLabelFadeout = 5.0f;
}
class FLevelViewportDropContextMenuImpl
{
public:
/**
* Fills in menu options for the actor add/replacement submenu
*
* @param bReplace true if we want to add a replace menu instead of add
* @param MenuBuilder The menu to add items to
*/
static void FillDropAddReplaceActorMenu( bool bReplace, class FMenuBuilder& MenuBuilder );
};
SLevelViewport::SLevelViewport()
: HighResScreenshotDialog( NULL )
, ViewTransitionType( EViewTransition::None )
, bViewTransitionAnimPending( false )
, DeviceProfile("Default")
, PIEOverlaySlotIndex(0)
, bPIEHasFocus(false)
, bPIEContainsFocus(false)
, UserAllowThrottlingValue(0)
{
}
SLevelViewport::~SLevelViewport()
{
// Clean up any actor preview viewports
for (FViewportActorPreview& ActorPreview : ActorPreviews)
{
ActorPreview.bIsPinned = false;
}
PreviewActors( TArray< AActor* >() );
FLevelViewportCommands::NewStatCommandDelegate.RemoveAll(this);
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
LevelEditor.OnRedrawLevelEditingViewports().RemoveAll( this );
LevelEditor.OnTakeHighResScreenShots().RemoveAll( this );
LevelEditor.OnActorSelectionChanged().RemoveAll( this );
LevelEditor.OnMapChanged().RemoveAll( this );
GEngine->OnLevelActorDeleted().RemoveAll( this );
GetMutableDefault<ULevelEditorViewportSettings>()->OnSettingChanged().RemoveAll( this );
// If this viewport has a high res screenshot window attached to it, close it
if (HighResScreenshotDialog.IsValid())
{
HighResScreenshotDialog.Pin()->RequestDestroyWindow();
HighResScreenshotDialog.Reset();
}
}
void SLevelViewport::HandleViewportSettingChanged(FName PropertyName)
{
if ( PropertyName == TEXT("bPreviewSelectedCameras") )
{
OnPreviewSelectedCamerasChange();
}
}
bool SLevelViewport::IsVisible() const
{
// The viewport is visible if we don't have a parent layout (likely a floating window) or this viewport is visible in the parent layout
return IsInForegroundTab() && SEditorViewport::IsVisible();
}
bool SLevelViewport::IsInForegroundTab() const
{
if (ViewportWidget.IsValid() && ParentLayout.IsValid() && !ConfigKey.IsEmpty())
{
return ParentLayout.Pin()->IsLevelViewportVisible(*ConfigKey);
}
return false;
}
void SLevelViewport::Construct(const FArguments& InArgs)
{
GetMutableDefault<ULevelEditorViewportSettings>()->OnSettingChanged().AddRaw(this, &SLevelViewport::HandleViewportSettingChanged);
ParentLayout = InArgs._ParentLayout;
ParentLevelEditor = StaticCastSharedRef<SLevelEditor>( InArgs._ParentLevelEditor.Pin().ToSharedRef() );
ConfigKey = InArgs._ConfigKey;
// Store border brushes for differentiating between active and inactive viewports
ActiveBorder = FEditorStyle::GetBrush( "LevelViewport.ActiveViewportBorder" );
NoBorder = FEditorStyle::GetBrush( "LevelViewport.NoViewportBorder" );
DebuggingBorder = FEditorStyle::GetBrush( "LevelViewport.DebugBorder" );
BlackBackground = FEditorStyle::GetBrush( "LevelViewport.BlackBackground" );
StartingPlayInEditorBorder = FEditorStyle::GetBrush( "LevelViewport.StartingPlayInEditorBorder" );
StartingSimulateBorder = FEditorStyle::GetBrush( "LevelViewport.StartingSimulateBorder" );
ReturningToEditorBorder = FEditorStyle::GetBrush( "LevelViewport.ReturningToEditorBorder" );
ConstructLevelEditorViewportClient( InArgs );
SEditorViewport::Construct( SEditorViewport::FArguments() );
ActiveViewport = SceneViewport;
ConstructViewportOverlayContent();
// If a map has already been loaded, this will test for it and copy the correct camera location out
OnMapChanged( GWorld, EMapChangeType::LoadMap );
// Important: We use raw bindings here because we are releasing our binding in our destructor (where a weak pointer would be invalid)
// It's imperative that our delegate is removed in the destructor for the level editor module to play nicely with reloading.
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
LevelEditor.OnRedrawLevelEditingViewports().AddRaw( this, &SLevelViewport::RedrawViewport );
LevelEditor.OnTakeHighResScreenShots().AddRaw( this, &SLevelViewport::TakeHighResScreenShot );
// Tell the level editor we want to be notified when selection changes
LevelEditor.OnActorSelectionChanged().AddRaw( this, &SLevelViewport::OnActorSelectionChanged );
// Tell the level editor we want to be notified when selection changes
LevelEditor.OnMapChanged().AddRaw( this, &SLevelViewport::OnMapChanged );
GEngine->OnLevelActorDeleted().AddRaw( this, &SLevelViewport::OnLevelActorsRemoved );
}
void SLevelViewport::ConstructViewportOverlayContent()
{
PIEViewportOverlayWidget = SNew( SOverlay );
int32 SlotIndex = 0;
#if ALLOW_PLAY_IN_VIEWPORT_GAMEUI
ViewportOverlay->AddSlot( SlotIndex )
[
SAssignNew(GameLayerManager, SGameLayerManager)
.SceneViewport(this, &SLevelViewport::GetGameSceneViewport)
[
PIEViewportOverlayWidget.ToSharedRef()
]
];
++SlotIndex;
#endif
ViewportOverlay->AddSlot( SlotIndex )
.HAlign(HAlign_Right)
[
SAssignNew( ActorPreviewHorizontalBox, SHorizontalBox )
];
ViewportOverlay->AddSlot(SlotIndex)
.VAlign(VAlign_Bottom)
.HAlign(HAlign_Left)
.Padding(5.0f)
[
SNew(SLevelViewportControlsPopup)
.Visibility(this, &SLevelViewport::GetViewportControlsVisibility)
];
ViewportOverlay->AddSlot( SlotIndex )
.VAlign( VAlign_Bottom )
.HAlign( HAlign_Right )
.Padding( 5.0f )
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
[
SNew(SHorizontalBox)
.Visibility(this, &SLevelViewport::GetCurrentFeatureLevelPreviewTextVisibility)
// Current level label
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
[
SNew(STextBlock)
.Text(this, &SLevelViewport::GetCurrentFeatureLevelPreviewText, true)
.Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font")))
.ShadowOffset(FVector2D(1, 1))
]
// Current level
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f, 1.0f, 2.0f, 1.0f)
[
SNew(STextBlock)
.Text(this, &SLevelViewport::GetCurrentFeatureLevelPreviewText, false)
.Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font")))
.ColorAndOpacity(FLinearColor(0.4f, 1.0f, 1.0f))
.ShadowOffset(FVector2D(1, 1))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
[
SNew(SHorizontalBox)
.Visibility(this, &SLevelViewport::GetCurrentLevelTextVisibility)
// Current level label
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
[
SNew(STextBlock)
.Text(this, &SLevelViewport::GetCurrentLevelText, true)
.Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font")))
.ShadowOffset(FVector2D(1, 1))
]
// Current level
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f, 1.0f, 2.0f, 1.0f)
[
SNew(STextBlock)
.Text(this, &SLevelViewport::GetCurrentLevelText, false)
.Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font")))
.ColorAndOpacity(FLinearColor(0.4f, 1.0f, 1.0f))
.ShadowOffset(FVector2D(1, 1))
]
]
];
// Add highres screenshot region capture editing widget
ViewportOverlay->AddSlot(SlotIndex)
.VAlign( VAlign_Fill )
.HAlign( HAlign_Fill )
.Padding( 0 )
[
SAssignNew(CaptureRegionWidget, SCaptureRegionWidget)
];
}
void SLevelViewport::ConstructLevelEditorViewportClient( const FArguments& InArgs )
{
if (InArgs._LevelEditorViewportClient.IsValid())
{
LevelViewportClient = InArgs._LevelEditorViewportClient;
}
else
{
LevelViewportClient = MakeShareable( new FLevelEditorViewportClient(SharedThis(this)) );
}
// Default level viewport client values for settings that could appear in layout config ini
FLevelEditorViewportInstanceSettings ViewportInstanceSettings;
ViewportInstanceSettings.ViewportType = InArgs._ViewportType;
ViewportInstanceSettings.PerspViewModeIndex = VMI_Lit;
ViewportInstanceSettings.OrthoViewModeIndex = VMI_BrushWireframe;
ViewportInstanceSettings.bIsRealtime = InArgs._Realtime;
FEngineShowFlags EditorShowFlags(ESFIM_Editor);
FEngineShowFlags GameShowFlags(ESFIM_Game);
// Use config key if it exists to set up the level viewport client
if(!ConfigKey.IsEmpty())
{
const FLevelEditorViewportInstanceSettings* const ViewportInstanceSettingsPtr = GetDefault<ULevelEditorViewportSettings>()->GetViewportInstanceSettings(ConfigKey);
ViewportInstanceSettings = (ViewportInstanceSettingsPtr) ? *ViewportInstanceSettingsPtr : LoadLegacyConfigFromIni(ConfigKey, ViewportInstanceSettings);
if(!ViewportInstanceSettings.EditorShowFlagsString.IsEmpty())
{
EditorShowFlags.SetFromString(*ViewportInstanceSettings.EditorShowFlagsString);
}
if(!ViewportInstanceSettings.GameShowFlagsString.IsEmpty())
{
GameShowFlags.SetFromString(*ViewportInstanceSettings.GameShowFlagsString);
}
if(!GetBufferVisualizationData().GetMaterial(ViewportInstanceSettings.BufferVisualizationMode))
{
ViewportInstanceSettings.BufferVisualizationMode = NAME_None;
}
}
if(ViewportInstanceSettings.ViewportType == LVT_Perspective)
{
ApplyViewMode(ViewportInstanceSettings.PerspViewModeIndex, true, EditorShowFlags);
ApplyViewMode(ViewportInstanceSettings.PerspViewModeIndex, true, GameShowFlags);
}
else
{
ApplyViewMode(ViewportInstanceSettings.OrthoViewModeIndex, false, EditorShowFlags);
ApplyViewMode(ViewportInstanceSettings.OrthoViewModeIndex, false, GameShowFlags);
}
// Disabling some features for orthographic views.
if(ViewportInstanceSettings.ViewportType != LVT_Perspective)
{
EditorShowFlags.MotionBlur = 0;
EditorShowFlags.Fog = 0;
EditorShowFlags.SetDepthOfField(false);
GameShowFlags.MotionBlur = 0;
GameShowFlags.Fog = 0;
GameShowFlags.SetDepthOfField(false);
}
EditorShowFlags.SetSnap(1);
GameShowFlags.SetSnap(1);
// Create level viewport client
LevelViewportClient->ParentLevelEditor = ParentLevelEditor.Pin();
LevelViewportClient->ViewportType = ViewportInstanceSettings.ViewportType;
LevelViewportClient->bSetListenerPosition = false;
LevelViewportClient->EngineShowFlags = EditorShowFlags;
LevelViewportClient->LastEngineShowFlags = GameShowFlags;
LevelViewportClient->CurrentBufferVisualizationMode = ViewportInstanceSettings.BufferVisualizationMode;
LevelViewportClient->ExposureSettings = ViewportInstanceSettings.ExposureSettings;
if(InArgs._ViewportType == LVT_Perspective)
{
LevelViewportClient->SetViewLocation( EditorViewportDefs::DefaultPerspectiveViewLocation );
LevelViewportClient->SetViewRotation( EditorViewportDefs::DefaultPerspectiveViewRotation );
LevelViewportClient->SetAllowCinematicPreview(true);
}
LevelViewportClient->SetRealtime(ViewportInstanceSettings.bIsRealtime);
LevelViewportClient->SetShowStats(ViewportInstanceSettings.bShowOnScreenStats);
if (ViewportInstanceSettings.bShowFPS_DEPRECATED)
{
GetMutableDefault<ULevelEditorViewportSettings>()->bSaveEngineStats = true;
ViewportInstanceSettings.EnabledStats.AddUnique(TEXT("FPS"));
}
if (GetDefault<ULevelEditorViewportSettings>()->bSaveEngineStats)
{
GEngine->SetEngineStats(GetWorld(), LevelViewportClient.Get(), ViewportInstanceSettings.EnabledStats, true);
}
LevelViewportClient->VisibilityDelegate.BindSP( this, &SLevelViewport::IsVisible );
LevelViewportClient->ImmersiveDelegate.BindSP( this, &SLevelViewport::IsImmersive );
LevelViewportClient->bDrawBaseInfo = true;
LevelViewportClient->bDrawVertices = true;
LevelViewportClient->ViewFOV = LevelViewportClient->FOVAngle = ViewportInstanceSettings.FOVAngle;
LevelViewportClient->OverrideFarClipPlane( ViewportInstanceSettings.FarViewPlane );
// Set the selection outline flag based on preferences
LevelViewportClient->EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline);
// Always composite editor objects after post processing in the editor
LevelViewportClient->EngineShowFlags.SetCompositeEditorPrimitives(true);
LevelViewportClient->SetViewModes(ViewportInstanceSettings.PerspViewModeIndex, ViewportInstanceSettings.OrthoViewModeIndex );
bShowFullToolbar = ViewportInstanceSettings.bShowFullToolbar;
}
const FSceneViewport* SLevelViewport::GetGameSceneViewport() const
{
return ActiveViewport.Get();
}
FReply SLevelViewport::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
{
FReply Reply = FReply::Unhandled();
if( HasPlayInEditorViewport() || LevelViewportClient->IsSimulateInEditorViewport() )
{
// Only process commands for pie when a play world is active
FPlayWorldCommands::GlobalPlayWorldActions->ProcessCommandBindings( InKeyEvent );
// Always handle commands in pie so they arent bubbled to editor only widgets
Reply = FReply::Handled();
}
if( !IsPlayInEditorViewportActive() )
{
Reply = SEditorViewport::OnKeyDown(MyGeometry,InKeyEvent);
// If we are in immersive mode and the event was not handled, we will check to see if the the
// optional parent level editor is set. If it is, we give it a chance to handle the key event.
// This command forwarding is currently only needed when in immersive mode because in that case
// the SLevelEditor is not a direct parent of the viewport.
if ( this->IsImmersive() && !Reply.IsEventHandled() )
{
TSharedPtr<ILevelEditor> ParentLevelEditorSharedPtr = ParentLevelEditor.Pin();
if( ParentLevelEditorSharedPtr.IsValid() )
{
Reply = ParentLevelEditorSharedPtr->OnKeyDownInViewport( MyGeometry, InKeyEvent );
}
}
}
return Reply;
}
void SLevelViewport::OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
// Prevent OnDragEnter from reentering because it will affect the drop preview placement and management.
// This may happen currently if an unloaded class is dragged from the class viewer and a slow task is triggered,
// which re-ticks slate and triggers another mouse move.
static bool bDragEnterReentranceGuard = false;
if ( !bDragEnterReentranceGuard )
{
bDragEnterReentranceGuard = true;
// Don't execute the dragdrop op if the current level is locked.
// This prevents duplicate warning messages firing on DragEnter and Placement.
ULevel* CurrentLevel = (GetWorld()) ? GetWorld()->GetCurrentLevel() : NULL;
if ( CurrentLevel && !FLevelUtils::IsLevelLocked(CurrentLevel) )
{
if ( HandleDragObjects(MyGeometry, DragDropEvent) )
{
if ( HandlePlaceDraggedObjects(MyGeometry, DragDropEvent, /*bCreateDropPreview=*/true) )
{
DragDropEvent.GetOperation()->SetDecoratorVisibility(false);
}
}
}
bDragEnterReentranceGuard = false;
}
}
void SLevelViewport::OnDragLeave( const FDragDropEvent& DragDropEvent )
{
if ( LevelViewportClient->HasDropPreviewActors() )
{
LevelViewportClient->DestroyDropPreviewActors();
}
if (DragDropEvent.GetOperation().IsValid())
{
DragDropEvent.GetOperation()->SetDecoratorVisibility(true);
}
}
bool SLevelViewport::HandleDragObjects(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
{
bool bValidDrag = false;
TArray<FAssetData> SelectedAssetDatas;
TSharedPtr< FDragDropOperation > Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return false;
}
if (Operation->IsOfType<FClassDragDropOp>())
{
auto ClassOperation = StaticCastSharedPtr<FClassDragDropOp>( Operation );
bValidDrag = true;
for (int32 DroppedAssetIdx = 0; DroppedAssetIdx < ClassOperation->ClassesToDrop.Num(); ++DroppedAssetIdx)
{
new(SelectedAssetDatas)FAssetData(ClassOperation->ClassesToDrop[DroppedAssetIdx].Get());
}
}
else if (Operation->IsOfType<FUnloadedClassDragDropOp>())
{
bValidDrag = true;
}
else if (Operation->IsOfType<FExportTextDragDropOp>())
{
bValidDrag = true;
}
else if (Operation->IsOfType<FBrushBuilderDragDropOp>())
{
bValidDrag = true;
auto BrushOperation = StaticCastSharedPtr<FBrushBuilderDragDropOp>( Operation );
new(SelectedAssetDatas) FAssetData(BrushOperation->GetBrushBuilder().Get());
}
else
{
SelectedAssetDatas = AssetUtil::ExtractAssetDataFromDrag( DragDropEvent );
if ( SelectedAssetDatas.Num() > 0 )
{
bValidDrag = true;
}
}
// Update cached mouse position
if ( bValidDrag )
{
// Grab viewport to offset click position correctly
FIntPoint ViewportOrigin, ViewportSize;
LevelViewportClient->GetViewportDimensions(ViewportOrigin, ViewportSize);
// Save off the local mouse position from the drop point for potential use later (with Drag Drop context menu)
CachedOnDropLocalMousePos = MyGeometry.AbsoluteToLocal( DragDropEvent.GetScreenSpacePosition() );
CachedOnDropLocalMousePos.X -= ViewportOrigin.X;
CachedOnDropLocalMousePos.Y -= ViewportOrigin.Y;
}
// Update the currently dragged actor if it exists
bool bDroppedObjectsVisible = true;
if (LevelViewportClient->UpdateDropPreviewActors(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y, DroppedObjects, bDroppedObjectsVisible))
{
// if dragged actors were hidden, show decorator
Operation->SetDecoratorVisibility(! bDroppedObjectsVisible);
}
Operation->SetCursorOverride(TOptional<EMouseCursor::Type>());
FText HintText;
// Determine if we can drop the assets
for ( auto InfoIt = SelectedAssetDatas.CreateConstIterator(); InfoIt; ++InfoIt )
{
const FAssetData& AssetData = *InfoIt;
// Ignore invalid assets
if ( !AssetData.IsValid() )
{
continue;
}
FDropQuery DropResult = LevelViewportClient->CanDropObjectsAtCoordinates(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y, AssetData);
if ( !DropResult.bCanDrop )
{
// At least one of the assets can't be dropped.
Operation->SetCursorOverride(EMouseCursor::SlashedCircle);
return false;
}
else
{
if ( HintText.IsEmpty() )
{
HintText = DropResult.HintText;
}
}
}
if ( Operation->IsOfType<FAssetDragDropOp>() )
{
auto AssetOperation = StaticCastSharedPtr<FAssetDragDropOp>(DragDropEvent.GetOperation());
AssetOperation->SetToolTip(HintText, NULL);
}
return bValidDrag;
}
FReply SLevelViewport::OnDragOver( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
if ( HandleDragObjects(MyGeometry, DragDropEvent) )
{
return FReply::Handled();
}
return FReply::Unhandled();
}
bool SLevelViewport::HandlePlaceDraggedObjects(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent, bool bCreateDropPreview)
{
bool bAllAssetWereLoaded = false;
bool bValidDrop = false;
UActorFactory* ActorFactory = NULL;
TSharedPtr< FDragDropOperation > Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return false;
}
// Don't handle the placement if we couldn't handle the drag
if (!HandleDragObjects(MyGeometry, DragDropEvent))
{
return false;
}
if (Operation->IsOfType<FClassDragDropOp>())
{
auto ClassOperation = StaticCastSharedPtr<FClassDragDropOp>( Operation );
DroppedObjects.Empty();
// Check if the asset is loaded, used to see if the context menu should be available
bAllAssetWereLoaded = true;
for (int32 DroppedAssetIdx = 0; DroppedAssetIdx < ClassOperation->ClassesToDrop.Num(); ++DroppedAssetIdx)
{
UObject* Object = ClassOperation->ClassesToDrop[DroppedAssetIdx].Get();
if(Object)
{
DroppedObjects.Add(Object);
}
else
{
bAllAssetWereLoaded = false;
}
}
bValidDrop = true;
}
else if (Operation->IsOfType<FUnloadedClassDragDropOp>())
{
TSharedPtr<FUnloadedClassDragDropOp> DragDropOp = StaticCastSharedPtr<FUnloadedClassDragDropOp>( Operation );
DroppedObjects.Empty();
// Check if the asset is loaded, used to see if the context menu should be available
bAllAssetWereLoaded = true;
TArray< FClassPackageData >& AssetArray = *(DragDropOp->AssetsToDrop.Get());
for (int32 DroppedAssetIdx = 0; DroppedAssetIdx < AssetArray.Num(); ++DroppedAssetIdx)
{
bValidDrop = true;
FString& AssetName = AssetArray[DroppedAssetIdx].AssetName;
// Check to see if the asset can be found, otherwise load it.
UObject* Object = FindObject<UObject>(NULL, *AssetName);
if(Object == NULL)
{
// Check to see if the dropped asset was a blueprint
const FString& PackageName = AssetArray[DroppedAssetIdx].GeneratedPackageName;
Object = FindObject<UObject>(NULL, *FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName));
if ( Object == NULL )
{
// Load the package.
GWarn->BeginSlowTask( LOCTEXT("OnDrop_FullyLoadPackage", "Fully Loading Package For Drop"), true, false );
UPackage* Package = LoadPackage(NULL, *PackageName, LOAD_NoRedirects );
if (Package)
{
Package->FullyLoad();
}
GWarn->EndSlowTask();
Object = FindObject<UObject>(Package, *AssetName);
}
}
// Check again if it has been loaded, if not, mark that all were not loaded and move on.
if(Object)
{
DroppedObjects.Add(Object);
}
else
{
bAllAssetWereLoaded = false;
}
}
}
else if (Operation->IsOfType<FAssetDragDropOp>())
{
bValidDrop = true;
DroppedObjects.Empty();
TSharedPtr<FAssetDragDropOp> DragDropOp = StaticCastSharedPtr<FAssetDragDropOp>( Operation );
ActorFactory = DragDropOp->ActorFactory.Get();
bAllAssetWereLoaded = true;
for (int32 AssetIdx = 0; AssetIdx < DragDropOp->AssetData.Num(); ++AssetIdx)
{
const FAssetData& AssetData = DragDropOp->AssetData[AssetIdx];
UObject* Asset = AssetData.GetAsset();
if ( Asset != NULL )
{
DroppedObjects.Add( Asset );
}
else
{
bAllAssetWereLoaded = false;
}
}
}
// OLE drops are blocking which causes problem when positioning and maintaining the drop preview
// Drop preview is disabled when dragging from external sources
else if ( !bCreateDropPreview && Operation->IsOfType<FExternalDragOperation>() )
{
bValidDrop = true;
DroppedObjects.Empty();
TArray<FAssetData> DroppedAssetDatas = AssetUtil::ExtractAssetDataFromDrag(DragDropEvent);
bAllAssetWereLoaded = true;
for (int32 AssetIdx = 0; AssetIdx < DroppedAssetDatas.Num(); ++AssetIdx)
{
const FAssetData& AssetData = DroppedAssetDatas[AssetIdx];
UObject* Asset = AssetData.GetAsset();
if ( Asset != NULL )
{
DroppedObjects.Add( Asset );
}
else
{
bAllAssetWereLoaded = false;
}
}
}
else if ( Operation->IsOfType<FExportTextDragDropOp>() )
{
bValidDrop = true;
TSharedPtr<FExportTextDragDropOp> DragDropOp = StaticCastSharedPtr<FExportTextDragDropOp>( Operation );
// Check if the asset is loaded, used to see if the context menu should be available
bAllAssetWereLoaded = true;
DroppedObjects.Empty();
// Create a container object to hold the export text and pass it into the actor placement code
UExportTextContainer* NewContainer = NewObject<UExportTextContainer>();
NewContainer->ExportText = DragDropOp->ActorExportText;
DroppedObjects.Add(NewContainer);
}
else if ( Operation->IsOfType<FBrushBuilderDragDropOp>() )
{
bValidDrop = true;
DroppedObjects.Empty();
TSharedPtr<FBrushBuilderDragDropOp> DragDropOp = StaticCastSharedPtr<FBrushBuilderDragDropOp>( Operation );
if(DragDropOp->GetBrushBuilder().IsValid())
{
DroppedObjects.Add(DragDropOp->GetBrushBuilder().Get());
}
}
if ( bValidDrop )
{
// Grab the hit proxy, used for the (potential) context menu
HHitProxy* HitProxy = LevelViewportClient->Viewport->GetHitProxy(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y);
// If Ctrl is down, pop in the context menu
const bool bShowDropContextMenu = !bCreateDropPreview && DragDropEvent.IsControlDown() && ( !HitProxy || !( HitProxy->IsA( HWidgetAxis::StaticGetType() ) ) );
bool bDropSuccessful = false;
// Make sure the drop preview is destroyed
LevelViewportClient->DestroyDropPreviewActors();
if( !bShowDropContextMenu || !bCreateDropPreview )
{
// Otherwise just attempt to drop the object(s)
TArray< AActor* > TemporaryActors;
// Only select actor on drop
const bool SelectActor = !bCreateDropPreview;
bDropSuccessful = LevelViewportClient->DropObjectsAtCoordinates(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y, DroppedObjects, TemporaryActors, false, bCreateDropPreview, SelectActor, ActorFactory);
}
else if ( bAllAssetWereLoaded && DroppedObjects.Num() > 0 )
{
FWidgetPath WidgetPath = DragDropEvent.GetEventPath() != nullptr ? *DragDropEvent.GetEventPath() : FWidgetPath();
FSlateApplication::Get().PushMenu(
SharedThis( this ),
WidgetPath,
BuildViewportDragDropContextMenu(),
DragDropEvent.GetScreenSpacePosition(),
FPopupTransitionEffect( FPopupTransitionEffect::ContextMenu ) );
bDropSuccessful = true;
}
// Give the editor focus (quick Undo/Redo support after a drag drop operation)
if(ParentLevelEditor.IsValid())
{
FGlobalTabmanager::Get()->DrawAttentionToTabManager(ParentLevelEditor.Pin()->GetTabManager().ToSharedRef());
}
if(bDropSuccessful)
{
SetKeyboardFocusToThisViewport();
}
return bDropSuccessful;
}
return false;
}
FReply SLevelViewport::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
ULevel* CurrentLevel = (GetWorld()) ? GetWorld()->GetCurrentLevel() : NULL;
if (CurrentLevel && !FLevelUtils::IsLevelLocked(CurrentLevel))
{
return HandlePlaceDraggedObjects(MyGeometry, DragDropEvent, /*bCreateDropPreview=*/false) ? FReply::Handled() : FReply::Unhandled();
}
else
{
FNotificationInfo Info(LOCTEXT("Error_OperationDisallowedOnLockedLevel", "The requested operation could not be completed because the level is locked."));
Info.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(Info);
return FReply::Handled();
}
}
void SLevelViewport::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
SEditorViewport::Tick( AllottedGeometry, InCurrentTime, InDeltaTime );
const bool bContainsFocus = HasFocusedDescendants();
// When we have focus we update the 'Allow Throttling' option in slate to be disabled so that interactions in the
// viewport with Slate widgets that are part of the game, don't throttle.
if ( GEditor->PlayWorld != nullptr && bPIEContainsFocus != bContainsFocus )
{
// We can arrive at this point before creating throttling manager (which registers the cvar), so create it explicitly.
static const FSlateThrottleManager & ThrottleManager = FSlateThrottleManager::Get();
static IConsoleVariable* AllowThrottling = IConsoleManager::Get().FindConsoleVariable(TEXT("Slate.bAllowThrottling"));
check(AllowThrottling);
if ( bContainsFocus )
{
UserAllowThrottlingValue = AllowThrottling->GetInt();
AllowThrottling->Set(0);
}
else
{
AllowThrottling->Set(UserAllowThrottlingValue);
}
bPIEContainsFocus = bContainsFocus;
}
// We defer starting animation playback because very often there may be a large hitch after the frame in which
// the animation was triggered, and we don't want to start animating until after that hitch. Otherwise, the
// user could miss part of the animation, or even the whole thing!
if( bViewTransitionAnimPending )
{
ViewTransitionAnim.Play(this->AsShared());
bViewTransitionAnimPending = false;
}
// If we've completed a transition, then start animating back to our regular border. We
// do this so that we can avoid a popping artifact after PIE/SIE ends.
if( !ViewTransitionAnim.IsPlaying() && ViewTransitionType != EViewTransition::None )
{
if(ViewTransitionType == EViewTransition::StartingPlayInEditor)
{
if(PIEOverlaySlotIndex)
{
PIEOverlayAnim = FCurveSequence(0.0f, SLevelViewportPIEAnimation::MouseControlLabelFadeout, ECurveEaseFunction::CubicInOut);
PIEOverlayAnim.Play(this->AsShared());
}
}
ViewTransitionType = EViewTransition::None;
ViewTransitionAnim = FCurveSequence( 0.0f, 0.25f, ECurveEaseFunction::QuadOut );
ViewTransitionAnim.PlayReverse(this->AsShared());
}
if(IsPlayInEditorViewportActive() && bPIEHasFocus != ActiveViewport->HasMouseCapture())
{
bPIEHasFocus = ActiveViewport->HasMouseCapture();
PIEOverlayAnim = FCurveSequence(0.0f, SLevelViewportPIEAnimation::MouseControlLabelFadeout, ECurveEaseFunction::CubicInOut);
PIEOverlayAnim.Play(this->AsShared());
}
// Update actor preview viewports, if we have any
UpdateActorPreviewViewports();
#if STATS
// Check to see if there are any new stat group which need registering with the viewports
extern CORE_API void CheckForRegisteredStatGroups();
CheckForRegisteredStatGroups();
#endif
}
TSharedRef< SWidget > SLevelViewport::BuildViewportDragDropContextMenu()
{
// Get all menu extenders for this context menu from the level editor module
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
TArray<FLevelEditorModule::FLevelViewportMenuExtender_SelectedObjects> MenuExtenderDelegates = LevelEditorModule.GetAllLevelViewportDragDropContextMenuExtenders();
TArray<TSharedPtr<FExtender>> Extenders;
for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i)
{
if (MenuExtenderDelegates[i].IsBound())
{
Extenders.Add(MenuExtenderDelegates[i].Execute(CommandList.ToSharedRef(), DroppedObjects));
}
}
TSharedPtr<FExtender> MenuExtender = FExtender::Combine(Extenders);
// Builds a context menu used to perform specific actions on actors selected within the editor
const bool bShouldCloseWindowAfterMenuSelection = true;
FMenuBuilder ViewportContextMenuBuilder( bShouldCloseWindowAfterMenuSelection, CommandList, MenuExtender );
{
FLevelViewportDropContextMenuImpl::FillDropAddReplaceActorMenu( false, ViewportContextMenuBuilder );
// If any actors are in the current editor selection, add submenu for swapping out those actors with an asset from the chosen factory
if( GEditor->GetSelectedActorCount() > 0 && !AssetSelectionUtils::IsBuilderBrushSelected() )
{
FLevelViewportDropContextMenuImpl::FillDropAddReplaceActorMenu( true, ViewportContextMenuBuilder );
}
if(DroppedObjects.Num() > 0)
{
// Grab the hit proxy, used for determining which object we're potentially targeting
const HHitProxy* DroppedUponProxy = LevelViewportClient->Viewport->GetHitProxy(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y);
UObject* FirstDroppedObject = DroppedObjects[0];
// If we're using a material asset, check if the apply material option(s) should be added
if(DroppedUponProxy && Cast<UMaterialInterface>(FirstDroppedObject) && LevelViewportClient->CanApplyMaterialToHitProxy(DroppedUponProxy))
{
ViewportContextMenuBuilder.BeginSection("ApplyMaterial");
{
ViewportContextMenuBuilder.AddMenuEntry( FLevelViewportCommands::Get().ApplyMaterialToActor );
}
ViewportContextMenuBuilder.EndSection();
}
}
}
return ViewportContextMenuBuilder.MakeWidget();
}
void SLevelViewport::OnMapChanged( UWorld* World, EMapChangeType MapChangeType )
{
if( World && ( ( World == GetWorld() ) || ( World->EditorViews[LevelViewportClient->ViewportType].CamUpdated ) ) )
{
if( MapChangeType == EMapChangeType::LoadMap )
{
if (World->EditorViews[LevelViewportClient->ViewportType].CamOrthoZoom == 0.0f)
{
World->EditorViews[LevelViewportClient->ViewportType].CamOrthoZoom = DEFAULT_ORTHOZOOM;
}
ResetNewLevelViewFlags();
LevelViewportClient->ResetCamera();
bool bInitializedOrthoViewport = false;
for (int32 ViewportType = 0; ViewportType < LVT_MAX; ViewportType++)
{
if (ViewportType == LVT_Perspective || !bInitializedOrthoViewport)
{
LevelViewportClient->SetInitialViewTransform(
static_cast<ELevelViewportType>(ViewportType),
World->EditorViews[ViewportType].CamPosition,
World->EditorViews[ViewportType].CamRotation,
World->EditorViews[ViewportType].CamOrthoZoom);
if (ViewportType != LVT_Perspective)
{
bInitializedOrthoViewport = true;
}
}
}
}
else if( MapChangeType == EMapChangeType::SaveMap )
{
//@todo there could potentially be more than one of the same viewport type. This effectively takes the last one of a specific type
World->EditorViews[LevelViewportClient->ViewportType] =
FLevelViewportInfo(
LevelViewportClient->GetViewLocation(),
LevelViewportClient->GetViewRotation(),
LevelViewportClient->GetOrthoZoom() );
}
else if( MapChangeType == EMapChangeType::NewMap )
{
ResetNewLevelViewFlags();
LevelViewportClient->ResetViewForNewMap();
}
World->EditorViews[LevelViewportClient->ViewportType].CamUpdated = false;
RedrawViewport(true);
}
}
void SLevelViewport::OnLevelActorsRemoved(AActor* InActor)
{
// Kill any existing actor previews that have expired
for( int32 PreviewIndex = 0; PreviewIndex < ActorPreviews.Num(); ++PreviewIndex )
{
AActor* ExistingActor = ActorPreviews[PreviewIndex].Actor.Get();
if ( !ExistingActor || ExistingActor == InActor )
{
// decrement index so we don't miss next preview after deleting
RemoveActorPreview( PreviewIndex-- );
}
}
}
void FLevelViewportDropContextMenuImpl::FillDropAddReplaceActorMenu( bool bReplace, FMenuBuilder& MenuBuilder )
{
// Builds a submenu for the Drag Drop context menu used to replace all actors in the current editor selection with a different asset
TArray<FAssetData> SelectedAssets;
AssetSelectionUtils::GetSelectedAssets( SelectedAssets );
FAssetData TargetAssetData;
if ( SelectedAssets.Num() > 0 )
{
TargetAssetData = SelectedAssets.Top();
}
TArray< FActorFactoryAssetProxy::FMenuItem > SelectedAssetMenuOptions;
FActorFactoryAssetProxy::GenerateActorFactoryMenuItems( TargetAssetData, &SelectedAssetMenuOptions, false );
if(SelectedAssetMenuOptions.Num() > 0)
{
FText AddReplaceTitle = (bReplace)? FText::GetEmpty() : LOCTEXT("DragDropContext_AddAsType", "Add As Type");
MenuBuilder.BeginSection("AddReplace", AddReplaceTitle);
{
for( int32 ItemIndex = 0; ItemIndex < SelectedAssetMenuOptions.Num(); ++ItemIndex )
{
const FActorFactoryAssetProxy::FMenuItem& MenuItem = SelectedAssetMenuOptions[ItemIndex];
if ( bReplace )
{
FUIAction Action( FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::ReplaceActors_Clicked, MenuItem.FactoryToUse, MenuItem.AssetData ) );
FText MenuEntryName = FText::Format( NSLOCTEXT("LevelEditor", "ReplaceActorMenuFormat", "Replace with {0}"), MenuItem.FactoryToUse->GetDisplayName() );
if ( MenuItem.AssetData.IsValid() )
{
MenuEntryName = FText::Format( NSLOCTEXT("LevelEditor", "ReplaceActorUsingAssetMenuFormat", "Replace with {0}: {1}"),
MenuItem.FactoryToUse->GetDisplayName(),
FText::FromName( MenuItem.AssetData.AssetName ) );
}
}
else
{
FUIAction Action( FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::AddActor_Clicked, MenuItem.FactoryToUse, MenuItem.AssetData, false ) );
FText MenuEntryName = FText::Format( NSLOCTEXT("SLevelViewport", "AddActorMenuFormat", "Add {0}"), MenuItem.FactoryToUse->GetDisplayName() );
if ( MenuItem.AssetData.IsValid() )
{
MenuEntryName = FText::Format( NSLOCTEXT("SLevelViewport", "AddActorUsingAssetMenuFormat", "Add {0}: {1}"),
MenuItem.FactoryToUse->GetDisplayName(),
FText::FromName( MenuItem.AssetData.AssetName ) );
}
}
}
}
MenuBuilder.EndSection();
}
}
/**
* Bound event Triggered via FLevelViewportCommands::ApplyMaterialToActor, attempts to apply a material selected in the content browser
* to an actor being hovered over in the Editor viewport.
*/
void SLevelViewport::OnApplyMaterialToViewportTarget()
{
if(DroppedObjects.Num() > 0)
{
// Grab the hit proxy, used for determining which object we're potentially targeting
const HHitProxy* DroppedUponProxy = LevelViewportClient->Viewport->GetHitProxy(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y);
UObject* FirstDroppedObject = DroppedObjects[0];
// Ensure we're dropping a material asset and our target is an acceptable receiver
if(DroppedUponProxy && Cast<UMaterialInterface>(FirstDroppedObject) && LevelViewportClient->CanApplyMaterialToHitProxy(DroppedUponProxy))
{
// Drop the object, but ensure we're only affecting the target actor, not whatever may be in the current selection
TArray< AActor* > TemporaryActors;
LevelViewportClient->DropObjectsAtCoordinates(CachedOnDropLocalMousePos.X,CachedOnDropLocalMousePos.Y, DroppedObjects, TemporaryActors, true);
}
}
}
void SLevelViewport::BindCommands()
{
SEditorViewport::BindCommands();
FUICommandList& UICommandListRef = *CommandList;
BindOptionCommands( UICommandListRef );
BindViewCommands( UICommandListRef );
BindShowCommands( UICommandListRef );
BindDropCommands( UICommandListRef );
if ( ParentLevelEditor.IsValid() )
{
UICommandListRef.Append(ParentLevelEditor.Pin()->GetLevelEditorActions().ToSharedRef());
}
UICommandListRef.SetCanProduceActionForCommand( FUICommandList::FCanProduceActionForCommand::CreateSP(this, &SLevelViewport::CanProduceActionForCommand) );
}
void SLevelViewport::BindOptionCommands( FUICommandList& OutCommandList )
{
const FLevelViewportCommands& ViewportActions = FLevelViewportCommands::Get();
OutCommandList.MapAction(
ViewportActions.AdvancedSettings,
FExecuteAction::CreateSP( this, &SLevelViewport::OnAdvancedSettings ) );
OutCommandList.MapAction(
ViewportActions.ToggleMaximize,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleMaximizeMode ),
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanToggleMaximizeMode ) );
OutCommandList.MapAction(
ViewportActions.ToggleGameView,
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleGameView ),
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanToggleGameView ),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsInGameView ) );
OutCommandList.MapAction(
ViewportActions.ToggleImmersive,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleImmersive),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsImmersive ) );
OutCommandList.MapAction(
ViewportActions.ToggleCinematicPreview,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllowCinematicPreview ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::AllowsCinematicPreview )
);
OutCommandList.MapAction(
ViewportActions.CreateCamera,
FExecuteAction::CreateSP( this, &SLevelViewport::OnCreateCameraActor ),
FCanExecuteAction(),
FCanExecuteAction::CreateSP( this, &SLevelViewport::IsPerspectiveViewport )
);
OutCommandList.MapAction(
ViewportActions.HighResScreenshot,
FExecuteAction::CreateSP( this, &SLevelViewport::OnTakeHighResScreenshot ),
FCanExecuteAction()
);
OutCommandList.MapAction(
ViewportActions.ToggleActorPilotCameraView,
FExecuteAction::CreateSP(this, &SLevelViewport::ToggleActorPilotCameraView),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsLockedCameraViewEnabled )
);
// Map each bookmark action
for( int32 BookmarkIndex = 0; BookmarkIndex < AWorldSettings::MAX_BOOKMARK_NUMBER; ++BookmarkIndex )
{
OutCommandList.MapAction(
ViewportActions.JumpToBookmarkCommands[BookmarkIndex],
FExecuteAction::CreateSP( this, &SLevelViewport::OnJumpToBookmark, BookmarkIndex )
);
OutCommandList.MapAction(
ViewportActions.SetBookmarkCommands[BookmarkIndex],
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetBookmark, BookmarkIndex )
);
OutCommandList.MapAction(
ViewportActions.ClearBookmarkCommands[BookmarkIndex],
FExecuteAction::CreateSP( this, &SLevelViewport::OnClearBookMark, BookmarkIndex )
);
}
OutCommandList.MapAction(
ViewportActions.ClearAllBookMarks,
FExecuteAction::CreateSP( this, &SLevelViewport::OnClearAllBookMarks )
);
OutCommandList.MapAction(
ViewportActions.ToggleViewportToolbar,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleShowFullToolbar ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::ShouldShowFullToolbar )
);
}
void SLevelViewport::BindViewCommands( FUICommandList& OutCommandList )
{
const FLevelViewportCommands& ViewportActions = FLevelViewportCommands::Get();
OutCommandList.MapAction(
ViewportActions.FindInLevelScriptBlueprint,
FExecuteAction::CreateSP( this, &SLevelViewport::FindSelectedInLevelScript ),
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanFindSelectedInLevelScript )
);
OutCommandList.MapAction(
ViewportActions.EjectActorPilot,
FExecuteAction::CreateSP( this, &SLevelViewport::OnActorUnlock ),
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanExecuteActorUnlock )
);
OutCommandList.MapAction(
ViewportActions.PilotSelectedActor,
FExecuteAction::CreateSP( this, &SLevelViewport::OnActorLockSelected ),
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanExecuteActorLockSelected )
);
OutCommandList.MapAction(
ViewportActions.ViewportConfig_OnePane,
FExecuteAction::CreateSP(this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::OnePane),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::OnePane));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_TwoPanesH,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::TwoPanesHoriz ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::TwoPanesHoriz ));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_TwoPanesV,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::TwoPanesVert ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::TwoPanesVert ));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_ThreePanesLeft,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::ThreePanesLeft ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::ThreePanesLeft ));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_ThreePanesRight,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::ThreePanesRight ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::ThreePanesRight ));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_ThreePanesTop,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::ThreePanesTop ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::ThreePanesTop ));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_ThreePanesBottom,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::ThreePanesBottom ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::ThreePanesBottom ));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_FourPanesLeft,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::FourPanesLeft ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::FourPanesLeft ));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_FourPanesRight,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::FourPanesRight ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::FourPanesRight ));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_FourPanesTop,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::FourPanesTop ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::FourPanesTop ));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_FourPanesBottom,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::FourPanesBottom ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::FourPanesBottom ));
OutCommandList.MapAction(
ViewportActions.ViewportConfig_FourPanes2x2,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::FourPanes2x2 ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::FourPanes2x2 ));
auto ProcessViewportTypeActions = [&](FName InViewportTypeName, const FViewportTypeDefinition& InDefinition){
if (InDefinition.ActivationCommand.IsValid())
{
OutCommandList.MapAction(InDefinition.ActivationCommand, FUIAction(
FExecuteAction::CreateSP(this, &SLevelViewport::ToggleViewportTypeActivationWithinLayout, InViewportTypeName),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SLevelViewport::IsViewportTypeWithinLayoutEqual, InViewportTypeName)
));
}
};
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
LevelEditorModule.IterateViewportTypes(ProcessViewportTypeActions);
// Map Buffer visualization mode actions
for (FLevelViewportCommands::TBufferVisualizationModeCommandMap::TConstIterator It = ViewportActions.BufferVisualizationModeCommands.CreateConstIterator(); It; ++It)
{
const FLevelViewportCommands::FBufferVisualizationRecord& Record = It.Value();
OutCommandList.MapAction(
Record.Command,
FExecuteAction::CreateSP( this, &SLevelViewport::ChangeBufferVisualizationMode, Record.Name ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsBufferVisualizationModeSelected, Record.Name ) );
}
}
void SLevelViewport::BindShowCommands( FUICommandList& OutCommandList )
{
OutCommandList.MapAction(
FLevelViewportCommands::Get().UseDefaultShowFlags,
FExecuteAction::CreateSP( this, &SLevelViewport::OnUseDefaultShowFlags, false ) );
const TArray<FShowFlagData>& ShowFlagData = GetShowFlagMenuItems();
// Bind each show flag to the same delegate. We use the delegate payload system to figure out what show flag we are dealing with
for( int32 ShowFlag = 0; ShowFlag < ShowFlagData.Num(); ++ShowFlag )
{
const FShowFlagData& SFData = ShowFlagData[ShowFlag];
// NOTE: There should be one command per show flag so using ShowFlag as the index to ShowFlagCommands is acceptable
OutCommandList.MapAction(
FLevelViewportCommands::Get().ShowFlagCommands[ ShowFlag ].ShowMenuItem,
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleShowFlag, SFData.EngineShowFlagIndex ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsShowFlagEnabled, SFData.EngineShowFlagIndex ) );
}
// Show Volumes
{
// Map 'Show All' and 'Hide All' commands
OutCommandList.MapAction(
FLevelViewportCommands::Get().ShowAllVolumes,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllVolumeActors, true ) );
OutCommandList.MapAction(
FLevelViewportCommands::Get().HideAllVolumes,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllVolumeActors, false ) );
// Get all known volume classes
TArray< UClass* > VolumeClasses;
UUnrealEdEngine::GetSortedVolumeClasses(&VolumeClasses);
for( int32 VolumeClassIndex = 0; VolumeClassIndex < VolumeClasses.Num(); ++VolumeClassIndex )
{
OutCommandList.MapAction(
FLevelViewportCommands::Get().ShowVolumeCommands[ VolumeClassIndex ].ShowMenuItem,
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleShowVolumeClass, VolumeClassIndex ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsVolumeVisible, VolumeClassIndex ) );
}
}
// Show Layers
{
// Map 'Show All' and 'Hide All' commands
OutCommandList.MapAction(
FLevelViewportCommands::Get().ShowAllLayers,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllLayers, true ) );
OutCommandList.MapAction(
FLevelViewportCommands::Get().HideAllLayers,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllLayers, false ) );
}
// Show Sprite Categories
{
// Map 'Show All' and 'Hide All' commands
OutCommandList.MapAction(
FLevelViewportCommands::Get().ShowAllSprites,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllSpriteCategories, true ) );
OutCommandList.MapAction(
FLevelViewportCommands::Get().HideAllSprites,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllSpriteCategories, false ) );
// Bind each show flag to the same delegate. We use the delegate payload system to figure out what show flag we are dealing with
for( int32 CategoryIndex = 0; CategoryIndex < GUnrealEd->SpriteIDToIndexMap.Num(); ++CategoryIndex )
{
OutCommandList.MapAction(
FLevelViewportCommands::Get().ShowSpriteCommands[ CategoryIndex ].ShowMenuItem,
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleSpriteCategory, CategoryIndex ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsSpriteCategoryVisible, CategoryIndex ) );
}
}
// Show Stat Categories
{
// Map 'Hide All' command
OutCommandList.MapAction(
FLevelViewportCommands::Get().HideAllStats,
FExecuteAction::CreateSP(this, &SLevelViewport::OnToggleAllStatCommands, false));
for (auto StatCatIt = FLevelViewportCommands::Get().ShowStatCatCommands.CreateConstIterator(); StatCatIt; ++StatCatIt)
{
const TArray< FLevelViewportCommands::FShowMenuCommand >& ShowStatCommands = StatCatIt.Value();
for (int32 StatIndex = 0; StatIndex < ShowStatCommands.Num(); ++StatIndex)
{
const FLevelViewportCommands::FShowMenuCommand& StatCommand = ShowStatCommands[StatIndex];
BindStatCommand(StatCommand.ShowMenuItem, StatCommand.LabelOverride.ToString());
}
}
// Bind a listener here for any additional stat commands that get registered later.
FLevelViewportCommands::NewStatCommandDelegate.AddRaw(this, &SLevelViewport::BindStatCommand);
}
}
void SLevelViewport::BindDropCommands( FUICommandList& OutCommandList )
{
OutCommandList.MapAction(
FLevelViewportCommands::Get().ApplyMaterialToActor,
FExecuteAction::CreateSP( this, &SLevelViewport::OnApplyMaterialToViewportTarget ) );
}
void SLevelViewport::BindStatCommand(const TSharedPtr<FUICommandInfo> InMenuItem, const FString& InCommandName)
{
CommandList->MapAction(
InMenuItem,
FExecuteAction::CreateSP(this, &SLevelViewport::ToggleStatCommand, InCommandName),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SLevelViewport::IsStatCommandVisible, InCommandName));
}
const FSlateBrush* SLevelViewport::OnGetViewportBorderBrush() const
{
const FSlateBrush* BorderBrush = NULL;
if( FSlateApplication::Get().IsNormalExecution() )
{
// Only show the active border if we have a valid client, its the current client being edited and we arent in immersive (in immersive there is only one visible viewport)
if( LevelViewportClient.IsValid() && LevelViewportClient.Get() == GCurrentLevelEditingViewportClient && !IsImmersive() )
{
BorderBrush = ActiveBorder;
}
else
{
BorderBrush = NoBorder;
}
// If a PIE/SIE/Editor transition just completed, then we'll draw a border effect to draw attention to it
if( ViewTransitionAnim.IsPlaying() )
{
switch( ViewTransitionType )
{
case EViewTransition::FadingIn:
BorderBrush = BlackBackground;
break;
case EViewTransition::StartingPlayInEditor:
BorderBrush = StartingPlayInEditorBorder;
break;
case EViewTransition::StartingSimulate:
BorderBrush = StartingSimulateBorder;
break;
case EViewTransition::ReturningToEditor:
BorderBrush = ReturningToEditorBorder;
break;
}
}
}
else
{
BorderBrush = DebuggingBorder;
}
return BorderBrush;
}
FSlateColor SLevelViewport::OnGetViewportBorderColorAndOpacity() const
{
FLinearColor ViewportBorderColorAndOpacity = FLinearColor::White;
if( FSlateApplication::Get().IsNormalExecution() )
{
if( ViewTransitionAnim.IsPlaying() )
{
ViewportBorderColorAndOpacity = FLinearColor( 1.0f, 1.0f, 1.0f, 1.0f - ViewTransitionAnim.GetLerp() );
}
}
return ViewportBorderColorAndOpacity;
}
EVisibility SLevelViewport::OnGetViewportContentVisibility() const
{
// Do not show any of the viewports inner slate content (active viewport borders, etc) when we are playing in editor and in immersive mode
// as they are meaningless in that situation
EVisibility BaseVisibility = SEditorViewport::OnGetViewportContentVisibility();
if (BaseVisibility != EVisibility::Visible)
{
return BaseVisibility;
}
return ( ( IsPlayInEditorViewportActive() && IsImmersive() ) || GEngine->IsStereoscopic3D( ActiveViewport.Get() ) ) ? EVisibility::Collapsed : EVisibility::Visible;
}
EVisibility SLevelViewport::GetToolBarVisibility() const
{
// Do not show the toolbar if this viewport has a play in editor session, or we're in the VR Editor
return ( IsPlayInEditorViewportActive() || GEngine->IsStereoscopic3D( ActiveViewport.Get() ) ) ? EVisibility::Collapsed : OnGetViewportContentVisibility();
}
EVisibility SLevelViewport::GetMaximizeToggleVisibility() const
{
bool bIsMaximizeSupported = false;
bool bShowMaximizeToggle = false;
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
if (LayoutPinned.IsValid())
{
bIsMaximizeSupported = LayoutPinned->IsMaximizeSupported();
bShowMaximizeToggle = !LayoutPinned->IsTransitioning();
}
// Do not show the maximize/minimize toggle when in immersive mode
return (!bIsMaximizeSupported || IsImmersive()) ? EVisibility::Collapsed : (bShowMaximizeToggle ? EVisibility::Visible : EVisibility::Hidden);
}
EVisibility SLevelViewport::GetCloseImmersiveButtonVisibility() const
{
// Do not show the Immersive toggle button when not in immersive mode
return IsImmersive() ? EVisibility::Visible : EVisibility::Collapsed;
}
EVisibility SLevelViewport::GetTransformToolbarVisibility() const
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
TSharedPtr<ILevelViewport> ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport();
// Am I the ActiveLevelViewport?
if( ActiveLevelViewport.Get() == this )
{
// Only return visible if we are/were the active viewport.
return EVisibility::Visible;
}
return EVisibility::Hidden;
}
bool SLevelViewport::IsMaximized() const
{
if( ParentLayout.IsValid() && !ConfigKey.IsEmpty() )
{
return ParentLayout.Pin()->IsViewportMaximized( *ConfigKey );
}
// Assume the viewport is always maximized if we have no layout for some reason
return true;
}
TSharedRef<FEditorViewportClient> SLevelViewport::MakeEditorViewportClient()
{
return LevelViewportClient.ToSharedRef();
}
TSharedPtr<SWidget> SLevelViewport::MakeViewportToolbar()
{
// Build our toolbar level toolbar
TSharedRef< SLevelViewportToolBar > ToolBar =
SNew( SLevelViewportToolBar )
.Viewport( SharedThis( this ) )
.Visibility( this, &SLevelViewport::GetToolBarVisibility )
.IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() );
return
SNew(SVerticalBox)
.Visibility( EVisibility::SelfHitTestInvisible )
+SVerticalBox::Slot()
.AutoHeight()
.VAlign(VAlign_Top)
[
ToolBar
]
+SVerticalBox::Slot()
.VAlign(VAlign_Top)
.HAlign(HAlign_Left)
[
SNew(SActorPilotViewportToolbar)
.Viewport( SharedThis( this ) )
.Visibility(this, &SLevelViewport::GetLockedIconVisibility)
];
}
void SLevelViewport::OnUndo()
{
GUnrealEd->Exec( GetWorld(), TEXT("TRANSACTION UNDO") );
}
void SLevelViewport::OnRedo()
{
GUnrealEd->Exec( GetWorld(), TEXT("TRANSACTION REDO") );
}
bool SLevelViewport::CanExecuteUndo() const
{
return GUnrealEd->Trans->CanUndo() && FSlateApplication::Get().IsNormalExecution();
}
bool SLevelViewport::CanExecuteRedo() const
{
return GUnrealEd->Trans->CanRedo() && FSlateApplication::Get().IsNormalExecution();
}
void SLevelViewport::OnAdvancedSettings()
{
FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer("Editor", "LevelEditor", "Viewport");
}
void SLevelViewport::OnToggleImmersive()
{
if( ParentLayout.IsValid() )
{
bool bWantImmersive = !IsImmersive();
bool bWantMaximize = IsMaximized();
// We always want to animate in response to user-interactive toggling of maximized state
const bool bAllowAnimation = true;
FName ViewportName = *ConfigKey;
if (!ViewportName.IsNone())
{
ParentLayout.Pin()->RequestMaximizeViewport( ViewportName, bWantMaximize, bWantImmersive, bAllowAnimation );
}
}
}
bool SLevelViewport::IsImmersive() const
{
if( ParentLayout.IsValid() && !ConfigKey.IsEmpty() )
{
return ParentLayout.Pin()->IsViewportImmersive( *ConfigKey );
}
// Assume the viewport is not immersive if we have no layout for some reason
return false;
}
void SLevelViewport::OnCreateCameraActor()
{
// Find the perspective viewport we were using
FViewport* pViewPort = GEditor->GetActiveViewport();
FLevelEditorViewportClient* ViewportClient = NULL;
for( int32 iView = 0; iView < GEditor->LevelViewportClients.Num(); iView++ )
{
if( GEditor->LevelViewportClients[ iView ]->IsPerspective() && GEditor->LevelViewportClients[ iView ]->Viewport == pViewPort )
{
ViewportClient = GEditor->LevelViewportClients[ iView ];
break;
}
}
if( ViewportClient == NULL )
{
// May fail to find viewport if shortcut key was pressed on an ortho viewport, if so early out.
// This function only works on perspective viewports so new camera can match perspective camera.
return;
}
const FScopedTransaction Transaction(NSLOCTEXT("LevelViewport", "CreateCameraHere", "Create Camera Here"));
// Set new camera to match viewport
ACameraActor* pNewCamera = ViewportClient->GetWorld()->SpawnActor<ACameraActor>();
pNewCamera->SetActorLocation( ViewportClient->GetViewLocation(), false );
pNewCamera->SetActorRotation( ViewportClient->GetViewRotation() );
pNewCamera->CameraComponent->FieldOfView = ViewportClient->ViewFOV;
// Deselect any currently selected actors
GUnrealEd->SelectNone( true, true );
GEditor->GetSelectedActors()->DeselectAll();
GEditor->GetSelectedObjects()->DeselectAll();
// Select newly created Camera
TArray<UObject *> SelectedActors;
GEditor->SelectActor( pNewCamera, true, false );
SelectedActors.Add( pNewCamera );
// Send notification about actors that may have changed
ULevel::LevelDirtiedEvent.Broadcast();
// Update the details window with the actors we have just selected
GUnrealEd->UpdateFloatingPropertyWindowsFromActorList( SelectedActors );
// Redraw viewports to show new camera
GEditor->RedrawAllViewports();
}
bool SLevelViewport::IsPerspectiveViewport() const
{
bool bIsPerspective = false;
FViewport* pViewPort = GEditor->GetActiveViewport();
if( pViewPort && pViewPort->GetClient()->IsOrtho() == false )
{
bIsPerspective = true;
}
return bIsPerspective;
}
void SLevelViewport::OnTakeHighResScreenshot()
{
HighResScreenshotDialog = SHighResScreenshotDialog::OpenDialog(ActiveViewport, CaptureRegionWidget);
}
void SLevelViewport::ToggleGameView()
{
if( LevelViewportClient->IsPerspective() )
{
bool bGameViewEnable = !LevelViewportClient->IsInGameView();
LevelViewportClient->SetGameView(bGameViewEnable);
}
}
bool SLevelViewport::CanToggleGameView() const
{
return LevelViewportClient->IsPerspective();
}
bool SLevelViewport::IsInGameView() const
{
return LevelViewportClient->IsInGameView();
}
void SLevelViewport::ChangeBufferVisualizationMode( FName InName )
{
LevelViewportClient->SetViewMode(VMI_VisualizeBuffer);
LevelViewportClient->CurrentBufferVisualizationMode = InName;
}
bool SLevelViewport::IsBufferVisualizationModeSelected( FName InName ) const
{
return LevelViewportClient->IsViewModeEnabled( VMI_VisualizeBuffer ) && LevelViewportClient->CurrentBufferVisualizationMode == InName;
}
void SLevelViewport::OnToggleAllVolumeActors( bool bVisible )
{
// Reinitialize the volume actor visibility flags to the new state. All volumes should be visible if "Show All" was selected and hidden if it was not selected.
LevelViewportClient->VolumeActorVisibility.Init( bVisible, LevelViewportClient->VolumeActorVisibility.Num() );
// Update visibility based on the new state
// All volume actor types should be taken since the user clicked on show or hide all to get here
GUnrealEd->UpdateVolumeActorVisibility( NULL, LevelViewportClient.Get() );
}
/** Called when the user toggles a volume visibility from Volumes sub-menu. **/
void SLevelViewport::ToggleShowVolumeClass( int32 VolumeID )
{
TArray< UClass* > VolumeClasses;
UUnrealEdEngine::GetSortedVolumeClasses(&VolumeClasses);
// Get the corresponding volume class for the clicked menu item.
UClass *SelectedVolumeClass = VolumeClasses[ VolumeID ];
LevelViewportClient->VolumeActorVisibility[ VolumeID ] = !LevelViewportClient->VolumeActorVisibility[ VolumeID ];
// Update the found actors visibility based on the new bitfield
GUnrealEd->UpdateVolumeActorVisibility( SelectedVolumeClass, LevelViewportClient.Get() );
}
/** Called to determine if vlume class is visible. **/
bool SLevelViewport::IsVolumeVisible( int32 VolumeID ) const
{
return LevelViewportClient->VolumeActorVisibility[ VolumeID ];
}
/** Called when a user selects show or hide all from the layers visibility menu. **/
void SLevelViewport::OnToggleAllLayers( bool bVisible )
{
if (bVisible)
{
// clear all hidden layers
LevelViewportClient->ViewHiddenLayers.Empty();
}
else
{
// hide them all
TArray<FName> AllLayerNames;
GEditor->Layers->AddAllLayerNamesTo(AllLayerNames);
LevelViewportClient->ViewHiddenLayers = AllLayerNames;
}
// update actor visibility for this view
GEditor->Layers->UpdatePerViewVisibility(LevelViewportClient.Get());
LevelViewportClient->Invalidate();
}
/** Called when the user toggles a layer from Layers sub-menu. **/
void SLevelViewport::ToggleShowLayer( FName LayerName )
{
int32 HiddenIndex = LevelViewportClient->ViewHiddenLayers.Find(LayerName);
if ( HiddenIndex == INDEX_NONE )
{
LevelViewportClient->ViewHiddenLayers.Add(LayerName);
}
else
{
LevelViewportClient->ViewHiddenLayers.RemoveAt(HiddenIndex);
}
// update actor visibility for this view
GEditor->Layers->UpdatePerViewVisibility(LevelViewportClient.Get(), LayerName);
LevelViewportClient->Invalidate();
}
/** Called to determine if a layer is visible. **/
bool SLevelViewport::IsLayerVisible( FName LayerName ) const
{
return LevelViewportClient->ViewHiddenLayers.Find(LayerName) == INDEX_NONE;
}
void SLevelViewport::ToggleShowFoliageType(TWeakObjectPtr<UFoliageType> InFoliageType)
{
UFoliageType* FoliageType = InFoliageType.Get();
if (FoliageType)
{
FoliageType->HiddenEditorViews^= (1ull << LevelViewportClient->ViewIndex);
// Notify UFoliageType that things have changed
FoliageType->OnHiddenEditorViewMaskChanged(GetWorld());
// Make sure to redraw viewport when user toggles foliage
LevelViewportClient->Invalidate();
}
}
void SLevelViewport::ToggleAllFoliageTypes(bool bVisible)
{
UWorld* CurrentWorld = GetWorld();
TArray<UFoliageType*> AllFoliageTypes = GEditor->GetFoliageTypesInWorld(CurrentWorld);
if (AllFoliageTypes.Num())
{
const uint64 ViewMask = (1ull << LevelViewportClient->ViewIndex);
for (UFoliageType* FoliageType : AllFoliageTypes)
{
if (bVisible)
{
FoliageType->HiddenEditorViews&= ~ViewMask;
}
else
{
FoliageType->HiddenEditorViews|= ViewMask;
}
FoliageType->OnHiddenEditorViewMaskChanged(CurrentWorld);
}
// Make sure to redraw viewport when user toggles meshes
LevelViewportClient->Invalidate();
}
}
bool SLevelViewport::IsFoliageTypeVisible(TWeakObjectPtr<UFoliageType> InFoliageType) const
{
const UFoliageType* FoliageType = InFoliageType.Get();
if (FoliageType)
{
return (FoliageType->HiddenEditorViews & (1ull << LevelViewportClient->ViewIndex)) == 0;
}
return false;
}
FViewport* SLevelViewport::GetActiveViewport()
{
return ActiveViewport->GetViewport();
}
void SLevelViewport::OnFocusViewportToSelection()
{
GUnrealEd->Exec( GetWorld(), TEXT("CAMERA ALIGN ACTIVEVIEWPORTONLY") );
}
/** Called when the user selects show or hide all from the sprite sub-menu. **/
void SLevelViewport::OnToggleAllSpriteCategories( bool bVisible )
{
LevelViewportClient->SetAllSpriteCategoryVisibility( bVisible );
LevelViewportClient->Invalidate();
}
/** Called when the user toggles a category from the sprite sub-menu. **/
void SLevelViewport::ToggleSpriteCategory( int32 CategoryID )
{
LevelViewportClient->SetSpriteCategoryVisibility( CategoryID, !LevelViewportClient->GetSpriteCategoryVisibility( CategoryID ) );
LevelViewportClient->Invalidate();
}
/** Called to determine if a category from the sprite sub-menu is visible. **/
bool SLevelViewport::IsSpriteCategoryVisible( int32 CategoryID ) const
{
return LevelViewportClient->GetSpriteCategoryVisibility( CategoryID );
}
void SLevelViewport::OnToggleAllStatCommands( bool bVisible )
{
check(bVisible == 0);
// If it's in the array, it's visible so just toggle it again
const TArray<FString>* EnabledStats = LevelViewportClient->GetEnabledStats();
check(EnabledStats);
while (EnabledStats->Num() > 0)
{
const FString& CommandName = EnabledStats->Last();
ToggleStatCommand(CommandName);
}
}
void SLevelViewport::OnUseDefaultShowFlags(bool bUseSavedDefaults)
{
// cache off the current viewmode as it gets trashed when applying FEngineShowFlags()
const EViewModeIndex CachedViewMode = LevelViewportClient->GetViewMode();
// Setting show flags to the defaults should not stomp on the current viewmode settings.
LevelViewportClient->SetGameView(false);
// Get default save flags
FEngineShowFlags EditorShowFlags(ESFIM_Editor);
FEngineShowFlags GameShowFlags(ESFIM_Game);
if (bUseSavedDefaults && !ConfigKey.IsEmpty())
{
FLevelEditorViewportInstanceSettings ViewportInstanceSettings;
ViewportInstanceSettings.ViewportType = LevelViewportClient->ViewportType;
// Get saved defaults if specified
const FLevelEditorViewportInstanceSettings* const ViewportInstanceSettingsPtr = GetDefault<ULevelEditorViewportSettings>()->GetViewportInstanceSettings(ConfigKey);
ViewportInstanceSettings = ViewportInstanceSettingsPtr ? *ViewportInstanceSettingsPtr : LoadLegacyConfigFromIni(ConfigKey, ViewportInstanceSettings);
if (!ViewportInstanceSettings.EditorShowFlagsString.IsEmpty())
{
EditorShowFlags.SetFromString(*ViewportInstanceSettings.EditorShowFlagsString);
}
if (!ViewportInstanceSettings.GameShowFlagsString.IsEmpty())
{
GameShowFlags.SetFromString(*ViewportInstanceSettings.GameShowFlagsString);
}
}
// this trashes the current viewmode!
LevelViewportClient->EngineShowFlags = EditorShowFlags;
// Restore the state of SelectionOutline based on user settings
LevelViewportClient->EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline);
LevelViewportClient->LastEngineShowFlags = GameShowFlags;
// re-apply the cached viewmode, as it was trashed with FEngineShowFlags()
ApplyViewMode(CachedViewMode, LevelViewportClient->IsPerspective(), LevelViewportClient->EngineShowFlags);
ApplyViewMode(CachedViewMode, LevelViewportClient->IsPerspective(), LevelViewportClient->LastEngineShowFlags);
// set volume / layer / sprite visibility defaults
if (!bUseSavedDefaults)
{
LevelViewportClient->InitializeVisibilityFlags();
GUnrealEd->UpdateVolumeActorVisibility(NULL, LevelViewportClient.Get());
GEditor->Layers->UpdatePerViewVisibility(LevelViewportClient.Get());
}
LevelViewportClient->Invalidate();
}
void SLevelViewport::SetKeyboardFocusToThisViewport()
{
if( ensure( ViewportWidget.IsValid() ) )
{
// Set keyboard focus directly
FSlateApplication::Get().SetKeyboardFocus( ViewportWidget.ToSharedRef() );
}
}
void SLevelViewport::SaveConfig(const FString& ConfigName) const
{
if(GUnrealEd && GetDefault<ULevelEditorViewportSettings>())
{
// When we startup the editor we always start it up in IsInGameView()=false mode
FEngineShowFlags& EditorShowFlagsToSave = LevelViewportClient->IsInGameView() ? LevelViewportClient->LastEngineShowFlags : LevelViewportClient->EngineShowFlags;
FEngineShowFlags& GameShowFlagsToSave = LevelViewportClient->IsInGameView() ? LevelViewportClient->EngineShowFlags : LevelViewportClient->LastEngineShowFlags;
FLevelEditorViewportInstanceSettings ViewportInstanceSettings;
ViewportInstanceSettings.ViewportType = LevelViewportClient->ViewportType;
ViewportInstanceSettings.PerspViewModeIndex = LevelViewportClient->GetPerspViewMode();
ViewportInstanceSettings.OrthoViewModeIndex = LevelViewportClient->GetOrthoViewMode();
ViewportInstanceSettings.EditorShowFlagsString = EditorShowFlagsToSave.ToString();
ViewportInstanceSettings.GameShowFlagsString = GameShowFlagsToSave.ToString();
ViewportInstanceSettings.BufferVisualizationMode = LevelViewportClient->CurrentBufferVisualizationMode;
ViewportInstanceSettings.ExposureSettings = LevelViewportClient->ExposureSettings;
ViewportInstanceSettings.FOVAngle = LevelViewportClient->FOVAngle;
ViewportInstanceSettings.bIsRealtime = LevelViewportClient->IsRealtime();
ViewportInstanceSettings.bShowOnScreenStats = LevelViewportClient->ShouldShowStats();
ViewportInstanceSettings.FarViewPlane = LevelViewportClient->GetFarClipPlaneOverride();
ViewportInstanceSettings.bShowFullToolbar = bShowFullToolbar;
if(GetDefault<ULevelEditorViewportSettings>()->bSaveEngineStats)
{
const TArray<FString>* EnabledStats = NULL;
// If the selected viewport is currently hosting a PIE session, we need to make sure we copy to stats from the active viewport
// Note: This happens if you close the editor while it's running because SwapStatCommands gets called after the config save when shutting down.
if(IsPlayInEditorViewportActive())
{
EnabledStats = ActiveViewport->GetClient()->GetEnabledStats();
}
else
{
EnabledStats = LevelViewportClient->GetEnabledStats();
}
check(EnabledStats);
ViewportInstanceSettings.EnabledStats = *EnabledStats;
}
GetMutableDefault<ULevelEditorViewportSettings>()->SetViewportInstanceSettings(ConfigName, ViewportInstanceSettings);
}
}
FLevelEditorViewportInstanceSettings SLevelViewport::LoadLegacyConfigFromIni(const FString& InConfigKey, const FLevelEditorViewportInstanceSettings& InDefaultSettings)
{
FLevelEditorViewportInstanceSettings ViewportInstanceSettings = InDefaultSettings;
const FString& IniSection = FLayoutSaveRestore::GetAdditionalLayoutConfigIni();
{
int32 ViewportTypeAsInt = ViewportInstanceSettings.ViewportType;
GConfig->GetInt(*IniSection, *(InConfigKey + TEXT(".Type")), ViewportTypeAsInt, GEditorPerProjectIni);
ViewportInstanceSettings.ViewportType = (ViewportTypeAsInt == -1 || ViewportTypeAsInt == 255) ? LVT_None : static_cast<ELevelViewportType>(ViewportTypeAsInt); // LVT_None used to be -1 or 255
if(ViewportInstanceSettings.ViewportType == LVT_None)
{
ViewportInstanceSettings.ViewportType = LVT_Perspective;
}
}
GConfig->GetString(*IniSection, *(InConfigKey + TEXT(".EditorShowFlags")), ViewportInstanceSettings.EditorShowFlagsString, GEditorPerProjectIni);
GConfig->GetString(*IniSection, *(InConfigKey + TEXT(".GameShowFlags")), ViewportInstanceSettings.GameShowFlagsString, GEditorPerProjectIni);
// A single view mode index has been deprecated in favor of separate perspective and orthographic settings
EViewModeIndex LegacyViewModeIndex = VMI_Unknown;
{
int32 LegacyVMIAsInt = VMI_Unknown;
GConfig->GetInt(*IniSection, *(InConfigKey+TEXT(".ViewModeIndex")), LegacyVMIAsInt, GEditorPerProjectIni);
LegacyViewModeIndex = (LegacyVMIAsInt == -1) ? VMI_Unknown : static_cast<EViewModeIndex>(LegacyVMIAsInt); // VMI_Unknown used to be -1
}
if(!GConfig->GetInt(*IniSection, *(InConfigKey+TEXT(".PerspViewModeIndex")), (int32&)ViewportInstanceSettings.PerspViewModeIndex, GEditorPerProjectIni))
{
if(ViewportInstanceSettings.ViewportType == LVT_Perspective)
{
// This viewport may pre-date the ViewModeIndex setting (VMI_Unknown), if so, try to be backward compatible
ViewportInstanceSettings.PerspViewModeIndex = (LegacyViewModeIndex == VMI_Unknown) ? FindViewMode(LevelViewportClient->EngineShowFlags) : LegacyViewModeIndex;
}
else
{
// Default to Lit for a perspective viewport
ViewportInstanceSettings.PerspViewModeIndex = VMI_Lit;
}
}
if(!GConfig->GetInt(*IniSection, *(InConfigKey+TEXT(".OrthoViewModeIndex")), (int32&)ViewportInstanceSettings.OrthoViewModeIndex, GEditorPerProjectIni))
{
// Default to Brush Wireframe for an orthographic viewport
ViewportInstanceSettings.OrthoViewModeIndex = (ViewportInstanceSettings.ViewportType != LVT_Perspective && LegacyViewModeIndex != VMI_Unknown) ? LegacyViewModeIndex : VMI_BrushWireframe;
}
{
FString BufferVisualizationModeString;
if(GConfig->GetString(*IniSection, *(InConfigKey+TEXT(".BufferVisualizationMode")), BufferVisualizationModeString, GEditorPerProjectIni))
{
ViewportInstanceSettings.BufferVisualizationMode = *BufferVisualizationModeString;
}
}
{
FString ExposureSettingsString;
if(GConfig->GetString(*IniSection, *(InConfigKey + TEXT(".ExposureSettings")), ExposureSettingsString, GEditorPerProjectIni))
{
ViewportInstanceSettings.ExposureSettings.SetFromString(*ExposureSettingsString);
}
}
GConfig->GetBool(*IniSection, *(InConfigKey + TEXT(".bIsRealtime")), ViewportInstanceSettings.bIsRealtime, GEditorPerProjectIni);
GConfig->GetBool(*IniSection, *(InConfigKey + TEXT(".bWantStats")), ViewportInstanceSettings.bShowOnScreenStats, GEditorPerProjectIni);
GConfig->GetBool(*IniSection, *(InConfigKey + TEXT(".bWantFPS")), ViewportInstanceSettings.bShowFPS_DEPRECATED, GEditorPerProjectIni);
GConfig->GetFloat(*IniSection, *(InConfigKey + TEXT(".FOVAngle")), ViewportInstanceSettings.FOVAngle, GEditorPerProjectIni);
return ViewportInstanceSettings;
}
void SLevelViewport::OnSetBookmark( int32 BookmarkIndex )
{
GLevelEditorModeTools().SetBookmark( BookmarkIndex, LevelViewportClient.Get() );
}
void SLevelViewport::OnJumpToBookmark( int32 BookmarkIndex )
{
const bool bShouldRestoreLevelVisibility = true;
GLevelEditorModeTools().JumpToBookmark( BookmarkIndex, bShouldRestoreLevelVisibility, LevelViewportClient.Get() );
}
void SLevelViewport::OnClearBookMark( int32 BookmarkIndex )
{
GLevelEditorModeTools().ClearBookmark( BookmarkIndex, LevelViewportClient.Get() );
}
void SLevelViewport::OnClearAllBookMarks()
{
GLevelEditorModeTools().ClearAllBookmarks( LevelViewportClient.Get() );
}
void SLevelViewport::OnToggleAllowCinematicPreview()
{
// Reset the FOV of Viewport for cases where we have been previewing the matinee with a changing FOV
LevelViewportClient->ViewFOV = LevelViewportClient->AllowsCinematicPreview() ? LevelViewportClient->ViewFOV : LevelViewportClient->FOVAngle;
LevelViewportClient->SetAllowCinematicPreview( !LevelViewportClient->AllowsCinematicPreview() );
LevelViewportClient->Invalidate( false );
}
bool SLevelViewport::AllowsCinematicPreview() const
{
return LevelViewportClient->AllowsCinematicPreview();
}
void SLevelViewport::OnIncrementPositionGridSize()
{
GEditor->GridSizeIncrement();
GEditor->RedrawLevelEditingViewports();
}
void SLevelViewport::OnDecrementPositionGridSize()
{
GEditor->GridSizeDecrement();
GEditor->RedrawLevelEditingViewports();
}
void SLevelViewport::OnIncrementRotationGridSize()
{
GEditor->RotGridSizeIncrement();
GEditor->RedrawLevelEditingViewports();
}
void SLevelViewport::OnDecrementRotationGridSize()
{
GEditor->RotGridSizeDecrement();
GEditor->RedrawLevelEditingViewports();
}
void SLevelViewport::OnActorLockToggleFromMenu(AActor* Actor)
{
if (Actor != NULL)
{
const bool bLockNewActor = Actor != LevelViewportClient->GetActiveActorLock().Get();
// Lock the new actor if it wasn't the same actor that we just unlocked
if (bLockNewActor)
{
// Unlock the previous actor
OnActorUnlock();
LockActorInternal(Actor);
}
}
}
bool SLevelViewport::IsActorLocked(const TWeakObjectPtr<AActor> Actor) const
{
return LevelViewportClient->IsActorLocked(Actor);
}
bool SLevelViewport::IsAnyActorLocked() const
{
return LevelViewportClient->IsAnyActorLocked();
}
void SLevelViewport::ToggleActorPilotCameraView()
{
LevelViewportClient->bLockedCameraView = !LevelViewportClient->bLockedCameraView;
}
bool SLevelViewport::IsLockedCameraViewEnabled() const
{
return LevelViewportClient->bLockedCameraView;
}
void SLevelViewport::FindSelectedInLevelScript()
{
GUnrealEd->FindSelectedActorsInLevelScript();
}
bool SLevelViewport::CanFindSelectedInLevelScript() const
{
AActor* Actor = GEditor->GetSelectedActors()->GetTop<AActor>();
return (Actor != NULL);
}
void SLevelViewport::OnActorUnlock()
{
if (AActor* LockedActor = LevelViewportClient->GetActiveActorLock().Get())
{
// Check to see if the locked actor was previously overriding the camera settings
if (CanGetCameraInformationFromActor(LockedActor))
{
// Reset the settings
LevelViewportClient->ViewFOV = LevelViewportClient->FOVAngle;
}
LevelViewportClient->SetActorLock(nullptr);
// remove roll and pitch from camera when unbinding from actors
GEditor->RemovePerspectiveViewRotation(true, true, false);
// If we had a camera actor locked, and it was selected, then we should re-show the inset preview
OnPreviewSelectedCamerasChange();
}
}
bool SLevelViewport::CanExecuteActorUnlock() const
{
return IsAnyActorLocked();
}
void SLevelViewport::OnActorLockSelected()
{
USelection* ActorSelection = GEditor->GetSelectedActors();
if (1 == ActorSelection->Num())
{
AActor* Actor = CastChecked<AActor>(ActorSelection->GetSelectedObject(0));
LockActorInternal(Actor);
}
}
bool SLevelViewport::CanExecuteActorLockSelected() const
{
USelection* ActorSelection = GEditor->GetSelectedActors();
if (1 == ActorSelection->Num())
{
return true;
}
return false;
}
bool SLevelViewport::IsSelectedActorLocked() const
{
USelection* ActorSelection = GEditor->GetSelectedActors();
if (1 == ActorSelection->Num() && IsAnyActorLocked())
{
AActor* Actor = CastChecked<AActor>(ActorSelection->GetSelectedObject(0));
if (LevelViewportClient->GetActiveActorLock().Get() == Actor)
{
return true;
}
}
return false;
}
float SLevelViewport::GetActorLockSceneOutlinerColumnWidth()
{
return 18.0f; // 16.0f for the icons and 2.0f padding
}
TSharedRef< ISceneOutlinerColumn > SLevelViewport::CreateActorLockSceneOutlinerColumn( ISceneOutliner& SceneOutliner ) const
{
/**
* A custom column for the SceneOutliner which shows whether an actor is locked to a viewport
*/
class FCustomColumn : public ISceneOutlinerColumn
{
public:
/**
* Constructor
*/
FCustomColumn( const SLevelViewport* const InViewport )
: Viewport(InViewport)
{
}
virtual ~FCustomColumn()
{
}
//////////////////////////////////////////////////////////////////////////
// Begin ISceneOutlinerColumn Implementation
virtual FName GetColumnID() override
{
return FName( "LockedToViewport" );
}
virtual SHeaderRow::FColumn::FArguments ConstructHeaderRowColumn() override
{
return SHeaderRow::Column( GetColumnID() )
.FixedWidth(SLevelViewport::GetActorLockSceneOutlinerColumnWidth())
[
SNew( SSpacer )
];
}
virtual const TSharedRef< SWidget > ConstructRowWidget( SceneOutliner::FTreeItemRef TreeItem, const STableRow<SceneOutliner::FTreeItemPtr>& InRow ) override
{
struct FConstructWidget : SceneOutliner::FColumnGenerator
{
const SLevelViewport* Viewport;
FConstructWidget(const SLevelViewport* InViewport) : Viewport(InViewport) {}
virtual TSharedRef<SWidget> GenerateWidget(SceneOutliner::FActorTreeItem& ActorItem) const override
{
AActor* Actor = ActorItem.Actor.Get();
if (!Actor)
{
return SNullWidget::NullWidget;
}
const bool bLocked = Viewport->IsActorLocked(Actor);
return SNew(SBox)
.WidthOverride(SLevelViewport::GetActorLockSceneOutlinerColumnWidth())
.Padding(FMargin(2.0f, 0.0f, 0.0f, 0.0f))
[
SNew(SImage)
.Image(FEditorStyle::GetBrush(bLocked ? "PropertyWindow.Locked" : "PropertyWindow.Unlocked"))
.ColorAndOpacity(bLocked ? FLinearColor::White : FLinearColor(1.0f, 1.0f, 1.0f, 0.5f))
];
}
};
FConstructWidget Visitor(Viewport);
TreeItem->Visit(Visitor);
if (Visitor.Widget.IsValid())
{
return Visitor.Widget.ToSharedRef();
}
else
{
return SNullWidget::NullWidget;
}
}
// End ISceneOutlinerColumn Implementation
//////////////////////////////////////////////////////////////////////////
private:
const SLevelViewport* Viewport;
};
return MakeShareable( new FCustomColumn( this ) );
}
void SLevelViewport::RedrawViewport( bool bInvalidateHitProxies )
{
if ( bInvalidateHitProxies )
{
// Invalidate hit proxies and display pixels.
LevelViewportClient->Viewport->Invalidate();
// Also update preview viewports
for( auto ActorPreviewIt = ActorPreviews.CreateConstIterator(); ActorPreviewIt; ++ActorPreviewIt )
{
auto& CurActorPreview = *ActorPreviewIt;
CurActorPreview.LevelViewportClient->Viewport->Invalidate();
}
}
else
{
// Invalidate only display pixels.
LevelViewportClient->Viewport->InvalidateDisplay();
// Also update preview viewports
for( auto ActorPreviewIt = ActorPreviews.CreateConstIterator(); ActorPreviewIt; ++ActorPreviewIt )
{
auto& CurActorPreview = *ActorPreviewIt;
CurActorPreview.LevelViewportClient->Viewport->InvalidateDisplay();
}
}
}
bool SLevelViewport::CanToggleMaximizeMode() const
{
TSharedPtr<FLevelViewportLayout> ParentLayoutPinned = ParentLayout.Pin();
return (ParentLayoutPinned.IsValid() && ParentLayoutPinned->IsMaximizeSupported() && !ParentLayoutPinned->IsTransitioning());
}
void SLevelViewport::OnToggleMaximizeMode()
{
OnToggleMaximize();
}
FReply SLevelViewport::OnToggleMaximize()
{
TSharedPtr<FLevelViewportLayout> ParentLayoutPinned = ParentLayout.Pin();
if (ParentLayoutPinned.IsValid() && ParentLayoutPinned->IsMaximizeSupported())
{
OnFloatingButtonClicked();
bool bWantImmersive = IsImmersive();
bool bWantMaximize = IsMaximized();
//When in Immersive mode we always want to toggle back to normal editing mode while retaining the previous maximized state
if( bWantImmersive )
{
bWantImmersive = false;
}
else
{
bWantMaximize = !bWantMaximize;
}
// We always want to animate in response to user-interactive toggling of maximized state
const bool bAllowAnimation = true;
FName ViewportName = *ConfigKey;
if (!ViewportName.IsNone())
{
ParentLayout.Pin()->RequestMaximizeViewport( ViewportName, bWantMaximize, bWantImmersive, bAllowAnimation );
}
}
return FReply::Handled();
}
void SLevelViewport::MakeImmersive( const bool bWantImmersive, const bool bAllowAnimation )
{
if( ensure( ParentLayout.IsValid() ) )
{
const bool bWantMaximize = IsMaximized();
FName ViewportName = *ConfigKey;
if (!ViewportName.IsNone())
{
ParentLayout.Pin()->RequestMaximizeViewport( ViewportName, bWantMaximize, bWantImmersive, bAllowAnimation );
}
}
}
/**
* Registers a game viewport with the Slate application so that specific messages can be routed directly to this level viewport if it is an active PIE viewport
*/
void SLevelViewport::RegisterGameViewportIfPIE()
{
if(ActiveViewport->IsPlayInEditorViewport())
{
FSlateApplication::Get().RegisterGameViewport(ViewportWidget.ToSharedRef());
}
}
bool SLevelViewport::HasPlayInEditorViewport() const
{
return ActiveViewport->IsPlayInEditorViewport() || ( InactiveViewport.IsValid() && InactiveViewport->IsPlayInEditorViewport() );
}
bool SLevelViewport::IsPlayInEditorViewportActive() const
{
return ActiveViewport->IsPlayInEditorViewport();
}
void SLevelViewport::OnActorSelectionChanged(const TArray<UObject*>& NewSelection, bool bForceRefresh)
{
// On the first actor selection after entering Game View, enable the selection show flag
if (IsVisible() && IsInGameView() && NewSelection.Num() != 0)
{
if( LevelViewportClient->bAlwaysShowModeWidgetAfterSelectionChanges )
{
LevelViewportClient->EngineShowFlags.SetModeWidgets(true);
}
LevelViewportClient->EngineShowFlags.SetSelection(true);
LevelViewportClient->EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline);
}
// Check to see if we have any actors that we should preview. Only do this if we're the active level viewport client.
// NOTE: We don't actively monitor which viewport is "current" and remove views, etc. This ends up OK though because
// the camera PIP views will feel "sticky" in the viewport that was active when you last selected objects
// to preview!
if (GetDefault<ULevelEditorViewportSettings>()->bPreviewSelectedCameras && GCurrentLevelEditingViewportClient == LevelViewportClient.Get())
{
PreviewSelectedCameraActors();
}
else
{
// We're no longer the active viewport client, so remove any existing previewed actors
PreviewActors(TArray<AActor*>());
}
}
void SLevelViewport::PreviewSelectedCameraActors()
{
TArray<AActor*> ActorsToPreview;
for (FSelectionIterator SelectionIt( *GEditor->GetSelectedActors()); SelectionIt; ++SelectionIt)
{
AActor* SelectedActor = CastChecked<AActor>( *SelectionIt );
if (LevelViewportClient->IsLockedToActor(SelectedActor))
{
// If this viewport is already locked to the specified camera, then we don't need to do anything
}
else if (CanGetCameraInformationFromActor(SelectedActor))
{
ActorsToPreview.Add(SelectedActor);
}
}
PreviewActors( ActorsToPreview );
}
class SActorPreview : public SCompoundWidget
{
public:
~SActorPreview();
SLATE_BEGIN_ARGS( SActorPreview )
: _ViewportWidth( 240 ),
_ViewportHeight( 180 ) {}
/** Width of the viewport */
SLATE_ARGUMENT( int32, ViewportWidth )
/** Height of the viewport */
SLATE_ARGUMENT( int32, ViewportHeight )
/** Actor being previewed.*/
SLATE_ARGUMENT( TWeakObjectPtr< AActor >, PreviewActor )
/** Parent Viewport this preview is part of.*/
SLATE_ARGUMENT( TWeakPtr<SLevelViewport>, ParentViewport )
SLATE_END_ARGS()
/** Called by Slate to construct this widget */
void Construct( const FArguments& InArgs );
/** @return Returns this actor preview's viewport widget */
const TSharedRef< SViewport > GetViewportWidget() const
{
return ViewportWidget.ToSharedRef();
}
/** SWidget overrides */
virtual void OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override;
virtual void OnMouseLeave( const FPointerEvent& MouseEvent ) override;
/** Highlight this preview window by flashing the border. Will replay the curve sequence if it is already in the middle of a highlight. */
void Highlight();
private:
/** Called when an actor in the world is selected */
void OnActorSelected(UObject* InActor);
/** @return Returns the color and opacity to use for this widget */
FLinearColor GetColorAndOpacity() const;
/** @return Returns the border color and opacity to use for this widget (FSlateColor version) */
FSlateColor GetBorderColorAndOpacity() const;
/** @return Gets the name of the preview actor.*/
FText OnReadText() const;
/** @return Gets the Width of the preview viewport.*/
FOptionalSize OnReadWidth() const;
/** @return Gets the Height of the preview viewport.*/
FOptionalSize OnReadHeight() const;
/** @return Get the Width to wrap the preview actor name at.*/
float OnReadTextWidth() const;
/** Called when the pin preview button is clicked */
FReply OnTogglePinnedButtonClicked();
/** Swap between the pinned and unpinned icons */
const FSlateBrush* GetPinButtonIconBrush() const;
/** @return the tooltip to display when hovering over the pin button */
FText GetPinButtonToolTipText() const;
/** Viewport widget for this actor preview */
TSharedPtr< SViewport > ViewportWidget;
/** Actor being previewed.*/
TWeakObjectPtr< AActor > PreviewActorPtr;
/** Parent Viewport this preview is part of.*/
TWeakPtr<SLevelViewport> ParentViewport;
/** Curve sequence for fading in and out */
FCurveSequence FadeSequence;
/** Curve sequence for flashing the border (highlighting) when a pinned preview is re-selected */
FCurveSequence HighlightSequence;
/** Padding around the preview actor name */
static const float PreviewTextPadding;
};
const float SActorPreview::PreviewTextPadding = 3.0f;
SActorPreview::~SActorPreview()
{
USelection::SelectObjectEvent.RemoveAll(this);
}
void SActorPreview::Construct( const FArguments& InArgs )
{
const int32 HorizSpacingBetweenViewports = 18;
const int32 PaddingBeforeBorder = 6;
USelection::SelectObjectEvent.AddRaw(this, &SActorPreview::OnActorSelected);
// We don't want the border to be hit testable, since it would just get in the way of other
// widgets that are added to the viewport overlay.
this->SetVisibility(EVisibility::SelfHitTestInvisible);
this->ChildSlot
[
SNew(SBorder)
.Padding(0)
.Visibility(EVisibility::SelfHitTestInvisible)
.BorderImage(FEditorStyle::GetBrush("NoBorder"))
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
.Padding(FMargin(0, 0, PaddingBeforeBorder, PaddingBeforeBorder))
[
SNew( SOverlay )
+SOverlay::Slot()
[
SNew( SBorder )
// We never want the user to be able to interact with this viewport. Clicks should go right though it!
.Visibility( EVisibility::HitTestInvisible )
.Padding( 16.0f )
.BorderImage( FEditorStyle::GetBrush( "UniformShadow_Tint" ) )
.BorderBackgroundColor( this, &SActorPreview::GetBorderColorAndOpacity )
.ColorAndOpacity( this, &SActorPreview::GetColorAndOpacity )
[
SNew( SBox )
.WidthOverride( this, &SActorPreview::OnReadWidth )
.HeightOverride(this, &SActorPreview::OnReadHeight )
[
SNew( SOverlay )
+SOverlay::Slot()
[
SAssignNew( ViewportWidget, SViewport )
.RenderDirectlyToWindow( false )
.IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() )
.EnableGammaCorrection( false ) // Scene rendering handles gamma correction
.EnableBlending( true )
]
+SOverlay::Slot()
.Padding(PreviewTextPadding)
.HAlign(HAlign_Center)
[
SNew( STextBlock )
.Text( this, &SActorPreview::OnReadText )
.Font( FSlateFontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 10 ) )
.ShadowOffset( FVector2D::UnitVector )
.WrapTextAt( this, &SActorPreview::OnReadTextWidth )
]
]
]
]
+SOverlay::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Bottom)
.Padding( 24.0f )
[
// Create a button to pin/unpin this viewport
SNew( SButton )
.ContentPadding(0)
.ForegroundColor( FSlateColor::UseForeground() )
.ButtonStyle( FEditorStyle::Get(), "ToggleButton" )
.IsFocusable(false)
[
SNew( SImage )
.Visibility( EVisibility::Visible )
.Image( this, &SActorPreview::GetPinButtonIconBrush )
]
// Bind the button's "on clicked" event to our object's method for this
.OnClicked( this, &SActorPreview::OnTogglePinnedButtonClicked )
.Visibility( EVisibility::Visible )
// Pass along the block's tool-tip string
.ToolTipText( this, &SActorPreview::GetPinButtonToolTipText )
]
]
];
// Setup animation curve for fading in and out. Note that we add a bit of lead-in time on the fade-in
// to avoid hysteresis as the user moves the mouse over the view
{
/** The amount of time to wait before fading in after the mouse leaves */
const float TimeBeforeFadingIn = 0.5f;
/** The amount of time spent actually fading in or out */
const float FadeTime = 0.25f;
FadeSequence = FCurveSequence( TimeBeforeFadingIn, FadeTime );
// Start fading in!
FadeSequence.Play(this->AsShared(), false, TimeBeforeFadingIn); // Skip the initial time delay and just fade straight in
}
HighlightSequence = FCurveSequence(0.f, 0.5f, ECurveEaseFunction::Linear);
PreviewActorPtr = InArgs._PreviewActor;
ParentViewport = InArgs._ParentViewport;
}
FReply SActorPreview::OnTogglePinnedButtonClicked()
{
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
if (ParentViewportPtr.IsValid())
{
ParentViewportPtr->ToggleActorPreviewIsPinned(PreviewActorPtr);
}
return FReply::Handled();
}
const FSlateBrush* SActorPreview::GetPinButtonIconBrush() const
{
const FSlateBrush* IconBrush = NULL;
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
if (ParentViewportPtr.IsValid())
{
if ( ParentViewportPtr->IsActorPreviewPinned(PreviewActorPtr) )
{
IconBrush = FEditorStyle::GetBrush( "ViewportActorPreview.Pinned" );
}
else
{
IconBrush = FEditorStyle::GetBrush( "ViewportActorPreview.Unpinned" );
}
}
return IconBrush;
}
FText SActorPreview::GetPinButtonToolTipText() const
{
FText CurrentToolTipText = LOCTEXT("PinPreviewActorTooltip", "Pin Preview");
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
if (ParentViewportPtr.IsValid())
{
if ( ParentViewportPtr->IsActorPreviewPinned(PreviewActorPtr) )
{
CurrentToolTipText = LOCTEXT("UnpinPreviewActorTooltip", "Unpin Preview");
}
}
return CurrentToolTipText;
}
void SActorPreview::OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
SCompoundWidget::OnMouseEnter( MyGeometry, MouseEvent );
// The viewport could potentially be moved around inside the toolbar when the mouse is captured
// If that is the case we do not play the fade transition
if( !FSlateApplication::Get().IsUsingHighPrecisionMouseMovment() )
{
if( FadeSequence.IsPlaying() )
{
if( FadeSequence.IsForward() )
{
// Fade in is already playing so just force the fade out curve to the end so we don't have a "pop"
// effect from quickly resetting the alpha
FadeSequence.JumpToStart();
}
}
else
{
FadeSequence.PlayReverse(this->AsShared());
}
}
}
void SActorPreview::OnMouseLeave( const FPointerEvent& MouseEvent )
{
SCompoundWidget::OnMouseLeave( MouseEvent );
// The viewport could potentially be moved around inside the toolbar when the mouse is captured
// If that is the case we do not play the fade transition
if( !FSlateApplication::Get().IsUsingHighPrecisionMouseMovment() )
{
if( FadeSequence.IsPlaying() )
{
if( FadeSequence.IsInReverse() )
{
FadeSequence.Reverse();
}
}
else
{
FadeSequence.Play(this->AsShared());
}
}
// Now is a good time to check if we need to remove any PreviewActors that might have been un-pinned
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
if (ParentViewportPtr.IsValid())
{
ParentViewportPtr->OnPreviewSelectedCamerasChange();
}
}
FLinearColor SActorPreview::GetColorAndOpacity() const
{
FLinearColor Color = FLinearColor::White;
const float HoveredOpacity = 0.4f;
const float NonHoveredOpacity = 1.0f;
Color.A = FMath::Lerp( HoveredOpacity, NonHoveredOpacity, FadeSequence.GetLerp() );
return Color;
}
void SActorPreview::OnActorSelected(UObject* InActor)
{
if (InActor && InActor == PreviewActorPtr && InActor->IsSelected())
{
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
const bool bIsPreviewPinned = ParentViewportPtr.IsValid() && ParentViewportPtr->IsActorPreviewPinned(PreviewActorPtr);
if (bIsPreviewPinned)
{
Highlight();
}
}
}
void SActorPreview::Highlight()
{
HighlightSequence.JumpToStart();
HighlightSequence.Play(this->AsShared());
}
FSlateColor SActorPreview::GetBorderColorAndOpacity() const
{
FLinearColor Color(0.f, 0.f, 0.f, 0.5f);
if (HighlightSequence.IsPlaying())
{
static const FName SelectionColorName("SelectionColor");
const FLinearColor SelectionColor = FEditorStyle::Get().GetSlateColor(SelectionColorName).GetSpecifiedColor().CopyWithNewOpacity(0.5f);
const float Interp = FMath::Sin(HighlightSequence.GetLerp()*6*PI) / 2 + 1;
Color = FMath::Lerp(SelectionColor, Color, Interp);
}
return Color;
}
FText SActorPreview::OnReadText() const
{
if( PreviewActorPtr.IsValid() )
{
return FText::FromString(PreviewActorPtr.Get()->GetActorLabel());
}
else
{
return FText::GetEmpty();
}
}
FOptionalSize SActorPreview::OnReadWidth() const
{
const float PreviewHeight = OnReadHeight().Get();
// See if the preview actor wants to constrain the aspect ratio first
if (AActor* PreviewActor = PreviewActorPtr.Get())
{
FMinimalViewInfo CameraInfo;
if (SLevelViewport::GetCameraInformationFromActor(PreviewActor, /*out*/ CameraInfo))
{
if (CameraInfo.bConstrainAspectRatio && (CameraInfo.AspectRatio > 0.0f))
{
return PreviewHeight * CameraInfo.AspectRatio;
}
}
}
// Otherwise try to match the parent viewport's aspect ratio
if ( ParentViewport.IsValid() )
{
return PreviewHeight * ParentViewport.Pin()->GetActiveViewport()->GetDesiredAspectRatio();
}
return PreviewHeight * 1.7777f;
}
FOptionalSize SActorPreview::OnReadHeight() const
{
const float MinimumHeight = 32;
// Also used as parent height in case valid parent viewport is not set
const float MaximumHeight = 428;
// Used to make sure default viewport scale * parent viewport height = roughly same size as original windows
const float PreviewScalingFactor = 0.06308f;
float ParentHeight = MaximumHeight;
if ( ParentViewport.IsValid() )
{
ParentHeight = ParentViewport.Pin()->GetActiveViewport()->GetSizeXY().Y;
}
return FMath::Clamp( GetDefault<ULevelEditorViewportSettings>()->CameraPreviewSize * ParentHeight * PreviewScalingFactor, MinimumHeight, MaximumHeight );
}
float SActorPreview::OnReadTextWidth() const
{
return OnReadWidth().Get() - (PreviewTextPadding*2.0f);
}
void SLevelViewport::PreviewActors( const TArray< AActor* >& ActorsToPreview )
{
TArray< AActor* > NewActorsToPreview;
TArray< AActor* > ActorsToStopPreviewing;
// Look for actors that we no longer want to preview
for( auto ActorPreviewIt = ActorPreviews.CreateConstIterator(); ActorPreviewIt; ++ActorPreviewIt )
{
auto ExistingActor = ActorPreviewIt->Actor.Get();
if( ExistingActor != NULL )
{
auto bShouldKeepActor = false;
for( auto ActorIt = ActorsToPreview.CreateConstIterator(); ActorIt; ++ActorIt )
{
auto CurActor = *ActorIt;
if( CurActor != NULL && CurActor == ExistingActor )
{
bShouldKeepActor = true;
break;
}
}
if( !bShouldKeepActor )
{
// We were asked to stop previewing this actor
ActorsToStopPreviewing.AddUnique( ExistingActor );
}
}
}
// Look for any new actors that we aren't previewing already
for( auto ActorIt = ActorsToPreview.CreateConstIterator(); ActorIt; ++ActorIt )
{
auto CurActor = *ActorIt;
// Check to see if we're already previewing this actor. If we are, we'll just skip it
auto bIsAlreadyPreviewed = false;
for( auto ExistingPreviewIt = ActorPreviews.CreateConstIterator(); ExistingPreviewIt; ++ExistingPreviewIt )
{
// There could be null actors in this list as we haven't actually removed them yet.
auto ExistingActor = ExistingPreviewIt->Actor.Get();
if( ExistingActor != NULL && CurActor == ExistingActor )
{
// Already previewing this actor. Ignore it.
bIsAlreadyPreviewed = true;
break;
}
}
if( !bIsAlreadyPreviewed )
{
// This is a new actor that we want to preview. Let's set that up.
NewActorsToPreview.Add( CurActor );
}
}
// Kill any existing actor previews that we don't want or have expired
for( int32 PreviewIndex = 0; PreviewIndex < ActorPreviews.Num(); ++PreviewIndex )
{
AActor* ExistingActor = ActorPreviews[PreviewIndex].Actor.Get();
if ( ExistingActor == NULL )
{
// decrement index so we don't miss next preview after deleting
RemoveActorPreview( PreviewIndex-- );
}
else
{
if ( !ActorPreviews[PreviewIndex].bIsPinned )
{
for( auto ActorIt = ActorsToStopPreviewing.CreateConstIterator(); ActorIt; ++ActorIt )
{
auto CurActor = *ActorIt;
if( ExistingActor == CurActor )
{
// Remove this preview!
// decrement index so we don't miss next preview after deleting
RemoveActorPreview( PreviewIndex-- );
break;
}
}
}
}
}
// Create previews for any actors that we need to
if( NewActorsToPreview.Num() > 0 )
{
for( auto ActorIt = NewActorsToPreview.CreateConstIterator(); ActorIt; ++ActorIt )
{
auto CurActor = *ActorIt;
TSharedPtr< FLevelEditorViewportClient > ActorPreviewLevelViewportClient = MakeShareable( new FLevelEditorViewportClient(SharedThis(this)) );
{
// NOTE: We don't bother setting ViewLocation, ViewRotation, etc, here. This is because we'll call
// PushControllingActorDataToViewportClient() below which will do this!
// ParentLevelEditor is used for summoning context menus, which should never happen for these preview
// viewports, but we'll keep the relationship intact anyway.
ActorPreviewLevelViewportClient->ParentLevelEditor = ParentLevelEditor.Pin();
ActorPreviewLevelViewportClient->ViewportType = LVT_Perspective;
ActorPreviewLevelViewportClient->bSetListenerPosition = false; // Preview viewports never be a listener
// Never draw the axes indicator in these small viewports
ActorPreviewLevelViewportClient->bDrawAxes = false;
// Default to "game" show flags for camera previews
// Still draw selection highlight though
ActorPreviewLevelViewportClient->EngineShowFlags = FEngineShowFlags(ESFIM_Game);
ActorPreviewLevelViewportClient->EngineShowFlags.SetSelection(true);
ActorPreviewLevelViewportClient->LastEngineShowFlags = FEngineShowFlags(ESFIM_Editor);
// We don't use view modes for preview viewports
ActorPreviewLevelViewportClient->SetViewMode( VMI_Unknown );
// User should never be able to interact with this viewport
ActorPreviewLevelViewportClient->bDisableInput = true;
// Never allow Matinee to possess these views
ActorPreviewLevelViewportClient->SetAllowCinematicPreview( false );
// Our preview viewport is always visible if our owning SLevelViewport is visible, so we hook up
// to the same IsVisible method
ActorPreviewLevelViewportClient->VisibilityDelegate.BindSP( this, &SLevelViewport::IsVisible );
// Push actor transform to view. From here on out, this will happen automatically in FLevelEditorViewportClient::Tick.
// The reason we allow the viewport client to update this is to avoid off-by-one-frame issues when dragging actors around.
ActorPreviewLevelViewportClient->SetActorLock( CurActor );
ActorPreviewLevelViewportClient->UpdateViewForLockedActor();
}
TSharedPtr< SActorPreview > ActorPreviewWidget = SNew(SActorPreview)
.PreviewActor(CurActor)
.ParentViewport(SharedThis(this));
auto ActorPreviewViewportWidget = ActorPreviewWidget->GetViewportWidget();
TSharedPtr< FSceneViewport > ActorPreviewSceneViewport = MakeShareable( new FSceneViewport( ActorPreviewLevelViewportClient.Get(), ViewportWidget ) );
{
ActorPreviewLevelViewportClient->Viewport = ActorPreviewSceneViewport.Get();
ActorPreviewViewportWidget->SetViewportInterface( ActorPreviewSceneViewport.ToSharedRef() );
}
FViewportActorPreview& NewActorPreview = *new( ActorPreviews ) FViewportActorPreview;
NewActorPreview.Actor = CurActor;
NewActorPreview.LevelViewportClient = ActorPreviewLevelViewportClient;
NewActorPreview.SceneViewport = ActorPreviewSceneViewport;
NewActorPreview.PreviewWidget = ActorPreviewWidget;
NewActorPreview.bIsPinned = false;
// Add our new widget to our viewport's overlay
// @todo camerapip: Consider using a canvas instead of an overlay widget -- our viewports get SQUASHED when the view shrinks!
ActorPreviewHorizontalBox->AddSlot()
.AutoWidth()
[
ActorPreviewWidget.ToSharedRef()
];
}
// OK, at least one new preview viewport was added, so update settings for all views immediately.
// This will also be repeated every time the SLevelViewport is ticked, just to make sure that
// feature such as "real-time" mode stay in sync.
UpdateActorPreviewViewports();
}
}
void SLevelViewport::ToggleActorPreviewIsPinned(TWeakObjectPtr<AActor> ActorToTogglePinned)
{
if (ActorToTogglePinned.IsValid())
{
AActor* ActorToTogglePinnedPtr = ActorToTogglePinned.Get();
for (FViewportActorPreview& ActorPreview : ActorPreviews)
{
if ( ActorPreview.Actor.IsValid() )
{
if ( ActorToTogglePinnedPtr == ActorPreview.Actor.Get() )
{
ActorPreview.ToggleIsPinned();
}
}
}
}
}
bool SLevelViewport::IsActorPreviewPinned( TWeakObjectPtr<AActor> PreviewActor )
{
if (PreviewActor.IsValid())
{
AActor* PreviewActorPtr = PreviewActor.Get();
for (FViewportActorPreview& ActorPreview : ActorPreviews)
{
if ( ActorPreview.Actor.IsValid() )
{
if ( PreviewActorPtr == ActorPreview.Actor.Get() )
{
return ActorPreview.bIsPinned;
}
}
}
}
return false;
}
void SLevelViewport::UpdateActorPreviewViewports()
{
// Remove any previews that are locked to the same actor as the level viewport client's actor lock
for( int32 PreviewIndex = 0; PreviewIndex < ActorPreviews.Num(); ++PreviewIndex )
{
AActor* ExistingActor = ActorPreviews[PreviewIndex].Actor.Get();
if (ExistingActor && LevelViewportClient->IsActorLocked(ExistingActor))
{
RemoveActorPreview( PreviewIndex-- );
}
}
// Look for actors that we no longer want to preview
for( auto ActorPreviewIt = ActorPreviews.CreateConstIterator(); ActorPreviewIt; ++ActorPreviewIt )
{
auto& CurActorPreview = *ActorPreviewIt;
CurActorPreview.LevelViewportClient->SetRealtime( LevelViewportClient->IsRealtime() );
CurActorPreview.LevelViewportClient->bDrawBaseInfo = LevelViewportClient->bDrawBaseInfo;
CurActorPreview.LevelViewportClient->bDrawVertices = LevelViewportClient->bDrawVertices;
CurActorPreview.LevelViewportClient->EngineShowFlags.SetSelectionOutline(LevelViewportClient->EngineShowFlags.SelectionOutline);
CurActorPreview.LevelViewportClient->EngineShowFlags.SetCompositeEditorPrimitives(LevelViewportClient->EngineShowFlags.CompositeEditorPrimitives);
}
}
void SLevelViewport::OnPreviewSelectedCamerasChange()
{
// Check to see if previewing selected cameras is enabled and if we're the active level viewport client.
if (GetDefault<ULevelEditorViewportSettings>()->bPreviewSelectedCameras && GCurrentLevelEditingViewportClient == LevelViewportClient.Get())
{
PreviewSelectedCameraActors();
}
else
{
// We're either not the active viewport client or preview selected cameras option is disabled, so remove any existing previewed actors
PreviewActors(TArray<AActor*>());
}
}
void SLevelViewport::SetDeviceProfileString( const FString& ProfileName )
{
DeviceProfile = ProfileName;
}
bool SLevelViewport::IsDeviceProfileStringSet( FString ProfileName ) const
{
return DeviceProfile == ProfileName;
}
FString SLevelViewport::GetDeviceProfileString( ) const
{
return DeviceProfile;
}
FText SLevelViewport::GetCurrentFeatureLevelPreviewText( bool bDrawOnlyLabel ) const
{
FText LabelName;
FText FeatureLevelText;
if (bDrawOnlyLabel)
{
LabelName = LOCTEXT("FeatureLevelLabel", "Feature Level:");
}
else
{
auto* World = GetWorld();
if (World != nullptr)
{
const auto FeatureLevel = World->FeatureLevel;
if (FeatureLevel != GMaxRHIFeatureLevel)
{
FName FeatureLevelName;
GetFeatureLevelName(FeatureLevel, FeatureLevelName);
FeatureLevelText = FText::Format(LOCTEXT("FeatureLevel", "{0}"), FText::FromName(FeatureLevelName));
}
}
}
if (bDrawOnlyLabel)
{
return LabelName;
}
return FeatureLevelText;
}
FText SLevelViewport::GetCurrentLevelText( bool bDrawOnlyLabel ) const
{
// Display the current level and current level grid volume in the status bar
FText LabelName;
FText CurrentLevelName;
if( ActiveViewport.IsValid() && (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) && GetWorld() && GetWorld()->GetCurrentLevel() != nullptr )
{
if( ActiveViewport->GetPlayInEditorIsSimulate() || !ActiveViewport->GetClient()->GetWorld()->IsGameWorld() )
{
if(bDrawOnlyLabel)
{
LabelName = LOCTEXT("CurrentLevelLabel", "Level:");
}
else
{
// Get the level name (without the number at the end)
FText ActualLevelName = FText::FromString(FPackageName::GetShortFName(GetWorld()->GetCurrentLevel()->GetOutermost()->GetFName()).GetPlainNameString());
if(GetWorld()->GetCurrentLevel() == GetWorld()->PersistentLevel)
{
FFormatNamedArguments Args;
Args.Add(TEXT("ActualLevelName"), ActualLevelName);
CurrentLevelName = FText::Format(LOCTEXT("LevelName", "{0} (Persistent)"), ActualLevelName);
}
else
{
CurrentLevelName = ActualLevelName;
}
}
if(bDrawOnlyLabel)
{
return LabelName;
}
}
}
return CurrentLevelName;
}
EVisibility SLevelViewport::GetCurrentLevelTextVisibility() const
{
EVisibility ContentVisibility = OnGetViewportContentVisibility();
if (ContentVisibility == EVisibility::Visible)
{
ContentVisibility = EVisibility::SelfHitTestInvisible;
}
return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) ? ContentVisibility : EVisibility::Collapsed;
}
EVisibility SLevelViewport::GetCurrentFeatureLevelPreviewTextVisibility() const
{
if (GetWorld())
{
return (GetWorld()->FeatureLevel != GMaxRHIFeatureLevel) ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed;
}
else
{
return EVisibility::Collapsed;
}
}
EVisibility SLevelViewport::GetViewportControlsVisibility() const
{
// Do not show the controls if this viewport has a play in editor session
// or is not the current viewport
return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient && !IsPlayInEditorViewportActive()) ? OnGetViewportContentVisibility() : EVisibility::Collapsed;
}
void SLevelViewport::OnSetViewportConfiguration(FName ConfigurationName)
{
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
if (LayoutPinned.IsValid())
{
TSharedPtr<FLevelViewportTabContent> ViewportTabPinned = LayoutPinned->GetParentTabContent().Pin();
if (ViewportTabPinned.IsValid())
{
// Viewport clients are going away. Any current one is invalid.
GCurrentLevelEditingViewportClient = nullptr;
ViewportTabPinned->SetViewportConfiguration(ConfigurationName);
FSlateApplication::Get().DismissAllMenus();
}
}
}
bool SLevelViewport::IsViewportConfigurationSet(FName ConfigurationName) const
{
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
if (LayoutPinned.IsValid())
{
TSharedPtr<FLevelViewportTabContent> ViewportTabPinned = LayoutPinned->GetParentTabContent().Pin();
if (ViewportTabPinned.IsValid())
{
return ViewportTabPinned->IsViewportConfigurationSet(ConfigurationName);
}
}
return false;
}
FName SLevelViewport::GetViewportTypeWithinLayout() const
{
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
if (LayoutPinned.IsValid() && !ConfigKey.IsEmpty())
{
TSharedPtr<IViewportLayoutEntity> Entity = LayoutPinned->GetViewports().FindRef(*ConfigKey);
if (Entity.IsValid())
{
return Entity->GetType();
}
}
return "Default";
}
void SLevelViewport::SetViewportTypeWithinLayout(FName InLayoutType)
{
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
if (LayoutPinned.IsValid() && !ConfigKey.IsEmpty())
{
// Important - RefreshViewportConfiguration does not save config values. We save its state first, to ensure that .TypeWithinLayout (below) doesn't get overwritten
TSharedPtr<FLevelViewportTabContent> ViewportTabPinned = LayoutPinned->GetParentTabContent().Pin();
if (ViewportTabPinned.IsValid())
{
ViewportTabPinned->SaveConfig();
}
const FString& IniSection = FLayoutSaveRestore::GetAdditionalLayoutConfigIni();
GConfig->SetString( *IniSection, *( ConfigKey + TEXT(".TypeWithinLayout") ), *InLayoutType.ToString(), GEditorPerProjectIni );
// Force a refresh of the tab content
// Viewport clients are going away. Any current one is invalid.
GCurrentLevelEditingViewportClient = nullptr;
ViewportTabPinned->RefreshViewportConfiguration();
FSlateApplication::Get().DismissAllMenus();
}
}
void SLevelViewport::ToggleViewportTypeActivationWithinLayout(FName InLayoutType)
{
if (GetViewportTypeWithinLayout() != InLayoutType)
{
SetViewportTypeWithinLayout(InLayoutType);
}
}
bool SLevelViewport::IsViewportTypeWithinLayoutEqual(FName InLayoutType)
{
return GetViewportTypeWithinLayout() == InLayoutType;
}
void SLevelViewport::StartPlayInEditorSession(UGameViewportClient* PlayClient, const bool bInSimulateInEditor)
{
check( !HasPlayInEditorViewport() );
check( !InactiveViewport.IsValid() );
// Ensure our active viewport is for level editing
check( ActiveViewport->GetClient() == LevelViewportClient.Get() );
// Save camera settings that may be adversely affected by PIE, so that they may be restored later
LevelViewportClient->PrepareCameraForPIE();
// Here we will swap the editor viewport client out for the client for the play in editor session
InactiveViewport = ActiveViewport;
// Store the content in the viewport widget (editor tool bar etc) so we can show the game UI content if it has any
InactiveViewportWidgetEditorContent = ViewportWidget->GetContent();
// Remove keyboard focus to send a focus lost message to the widget to clean up any saved state from the viewport interface thats about to be swapped out
// Focus will be set when the game viewport is registered
FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::SetDirectly);
// Attach global play world actions widget to view port
ActiveViewport = MakeShareable( new FSceneViewport( PlayClient, ViewportWidget) );
ActiveViewport->SetPlayInEditorViewport( true );
// Whether to start with the game taking mouse control or leaving it shown in the editor
ActiveViewport->SetPlayInEditorGetsMouseControl(GetDefault<ULevelEditorPlaySettings>()->GameGetsMouseControl);
ActiveViewport->SetPlayInEditorIsSimulate(bInSimulateInEditor);
ActiveViewport->OnPlayWorldViewportSwapped( *InactiveViewport );
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared());
PlayClient->SetViewportOverlayWidget(ParentWindow, PIEViewportOverlayWidget.ToSharedRef());
PlayClient->SetGameLayerManager(GameLayerManager);
// Our viewport widget should start rendering the new viewport for the play in editor scene
ViewportWidget->SetViewportInterface( ActiveViewport.ToSharedRef() );
// Let the viewport client know what viewport it is associated with
PlayClient->Viewport = ActiveViewport.Get();
// Register the new viewport widget with Slate for viewport specific message routing.
FSlateApplication::Get().RegisterGameViewport(ViewportWidget.ToSharedRef() );
ULevelEditorPlaySettings const* EditorPlayInSettings = GetDefault<ULevelEditorPlaySettings>();
check(EditorPlayInSettings);
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::StartingPlayInEditor;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
if (EditorPlayInSettings->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/GamePreview/StartPlayInEditor_Cue.StartPlayInEditor_Cue"));
}
bPIEHasFocus = ActiveViewport->HasMouseCapture();
if(EditorPlayInSettings->ShowMouseControlLabel && !GEngine->IsStereoscopic3D( ActiveViewport.Get() ) )
{
ELabelAnchorMode AnchorMode = EditorPlayInSettings->MouseControlLabelPosition.GetValue();
ShowMouseCaptureLabel(AnchorMode);
}
GEngine->BroadcastLevelActorListChanged();
}
EVisibility SLevelViewport::GetMouseCaptureLabelVisibility() const
{
if (GEditor->PlayWorld)
{
// Show the label if the local player's PC isn't set to show the cursor
auto const TargetPlayer = GEngine->GetLocalPlayerFromControllerId(GEditor->PlayWorld, 0);
if (TargetPlayer && TargetPlayer->PlayerController && !TargetPlayer->PlayerController->bShowMouseCursor)
{
return EVisibility::HitTestInvisible;
}
}
return EVisibility::Collapsed;
}
FLinearColor SLevelViewport::GetMouseCaptureLabelColorAndOpacity() const
{
static const FName DefaultForegroundName("DefaultForeground");
FSlateColor SlateColor = FEditorStyle::GetSlateColor(DefaultForegroundName);
FLinearColor Col = SlateColor.IsColorSpecified() ? SlateColor.GetSpecifiedColor() : FLinearColor::White;
float Alpha = 0.0f;
if(ViewTransitionAnim.IsPlaying() && ViewTransitionType == EViewTransition::StartingPlayInEditor)
{
Alpha = ViewTransitionAnim.GetLerp();
}
else if(PIEOverlayAnim.IsPlaying())
{
Alpha = 1.0f - PIEOverlayAnim.GetLerp();
}
return Col.CopyWithNewOpacity(Alpha);
}
FText SLevelViewport::GetMouseCaptureLabelText() const
{
if(ActiveViewport->HasMouseCapture())
{
// Default Shift+F1 if a valid chord is not found
static FInputChord Chord(EKeys::F1, EModifierKey::Shift);
TSharedPtr<FUICommandInfo> UICommand = FInputBindingManager::Get().FindCommandInContext(TEXT("PlayWorld"), TEXT("GetMouseControl"));
if (UICommand.IsValid())
{
TSharedRef<const FInputChord> ActiveChord = UICommand->GetActiveChord();
Chord = ActiveChord.Get();
}
FFormatNamedArguments Args;
Args.Add(TEXT("InputText"), Chord.GetInputText());
return FText::Format( LOCTEXT("ShowMouseCursorLabel", "{InputText} for Mouse Cursor"), Args );
}
else
{
return LOCTEXT("GameMouseControlLabel", "Click for Mouse Control");
}
}
void SLevelViewport::ShowMouseCaptureLabel(ELabelAnchorMode AnchorMode)
{
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopLeft / 3) + 1) == EVerticalAlignment::VAlign_Top && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopLeft % 3) + 1) == EHorizontalAlignment::HAlign_Left, "Alignment from ELabelAnchorMode error.");
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopCenter / 3) + 1) == EVerticalAlignment::VAlign_Top && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopCenter % 3) + 1) == EHorizontalAlignment::HAlign_Center, "Alignment from ELabelAnchorMode error.");
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopRight / 3) + 1) == EVerticalAlignment::VAlign_Top && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopRight % 3) + 1) == EHorizontalAlignment::HAlign_Right, "Alignment from ELabelAnchorMode error.");
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_CenterLeft / 3) + 1) == EVerticalAlignment::VAlign_Center && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_CenterLeft % 3) + 1) == EHorizontalAlignment::HAlign_Left, "Alignment from ELabelAnchorMode error.");
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_Centered / 3) + 1) == EVerticalAlignment::VAlign_Center && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_Centered % 3) + 1) == EHorizontalAlignment::HAlign_Center, "Alignment from ELabelAnchorMode error.");
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_CenterRight / 3) + 1) == EVerticalAlignment::VAlign_Center && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_CenterRight % 3) + 1) == EHorizontalAlignment::HAlign_Right, "Alignment from ELabelAnchorMode error.");
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomLeft / 3) + 1) == EVerticalAlignment::VAlign_Bottom && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomLeft % 3) + 1) == EHorizontalAlignment::HAlign_Left, "Alignment from ELabelAnchorMode error.");
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomCenter / 3) + 1) == EVerticalAlignment::VAlign_Bottom && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomCenter % 3) + 1) == EHorizontalAlignment::HAlign_Center, "Alignment from ELabelAnchorMode error.");
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomRight / 3) + 1) == EVerticalAlignment::VAlign_Bottom && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomRight % 3) + 1) == EHorizontalAlignment::HAlign_Right, "Alignment from ELabelAnchorMode error.");
EVerticalAlignment VAlign = (EVerticalAlignment)((AnchorMode/3)+1);
EHorizontalAlignment HAlign = (EHorizontalAlignment)((AnchorMode%3)+1);
SOverlay::FOverlaySlot& Slot = ViewportOverlay->AddSlot();
PIEOverlaySlotIndex = Slot.ZOrder;
Slot.HAlign(HAlign)
.VAlign(VAlign)
[
SNew( SBorder )
.BorderImage( FEditorStyle::GetBrush("NoBorder") )
.Visibility(this, &SLevelViewport::GetMouseCaptureLabelVisibility)
.ColorAndOpacity( this, &SLevelViewport::GetMouseCaptureLabelColorAndOpacity )
.ForegroundColor( FLinearColor::White )
.Padding(15.0f)
[
SNew( SButton )
.ButtonStyle( FEditorStyle::Get(), "EditorViewportToolBar.MenuButton" )
.IsFocusable(false)
.ButtonColorAndOpacity( FSlateColor(FLinearColor::Black) )
.ForegroundColor( FLinearColor::White )
[
SNew( SHorizontalBox )
+ SHorizontalBox::Slot()
.MaxWidth(32.f)
.VAlign(VAlign_Center)
.Padding(0.0f, 2.0f, 2.0f, 2.0f)
[
SNew( SVerticalBox )
+ SVerticalBox::Slot()
.MaxHeight(16.f)
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("LevelViewport.CursorIcon"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.Padding(2.0f, 2.0f)
[
SNew(STextBlock)
.Text(this, &SLevelViewport::GetMouseCaptureLabelText)
.Font( FSlateFontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 9 ) )
.ColorAndOpacity(FLinearColor::White)
]
]
]
];
}
void SLevelViewport::HideMouseCaptureLabel()
{
ViewportOverlay->RemoveSlot(PIEOverlaySlotIndex);
PIEOverlaySlotIndex = 0;
}
void SLevelViewport::ResetNewLevelViewFlags()
{
const bool bUseSavedDefaults = true;
OnUseDefaultShowFlags(bUseSavedDefaults);
}
void SLevelViewport::EndPlayInEditorSession()
{
check( HasPlayInEditorViewport() );
FSlateApplication::Get().UnregisterGameViewport();
check( InactiveViewport.IsValid() );
if( IsPlayInEditorViewportActive() )
{
{
TSharedPtr<FSceneViewport> GameViewport = ActiveViewport;
ActiveViewport = InactiveViewport;
ActiveViewport->OnPlayWorldViewportSwapped( *GameViewport );
// Play in editor viewport was active, swap back to our level editor viewport
GameViewport->SetViewportClient( nullptr );
// We should be the only thing holding on to viewports
check( GameViewport.IsUnique() );
}
// Ensure our active viewport is for level editing
check( ActiveViewport->GetClient() == LevelViewportClient.Get() );
// If we're going back to VR Editor, refresh the level viewport's render target so the HMD will present frames here
if( GEngine->IsStereoscopic3D( ActiveViewport.Get() ) )
{
ActiveViewport->UpdateViewportRHI( false, ActiveViewport->GetSizeXY().X, ActiveViewport->GetSizeXY().Y, ActiveViewport->GetWindowMode(), PF_Unknown );
}
else
{
// Restore camera settings that may be adversely affected by PIE
LevelViewportClient->RestoreCameraFromPIE();
RedrawViewport(true);
// Remove camera roll from any PIE camera applied in this viewport. A rolled camera is hard to use for editing
LevelViewportClient->RemoveCameraRoll();
}
}
else
{
InactiveViewport->SetViewportClient( nullptr );
}
// Reset the inactive viewport
InactiveViewport.Reset();
// Viewport widget should begin drawing the editor viewport
ViewportWidget->SetViewportInterface( ActiveViewport.ToSharedRef() );
ViewportWidget->SetContent( InactiveViewportWidgetEditorContent );
// No longer need to store the content
InactiveViewportWidgetEditorContent.Reset();
if(PIEOverlaySlotIndex)
{
HideMouseCaptureLabel();
}
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::ReturningToEditor;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
if (GetDefault<ULevelEditorPlaySettings>()->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/EndPlayInEditor_Cue.EndPlayInEditor_Cue" ) );
}
GEngine->BroadcastLevelActorListChanged();
}
void SLevelViewport::SwapViewportsForSimulateInEditor()
{
// Ensure our active viewport was the play in editor viewport
check( IsPlayInEditorViewportActive() );
// Remove the mouse control label - not relevant for SIE
if(PIEOverlaySlotIndex)
{
HideMouseCaptureLabel();
}
// Unregister the game viewport with slate which will release mouse capture and lock
FSlateApplication::Get().UnregisterGameViewport();
// Swap between the active and inactive viewport
TSharedPtr<FSceneViewport> TempViewport = ActiveViewport;
ActiveViewport = InactiveViewport;
InactiveViewport = TempViewport;
ViewportWidget->SetContent( InactiveViewportWidgetEditorContent );
// Resize the viewport to be the same size the previously active viewport
// When starting in immersive mode its possible that the viewport has not been resized yet
ActiveViewport->OnPlayWorldViewportSwapped( *InactiveViewport );
ViewportWidget->SetViewportInterface( ActiveViewport.ToSharedRef() );
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::StartingSimulate;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/PossessPlayer_Cue.PossessPlayer_Cue" ) );
}
void SLevelViewport::SwapViewportsForPlayInEditor()
{
// Ensure our inactive viewport was the play in editor viewport
check( !IsPlayInEditorViewportActive() && HasPlayInEditorViewport() );
// Put the mouse control label up again.
ULevelEditorPlaySettings const* EditorPlayInSettings = GetDefault<ULevelEditorPlaySettings>();
check(EditorPlayInSettings);
if(EditorPlayInSettings->ShowMouseControlLabel && !GEngine->IsStereoscopic3D( ActiveViewport.Get() ) )
{
ELabelAnchorMode AnchorMode = EditorPlayInSettings->MouseControlLabelPosition.GetValue();
ShowMouseCaptureLabel(AnchorMode);
}
// Swap between the active and inactive viewport
TSharedPtr<FSceneViewport> TempViewport = ActiveViewport;
ActiveViewport = InactiveViewport;
InactiveViewport = TempViewport;
// Resize the viewport to be the same size the previously active viewport
// When starting in immersive mode its possible that the viewport has not been resized yet
ActiveViewport->OnPlayWorldViewportSwapped( *InactiveViewport );
InactiveViewportWidgetEditorContent = ViewportWidget->GetContent();
ViewportWidget->SetViewportInterface( ActiveViewport.ToSharedRef() );
// Register the game viewport with slate which will capture the mouse and lock it to the viewport
FSlateApplication::Get().RegisterGameViewport( ViewportWidget.ToSharedRef() );
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::StartingPlayInEditor;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
if (EditorPlayInSettings->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/EjectFromPlayer_Cue.EjectFromPlayer_Cue" ) );
}
}
void SLevelViewport::OnSimulateSessionStarted()
{
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::StartingSimulate;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
if (GetDefault<ULevelEditorPlaySettings>()->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/StartSimulate_Cue.StartSimulate_Cue" ) );
}
// Make sure the viewport's hit proxies are invalidated. If not done, clicking in the viewport could select an editor world actor
ActiveViewport->InvalidateHitProxy();
}
void SLevelViewport::OnSimulateSessionFinished()
{
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::ReturningToEditor;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
if (GetDefault<ULevelEditorPlaySettings>()->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/EndSimulate_Cue.EndSimulate_Cue" ) );
}
// Make sure the viewport's hit proxies are invalidated. If not done, clicking in the viewport could select a pie world actor
ActiveViewport->InvalidateHitProxy();
}
EVisibility SLevelViewport::GetLockedIconVisibility() const
{
return IsAnyActorLocked() ? EVisibility::Visible : EVisibility::Collapsed;
}
FText SLevelViewport::GetLockedIconToolTip() const
{
FText ToolTipText;
if (IsAnyActorLocked())
{
ToolTipText = FText::Format( LOCTEXT("ActorLockedIcon_ToolTip", "Viewport Locked to {0}"), FText::FromString( LevelViewportClient->GetActiveActorLock().Get()->GetActorLabel() ) );
}
return ToolTipText;
}
UWorld* SLevelViewport::GetWorld() const
{
return ParentLevelEditor.IsValid() ? ParentLevelEditor.Pin()->GetWorld() : NULL;
}
void SLevelViewport::RemoveActorPreview( int32 PreviewIndex )
{
// Remove widget from viewport overlay
ActorPreviewHorizontalBox->RemoveSlot( ActorPreviews[PreviewIndex].PreviewWidget.ToSharedRef() );
// Clean up our level viewport client
if( ActorPreviews[PreviewIndex].LevelViewportClient.IsValid() )
{
ActorPreviews[PreviewIndex].LevelViewportClient->Viewport = NULL;
}
// Remove from our list of actor previews. This will destroy our level viewport client and viewport widget.
ActorPreviews.RemoveAt( PreviewIndex );
}
void SLevelViewport::AddOverlayWidget(TSharedRef<SWidget> OverlaidWidget)
{
ViewportOverlay->AddSlot()
[
OverlaidWidget
];
}
void SLevelViewport::RemoveOverlayWidget(TSharedRef<SWidget> OverlaidWidget)
{
ViewportOverlay->RemoveSlot(OverlaidWidget);
}
bool SLevelViewport::CanProduceActionForCommand(const TSharedRef<const FUICommandInfo>& Command) const
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
TSharedPtr<ILevelViewport> ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport();
if ( ActiveLevelViewport.IsValid() )
{
return ActiveLevelViewport == SharedThis(this);
}
return false;
}
void SLevelViewport::LockActorInternal(AActor* NewActorToLock)
{
if (NewActorToLock != NULL)
{
LevelViewportClient->SetActorLock(NewActorToLock);
if (LevelViewportClient->IsPerspective() && LevelViewportClient->GetActiveActorLock().IsValid())
{
LevelViewportClient->MoveCameraToLockedActor();
}
}
// Make sure the inset preview is closed if we are locking a camera that was already part of the selection set and thus being previewed.
OnPreviewSelectedCamerasChange();
}
bool SLevelViewport::GetCameraInformationFromActor(AActor* Actor, FMinimalViewInfo& out_CameraInfo)
{
// @todo camerapip: Could support actors other than cameras too! (Character views?)
//@TODO: CAMERA: Support richer camera interactions in SIE; this may shake out naturally if everything uses camera components though
TArray<UCameraComponent*> CamComps;
Actor->GetComponents<UCameraComponent>(CamComps);
for (UCameraComponent* CamComp : CamComps)
{
if (CamComp->bIsActive)
{
// first active camera, use it and be done
CamComp->GetCameraView(0.0f, out_CameraInfo);
return true;
}
}
// see if any actors are attached to us, directly or indirectly, that have an active camera component we might want to use
// #note: assumption here that attachment cannot be circular
TArray<AActor*> AttachedActors;
Actor->GetAttachedActors(AttachedActors);
for (AActor* AttachedActor : AttachedActors)
{
if (GetCameraInformationFromActor(AttachedActor, out_CameraInfo))
{
return true;
}
}
// no active cameras
return false;
}
bool SLevelViewport::CanGetCameraInformationFromActor(AActor* Actor)
{
FMinimalViewInfo CameraInfo;
return GetCameraInformationFromActor(Actor, /*out*/ CameraInfo);
}
void SLevelViewport::TakeHighResScreenShot()
{
if( LevelViewportClient.IsValid() )
{
LevelViewportClient->TakeHighResScreenShot();
}
}
void SLevelViewport::OnFloatingButtonClicked()
{
// if one of the viewports floating buttons has been clicked, update the global viewport ptr
LevelViewportClient->SetLastKeyViewport();
}
#undef LOCTEXT_NAMESPACE