Files
UnrealEngineUWP/Engine/Source/Runtime/Slate/Private/Framework/Application/SlateApplication.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

6633 lines
219 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "Framework/Application/SlateApplication.h"
#include "Rendering/SlateDrawBuffer.h"
#include "Misc/CommandLine.h"
#include "Misc/ScopeLock.h"
#include "Misc/TimeGuard.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/CoreDelegates.h"
#include "Misc/App.h"
#include "Modules/ModuleManager.h"
#include "InputCoreModule.h"
#include "Layout/LayoutUtils.h"
#include "Sound/ISlateSoundDevice.h"
#include "Sound/NullSlateSoundDevice.h"
#include "Framework/Text/PlatformTextField.h"
#include "Framework/Application/NavigationConfig.h"
#include "Widgets/SWeakWidget.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/SToolTip.h"
#include "Widgets/SViewport.h"
#include "Framework/Application/SWindowTitleBar.h"
#include "Input/HittestGrid.h"
#include "Stats/SlateStats.h"
#include "HardwareCursor.h"
#include "Framework/Application/IWidgetReflector.h"
#include "Framework/Commands/GenericCommands.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Framework/Application/IInputProcessor.h"
#include "GenericPlatform/ITextInputMethodSystem.h"
#include "ToolboxModule.h"
#include "Framework/Docking/TabCommands.h"
#define SLATE_HAS_WIDGET_REFLECTOR !UE_BUILD_SHIPPING || PLATFORM_DESKTOP
#if PLATFORM_WINDOWS
#include "WindowsHWrapper.h"
#endif
extern SLATECORE_API TOptional<FShortRect> GSlateScissorRect;
class FEventRouter
{
// @todo slate : making too many event copies when translating events( i.e. Translate<EventType>::PointerEvent ).
// @todo slate : Widget Reflector should log: (1) Every process reply (2) Every time the event is handled and by who.
// @todo slate : Remove remaining [&]-style mass captures.
// @todo slate : Eliminate all ad-hoc uses of SetEventPath()
// @todo slate : Remove CALL_WIDGET_FUNCTION
public:
class FDirectPolicy
{
public:
FDirectPolicy( const FWidgetAndPointer& InTarget, const FWidgetPath& InRoutingPath )
: bEventSent(false)
, RoutingPath(InRoutingPath)
, Target(InTarget)
{
}
bool ShouldKeepGoing() const
{
return !bEventSent;
}
void Next()
{
bEventSent = true;
}
FWidgetAndPointer GetWidget() const
{
return Target;
}
const FWidgetPath& GetRoutingPath() const
{
return RoutingPath;
}
private:
bool bEventSent;
const FWidgetPath& RoutingPath;
const FWidgetAndPointer& Target;
};
class FToLeafmostPolicy
{
public:
FToLeafmostPolicy( const FWidgetPath& InRoutingPath )
: bEventSent(false)
, RoutingPath(InRoutingPath)
{
}
bool ShouldKeepGoing() const
{
return !bEventSent && RoutingPath.Widgets.Num() > 0;
}
void Next()
{
bEventSent = true;
}
FWidgetAndPointer GetWidget() const
{
const int32 WidgetIndex = RoutingPath.Widgets.Num()-1;
return FWidgetAndPointer(RoutingPath.Widgets[WidgetIndex], RoutingPath.VirtualPointerPositions[WidgetIndex]);
}
const FWidgetPath& GetRoutingPath() const
{
return RoutingPath;
}
private:
bool bEventSent;
const FWidgetPath& RoutingPath;
};
class FTunnelPolicy
{
public:
FTunnelPolicy( const FWidgetPath& InRoutingPath )
: WidgetIndex(0)
, RoutingPath(InRoutingPath)
{
}
bool ShouldKeepGoing() const
{
return WidgetIndex < RoutingPath.Widgets.Num();
}
void Next()
{
++WidgetIndex;
}
FWidgetAndPointer GetWidget() const
{
return FWidgetAndPointer(RoutingPath.Widgets[WidgetIndex], RoutingPath.VirtualPointerPositions[WidgetIndex]);
}
const FWidgetPath& GetRoutingPath() const
{
return RoutingPath;
}
private:
int32 WidgetIndex;
const FWidgetPath& RoutingPath;
};
class FBubblePolicy
{
public:
FBubblePolicy( const FWidgetPath& InRoutingPath )
: WidgetIndex( InRoutingPath.Widgets.Num()-1 )
, RoutingPath (InRoutingPath)
{
}
bool ShouldKeepGoing() const
{
return WidgetIndex >= 0;
}
void Next()
{
--WidgetIndex;
}
FWidgetAndPointer GetWidget() const
{
return FWidgetAndPointer(RoutingPath.Widgets[WidgetIndex], RoutingPath.VirtualPointerPositions[WidgetIndex]);
}
const FWidgetPath& GetRoutingPath() const
{
return RoutingPath;
}
private:
int32 WidgetIndex;
const FWidgetPath& RoutingPath;
};
static void LogEvent( FSlateApplication* ThisApplication, const FInputEvent& Event, const FReplyBase& Reply )
{
TSharedPtr<IWidgetReflector> Reflector = ThisApplication->WidgetReflectorPtr.Pin();
if (Reflector.IsValid() && Reply.IsEventHandled())
{
Reflector->OnEventProcessed( Event, Reply );
}
}
/**
* Route an event along a focus path (as opposed to PointerPath)
*
* Focus paths are used focus devices.(e.g. Keyboard or Game Pads)
* Focus paths change when the user navigates focus (e.g. Tab or
* Shift Tab, clicks on a focusable widget, or navigation with keyboard/game pad.)
*/
template< typename RoutingPolicyType, typename FuncType, typename EventType >
static FReply RouteAlongFocusPath( FSlateApplication* ThisApplication, RoutingPolicyType RoutingPolicy, EventType KeyEventCopy, const FuncType& Lambda )
{
return Route<FReply>(ThisApplication, RoutingPolicy, KeyEventCopy, Lambda);
}
/**
* Route an event based on the Routing Policy.
*/
template< typename ReplyType, typename RoutingPolicyType, typename EventType, typename FuncType >
static ReplyType Route( FSlateApplication* ThisApplication, RoutingPolicyType RoutingPolicy, EventType EventCopy, const FuncType& Lambda )
{
ReplyType Reply = ReplyType::Unhandled();
const FWidgetPath& RoutingPath = RoutingPolicy.GetRoutingPath();
EventCopy.SetEventPath( RoutingPath );
for ( ; !Reply.IsEventHandled() && RoutingPolicy.ShouldKeepGoing(); RoutingPolicy.Next() )
{
const FWidgetAndPointer& ArrangedWidget = RoutingPolicy.GetWidget();
const EventType TranslatedEvent = Translate<EventType>::PointerEvent( ArrangedWidget.PointerPosition, EventCopy );
Reply = Lambda( ArrangedWidget, TranslatedEvent ).SetHandler( ArrangedWidget.Widget );
ProcessReply(ThisApplication, RoutingPath, Reply, &RoutingPath, &TranslatedEvent);
}
LogEvent(ThisApplication, EventCopy, Reply);
return Reply;
}
static void ProcessReply( FSlateApplication* Application, const FWidgetPath& RoutingPath, const FNoReply& Reply, const FWidgetPath* WidgetsUnderCursor, const FInputEvent* )
{
}
static void ProcessReply( FSlateApplication* Application, const FWidgetPath& RoutingPath, const FCursorReply& Reply, const FWidgetPath* WidgetsUnderCursor, const FInputEvent* )
{
}
static void ProcessReply( FSlateApplication* Application, const FWidgetPath& RoutingPath, const FReply& Reply, const FWidgetPath* WidgetsUnderCursor, const FInputEvent* PointerEvent )
{
Application->ProcessReply(RoutingPath, Reply, WidgetsUnderCursor, nullptr, PointerEvent->GetUserIndex());
}
static void ProcessReply( FSlateApplication* Application, const FWidgetPath& RoutingPath, const FReply& Reply, const FWidgetPath* WidgetsUnderCursor, const FPointerEvent* PointerEvent )
{
Application->ProcessReply(RoutingPath, Reply, WidgetsUnderCursor, PointerEvent, PointerEvent->GetUserIndex());
}
template<typename EventType>
struct Translate
{
static EventType PointerEvent( const TSharedPtr<FVirtualPointerPosition>& InPosition, const EventType& InEvent )
{
// Most events do not do any coordinate translation.
return InEvent;
}
};
};
template<>
struct FEventRouter::Translate<FPointerEvent>
{
static FPointerEvent PointerEvent( const TSharedPtr<FVirtualPointerPosition>& InPosition, const FPointerEvent& InEvent )
{
// Pointer events are translated into the virtual window space. For 3D Widget Components this means
if ( !InPosition.IsValid() )
{
return InEvent;
}
else
{
return FPointerEvent::MakeTranslatedEvent<FPointerEvent>( InEvent, *InPosition );
}
}
};
FSlateUser::FSlateUser(int32 InUserIndex, bool InVirtualUser)
: UserIndex(InUserIndex)
, bVirtualUser(InVirtualUser)
{
Focus.WidgetPath = FWidgetPath();
Focus.FocusCause = EFocusCause::Cleared;
Focus.ShowFocus = false;
}
FSlateUser::~FSlateUser()
{
}
TSharedPtr<SWidget> FSlateUser::GetFocusedWidget() const
{
if ( Focus.WidgetPath.IsValid() )
{
return Focus.WidgetPath.GetLastWidget().Pin();
}
return TSharedPtr<SWidget>();
}
FSlateVirtualUser::FSlateVirtualUser(int32 InUserIndex, int32 InVirtualUserIndex)
: UserIndex(InUserIndex)
, VirtualUserIndex(InVirtualUserIndex)
{
}
FSlateVirtualUser::~FSlateVirtualUser()
{
if ( FSlateApplication::IsInitialized() )
{
FSlateApplication::Get().UnregisterUser(UserIndex);
}
}
DECLARE_CYCLE_STAT( TEXT("Message Tick Time"), STAT_SlateMessageTick, STATGROUP_Slate );
DECLARE_CYCLE_STAT( TEXT("Update Tooltip Time"), STAT_SlateUpdateTooltip, STATGROUP_Slate );
DECLARE_CYCLE_STAT( TEXT("Total Slate Tick Time"), STAT_SlateTickTime, STATGROUP_Slate );
DECLARE_CYCLE_STAT( TEXT("SlatePrepass"), STAT_SlatePrepass, STATGROUP_Slate );
DECLARE_CYCLE_STAT( TEXT("Draw Window And Children Time"), STAT_SlateDrawWindowTime, STATGROUP_Slate );
DECLARE_CYCLE_STAT( TEXT("TickWidgets"), STAT_SlateTickWidgets, STATGROUP_Slate );
DECLARE_CYCLE_STAT( TEXT("TickRegisteredWidgets"), STAT_SlateTickRegisteredWidgets, STATGROUP_Slate );
DECLARE_CYCLE_STAT( TEXT("Slate::PreTickEvent"), STAT_SlatePreTickEvent, STATGROUP_Slate );
DECLARE_CYCLE_STAT(TEXT("ShowVirtualKeyboard"), STAT_ShowVirtualKeyboard, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessKeyDown"), STAT_ProcessKeyDown, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessKeyUp"), STAT_ProcessKeyUp, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessKeyChar"), STAT_ProcessKeyChar, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessKeyChar (route focus)"), STAT_ProcessKeyChar_RouteAlongFocusPath, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessKeyChar (call OnKeyChar)"), STAT_ProcessKeyChar_Call_OnKeyChar, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessAnalogInput"), STAT_ProcessAnalogInput, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessMouseButtonDown"), STAT_ProcessMouseButtonDown, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessMouseButtonDoubleClick"), STAT_ProcessMouseButtonDoubleClick, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessMouseButtonUp"), STAT_ProcessMouseButtonUp, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessMouseWheelGesture"), STAT_ProcessMouseWheelGesture, STATGROUP_Slate);
DECLARE_CYCLE_STAT(TEXT("ProcessMouseMove"), STAT_ProcessMouseMove, STATGROUP_Slate);
SLATE_DECLARE_CYCLE_COUNTER(GSlateTotalTickTime, "Total Slate Tick Time");
SLATE_DECLARE_CYCLE_COUNTER(GMessageTickTime, "Message Tick Time");
SLATE_DECLARE_CYCLE_COUNTER(GUpdateTooltipTime, "Update Tooltip Time");
SLATE_DECLARE_CYCLE_COUNTER(GSlateSynthesizeMouseMove, "Synthesize Mouse Move");
SLATE_DECLARE_CYCLE_COUNTER(GTickWidgets, "TickWidgets");
SLATE_DECLARE_CYCLE_COUNTER(GSlateTickNotificationManager, "NotificationManager Tick");
SLATE_DECLARE_CYCLE_COUNTER(GSlateDrawWindows, "DrawWindows");
SLATE_DECLARE_CYCLE_COUNTER(GSlateDrawWindowAndChildren, "Draw Window And Children");
SLATE_DECLARE_CYCLE_COUNTER(GSlateRendererDrawWindows, "Renderer DrawWindows");
SLATE_DECLARE_CYCLE_COUNTER(GSlateDrawPrepass, "DrawPrepass");
SLATE_DECLARE_CYCLE_COUNTER(GSlatePrepassWindowAndChildren, "Prepass Window And Children");
// Slate Event Logging is enabled to allow crash log dumping
#define LOG_SLATE_EVENTS 0
#if LOG_SLATE_EVENTS
#define LOG_EVENT_CONTENT( EventType, AdditionalContent, WidgetOrReply ) LogSlateEvent(EventLogger, EventType, AdditionalContent, WidgetOrReply);
#define LOG_EVENT( EventType, WidgetOrReply ) LOG_EVENT_CONTENT( EventType, FString(), WidgetOrReply )
static void LogSlateEvent( const TSharedPtr<IEventLogger>& EventLogger, EEventLog::Type Event, const FString& AdditionalContent, const TSharedPtr<SWidget>& HandlerWidget )
{
if (EventLogger.IsValid())
{
EventLogger->Log( Event, AdditionalContent, HandlerWidget );
}
}
static void LogSlateEvent( const TSharedPtr<IEventLogger>& EventLogger, EEventLog::Type Event, const FString& AdditionalContent, const FReply& InReply )
{
if ( EventLogger.IsValid() && InReply.IsEventHandled() )
{
EventLogger->Log( Event, AdditionalContent, InReply.GetHandler() );
}
}
#else
#define LOG_EVENT_CONTENT( EventType, AdditionalContent, WidgetOrReply )
#define LOG_EVENT( Event, WidgetOrReply ) CheckReplyCorrectness(WidgetOrReply);
static void CheckReplyCorrectness(const TSharedPtr<SWidget>& HandlerWidget)
{
}
static void CheckReplyCorrectness(const FReply& InReply)
{
check( !InReply.IsEventHandled() || InReply.GetHandler().IsValid() );
}
#endif
namespace SlateDefs
{
// How far tool tips should be offset from the mouse cursor position, in pixels
static const FVector2D ToolTipOffsetFromMouse( 12.0f, 8.0f );
// How far tool tips should be pushed out from a force field border, in pixels
static const FVector2D ToolTipOffsetFromForceField( 4.0f, 3.0f );
}
/** True if we should allow throttling based on mouse movement activity. int32 instead of bool only for console variable system. */
TAutoConsoleVariable<int32> ThrottleWhenMouseIsMoving(
TEXT( "Slate.ThrottleWhenMouseIsMoving" ),
false,
TEXT( "Whether to attempt to increase UI responsiveness based on mouse cursor movement." ) );
/** Minimum sustained average frame rate required before we consider the editor to be "responsive" for a smooth UI experience */
TAutoConsoleVariable<int32> TargetFrameRateForResponsiveness(
TEXT( "Slate.TargetFrameRateForResponsiveness" ),
35, // Frames per second
TEXT( "Minimum sustained average frame rate required before we consider the editor to be \"responsive\" for a smooth UI experience" ) );
/** Whether to skip the second Slate PrePass call (the one right before rendering). */
TAutoConsoleVariable<int32> SkipSecondPrepass(
TEXT("Slate.SkipSecondPrepass"),
0,
TEXT("Whether to skip the second Slate PrePass call (the one right before rendering)."));
/** Whether Slate should go to sleep when there are no active timers and the user is idle */
TAutoConsoleVariable<int32> AllowSlateToSleep(
TEXT("Slate.AllowSlateToSleep"),
true,
TEXT("Whether Slate should go to sleep when there are no active timers and the user is idle"));
/** The amount of time that must pass without any user action before Slate is put to sleep (provided that there are no active timers). */
TAutoConsoleVariable<float> SleepBufferPostInput(
TEXT("Slate.SleepBufferPostInput"),
0.0f,
TEXT("The amount of time that must pass without any user action before Slate is put to sleep (provided that there are no active timers)."));
//////////////////////////////////////////////////////////////////////////
bool FSlateApplication::MouseCaptorHelper::HasCapture() const
{
for (auto PointerPathPair : PointerIndexToMouseCaptorWeakPathMap)
{
if (PointerPathPair.Value.IsValid())
{
return true;
}
}
return false;
}
bool FSlateApplication::MouseCaptorHelper::HasCaptureForUser(uint32 UserIndex) const
{
for ( auto PointerPathPair : PointerIndexToMouseCaptorWeakPathMap )
{
const FUserAndPointer& UserAndPointer = PointerPathPair.Key;
if ( UserAndPointer.UserIndex == UserIndex )
{
if ( PointerPathPair.Value.IsValid() )
{
return true;
}
}
}
return false;
}
bool FSlateApplication::MouseCaptorHelper::HasCaptureForPointerIndex(uint32 UserIndex, uint32 PointerIndex) const
{
const FWeakWidgetPath* MouseCaptorWeakPath = PointerIndexToMouseCaptorWeakPathMap.Find( FUserAndPointer(UserIndex,PointerIndex) );
return MouseCaptorWeakPath && MouseCaptorWeakPath->IsValid();
}
bool FSlateApplication::MouseCaptorHelper::DoesWidgetHaveMouseCaptureByUser(const TSharedPtr<const SWidget> Widget, int32 UserIndex, TOptional<int32> PointerIndex) const
{
for ( const auto& PointerPathPair : PointerIndexToMouseCaptorWeakPathMap )
{
const FUserAndPointer& UserAndPointer = PointerPathPair.Key;
if ( UserAndPointer.UserIndex == UserIndex )
{
// If the pointer index is set, filter on that as well.
if ( PointerIndex.IsSet() && UserAndPointer.PointerIndex != PointerIndex.GetValue() )
{
continue;
}
if ( PointerPathPair.Value.IsValid() )
{
TSharedPtr<SWidget> LastWidget = PointerPathPair.Value.GetLastWidget().Pin();
if ( LastWidget == Widget )
{
return true;
}
}
}
}
return false;
}
bool FSlateApplication::MouseCaptorHelper::DoesWidgetHaveMouseCapture(const TSharedPtr<const SWidget> Widget) const
{
for ( const auto& IndexPathPair : PointerIndexToMouseCaptorWeakPathMap )
{
TSharedPtr<SWidget> LastWidget = IndexPathPair.Value.GetLastWidget().Pin();
if ( LastWidget == Widget )
{
return true;
}
}
return false;
}
TSharedPtr< SWidget > FSlateApplication::MouseCaptorHelper::ToSharedWidget(uint32 UserIndex, uint32 PointerIndex) const
{
// If the path is valid then get the last widget, this is the current mouse captor
TSharedPtr< SWidget > SharedWidgetPtr;
const FWeakWidgetPath* MouseCaptorWeakPath = PointerIndexToMouseCaptorWeakPathMap.Find( FUserAndPointer(UserIndex,PointerIndex) );
if (MouseCaptorWeakPath && MouseCaptorWeakPath->IsValid() )
{
TWeakPtr< SWidget > WeakWidgetPtr = MouseCaptorWeakPath->GetLastWidget();
SharedWidgetPtr = WeakWidgetPtr.Pin();
}
return SharedWidgetPtr;
}
TArray<TSharedRef<SWidget>> FSlateApplication::MouseCaptorHelper::ToSharedWidgets() const
{
TArray<TSharedRef<SWidget>> Widgets;
Widgets.Empty(PointerIndexToMouseCaptorWeakPathMap.Num());
for (const auto& IndexPathPair : PointerIndexToMouseCaptorWeakPathMap)
{
TSharedPtr<SWidget> LastWidget = IndexPathPair.Value.GetLastWidget().Pin();
if (LastWidget.IsValid())
{
Widgets.Add(LastWidget.ToSharedRef());
}
}
return Widgets;
}
TSharedPtr< SWidget > FSlateApplication::MouseCaptorHelper::ToSharedWindow(uint32 UserIndex, uint32 PointerIndex)
{
// if the path is valid then we can get the window the current mouse captor belongs to
FWidgetPath MouseCaptorPath = ToWidgetPath( UserIndex, PointerIndex );
if ( MouseCaptorPath.IsValid() )
{
return MouseCaptorPath.GetWindow();
}
return TSharedPtr< SWidget >();
}
void FSlateApplication::MouseCaptorHelper::SetMouseCaptor(uint32 UserIndex, uint32 PointerIndex, const FWidgetPath& EventPath, TSharedPtr< SWidget > Widget)
{
// Caller is trying to set a new mouse captor, so invalidate the current one - when the function finishes
// it still may not have a valid captor widget, this is ok
InvalidateCaptureForPointer(UserIndex, PointerIndex);
if ( Widget.IsValid() )
{
TSharedRef< SWidget > WidgetRef = Widget.ToSharedRef();
FWidgetPath NewMouseCaptorPath = EventPath.GetPathDownTo( WidgetRef );
const auto IsPathToCaptorFound = []( const FWidgetPath& PathToTest, const TSharedRef<SWidget>& WidgetToFind )
{
return PathToTest.Widgets.Num() > 0 && PathToTest.Widgets.Last().Widget == WidgetToFind;
};
FWeakWidgetPath MouseCaptorWeakPath;
if ( IsPathToCaptorFound( NewMouseCaptorPath, WidgetRef ) )
{
MouseCaptorWeakPath = NewMouseCaptorPath;
}
else if (EventPath.Widgets.Num() > 0)
{
// If the target widget wasn't found on the event path then start the search from the root
NewMouseCaptorPath = EventPath.GetPathDownTo( EventPath.Widgets[0].Widget );
NewMouseCaptorPath.ExtendPathTo( FWidgetMatcher( WidgetRef ) );
MouseCaptorWeakPath = IsPathToCaptorFound( NewMouseCaptorPath, WidgetRef )
? NewMouseCaptorPath
: FWeakWidgetPath();
}
else
{
ensureMsgf(EventPath.Widgets.Num() > 0, TEXT("An unknown widget is attempting to set capture to %s"), *Widget->ToString() );
}
if (MouseCaptorWeakPath.IsValid())
{
PointerIndexToMouseCaptorWeakPathMap.Add(FUserAndPointer(UserIndex,PointerIndex), MouseCaptorWeakPath);
}
}
}
void FSlateApplication::MouseCaptorHelper::InvalidateCaptureForAllPointers()
{
TArray<FUserAndPointer> PointerIndices;
PointerIndexToMouseCaptorWeakPathMap.GenerateKeyArray(PointerIndices);
for (FUserAndPointer UserAndPointer : PointerIndices)
{
InvalidateCaptureForPointer(UserAndPointer.UserIndex, UserAndPointer.PointerIndex);
}
}
void FSlateApplication::MouseCaptorHelper::InvalidateCaptureForUser(uint32 UserIndex)
{
TArray<FUserAndPointer> PointerIndices;
PointerIndexToMouseCaptorWeakPathMap.GenerateKeyArray(PointerIndices);
for ( FUserAndPointer UserAndPointer : PointerIndices )
{
if ( UserAndPointer.UserIndex == UserIndex )
{
InvalidateCaptureForPointer(UserAndPointer.UserIndex, UserAndPointer.PointerIndex);
}
}
}
FWidgetPath FSlateApplication::MouseCaptorHelper::ToWidgetPath( FWeakWidgetPath::EInterruptedPathHandling::Type InterruptedPathHandling, const FPointerEvent* PointerEvent )
{
FWidgetPath WidgetPath;
const FWeakWidgetPath* MouseCaptorWeakPath = PointerIndexToMouseCaptorWeakPathMap.Find(FUserAndPointer(PointerEvent->GetUserIndex(),PointerEvent->GetPointerIndex()));
if ( MouseCaptorWeakPath->IsValid() )
{
if ( MouseCaptorWeakPath->ToWidgetPath( WidgetPath, InterruptedPathHandling, PointerEvent ) == FWeakWidgetPath::EPathResolutionResult::Truncated )
{
// If the path was truncated then it means this widget is no longer part of the active set,
// so we make sure to invalidate its capture
InvalidateCaptureForPointer(PointerEvent->GetUserIndex(), PointerEvent->GetPointerIndex());
}
}
return WidgetPath;
}
FWidgetPath FSlateApplication::MouseCaptorHelper::ToWidgetPath(uint32 UserIndex, uint32 PointerIndex, FWeakWidgetPath::EInterruptedPathHandling::Type InterruptedPathHandling)
{
FWidgetPath WidgetPath;
const FWeakWidgetPath* MouseCaptorWeakPath = PointerIndexToMouseCaptorWeakPathMap.Find(FUserAndPointer(UserIndex,PointerIndex));
if (MouseCaptorWeakPath && MouseCaptorWeakPath->IsValid() )
{
if ( MouseCaptorWeakPath->ToWidgetPath( WidgetPath, InterruptedPathHandling ) == FWeakWidgetPath::EPathResolutionResult::Truncated )
{
// If the path was truncated then it means this widget is no longer part of the active set,
// so we make sure to invalidate its capture
InvalidateCaptureForPointer(UserIndex,PointerIndex);
}
}
return WidgetPath;
}
void FSlateApplication::MouseCaptorHelper::InvalidateCaptureForPointer(uint32 UserIndex, uint32 PointerIndex)
{
InformCurrentCaptorOfCaptureLoss(UserIndex, PointerIndex);
PointerIndexToMouseCaptorWeakPathMap.Remove( FUserAndPointer(UserIndex, PointerIndex) );
}
TArray<FWidgetPath> FSlateApplication::MouseCaptorHelper::ToWidgetPaths()
{
TArray<FWidgetPath> WidgetPaths;
TArray<FUserAndPointer> PointerIndices;
PointerIndexToMouseCaptorWeakPathMap.GenerateKeyArray(PointerIndices);
for (auto Index : PointerIndices)
{
WidgetPaths.Add(ToWidgetPath(Index.UserIndex,Index.PointerIndex));
}
return WidgetPaths;
}
FWeakWidgetPath FSlateApplication::MouseCaptorHelper::ToWeakPath(uint32 UserIndex, uint32 PointerIndex) const
{
const FWeakWidgetPath* MouseCaptorWeakPath = PointerIndexToMouseCaptorWeakPathMap.Find(FUserAndPointer(UserIndex,PointerIndex));
if (MouseCaptorWeakPath)
{
return *MouseCaptorWeakPath;
}
return FWeakWidgetPath();
}
void FSlateApplication::MouseCaptorHelper::InformCurrentCaptorOfCaptureLoss(uint32 UserIndex,uint32 PointerIndex) const
{
// if we have a path to a widget then it is the current mouse captor and needs to know it has lost capture
const FWeakWidgetPath* MouseCaptorWeakPath = PointerIndexToMouseCaptorWeakPathMap.Find(FUserAndPointer(UserIndex,PointerIndex));
if (MouseCaptorWeakPath && MouseCaptorWeakPath->IsValid() )
{
TWeakPtr< SWidget > WeakWidgetPtr = MouseCaptorWeakPath->GetLastWidget();
TSharedPtr< SWidget > SharedWidgetPtr = WeakWidgetPtr.Pin();
if ( SharedWidgetPtr.IsValid() )
{
SharedWidgetPtr->OnMouseCaptureLost();
}
}
}
//////////////////////////////////////////////////////////////////////////
void FSlateApplication::FDragDetector::StartDragDetection(const FWidgetPath& PathToWidget, int32 UserIndex, int32 PointerIndex, FKey DragButton, FVector2D StartLocation)
{
PointerIndexToDragState.Add(FUserAndPointer(UserIndex, PointerIndex), FDragDetectionState(PathToWidget, UserIndex, PointerIndex, DragButton, StartLocation));
}
bool FSlateApplication::FDragDetector::IsDetectingDrag(const FPointerEvent& PointerEvent)
{
FUserAndPointer UserAndPointer(PointerEvent.GetUserIndex(), PointerEvent.GetPointerIndex());
FDragDetectionState* DetectionState = PointerIndexToDragState.Find(UserAndPointer);
if ( DetectionState )
{
return true;
}
return false;
}
bool FSlateApplication::FDragDetector::DetectDrag(const FPointerEvent& PointerEvent, float InDragTriggerDistance, FWeakWidgetPath*& OutWeakWidgetPath)
{
FUserAndPointer UserAndPointer(PointerEvent.GetUserIndex(), PointerEvent.GetPointerIndex());
FDragDetectionState* DetectionState = PointerIndexToDragState.Find(UserAndPointer);
if ( DetectionState )
{
if ( DetectionState->DetectDragUserIndex == PointerEvent.GetUserIndex() &&
DetectionState->DetectDragPointerIndex == PointerEvent.GetPointerIndex() )
{
const FVector2D DragDelta = DetectionState->DetectDragStartLocation - PointerEvent.GetScreenSpacePosition();
const bool bDragDetected = ( DragDelta.SizeSquared() > FMath::Square(InDragTriggerDistance) );
if ( bDragDetected )
{
OutWeakWidgetPath = &DetectionState->DetectDragForWidget;
return true;
}
}
}
OutWeakWidgetPath = nullptr;
return false;
}
void FSlateApplication::FDragDetector::OnPointerRelease(const FPointerEvent& PointerEvent)
{
FUserAndPointer UserAndPointer(PointerEvent.GetUserIndex(), PointerEvent.GetPointerIndex());
FDragDetectionState* DetectionState = PointerIndexToDragState.Find(UserAndPointer);
if ( DetectionState )
{
if ( DetectionState->DetectDragButton == PointerEvent.GetEffectingButton() &&
DetectionState->DetectDragUserIndex == PointerEvent.GetUserIndex() &&
DetectionState->DetectDragPointerIndex == PointerEvent.GetPointerIndex() )
{
// The user has released the button (or finger) that was supposed to start the drag; stop detecting it.
PointerIndexToDragState.Remove(UserAndPointer);
}
}
}
void FSlateApplication::FDragDetector::ResetDetection()
{
PointerIndexToDragState.Reset();
}
//////////////////////////////////////////////////////////////////////////
FDelegateHandle FPopupSupport::RegisterClickNotification( const TSharedRef<SWidget>& NotifyWhenClickedOutsideMe, const FOnClickedOutside& InNotification )
{
// If the subscriber or a zone object is destroyed, the subscription is
// no longer active. Clean it up here so that consumers of this API have an
// easy time with resource management.
struct { void operator()( TArray<FClickSubscriber>& Notifications ) {
for ( int32 SubscriberIndex=0; SubscriberIndex < Notifications.Num(); )
{
if ( !Notifications[SubscriberIndex].ShouldKeep() )
{
Notifications.RemoveAtSwap(SubscriberIndex);
}
else
{
SubscriberIndex++;
}
}
}} ClearOutStaleNotifications;
ClearOutStaleNotifications( ClickZoneNotifications );
// Add a new notification.
ClickZoneNotifications.Add( FClickSubscriber( NotifyWhenClickedOutsideMe, InNotification ) );
return ClickZoneNotifications.Last().Notification.GetHandle();
}
void FPopupSupport::UnregisterClickNotification( FDelegateHandle Handle )
{
for (int32 SubscriptionIndex=0; SubscriptionIndex < ClickZoneNotifications.Num();)
{
if (ClickZoneNotifications[SubscriptionIndex].Notification.GetHandle() == Handle)
{
ClickZoneNotifications.RemoveAtSwap(SubscriptionIndex);
}
else
{
SubscriptionIndex++;
}
}
}
void FPopupSupport::SendNotifications( const FWidgetPath& WidgetsUnderCursor )
{
struct FArrangedWidgetMatcher
{
FArrangedWidgetMatcher( const TSharedRef<SWidget>& InWidgetToMatch )
: WidgetToMatch( InWidgetToMatch )
{}
bool operator()(const FArrangedWidget& Candidate) const
{
return WidgetToMatch == Candidate.Widget;
}
const TSharedRef<SWidget>& WidgetToMatch;
};
// For each subscription, if the widget in question is not being clicked, send the notification.
// i.e. Notifications are saying "some widget outside you was clicked".
for (int32 SubscriberIndex=0; SubscriberIndex < ClickZoneNotifications.Num(); ++SubscriberIndex)
{
FClickSubscriber& Subscriber = ClickZoneNotifications[SubscriberIndex];
if (Subscriber.DetectClicksOutsideMe.IsValid())
{
// Did we click outside the region in this subscription? If so send the notification.
FArrangedWidgetMatcher Matcher(Subscriber.DetectClicksOutsideMe.Pin().ToSharedRef());
const bool bClickedOutsideOfWidget = WidgetsUnderCursor.Widgets.GetInternalArray().IndexOfByPredicate(Matcher) == INDEX_NONE;
if ( bClickedOutsideOfWidget )
{
Subscriber.Notification.ExecuteIfBound();
}
}
}
}
void FSlateApplication::SetPlatformApplication(const TSharedRef<class GenericApplication>& InPlatformApplication)
{
PlatformApplication->SetMessageHandler(MakeShareable(new FGenericApplicationMessageHandler()));
PlatformApplication = InPlatformApplication;
PlatformApplication->SetMessageHandler(CurrentApplication.ToSharedRef());
}
void FSlateApplication::Create()
{
Create(MakeShareable(FPlatformMisc::CreateApplication()));
}
TSharedRef<FSlateApplication> FSlateApplication::Create(const TSharedRef<class GenericApplication>& InPlatformApplication)
{
EKeys::Initialize();
FCoreStyle::ResetToDefault();
CurrentApplication = MakeShareable( new FSlateApplication() );
CurrentBaseApplication = CurrentApplication;
PlatformApplication = InPlatformApplication;
PlatformApplication->SetMessageHandler( CurrentApplication.ToSharedRef() );
// The grid needs to know the size and coordinate system of the desktop.
// Some monitor setups have a primary monitor on the right and below the
// left one, so the leftmost upper right monitor can be something like (-1280, -200)
{
// Get an initial value for the VirtualDesktop geometry
CurrentApplication->VirtualDesktopRect = []()
{
FDisplayMetrics DisplayMetrics;
FSlateApplicationBase::Get().GetDisplayMetrics(DisplayMetrics);
const FPlatformRect& VirtualDisplayRect = DisplayMetrics.VirtualDisplayRect;
return FSlateRect(VirtualDisplayRect.Left, VirtualDisplayRect.Top, VirtualDisplayRect.Right, VirtualDisplayRect.Bottom);
}();
// Sign up for updates from the OS. Polling this every frame is too expensive on at least some OSs.
PlatformApplication->OnDisplayMetricsChanged().AddSP(CurrentApplication.ToSharedRef(), &FSlateApplication::OnVirtualDesktopSizeChanged);
}
return CurrentApplication.ToSharedRef();
}
void FSlateApplication::Shutdown(bool bShutdownPlatform)
{
if (FSlateApplication::IsInitialized())
{
CurrentApplication->OnShutdown();
CurrentApplication->DestroyRenderer();
CurrentApplication->Renderer.Reset();
if (bShutdownPlatform)
{
PlatformApplication->DestroyApplication();
}
PlatformApplication.Reset();
CurrentApplication.Reset();
CurrentBaseApplication.Reset();
}
}
TSharedPtr<FSlateApplication> FSlateApplication::CurrentApplication = nullptr;
FSlateApplication::FSlateApplication()
: SynthesizeMouseMovePending(0)
, bAppIsActive(true)
, bSlateWindowActive(true)
, Scale( 1.0f )
, DragTriggerDistance( 5.0f )
, CursorRadius( 0.0f )
, LastUserInteractionTime( 0.0 )
, LastUserInteractionTimeForThrottling( 0.0 )
, LastMouseMoveTime( 0.0 )
, SlateSoundDevice( MakeShareable(new FNullSlateSoundDevice()) )
, CurrentTime( FPlatformTime::Seconds() )
, LastTickTime( 0.0 )
, AverageDeltaTime( 1.0f / 30.0f ) // Prime the running average with a typical frame rate so it doesn't have to spin up from zero
, AverageDeltaTimeForResponsiveness( 1.0f / 30.0f )
, OnExitRequested()
, EventLogger( TSharedPtr<IEventLogger>() )
, NumExternalModalWindowsActive( 0 )
, bAllowToolTips( true )
, ToolTipDelay( 0.15f )
, ToolTipFadeInDuration( 0.1f )
, ToolTipSummonTime( 0.0 )
, DesiredToolTipLocation( FVector2D::ZeroVector )
, ToolTipOffsetDirection( EToolTipOffsetDirection::Undetermined )
, bRequestLeaveDebugMode( false )
, bLeaveDebugForSingleStep( false )
, CVarAllowToolTips(
TEXT( "Slate.AllowToolTips" ),
bAllowToolTips,
TEXT( "Whether to allow tool-tips to spawn at all." ) )
, CVarToolTipDelay(
TEXT( "Slate.ToolTipDelay" ),
ToolTipDelay,
TEXT( "Delay in seconds before a tool-tip is displayed near the mouse cursor when hovering over widgets that supply tool-tip data." ) )
, CVarToolTipFadeInDuration(
TEXT( "Slate.ToolTipFadeInDuration" ),
ToolTipFadeInDuration,
TEXT( "How long it takes for a tool-tip to fade in, in seconds." ) )
, bIsExternalUIOpened( false )
, SlateTextField( nullptr )
, bIsFakingTouch(FParse::Param(FCommandLine::Get(), TEXT("simmobile")) || FParse::Param(FCommandLine::Get(), TEXT("faketouches")))
, bIsGameFakingTouch( false )
, bIsFakingTouched( false )
, bTouchFallbackToMouse( true )
, bSoftwareCursorAvailable( false )
, bQueryCursorRequested( false )
, bMenuAnimationsEnabled( true )
, AppIcon( FCoreStyle::Get().GetBrush("DefaultAppIcon") )
, VirtualDesktopRect( 0,0,0,0 )
, NavigationConfig(MakeShareable(new FNavigationConfig()))
, ProcessingInput(0)
{
#if WITH_UNREAL_DEVELOPER_TOOLS
FModuleManager::Get().LoadModule(TEXT("Settings"));
#endif
if (GConfig)
{
GConfig->GetBool(TEXT("MobileSlateUI"),TEXT("bTouchFallbackToMouse"),bTouchFallbackToMouse,GEngineIni);
GConfig->GetBool(TEXT("CursorControl"), TEXT("bAllowSoftwareCursor"), bSoftwareCursorAvailable, GEngineIni);
}
// causes InputCore to initialize, even if statically linked
FInputCoreModule& InputCore = FModuleManager::LoadModuleChecked<FInputCoreModule>(TEXT("InputCore"));
FGenericCommands::Register();
FTabCommands::Register();
NormalExecutionGetter.BindRaw( this, &FSlateApplication::IsNormalExecution );
PointerIndexLastPositionMap.Add(CursorPointerIndex, FVector2D::ZeroVector);
// Add the standard 'default' user because there's always 1 user.
RegisterUser(MakeShareable(new FSlateUser(0, false)));
}
FSlateApplication::~FSlateApplication()
{
FTabCommands::Unregister();
FGenericCommands::Unregister();
if (SlateTextField != nullptr)
{
delete SlateTextField;
SlateTextField = nullptr;
}
}
const FStyleNode* FSlateApplication::GetRootStyle() const
{
return RootStyleNode;
}
bool FSlateApplication::InitializeRenderer( TSharedRef<FSlateRenderer> InRenderer, bool bQuietMode )
{
Renderer = InRenderer;
bool bResult = Renderer->Initialize();
if (!bResult && !bQuietMode)
{
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *NSLOCTEXT("SlateD3DRenderer", "ProblemWithGraphicsCard", "There is a problem with your graphics card. Please ensure your card meets the minimum system requirements and that you have the latest drivers installed.").ToString(), *NSLOCTEXT("SlateD3DRenderer", "UnsupportedVideoCardErrorTitle", "Unsupported Graphics Card").ToString());
}
return bResult;
}
void FSlateApplication::InitializeSound( const TSharedRef<ISlateSoundDevice>& InSlateSoundDevice )
{
SlateSoundDevice = InSlateSoundDevice;
}
void FSlateApplication::DestroyRenderer()
{
if( Renderer.IsValid() )
{
Renderer->Destroy();
}
}
/**
* Called when the user closes the outermost frame (ie quitting the app). Uses standard UE4 global variable
* so normal UE4 applications work as expected
*/
static void OnRequestExit()
{
GIsRequestingExit = true;
}
void FSlateApplication::PlaySound( const FSlateSound& SoundToPlay, int32 UserIndex ) const
{
SlateSoundDevice->PlaySound(SoundToPlay, UserIndex);
}
float FSlateApplication::GetSoundDuration(const FSlateSound& Sound) const
{
return SlateSoundDevice->GetSoundDuration(Sound);
}
FVector2D FSlateApplication::GetCursorPos() const
{
if ( ICursor* Cursor = PlatformApplication->Cursor.Get() )
{
return Cursor->GetPosition();
}
return FVector2D( 0, 0 );
}
FVector2D FSlateApplication::GetLastCursorPos() const
{
return PointerIndexLastPositionMap[CursorPointerIndex];
}
void FSlateApplication::SetCursorPos( const FVector2D& MouseCoordinate )
{
if (ICursor* Cursor = PlatformApplication->Cursor.Get())
{
return Cursor->SetPosition( MouseCoordinate.X, MouseCoordinate.Y );
}
}
FWidgetPath FSlateApplication::LocateWindowUnderMouse( FVector2D ScreenspaceMouseCoordinate, const TArray< TSharedRef< SWindow > >& Windows, bool bIgnoreEnabledStatus )
{
// First, give the OS a chance to tell us which window to use, in case a child window is not guaranteed to stay on top of its parent window
TSharedPtr<FGenericWindow> NativeWindowUnderMouse = PlatformApplication->GetWindowUnderCursor();
if (NativeWindowUnderMouse.IsValid())
{
TSharedPtr<SWindow> Window = FSlateWindowHelper::FindWindowByPlatformWindow(Windows, NativeWindowUnderMouse.ToSharedRef());
if (Window.IsValid())
{
FWidgetPath PathToLocatedWidget = LocateWidgetInWindow(ScreenspaceMouseCoordinate, Window.ToSharedRef(), bIgnoreEnabledStatus);
if (PathToLocatedWidget.IsValid())
{
return PathToLocatedWidget;
}
}
}
bool bPrevWindowWasModal = false;
for (int32 WindowIndex = Windows.Num() - 1; WindowIndex >= 0; --WindowIndex)
{
const TSharedRef<SWindow>& Window = Windows[WindowIndex];
if ( !Window->IsVisible() || Window->IsWindowMinimized())
{
continue;
}
// Hittest the window's children first.
FWidgetPath ResultingPath = LocateWindowUnderMouse(ScreenspaceMouseCoordinate, Window->GetChildWindows(), bIgnoreEnabledStatus);
if (ResultingPath.IsValid())
{
return ResultingPath;
}
// If none of the children were hit, hittest the parent.
// Only accept input if the current window accepts input and the current window is not under a modal window or an interactive tooltip
if (!bPrevWindowWasModal)
{
FWidgetPath PathToLocatedWidget = LocateWidgetInWindow(ScreenspaceMouseCoordinate, Window, bIgnoreEnabledStatus);
if (PathToLocatedWidget.IsValid())
{
return PathToLocatedWidget;
}
}
}
return FWidgetPath();
}
bool FSlateApplication::IsWindowHousingInteractiveTooltip(const TSharedRef<const SWindow>& WindowToTest) const
{
const TSharedPtr<IToolTip> ActiveToolTipPtr = ActiveToolTip.Pin();
const TSharedPtr<SWindow> ToolTipWindowPtr = ToolTipWindow.Pin();
const bool bIsHousingInteractiveTooltip =
WindowToTest == ToolTipWindowPtr &&
ActiveToolTipPtr.IsValid() &&
ActiveToolTipPtr->IsInteractive();
return bIsHousingInteractiveTooltip;
}
void FSlateApplication::DrawWindows()
{
SLATE_CYCLE_COUNTER_SCOPE(GSlateDrawWindows);
FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::DrawWindows");
PrivateDrawWindows();
FPlatformMisc::EndNamedEvent();
}
struct FDrawWindowArgs
{
FDrawWindowArgs( FSlateDrawBuffer& InDrawBuffer, const FWidgetPath& InWidgetsUnderCursor )
: OutDrawBuffer( InDrawBuffer )
, WidgetsUnderCursor( InWidgetsUnderCursor )
{}
FSlateDrawBuffer& OutDrawBuffer;
const FWidgetPath& WidgetsUnderCursor;
};
void FSlateApplication::DrawWindowAndChildren( const TSharedRef<SWindow>& WindowToDraw, FDrawWindowArgs& DrawWindowArgs )
{
// On Mac, where child windows can be on screen even if their parent is hidden or minimized, we want to always draw child windows.
// On other platforms we set bDrawChildWindows to true only if we draw the current window.
bool bDrawChildWindows = PLATFORM_MAC;
// Only draw visible windows
if( WindowToDraw->IsVisible() && (!WindowToDraw->IsWindowMinimized() || FApp::UseVRFocus()) )
{
SLATE_CYCLE_COUNTER_SCOPE_CUSTOM(GSlateDrawWindowAndChildren, WindowToDraw->GetCreatedInLocation());
// Switch to the appropriate world for drawing
FScopedSwitchWorldHack SwitchWorld( WindowToDraw );
//FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::DrawPrep");
FSlateWindowElementList& WindowElementList = DrawWindowArgs.OutDrawBuffer.AddWindowElementList( WindowToDraw );
//FPlatformMisc::EndNamedEvent();
// Drawing is done in window space, so null out the positions and keep the size.
FGeometry WindowGeometry = WindowToDraw->GetWindowGeometryInWindow();
int32 MaxLayerId = 0;
{
//FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::ClearHitTestGrid");
WindowToDraw->GetHittestGrid()->ClearGridForNewFrame( VirtualDesktopRect );
//FPlatformMisc::EndNamedEvent();
FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::DrawWindow");
MaxLayerId = WindowToDraw->PaintWindow(
FPaintArgs(WindowToDraw.Get(), *WindowToDraw->GetHittestGrid(), WindowToDraw->GetPositionInScreen(), GetCurrentTime(), GetDeltaTime()),
WindowGeometry, WindowToDraw->GetClippingRectangleInWindow(),
WindowElementList,
0,
FWidgetStyle(),
WindowToDraw->IsEnabled() );
FPlatformMisc::EndNamedEvent();
// Draw drag drop operation if it's windowless.
if ( IsDragDropping() && DragDropContent->IsWindowlessOperation() )
{
TSharedPtr<SWindow> DragDropWindow = DragDropWindowPtr.Pin();
if ( DragDropWindow.IsValid() && DragDropWindow == WindowToDraw )
{
TSharedPtr<SWidget> DecoratorWidget = DragDropContent->GetDefaultDecorator();
if ( DecoratorWidget.IsValid() && DecoratorWidget->GetVisibility().IsVisible() )
{
DecoratorWidget->SetVisibility(EVisibility::HitTestInvisible);
DecoratorWidget->SlatePrepass(GetApplicationScale()*DragDropWindow->GetNativeWindow()->GetDPIScaleFactor());
FVector2D DragDropContentInWindowSpace = WindowToDraw->GetWindowGeometryInScreen().AbsoluteToLocal(DragDropContent->GetDecoratorPosition());
const FGeometry DragDropContentGeometry = FGeometry::MakeRoot(DecoratorWidget->GetDesiredSize(), FSlateLayoutTransform(DragDropContentInWindowSpace));
DecoratorWidget->Paint(
FPaintArgs(WindowToDraw.Get(), *WindowToDraw->GetHittestGrid(), WindowToDraw->GetPositionInScreen(), GetCurrentTime(), GetDeltaTime()),
DragDropContentGeometry, WindowToDraw->GetClippingRectangleInWindow(),
WindowElementList,
++MaxLayerId,
FWidgetStyle(),
WindowToDraw->IsEnabled());
}
}
}
// Draw Software Cursor
TSharedPtr<SWindow> CursorWindow = CursorWindowPtr.Pin();
if (CursorWindow.IsValid() && WindowToDraw == CursorWindow)
{
TSharedPtr<SWidget> CursorWidget = CursorWidgetPtr.Pin();
if (CursorWidget.IsValid())
{
CursorWidget->SlatePrepass(GetApplicationScale()*CursorWindow->GetNativeWindow()->GetDPIScaleFactor());
FVector2D CursorPosInWindowSpace = WindowToDraw->GetWindowGeometryInScreen().AbsoluteToLocal(GetCursorPos());
CursorPosInWindowSpace += (CursorWidget->GetDesiredSize() * -0.5);
const FGeometry CursorGeometry = FGeometry::MakeRoot(CursorWidget->GetDesiredSize(), FSlateLayoutTransform(CursorPosInWindowSpace));
CursorWidget->Paint(
FPaintArgs(WindowToDraw.Get(), *WindowToDraw->GetHittestGrid(), WindowToDraw->GetPositionInScreen(), GetCurrentTime(), GetDeltaTime()),
CursorGeometry, WindowToDraw->GetClippingRectangleInWindow(),
WindowElementList,
++MaxLayerId,
FWidgetStyle(),
WindowToDraw->IsEnabled());
}
}
}
#if SLATE_HAS_WIDGET_REFLECTOR
// The widget reflector may want to paint some additional stuff as part of the Widget introspection that it performs.
// For example: it may draw layout rectangles for hovered widgets.
const bool bVisualizeLayoutUnderCursor = DrawWindowArgs.WidgetsUnderCursor.IsValid();
const bool bCapturingFromThisWindow = bVisualizeLayoutUnderCursor && DrawWindowArgs.WidgetsUnderCursor.TopLevelWindow == WindowToDraw;
TSharedPtr<IWidgetReflector> WidgetReflector = WidgetReflectorPtr.Pin();
if ( bCapturingFromThisWindow || (WidgetReflector.IsValid() && WidgetReflector->ReflectorNeedsToDrawIn(WindowToDraw)) )
{
MaxLayerId = WidgetReflector->Visualize( DrawWindowArgs.WidgetsUnderCursor, WindowElementList, MaxLayerId );
}
// Visualize pointer presses and pressed keys for demo-recording purposes.
const bool bVisualiseMouseClicks = WidgetReflector.IsValid() && PlatformApplication->Cursor.IsValid() && PlatformApplication->Cursor->GetType() != EMouseCursor::None;
if (bVisualiseMouseClicks )
{
MaxLayerId = WidgetReflector->VisualizeCursorAndKeys( WindowElementList, MaxLayerId );
}
#endif
// This window is visible, so draw its child windows as well
bDrawChildWindows = true;
}
if (bDrawChildWindows)
{
// Draw the child windows
const TArray< TSharedRef<SWindow> >& WindowChildren = WindowToDraw->GetChildWindows();
for (int32 ChildIndex=0; ChildIndex < WindowChildren.Num(); ++ChildIndex)
{
DrawWindowAndChildren( WindowChildren[ChildIndex], DrawWindowArgs );
}
}
}
static void PrepassWindowAndChildren( TSharedRef<SWindow> WindowToPrepass )
{
if (UNLIKELY(!FApp::CanEverRender()))
{
return;
}
if ( WindowToPrepass->IsVisible() && !WindowToPrepass->IsWindowMinimized() )
{
SLATE_CYCLE_COUNTER_SCOPE_CUSTOM(GSlatePrepassWindowAndChildren, WindowToPrepass->GetCreatedInLocation());
FScopedSwitchWorldHack SwitchWorld(WindowToPrepass);
{
SCOPE_CYCLE_COUNTER(STAT_SlatePrepass);
WindowToPrepass->SlatePrepass(FSlateApplication::Get().GetApplicationScale() * WindowToPrepass->GetNativeWindow()->GetDPIScaleFactor());
}
if ( WindowToPrepass->IsAutosized() )
{
WindowToPrepass->Resize(WindowToPrepass->GetDesiredSizeDesktopPixels());
}
for ( const TSharedRef<SWindow>& ChildWindow : WindowToPrepass->GetChildWindows() )
{
PrepassWindowAndChildren(ChildWindow);
}
}
}
void FSlateApplication::DrawPrepass( TSharedPtr<SWindow> DrawOnlyThisWindow )
{
SLATE_CYCLE_COUNTER_SCOPE(GSlateDrawPrepass);
TSharedPtr<SWindow> ActiveModalWindow = GetActiveModalWindow();
if (ActiveModalWindow.IsValid())
{
PrepassWindowAndChildren( ActiveModalWindow.ToSharedRef() );
for (TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt(SlateWindows); CurrentWindowIt; ++CurrentWindowIt)
{
const TSharedRef<SWindow>& CurrentWindow = *CurrentWindowIt;
if (CurrentWindow->IsTopmostWindow())
{
PrepassWindowAndChildren( CurrentWindow );
}
}
TArray< TSharedRef<SWindow> > NotificationWindows;
FSlateNotificationManager::Get().GetWindows(NotificationWindows);
for (auto CurrentWindowIt(NotificationWindows.CreateIterator()); CurrentWindowIt; ++CurrentWindowIt)
{
PrepassWindowAndChildren(*CurrentWindowIt );
}
}
else if (DrawOnlyThisWindow.IsValid())
{
PrepassWindowAndChildren(DrawOnlyThisWindow.ToSharedRef());
}
else
{
// Draw all windows
for (const TSharedRef<SWindow>& CurrentWindow : SlateWindows)
{
PrepassWindowAndChildren(CurrentWindow);
}
}
}
TArray<TSharedRef<SWindow>> GatherAllDescendants(const TArray< TSharedRef<SWindow> >& InWindowList)
{
TArray<TSharedRef<SWindow>> GatheredDescendants(InWindowList);
for (const TSharedRef<SWindow>& SomeWindow : InWindowList)
{
GatheredDescendants.Append( GatherAllDescendants( SomeWindow->GetChildWindows() ) );
}
return GatheredDescendants;
}
void FSlateApplication::PrivateDrawWindows( TSharedPtr<SWindow> DrawOnlyThisWindow )
{
check(Renderer.IsValid());
#if SLATE_HAS_WIDGET_REFLECTOR
// Is user expecting visual feedback from the Widget Reflector?
const bool bVisualizeLayoutUnderCursor = WidgetReflectorPtr.IsValid() && WidgetReflectorPtr.Pin()->IsVisualizingLayoutUnderCursor();
#else
const bool bVisualizeLayoutUnderCursor = false;
#endif
FWidgetPath WidgetsUnderCursor = bVisualizeLayoutUnderCursor
? WidgetsUnderCursorLastEvent.FindRef(FUserAndPointer(CursorUserIndex,CursorPointerIndex)).ToWidgetPath()
: FWidgetPath();
if ( !SkipSecondPrepass.GetValueOnGameThread() )
{
FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::Prepass");
DrawPrepass( DrawOnlyThisWindow );
FPlatformMisc::EndNamedEvent();
}
//FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::GetDrawBuffer");
FDrawWindowArgs DrawWindowArgs( Renderer->GetDrawBuffer(), WidgetsUnderCursor );
//FPlatformMisc::EndNamedEvent();
{
SCOPE_CYCLE_COUNTER( STAT_SlateDrawWindowTime );
TSharedPtr<SWindow> ActiveModalWindow = GetActiveModalWindow();
if (ActiveModalWindow.IsValid())
{
DrawWindowAndChildren( ActiveModalWindow.ToSharedRef(), DrawWindowArgs );
for( TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
{
const TSharedRef<SWindow>& CurrentWindow = *CurrentWindowIt;
if ( CurrentWindow->IsTopmostWindow() )
{
DrawWindowAndChildren(CurrentWindow, DrawWindowArgs);
}
}
TArray< TSharedRef<SWindow> > NotificationWindows;
FSlateNotificationManager::Get().GetWindows(NotificationWindows);
for( auto CurrentWindowIt( NotificationWindows.CreateIterator() ); CurrentWindowIt; ++CurrentWindowIt )
{
DrawWindowAndChildren(*CurrentWindowIt, DrawWindowArgs);
}
}
else if( DrawOnlyThisWindow.IsValid() )
{
DrawWindowAndChildren( DrawOnlyThisWindow.ToSharedRef(), DrawWindowArgs );
}
else
{
// Draw all windows
for( TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
{
TSharedRef<SWindow> CurrentWindow = *CurrentWindowIt;
if ( CurrentWindow->IsVisible() )
{
DrawWindowAndChildren( CurrentWindow, DrawWindowArgs );
}
}
}
}
// This is potentially dangerous on the movie playback thread that slate sometimes runs on
if(!IsInSlateThread())
{
// Some windows may have been destroyed/removed.
// Do not attempt to draw any windows that have been removed.
TArray<TSharedRef<SWindow>> AllWindows = GatherAllDescendants(SlateWindows);
DrawWindowArgs.OutDrawBuffer.GetWindowElementLists().RemoveAll([&](TSharedPtr<FSlateWindowElementList>& Candidate)
{
TSharedPtr<SWindow> CandidateWindow = Candidate->GetWindow();
return !CandidateWindow.IsValid() || !AllWindows.Contains(CandidateWindow.ToSharedRef());
});
}
{
SLATE_CYCLE_COUNTER_SCOPE(GSlateRendererDrawWindows);
Renderer->DrawWindows( DrawWindowArgs.OutDrawBuffer );
}
}
void FSlateApplication::PollGameDeviceState()
{
if( ActiveModalWindows.Num() == 0 && !GIntraFrameDebuggingGameThread )
{
// Don't poll when a modal window open or intra frame debugging is happening
PlatformApplication->PollGameDeviceState( GetDeltaTime() );
}
}
void FSlateApplication::FinishedInputThisFrame()
{
const float DeltaTime = GetDeltaTime();
if (InputPreProcessor.IsValid() && PlatformApplication->Cursor.IsValid())
{
InputPreProcessor->Tick(DeltaTime, *this, PlatformApplication->Cursor.ToSharedRef());
}
// All the input events have been processed.
// Any widgets that may have received pointer input events
// are given a chance to process accumulated values.
if (MouseCaptor.HasCapture())
{
TArray<TSharedRef<SWidget>> Captors = MouseCaptor.ToSharedWidgets();
for (const auto & Captor : Captors )
{
Captor->OnFinishedPointerInput();
}
}
else
{
for( auto LastWidgetIterator = WidgetsUnderCursorLastEvent.CreateConstIterator(); LastWidgetIterator; ++LastWidgetIterator )
{
FWeakWidgetPath Path = LastWidgetIterator.Value();
for ( const TWeakPtr<SWidget>& WidgetPtr : Path.Widgets )
{
const TSharedPtr<SWidget>& Widget = WidgetPtr.Pin();
if (Widget.IsValid())
{
Widget->OnFinishedPointerInput();
}
else
{
break;
}
}
}
}
// Any widgets that may have recieved key events
// are given a chance to process accumulated values.
ForEachUser([&] (FSlateUser* User) {
const FSlateUser::FUserFocusEntry& UserFocusEntry = User->Focus;
for ( const TWeakPtr<SWidget>& WidgetPtr : UserFocusEntry.WidgetPath.Widgets )
{
const TSharedPtr<SWidget>& Widget = WidgetPtr.Pin();
if ( Widget.IsValid() )
{
Widget->OnFinishedKeyInput();
}
else
{
break;
}
}
});
}
void FSlateApplication::Tick(ESlateTickType TickType)
{
SCOPE_TIME_GUARD(TEXT("FSlateApplication::Tick"));
// It is not valid to tick Slate on any other thread but the game thread unless we are only updating time
check(IsInGameThread() || TickType == ESlateTickType::TimeOnly);
FScopeLock SlateTickAccess(&SlateTickCriticalSection);
FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::Tick");
{
SCOPE_CYCLE_COUNTER(STAT_SlateTickTime);
SLATE_CYCLE_COUNTER_SCOPE(GSlateTotalTickTime);
const float DeltaTime = GetDeltaTime();
if (TickType == ESlateTickType::All)
{
TickPlatform(DeltaTime);
}
TickApplication(TickType, DeltaTime);
}
// Update Slate Stats
SLATE_STATS_END_FRAME(GetCurrentTime());
FPlatformMisc::EndNamedEvent();
}
void FSlateApplication::TickPlatform(float DeltaTime)
{
FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::TickPlatform");
{
SCOPE_CYCLE_COUNTER(STAT_SlateMessageTick);
// We need to pump messages here so that slate can receive input.
if ( ( ActiveModalWindows.Num() > 0 ) || GIntraFrameDebuggingGameThread )
{
// We only need to pump messages for slate when a modal window or blocking mode is active is up because normally message pumping is handled in FEngineLoop::Tick
PlatformApplication->PumpMessages(DeltaTime);
if ( FCoreDelegates::StarvedGameLoop.IsBound() )
{
FCoreDelegates::StarvedGameLoop.Execute();
}
}
PlatformApplication->Tick(DeltaTime);
PlatformApplication->ProcessDeferredEvents(DeltaTime);
}
FPlatformMisc::EndNamedEvent();
}
void FSlateApplication::TickApplication(ESlateTickType TickType, float DeltaTime)
{
if (Renderer.IsValid())
{
// Release any temporary material or texture resources we may have cached and are reporting to prevent
// GC on those resources. We don't need to force it, we just need to let the ones used last frame to
// be queued up to be released.
Renderer->ReleaseAccessedResources(/* Flush State */ false);
}
if(TickType == ESlateTickType::All)
{
FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::PreTick");
{
SCOPE_CYCLE_COUNTER(STAT_SlatePreTickEvent);
PreTickEvent.Broadcast(DeltaTime);
}
FPlatformMisc::EndNamedEvent();
//FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::UpdateCursorLockRegion");
// The widget locking the cursor to its bounds may have been reshaped.
// Check if the widget was reshaped and update the cursor lock
// bounds if needed.
UpdateCursorLockRegion();
//FPlatformMisc::EndNamedEvent();
//FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::CaptureAndToolTipUpdate");
// When Slate captures the mouse, it is up to us to set the cursor
// because the OS assumes that we own the mouse.
if (MouseCaptor.HasCapture() || bQueryCursorRequested)
{
QueryCursor();
}
{
SCOPE_CYCLE_COUNTER(STAT_SlateUpdateTooltip);
SLATE_CYCLE_COUNTER_SCOPE(GUpdateTooltipTime);
// Update tool tip, if we have one
const bool AllowSpawningOfToolTips = false;
UpdateToolTip(AllowSpawningOfToolTips);
}
}
//FPlatformMisc::EndNamedEvent();
// Advance time
LastTickTime = CurrentTime;
CurrentTime = FPlatformTime::Seconds();
// Update average time between ticks. This is used to monitor how responsive the application "feels".
// Note that we calculate this before we apply the max quantum clamping below, because we want to store
// the actual frame rate, even if it is very low.
if (TickType == ESlateTickType::All)
{
// Scalar percent of new delta time that contributes to running average. Use a lower value to add more smoothing
// to the average frame rate. A value of 1.0 will disable smoothing.
const float RunningAverageScale = 0.1f;
AverageDeltaTime = AverageDeltaTime * ( 1.0f - RunningAverageScale ) + GetDeltaTime() * RunningAverageScale;
// Don't update average delta time if we're in an exceptional situation, such as when throttling mode
// is active, because the measured tick time will not be representative of the application's performance.
// In these cases, the cached average delta time from before the throttle activated will be used until
// throttling has finished.
if( FSlateThrottleManager::Get().IsAllowingExpensiveTasks() )
{
// Clamp to avoid including huge hitchy frames in our average
const float ClampedDeltaTime = FMath::Clamp( GetDeltaTime(), 0.0f, 1.0f );
AverageDeltaTimeForResponsiveness = AverageDeltaTimeForResponsiveness * ( 1.0f - RunningAverageScale ) + ClampedDeltaTime * RunningAverageScale;
}
}
// Handle large quantums
const double MaxQuantumBeforeClamp = 1.0 / 8.0; // 8 FPS
if( GetDeltaTime() > MaxQuantumBeforeClamp )
{
LastTickTime = CurrentTime - MaxQuantumBeforeClamp;
}
if (TickType == ESlateTickType::All)
{
const bool bNeedsSyntheticMouseMouse = SynthesizeMouseMovePending > 0;
if (bNeedsSyntheticMouseMouse && (!GIsGameThreadIdInitialized || IsInGameThread()))
{
// Force a mouse move event to make sure all widgets know whether there is a mouse cursor hovering over them
SynthesizeMouseMove();
--SynthesizeMouseMovePending;
}
// Update auto-throttling based on elapsed time since user interaction
ThrottleApplicationBasedOnMouseMovement();
TSharedPtr<SWindow> ActiveModalWindow = GetActiveModalWindow();
const float SleepThreshold = SleepBufferPostInput.GetValueOnGameThread();
const double TimeSinceInput = LastTickTime - LastUserInteractionTime;
const double TimeSinceMouseMove = LastTickTime - LastMouseMoveTime;
const bool bIsUserIdle = (TimeSinceInput > SleepThreshold) && (TimeSinceMouseMove > SleepThreshold);
const bool bAnyActiveTimersPending = AnyActiveTimersArePending();
if (bAnyActiveTimersPending)
{
// Some UI might slide under the cursor. To a widget, this is
// as if the cursor moved over it.
QueueSynthesizedMouseMove();
}
// Check if any element lists used for caching need to be released
{
for (int32 CacheIndex = 0; CacheIndex < ReleasedCachedElementLists.Num(); CacheIndex++)
{
if (ReleasedCachedElementLists[CacheIndex]->IsInUse() == false)
{
ensure(ReleasedCachedElementLists[CacheIndex].IsUnique());
ReleasedCachedElementLists[CacheIndex].Reset();
ReleasedCachedElementLists.RemoveAtSwap(CacheIndex, 1, false);
CacheIndex--;
}
}
}
// skip tick/draw if we are idle and there are no active timers registered that we need to drive slate for.
// This effectively means the slate application is totally idle and we don't need to update the UI.
// This relies on Widgets properly registering for Active timer when they need something to happen even
// when the user is not providing any input (ie, animations, viewport rendering, async polling, etc).
bIsSlateAsleep = true;
if (!AllowSlateToSleep.GetValueOnGameThread() || bAnyActiveTimersPending || !bIsUserIdle || bNeedsSyntheticMouseMouse || FApp::UseVRFocus())
{
bIsSlateAsleep = false; // if we get here, then Slate is not sleeping
// Update any notifications - this needs to be done after windows have updated themselves
// (so they know their size)
{
SLATE_CYCLE_COUNTER_SCOPE(GSlateTickNotificationManager);
FSlateNotificationManager::Get().Tick();
}
// Draw all windows
DrawWindows();
}
PostTickEvent.Broadcast(DeltaTime);
}
}
void FSlateApplication::PumpMessages()
{
PlatformApplication->PumpMessages( GetDeltaTime() );
}
void FSlateApplication::ThrottleApplicationBasedOnMouseMovement()
{
bool bShouldThrottle = false;
if( ThrottleWhenMouseIsMoving.GetValueOnGameThread() ) // Interpreted as bool here
{
// We only want to engage the throttle for a short amount of time after the mouse stops moving
const float TimeToThrottleAfterMouseStops = 0.1f;
// After a key or mouse button is pressed, we'll leave the throttle disengaged for awhile so the
// user can use the keys to navigate in a viewport, for example.
const float MinTimeSinceButtonPressToThrottle = 1.0f;
// Use a small movement threshold to avoid engaging the throttle when the user bumps the mouse
const float MinMouseMovePixelsBeforeThrottle = 2.0f;
const FVector2D& CursorPos = GetCursorPos();
static FVector2D LastCursorPos = GetCursorPos();
//static double LastMouseMoveTime = FPlatformTime::Seconds();
static bool bIsMouseMoving = false;
if( CursorPos != LastCursorPos )
{
// Did the cursor move far enough that we care?
if( bIsMouseMoving || ( CursorPos - LastCursorPos ).SizeSquared() >= MinMouseMovePixelsBeforeThrottle * MinMouseMovePixelsBeforeThrottle )
{
bIsMouseMoving = true;
LastMouseMoveTime = this->GetCurrentTime();
LastCursorPos = CursorPos;
}
}
const float TimeSinceLastUserInteraction = CurrentTime - LastUserInteractionTimeForThrottling;
const float TimeSinceLastMouseMove = CurrentTime - LastMouseMoveTime;
if( TimeSinceLastMouseMove < TimeToThrottleAfterMouseStops )
{
// Only throttle if a Slate window is currently active. If a Wx window (such as Matinee) is
// being used, we don't want to throttle
if( this->GetActiveTopLevelWindow().IsValid() )
{
// Only throttle if the user hasn't pressed a button in awhile
if( TimeSinceLastUserInteraction > MinTimeSinceButtonPressToThrottle )
{
// If a widget has the mouse captured, then we won't bother throttling
if( !MouseCaptor.HasCapture() )
{
// If there is no Slate window under the mouse, then we won't engage throttling
if( LocateWindowUnderMouse( GetCursorPos(), GetInteractiveTopLevelWindows() ).IsValid() )
{
bShouldThrottle = true;
}
}
}
}
}
else
{
// Mouse hasn't moved in a bit, so reset our movement state
bIsMouseMoving = false;
LastCursorPos = CursorPos;
}
}
if( bShouldThrottle )
{
if( !UserInteractionResponsivnessThrottle.IsValid() )
{
// Engage throttling
UserInteractionResponsivnessThrottle = FSlateThrottleManager::Get().EnterResponsiveMode();
}
}
else
{
if( UserInteractionResponsivnessThrottle.IsValid() )
{
// Disengage throttling
FSlateThrottleManager::Get().LeaveResponsiveMode( UserInteractionResponsivnessThrottle );
}
}
}
FWidgetPath FSlateApplication::LocateWidgetInWindow(FVector2D ScreenspaceMouseCoordinate, const TSharedRef<SWindow>& Window, bool bIgnoreEnabledStatus) const
{
const bool bAcceptsInput = Window->IsVisible() && (Window->AcceptsInput() || IsWindowHousingInteractiveTooltip(Window));
if (bAcceptsInput && Window->IsScreenspaceMouseWithin(ScreenspaceMouseCoordinate))
{
const TArray<FWidgetAndPointer> WidgetsAndCursors = Window->GetHittestGrid()->GetBubblePath(ScreenspaceMouseCoordinate, GetCursorRadius(), bIgnoreEnabledStatus);
return FWidgetPath(WidgetsAndCursors);
}
else
{
return FWidgetPath();
}
}
TSharedRef<SWindow> FSlateApplication::AddWindow( TSharedRef<SWindow> InSlateWindow, const bool bShowImmediately )
{
// Add the Slate window to the Slate application's top-level window array. Note that neither the Slate window
// or the native window are ready to be used yet, however we need to make sure they're in the Slate window
// array so that we can properly respond to OS window messages as soon as they're sent. For example, a window
// activation message may be sent by the OS as soon as the window is shown (in the Init function), and if we
// don't add the Slate window to our window list, we wouldn't be able to route that message to the window.
FSlateWindowHelper::ArrangeWindowToFront(SlateWindows, InSlateWindow);
TSharedRef<FGenericWindow> NewWindow = MakeWindow( InSlateWindow, bShowImmediately );
if( bShowImmediately )
{
InSlateWindow->ShowWindow();
//@todo Slate: Potentially dangerous and annoying if all slate windows that are created steal focus.
if( InSlateWindow->SupportsKeyboardFocus() && InSlateWindow->IsFocusedInitially() )
{
InSlateWindow->GetNativeWindow()->SetWindowFocus();
}
}
return InSlateWindow;
}
TSharedRef< FGenericWindow > FSlateApplication::MakeWindow( TSharedRef<SWindow> InSlateWindow, const bool bShowImmediately )
{
TSharedPtr<FGenericWindow> NativeParent = nullptr;
TSharedPtr<SWindow> ParentWindow = InSlateWindow->GetParentWindow();
if ( ParentWindow.IsValid() )
{
NativeParent = ParentWindow->GetNativeWindow();
}
TSharedRef< FGenericWindowDefinition > Definition = MakeShareable( new FGenericWindowDefinition() );
Definition->Type = InSlateWindow->GetType();
const FVector2D Size = InSlateWindow->GetInitialDesiredSizeInScreen();
Definition->WidthDesiredOnScreen = Size.X;
Definition->HeightDesiredOnScreen = Size.Y;
const FVector2D Position = InSlateWindow->GetInitialDesiredPositionInScreen();
Definition->XDesiredPositionOnScreen = Position.X;
Definition->YDesiredPositionOnScreen = Position.Y;
Definition->HasOSWindowBorder = InSlateWindow->HasOSWindowBorder();
Definition->TransparencySupport = InSlateWindow->GetTransparencySupport();
Definition->AppearsInTaskbar = InSlateWindow->AppearsInTaskbar();
Definition->IsTopmostWindow = InSlateWindow->IsTopmostWindow();
Definition->AcceptsInput = InSlateWindow->AcceptsInput();
Definition->ActivationPolicy = InSlateWindow->ActivationPolicy();
Definition->FocusWhenFirstShown = InSlateWindow->IsFocusedInitially();
Definition->HasCloseButton = InSlateWindow->HasCloseBox();
Definition->SupportsMinimize = InSlateWindow->HasMinimizeBox();
Definition->SupportsMaximize = InSlateWindow->HasMaximizeBox();
Definition->IsModalWindow = InSlateWindow->IsModalWindow();
Definition->IsRegularWindow = InSlateWindow->IsRegularWindow();
Definition->HasSizingFrame = InSlateWindow->HasSizingFrame();
Definition->SizeWillChangeOften = InSlateWindow->SizeWillChangeOften();
Definition->ShouldPreserveAspectRatio = InSlateWindow->ShouldPreserveAspectRatio();
Definition->ExpectedMaxWidth = InSlateWindow->GetExpectedMaxWidth();
Definition->ExpectedMaxHeight = InSlateWindow->GetExpectedMaxHeight();
Definition->Title = InSlateWindow->GetTitle().ToString();
Definition->Opacity = InSlateWindow->GetOpacity();
Definition->CornerRadius = InSlateWindow->GetCornerRadius();
Definition->SizeLimits = InSlateWindow->GetSizeLimits();
TSharedRef< FGenericWindow > NewWindow = PlatformApplication->MakeWindow();
if (LIKELY(FApp::CanEverRender()))
{
InSlateWindow->SetNativeWindow(NewWindow);
InSlateWindow->SetCachedScreenPosition( Position );
InSlateWindow->SetCachedSize( Size );
PlatformApplication->InitializeWindow( NewWindow, Definition, NativeParent, bShowImmediately );
ITextInputMethodSystem* const TextInputMethodSystem = PlatformApplication->GetTextInputMethodSystem();
if ( TextInputMethodSystem )
{
TextInputMethodSystem->ApplyDefaults( NewWindow );
}
}
else
{
InSlateWindow->SetNativeWindow(MakeShareable(new FGenericWindow()));
}
return NewWindow;
}
bool FSlateApplication::CanAddModalWindow() const
{
// A modal window cannot be opened until the renderer has been created.
return CanDisplayWindows();
}
bool FSlateApplication::CanDisplayWindows() const
{
// The renderer must be created and global shaders be available
return Renderer.IsValid() && Renderer->AreShadersInitialized();
}
EUINavigation FSlateApplication::GetNavigationDirectionFromKey(const FKeyEvent& InKeyEvent) const
{
return NavigationConfig->GetNavigationDirectionFromKey(InKeyEvent);
}
void FSlateApplication::AddModalWindow( TSharedRef<SWindow> InSlateWindow, const TSharedPtr<const SWidget> InParentWidget, bool bSlowTaskWindow )
{
if( !CanAddModalWindow() )
{
// Bail out. The incoming window will never be added, and no native window will be created.
return;
}
// Push the active modal window onto the stack.
ActiveModalWindows.AddUnique( InSlateWindow );
// Close the open tooltip when a new window is open. Tooltips from non-modal windows can be dangerous and cause rentrancy into code that shouldnt execute in a modal state.
CloseToolTip();
// Set the modal flag on the window
InSlateWindow->SetAsModalWindow();
// Make sure we aren't in the middle of using a slate draw buffer
Renderer->FlushCommands();
// In slow task windows, depending on the frequency with which the window is updated, it could be quite some time
// before the window is ticked (and drawn) so we hide the window by default and the slow task window will show it when needed
const bool bShowWindow = !bSlowTaskWindow;
// Create the new window
// Note: generally a modal window should not be added without a parent but
// due to this being called from wxWidget editors, this is not always possible
if( InParentWidget.IsValid() )
{
// Find the window of the parent widget
FWidgetPath WidgetPath;
GeneratePathToWidgetChecked( InParentWidget.ToSharedRef(), WidgetPath );
AddWindowAsNativeChild( InSlateWindow, WidgetPath.GetWindow(), bShowWindow );
}
else
{
AddWindow( InSlateWindow, bShowWindow );
}
if ( ActiveModalWindows.Num() == 1 )
{
// Signal that a slate modal window has opened so external windows may be disabled as well
ModalWindowStackStartedDelegate.ExecuteIfBound();
}
// Release mouse capture here in case the new modal window has been added in one of the mouse button
// event callbacks. Otherwise it will be unresponsive until the next mouse up event.
ReleaseMouseCapture();
// Clear the cached pressed mouse buttons, in case a new modal window has been added between the mouse down and mouse up of another window.
PressedMouseButtons.Empty();
// Also force the platform capture off as the call to ReleaseMouseCapture() above still relies on mouse up messages to clear the capture
PlatformApplication->SetCapture( nullptr );
// Disable high precision mouse mode when a modal window is added. On some OS'es even when a window is diabled, raw input is sent to it.
PlatformApplication->SetHighPrecisionMouseMode( false, nullptr );
// Block on all modal windows unless its a slow task. In that case the game thread is allowed to run.
if( !bSlowTaskWindow )
{
// Show the cursor if it was previously hidden so users can interact with the window
if ( PlatformApplication->Cursor.IsValid() )
{
PlatformApplication->Cursor->Show( true );
}
//Throttle loop data
float LastLoopTime = (float)FPlatformTime::Seconds();
const float MinThrottlePeriod = (1.0f / 60.0f); //Throttle the loop to a maximum of 60Hz
// Tick slate from here in the event that we should not return until the modal window is closed.
while( InSlateWindow == GetActiveModalWindow() )
{
//Throttle the loop
const float CurrentLoopTime = FPlatformTime::Seconds();
const float SleepTime = MinThrottlePeriod - (CurrentLoopTime-LastLoopTime);
LastLoopTime = CurrentLoopTime;
if (SleepTime > 0.0f)
{
// Sleep a bit to not eat up all CPU time
FPlatformProcess::Sleep(SleepTime);
}
const float DeltaTime = GetDeltaTime();
// Tick any other systems that need to update during modal dialogs
ModalLoopTickEvent.Broadcast(DeltaTime);
FPlatformMisc::BeginNamedEvent(FColor::Magenta, "Slate::Tick");
{
SCOPE_CYCLE_COUNTER(STAT_SlateTickTime);
SLATE_CYCLE_COUNTER_SCOPE(GSlateTotalTickTime);
// Tick and pump messages for the platform.
TickPlatform(DeltaTime);
// It's possible that during ticking the platform we'll find out the modal dialog was closed.
// in which case we need to abort the current flow.
if ( InSlateWindow != GetActiveModalWindow() )
{
break;
}
// Tick and render Slate
TickApplication(ESlateTickType::All, DeltaTime);
}
// Update Slate Stats
SLATE_STATS_END_FRAME(GetCurrentTime());
FPlatformMisc::EndNamedEvent();
// Synchronize the game thread and the render thread so that the render thread doesn't get too far behind.
Renderer->Sync();
}
}
}
void FSlateApplication::SetModalWindowStackStartedDelegate(FModalWindowStackStarted StackStartedDelegate)
{
ModalWindowStackStartedDelegate = StackStartedDelegate;
}
void FSlateApplication::SetModalWindowStackEndedDelegate(FModalWindowStackEnded StackEndedDelegate)
{
ModalWindowStackEndedDelegate = StackEndedDelegate;
}
TSharedRef<SWindow> FSlateApplication::AddWindowAsNativeChild( TSharedRef<SWindow> InSlateWindow, TSharedRef<SWindow> InParentWindow, const bool bShowImmediately )
{
// @VREDITOR HACK
// Parent window must already have been added
//checkSlow(FSlateWindowHelper::ContainsWindow(SlateWindows, InParentWindow));
// Add the Slate window to the Slate application's top-level window array. Note that neither the Slate window
// or the native window are ready to be used yet, however we need to make sure they're in the Slate window
// array so that we can properly respond to OS window messages as soon as they're sent. For example, a window
// activation message may be sent by the OS as soon as the window is shown (in the Init function), and if we
// don't add the Slate window to our window list, we wouldn't be able to route that message to the window.
InParentWindow->AddChildWindow( InSlateWindow );
// Only make native generic windows if the parent has one. Nullrhi makes only generic windows, whose handles are always null
if ( InParentWindow->GetNativeWindow()->GetOSWindowHandle() || !FApp::CanEverRender() )
{
TSharedRef<FGenericWindow> NewWindow = MakeWindow(InSlateWindow, bShowImmediately);
if ( bShowImmediately )
{
InSlateWindow->ShowWindow();
//@todo Slate: Potentially dangerous and annoying if all slate windows that are created steal focus.
if ( InSlateWindow->SupportsKeyboardFocus() && InSlateWindow->IsFocusedInitially() )
{
InSlateWindow->GetNativeWindow()->SetWindowFocus();
}
}
}
return InSlateWindow;
}
TSharedPtr<IMenu> FSlateApplication::PushMenu(const TSharedRef<SWidget>& InParentWidget, const FWidgetPath& InOwnerPath, const TSharedRef<SWidget>& InContent, const FVector2D& SummonLocation, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately, const FVector2D& SummonLocationSize, TOptional<EPopupMethod> Method, const bool bIsCollapsedByParent)
{
// Caller supplied a valid path? Pass it to the menu stack.
if (InOwnerPath.IsValid())
{
return MenuStack.Push(InOwnerPath, InContent, SummonLocation, TransitionEffect, bFocusImmediately, SummonLocationSize, Method, bIsCollapsedByParent);
}
// If the caller doesn't specify a valid event path we'll generate one from InParentWidget
FWidgetPath WidgetPath;
if (GeneratePathToWidgetUnchecked(InParentWidget, WidgetPath))
{
return MenuStack.Push(WidgetPath, InContent, SummonLocation, TransitionEffect, bFocusImmediately, SummonLocationSize, Method, bIsCollapsedByParent);
}
UE_LOG(LogSlate, Warning, TEXT("Menu could not be pushed. A path to the parent widget(%s) could not be found"), *InParentWidget->ToString());
return TSharedPtr<IMenu>();
}
TSharedPtr<IMenu> FSlateApplication::PushMenu(const TSharedPtr<IMenu>& InParentMenu, const TSharedRef<SWidget>& InContent, const FVector2D& SummonLocation, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately, const FVector2D& SummonLocationSize, const bool bIsCollapsedByParent)
{
return MenuStack.Push(InParentMenu, InContent, SummonLocation, TransitionEffect, bFocusImmediately, SummonLocationSize, bIsCollapsedByParent);
}
TSharedPtr<IMenu> FSlateApplication::PushHostedMenu(const TSharedRef<SWidget>& InParentWidget, const FWidgetPath& InOwnerPath, const TSharedRef<IMenuHost>& InMenuHost, const TSharedRef<SWidget>& InContent, TSharedPtr<SWidget>& OutWrappedContent, const FPopupTransitionEffect& TransitionEffect, EShouldThrottle ShouldThrottle, const bool bIsCollapsedByParent)
{
// Caller supplied a valid path? Pass it to the menu stack.
if (InOwnerPath.IsValid())
{
return MenuStack.PushHosted(InOwnerPath, InMenuHost, InContent, OutWrappedContent, TransitionEffect, ShouldThrottle, bIsCollapsedByParent);
}
// If the caller doesn't specify a valid event path we'll generate one from InParentWidget
FWidgetPath WidgetPath;
if (GeneratePathToWidgetUnchecked(InParentWidget, WidgetPath))
{
return MenuStack.PushHosted(WidgetPath, InMenuHost, InContent, OutWrappedContent, TransitionEffect, ShouldThrottle, bIsCollapsedByParent);
}
return TSharedPtr<IMenu>();
}
TSharedPtr<IMenu> FSlateApplication::PushHostedMenu(const TSharedPtr<IMenu>& InParentMenu, const TSharedRef<IMenuHost>& InMenuHost, const TSharedRef<SWidget>& InContent, TSharedPtr<SWidget>& OutWrappedContent, const FPopupTransitionEffect& TransitionEffect, EShouldThrottle ShouldThrottle, const bool bIsCollapsedByParent)
{
return MenuStack.PushHosted(InParentMenu, InMenuHost, InContent, OutWrappedContent, TransitionEffect, ShouldThrottle, bIsCollapsedByParent);
}
bool FSlateApplication::HasOpenSubMenus(TSharedPtr<IMenu> InMenu) const
{
return MenuStack.HasOpenSubMenus(InMenu);
}
bool FSlateApplication::AnyMenusVisible() const
{
return MenuStack.HasMenus();
}
TSharedPtr<IMenu> FSlateApplication::FindMenuInWidgetPath(const FWidgetPath& InWidgetPath) const
{
return MenuStack.FindMenuInWidgetPath(InWidgetPath);
}
TSharedPtr<SWindow> FSlateApplication::GetVisibleMenuWindow() const
{
return MenuStack.GetHostWindow();
}
void FSlateApplication::DismissAllMenus()
{
MenuStack.DismissAll();
}
void FSlateApplication::DismissMenu(const TSharedPtr<IMenu>& InFromMenu)
{
MenuStack.DismissFrom(InFromMenu);
}
void FSlateApplication::DismissMenuByWidget(const TSharedRef<SWidget>& InWidgetInMenu)
{
FWidgetPath WidgetPath;
if (GeneratePathToWidgetUnchecked(InWidgetInMenu, WidgetPath))
{
TSharedPtr<IMenu> Menu = MenuStack.FindMenuInWidgetPath(WidgetPath);
if (Menu.IsValid())
{
MenuStack.DismissFrom(Menu);
}
}
}
void FSlateApplication::RequestDestroyWindow( TSharedRef<SWindow> InWindowToDestroy )
{
// Logging to track down window shutdown issues with movie loading threads. Too spammy in editor builds with all the windows
#if !WITH_EDITOR
UE_LOG(LogSlate, Log, TEXT("Request Window '%s' being destroyed"), *InWindowToDestroy->GetTitle().ToString() );
#endif
struct local
{
static void Helper( const TSharedRef<SWindow> WindowToDestroy, TArray< TSharedRef<SWindow> >& OutWindowDestroyQueue)
{
/** @return the list of this window's child windows */
TArray< TSharedRef<SWindow> >& ChildWindows = WindowToDestroy->GetChildWindows();
// Children need to be destroyed first.
if( ChildWindows.Num() > 0 )
{
for( int32 ChildIndex = 0; ChildIndex < ChildWindows.Num(); ++ChildIndex )
{
// Recursively request that the window is destroyed which will also queue any children of children etc...
Helper( ChildWindows[ ChildIndex ], OutWindowDestroyQueue );
}
}
OutWindowDestroyQueue.AddUnique( WindowToDestroy );
}
};
local::Helper( InWindowToDestroy, WindowDestroyQueue );
DestroyWindowsImmediately();
}
void FSlateApplication::DestroyWindowImmediately( TSharedRef<SWindow> WindowToDestroy )
{
// Request that the window and its children are destroyed
RequestDestroyWindow( WindowToDestroy );
DestroyWindowsImmediately();
}
void FSlateApplication::ExternalModalStart()
{
if( NumExternalModalWindowsActive++ == 0 )
{
// Close all open menus.
DismissAllMenus();
// Close tool-tips
CloseToolTip();
// Tick and render Slate so that it can destroy any menu windows if necessary before we disable.
Tick();
Renderer->Sync();
if( ActiveModalWindows.Num() > 0 )
{
// There are still modal windows so only enable the new active modal window.
GetActiveModalWindow()->EnableWindow( false );
}
else
{
// We are creating a modal window so all other windows need to be disabled.
for( TArray< TSharedRef<SWindow> >::TIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
{
TSharedRef<SWindow> CurrentWindow = ( *CurrentWindowIt );
CurrentWindow->EnableWindow( false );
}
}
}
}
void FSlateApplication::ExternalModalStop()
{
check(NumExternalModalWindowsActive > 0);
if( --NumExternalModalWindowsActive == 0 )
{
if( ActiveModalWindows.Num() > 0 )
{
// There are still modal windows so only enable the new active modal window.
GetActiveModalWindow()->EnableWindow( true );
}
else
{
// We are creating a modal window so all other windows need to be disabled.
for( TArray< TSharedRef<SWindow> >::TIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
{
TSharedRef<SWindow> CurrentWindow = ( *CurrentWindowIt );
CurrentWindow->EnableWindow( true );
}
}
}
}
void FSlateApplication::InvalidateAllViewports()
{
Renderer->InvalidateAllViewports();
}
void FSlateApplication::RegisterGameViewport( TSharedRef<SViewport> InViewport )
{
RegisterViewport(InViewport);
if (GameViewportWidget != InViewport)
{
InViewport->SetActive(true);
GameViewportWidget = InViewport;
}
ActivateGameViewport();
}
void FSlateApplication::RegisterViewport(TSharedRef<SViewport> InViewport)
{
TSharedPtr<SWindow> ParentWindow = FindWidgetWindow(InViewport);
if (ParentWindow.IsValid())
{
TWeakPtr<ISlateViewport> SlateViewport = InViewport->GetViewportInterface();
if (ensure(SlateViewport.IsValid()))
{
ParentWindow->SetViewport(SlateViewport.Pin().ToSharedRef());
}
}
}
void FSlateApplication::UnregisterGameViewport()
{
ResetToDefaultPointerInputSettings();
if (GameViewportWidget.IsValid())
{
GameViewportWidget.Pin()->SetActive(false);
}
GameViewportWidget.Reset();
}
void FSlateApplication::RegisterVirtualWindow(TSharedRef<SWindow> InWindow)
{
SlateVirtualWindows.AddUnique(InWindow);
}
void FSlateApplication::UnregisterVirtualWindow(TSharedRef<SWindow> InWindow)
{
SlateVirtualWindows.Remove(InWindow);
}
void FSlateApplication::FlushRenderState()
{
if ( Renderer.IsValid() )
{
// Release any temporary material or texture resources we may have cached and are reporting to prevent
// GC on those resources. If the game viewport is being unregistered, we need to flush these resources
// to allow for them to be GC'ed.
Renderer->ReleaseAccessedResources(/* Flush State */ true);
}
}
TSharedPtr<SViewport> FSlateApplication::GetGameViewport() const
{
return GameViewportWidget.Pin();
}
int32 FSlateApplication::GetUserIndexForKeyboard() const
{
//@Todo Slate: Fix this to actual be a map and add API for the user to edit the mapping.
// HACK! Just directly mapping the keyboard to User Index 0.
return 0;
}
int32 FSlateApplication::GetUserIndexForController(int32 ControllerId) const
{
//@Todo Slate: Fix this to actual be a map and add API for the user to edit the mapping.
// HACK! Just directly mapping a controller to a User Index.
return ControllerId;
}
void FSlateApplication::SetUserFocusToGameViewport(uint32 UserIndex, EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/)
{
TSharedPtr<SViewport> CurrentGameViewportWidget = GameViewportWidget.Pin();
if (CurrentGameViewportWidget.IsValid())
{
SetUserFocus(UserIndex, CurrentGameViewportWidget, ReasonFocusIsChanging);
}
}
void FSlateApplication::SetAllUserFocusToGameViewport(EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/)
{
TSharedPtr< SViewport > CurrentGameViewportWidget = GameViewportWidget.Pin();
if (CurrentGameViewportWidget.IsValid())
{
FWidgetPath PathToWidget;
FSlateWindowHelper::FindPathToWidget(SlateWindows, CurrentGameViewportWidget.ToSharedRef(), /*OUT*/ PathToWidget);
ForEachUser([&] (FSlateUser* User) {
SetUserFocus(User, PathToWidget, ReasonFocusIsChanging);
});
}
}
void FSlateApplication::ActivateGameViewport()
{
// Only focus the window if the application is active, if not the application activation sequence will take care of it.
if (bAppIsActive && GameViewportWidget.IsValid())
{
TSharedRef<SViewport> GameViewportWidgetRef = GameViewportWidget.Pin().ToSharedRef();
FWidgetPath PathToViewport;
// If we cannot find the window it could have been destroyed.
if (FSlateWindowHelper::FindPathToWidget(SlateWindows, GameViewportWidgetRef, PathToViewport, EVisibility::All))
{
TSharedRef<SWindow> Window = PathToViewport.GetWindow();
// Set keyboard focus on the actual OS window for the top level Slate window in the viewport path
// This is needed because some OS messages are only sent to the window with keyboard focus
// Slate will translate the message and send it to the actual widget with focus.
// Without this we don't get WM_KEYDOWN or WM_CHAR messages in play in viewport sessions.
Window->GetNativeWindow()->SetWindowFocus();
// Activate the viewport and process the reply
FWindowActivateEvent ActivateEvent(FWindowActivateEvent::EA_Activate, Window);
FReply ViewportActivatedReply = GameViewportWidgetRef->OnViewportActivated(ActivateEvent);
if (ViewportActivatedReply.IsEventHandled())
{
ProcessReply(PathToViewport, ViewportActivatedReply, nullptr, nullptr);
}
}
}
}
bool FSlateApplication::SetUserFocus(uint32 UserIndex, const TSharedPtr<SWidget>& WidgetToFocus, EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/)
{
const bool bValidWidget = WidgetToFocus.IsValid();
ensureMsgf(bValidWidget, TEXT("Attempting to focus an invalid widget. If your intent is to clear focus use ClearUserFocus()"));
if (bValidWidget)
{
if (FSlateUser* User = GetOrCreateUser(UserIndex))
{
FWidgetPath PathToWidget;
const bool bFound = FSlateWindowHelper::FindPathToWidget(SlateWindows, WidgetToFocus.ToSharedRef(), /*OUT*/ PathToWidget);
if (bFound)
{
return SetUserFocus(User, PathToWidget, ReasonFocusIsChanging);
}
else
{
const bool bFoundVirtual = FSlateWindowHelper::FindPathToWidget(SlateVirtualWindows, WidgetToFocus.ToSharedRef(), /*OUT*/ PathToWidget);
if (bFoundVirtual)
{
return SetUserFocus(User, PathToWidget, ReasonFocusIsChanging);
}
else
{
//ensureMsgf(bFound, TEXT("Attempting to focus a widget that isn't in the tree and visible: %s. If your intent is to clear focus use ClearUserFocus()"), WidgetToFocus->ToString());
}
}
}
}
return false;
}
void FSlateApplication::SetAllUserFocus(const TSharedPtr<SWidget>& WidgetToFocus, EFocusCause ReasonFocusIsChanging /*= EFocusCause::SetDirectly*/)
{
const bool bValidWidget = WidgetToFocus.IsValid();
ensureMsgf(bValidWidget, TEXT("Attempting to focus an invalid widget. If your intent is to clear focus use ClearAllUserFocus()"));
if (bValidWidget)
{
FWidgetPath PathToWidget;
const bool bFound = FSlateWindowHelper::FindPathToWidget(SlateWindows, WidgetToFocus.ToSharedRef(), /*OUT*/ PathToWidget);
if (bFound)
{
SetAllUserFocus(PathToWidget, ReasonFocusIsChanging);
}
else
{
//ensureMsgf(bFound, TEXT("Attempting to focus a widget that isn't in the tree and visible: %s. If your intent is to clear focus use ClearAllUserFocus()"), WidgetToFocus->ToString());
}
}
}
TSharedPtr<SWidget> FSlateApplication::GetUserFocusedWidget(uint32 UserIndex) const
{
if ( const FSlateUser* User = GetUser(UserIndex) )
{
return User->GetFocusedWidget();
}
return TSharedPtr<SWidget>();
}
void FSlateApplication::ClearUserFocus(uint32 UserIndex, EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/)
{
SetUserFocus(UserIndex, FWidgetPath(), ReasonFocusIsChanging);
}
void FSlateApplication::ClearAllUserFocus(EFocusCause ReasonFocusIsChanging /*= EFocusCause::SetDirectly*/)
{
SetAllUserFocus(FWidgetPath(), ReasonFocusIsChanging);
}
bool FSlateApplication::SetKeyboardFocus(const TSharedPtr< SWidget >& OptionalWidgetToFocus, EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/)
{
return SetUserFocus(GetUserIndexForKeyboard(), OptionalWidgetToFocus, ReasonFocusIsChanging);
}
void FSlateApplication::ClearKeyboardFocus(const EFocusCause ReasonFocusIsChanging)
{
SetUserFocus(GetUserIndexForKeyboard(), FWidgetPath(), ReasonFocusIsChanging);
}
void FSlateApplication::ResetToDefaultInputSettings()
{
ProcessReply(FWidgetPath(), FReply::Handled().ClearUserFocus(true), nullptr, nullptr);
ResetToDefaultPointerInputSettings();
}
void FSlateApplication::ResetToDefaultPointerInputSettings()
{
for (auto MouseCaptorPath : MouseCaptor.ToWidgetPaths())
{
ProcessReply(MouseCaptorPath, FReply::Handled().ReleaseMouseCapture(), nullptr, nullptr);
}
ProcessReply(FWidgetPath(), FReply::Handled().ReleaseMouseLock(), nullptr, nullptr);
if ( PlatformApplication->Cursor.IsValid() )
{
PlatformApplication->Cursor->SetType(EMouseCursor::Default);
}
}
void* FSlateApplication::GetMouseCaptureWindow() const
{
return PlatformApplication->GetCapture();
}
void FSlateApplication::ReleaseMouseCapture()
{
MouseCaptor.InvalidateCaptureForAllPointers();
}
void FSlateApplication::ReleaseMouseCaptureForUser(int32 UserIndex)
{
MouseCaptor.InvalidateCaptureForUser(UserIndex);
}
FDelegateHandle FSlateApplication::RegisterOnWindowActionNotification(const FOnWindowAction& Notification)
{
OnWindowActionNotifications.Add(Notification);
return OnWindowActionNotifications.Last().GetHandle();
}
void FSlateApplication::UnregisterOnWindowActionNotification(FDelegateHandle Handle)
{
for (int32 Index = 0; Index < OnWindowActionNotifications.Num();)
{
if (OnWindowActionNotifications[Index].GetHandle() == Handle)
{
OnWindowActionNotifications.RemoveAtSwap(Index);
}
else
{
Index++;
}
}
}
TSharedPtr<SWindow> FSlateApplication::FindBestParentWindowForDialogs(const TSharedPtr<SWidget>& InWidget)
{
TSharedPtr<SWindow> ParentWindow = ( InWidget.IsValid() ) ? FindWidgetWindow(InWidget.ToSharedRef()) : TSharedPtr<SWindow>();
if ( !ParentWindow.IsValid() )
{
// First check the active top level window.
TSharedPtr<SWindow> ActiveTopWindow = GetActiveTopLevelWindow();
if ( ActiveTopWindow.IsValid() && ActiveTopWindow->IsRegularWindow() )
{
ParentWindow = ActiveTopWindow;
}
else
{
// If the active top level window isn't a good host, lets just try and find the first
// reasonable window we can host new dialogs off of.
for ( TSharedPtr<SWindow> SlateWindow : SlateWindows )
{
if ( SlateWindow->IsVisible() && SlateWindow->IsRegularWindow() )
{
ParentWindow = SlateWindow;
break;
}
}
}
}
return ParentWindow;
}
const void* FSlateApplication::FindBestParentWindowHandleForDialogs(const TSharedPtr<SWidget>& InWidget)
{
TSharedPtr<SWindow> ParentWindow = FindBestParentWindowForDialogs(InWidget);
const void* ParentWindowWindowHandle = nullptr;
if ( ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid() )
{
ParentWindowWindowHandle = ParentWindow->GetNativeWindow()->GetOSWindowHandle();
}
return ParentWindowWindowHandle;
}
TSharedPtr<SWindow> FSlateApplication::GetActiveTopLevelWindow() const
{
return ActiveTopLevelWindow.Pin();
}
TSharedPtr<SWindow> FSlateApplication::GetActiveModalWindow() const
{
return (ActiveModalWindows.Num() > 0) ? ActiveModalWindows.Last() : nullptr;
}
bool FSlateApplication::SetKeyboardFocus(const FWidgetPath& InFocusPath, const EFocusCause InCause /*= EFocusCause::SetDirectly*/)
{
return SetUserFocus(GetUserIndexForKeyboard(), InFocusPath, InCause);
}
bool FSlateApplication::SetUserFocus(const uint32 InUserIndex, const FWidgetPath& InFocusPath, const EFocusCause InCause)
{
return SetUserFocus(GetOrCreateUser(InUserIndex), InFocusPath, InCause);
}
bool FSlateApplication::SetUserFocus(FSlateUser* User, const FWidgetPath& InFocusPath, const EFocusCause InCause)
{
if ( !User )
{
return false;
}
FUserFocusEntry& UserFocusEntry = User->Focus;
TSharedPtr<IWidgetReflector> WidgetReflector = WidgetReflectorPtr.Pin();
const bool bReflectorShowingFocus = WidgetReflector.IsValid() && WidgetReflector->IsShowingFocus();
// Get the old Widget information
const FWeakWidgetPath OldFocusedWidgetPath = UserFocusEntry.WidgetPath;
TSharedPtr<SWidget> OldFocusedWidget = OldFocusedWidgetPath.IsValid() ? OldFocusedWidgetPath.GetLastWidget().Pin() : TSharedPtr< SWidget >();
// Get the new widget information by finding the first widget in the path that supports focus
FWidgetPath NewFocusedWidgetPath;
TSharedPtr<SWidget> NewFocusedWidget;
if (InFocusPath.IsValid())
{
//UE_LOG(LogSlate, Warning, TEXT("Focus for user %i seeking focus path:\n%s"), InUserIndex, *InFocusPath.ToString());
for (int32 WidgetIndex = InFocusPath.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
{
const FArrangedWidget& WidgetToFocus = InFocusPath.Widgets[WidgetIndex];
// Does this widget support keyboard focus? If so, then we'll go ahead and set it!
if (WidgetToFocus.Widget->SupportsKeyboardFocus())
{
// Is we aren't changing focus then simply return
if (WidgetToFocus.Widget == OldFocusedWidget)
{
//UE_LOG(LogSlate, Warning, TEXT("--Focus Has Not Changed--"));
return false;
}
NewFocusedWidget = WidgetToFocus.Widget;
NewFocusedWidgetPath = InFocusPath.GetPathDownTo(NewFocusedWidget.ToSharedRef());
break;
}
}
}
// Notify widgets in the old focus path that focus is changing
if (OldFocusedWidgetPath.IsValid())
{
FScopedSwitchWorldHack SwitchWorld(OldFocusedWidgetPath.Window.Pin());
for (int32 ChildIndex = 0; ChildIndex < OldFocusedWidgetPath.Widgets.Num(); ++ChildIndex)
{
TSharedPtr<SWidget> SomeWidget = OldFocusedWidgetPath.Widgets[ChildIndex].Pin();
if (SomeWidget.IsValid())
{
SomeWidget->OnFocusChanging(OldFocusedWidgetPath, NewFocusedWidgetPath, FFocusEvent(InCause, User->GetUserIndex()));
}
}
}
// Notify widgets in the new focus path that focus is changing
if (NewFocusedWidgetPath.IsValid())
{
FScopedSwitchWorldHack SwitchWorld(NewFocusedWidgetPath.GetWindow());
for (int32 ChildIndex = 0; ChildIndex < NewFocusedWidgetPath.Widgets.Num(); ++ChildIndex)
{
TSharedPtr<SWidget> SomeWidget = NewFocusedWidgetPath.Widgets[ChildIndex].Widget;
if (SomeWidget.IsValid())
{
SomeWidget->OnFocusChanging(OldFocusedWidgetPath, NewFocusedWidgetPath, FFocusEvent(InCause, User->GetUserIndex()));
}
}
}
//UE_LOG(LogSlate, Warning, TEXT("Focus for user %i set to %s."), User->GetUserIndex(), NewFocusedWidget.IsValid() ? *NewFocusedWidget->ToString() : TEXT("Invalid"));
// Store a weak widget path to the widget that's taking focus
UserFocusEntry.WidgetPath = FWeakWidgetPath(NewFocusedWidgetPath);
// Store the cause of the focus
UserFocusEntry.FocusCause = InCause;
// Figure out if we should show focus for this focus entry
UserFocusEntry.ShowFocus = false;
if (NewFocusedWidgetPath.IsValid())
{
UserFocusEntry.ShowFocus = InCause == EFocusCause::Navigation;
for (int32 WidgetIndex = NewFocusedWidgetPath.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
{
TOptional<bool> ShowFocus = NewFocusedWidgetPath.Widgets[WidgetIndex].Widget->OnQueryShowFocus(InCause);
if (ShowFocus.IsSet())
{
UserFocusEntry.ShowFocus = ShowFocus.GetValue();
break;
}
}
}
// Let the old widget know that it lost keyboard focus
if(OldFocusedWidget.IsValid())
{
// Switch worlds for widgets in the old path
FScopedSwitchWorldHack SwitchWorld(OldFocusedWidgetPath.Window.Pin());
// Let previously-focused widget know that it's losing focus
OldFocusedWidget->OnFocusLost(FFocusEvent(InCause, User->GetUserIndex()));
}
if (bReflectorShowingFocus)
{
WidgetReflector->SetWidgetsToVisualize(NewFocusedWidgetPath);
}
// Let the new widget know that it's received keyboard focus
if (NewFocusedWidget.IsValid())
{
TSharedPtr<SWindow> FocusedWindow = NewFocusedWidgetPath.GetWindow();
// Switch worlds for widgets in the new path
FScopedSwitchWorldHack SwitchWorld(FocusedWindow);
// Set ActiveTopLevelWindow to the newly focused window
ActiveTopLevelWindow = FocusedWindow;
const FArrangedWidget& WidgetToFocus = NewFocusedWidgetPath.Widgets.Last();
FReply Reply = NewFocusedWidget->OnFocusReceived(WidgetToFocus.Geometry, FFocusEvent(InCause, User->GetUserIndex()));
if (Reply.IsEventHandled())
{
ProcessReply(InFocusPath, Reply, nullptr, nullptr, User->GetUserIndex());
}
}
return true;
}
void FSlateApplication::SetAllUserFocus(const FWidgetPath& InFocusPath, const EFocusCause InCause)
{
ForEachUser([&] (FSlateUser* User) {
SetUserFocus(User, InFocusPath, InCause);
});
}
void FSlateApplication::SetAllUserFocusAllowingDescendantFocus(const FWidgetPath& InFocusPath, const EFocusCause InCause)
{
TSharedRef<SWidget> FocusWidget = InFocusPath.Widgets.Last().Widget;
ForEachUser([&] (FSlateUser* User) {
const FUserFocusEntry& UserFocusEntry = User->Focus;
if (!UserFocusEntry.WidgetPath.ContainsWidget(FocusWidget))
{
SetUserFocus(User, InFocusPath, InCause);
}
});
}
FModifierKeysState FSlateApplication::GetModifierKeys() const
{
return PlatformApplication->GetModifierKeys();
}
void FSlateApplication::OnShutdown()
{
CloseAllWindowsImmediately();
}
void FSlateApplication::CloseAllWindowsImmediately()
{
// Clean up our tooltip window
TSharedPtr< SWindow > PinnedToolTipWindow(ToolTipWindow.Pin());
if (PinnedToolTipWindow.IsValid())
{
PinnedToolTipWindow->RequestDestroyWindow();
ToolTipWindow.Reset();
}
for (int32 WindowIndex = 0; WindowIndex < SlateWindows.Num(); ++WindowIndex)
{
// Destroy all top level windows. This will also request that all children of each window be destroyed
RequestDestroyWindow(SlateWindows[WindowIndex]);
}
DestroyWindowsImmediately();
}
void FSlateApplication::DestroyWindowsImmediately()
{
// Destroy any windows that were queued for deletion.
// Thomas.Sarkanen: I've changed this from a for() to a while() loop so that it is now valid to call RequestDestroyWindow()
// in the callstack of another call to RequestDestroyWindow(). Previously this would cause a stack overflow, as the
// WindowDestroyQueue would be continually added to each time the for() loop ran.
while ( WindowDestroyQueue.Num() > 0 )
{
TSharedRef<SWindow> CurrentWindow = WindowDestroyQueue[0];
WindowDestroyQueue.Remove(CurrentWindow);
if( ActiveModalWindows.Num() > 0 && ActiveModalWindows.Contains( CurrentWindow ) )
{
ActiveModalWindows.Remove( CurrentWindow );
if( ActiveModalWindows.Num() > 0 )
{
// There are still modal windows so only enable the new active modal window.
GetActiveModalWindow()->EnableWindow( true );
}
else
{
// There are no modal windows so renable all slate windows
for ( TArray< TSharedRef<SWindow> >::TConstIterator SlateWindowIter( SlateWindows ); SlateWindowIter; ++SlateWindowIter )
{
// All other windows need to be re-enabled BEFORE a modal window is destroyed or focus will not be set correctly
(*SlateWindowIter)->EnableWindow( true );
}
// Signal that all slate modal windows are closed
ModalWindowStackEndedDelegate.ExecuteIfBound();
}
}
// Any window being destroyed should be removed from the menu stack if its in it
MenuStack.OnWindowDestroyed(CurrentWindow);
// Perform actual cleanup of the window
PrivateDestroyWindow( CurrentWindow );
}
WindowDestroyQueue.Empty();
}
void FSlateApplication::SetExitRequestedHandler( const FSimpleDelegate& OnExitRequestedHandler )
{
OnExitRequested = OnExitRequestedHandler;
}
bool FSlateApplication::GeneratePathToWidgetUnchecked( TSharedRef< const SWidget > InWidget, FWidgetPath& OutWidgetPath, EVisibility VisibilityFilter ) const
{
if ( !FSlateWindowHelper::FindPathToWidget(SlateWindows, InWidget, OutWidgetPath, VisibilityFilter) )
{
return FSlateWindowHelper::FindPathToWidget(SlateVirtualWindows, InWidget, OutWidgetPath, VisibilityFilter);
}
return true;
}
void FSlateApplication::GeneratePathToWidgetChecked( TSharedRef< const SWidget > InWidget, FWidgetPath& OutWidgetPath, EVisibility VisibilityFilter ) const
{
if ( !FSlateWindowHelper::FindPathToWidget(SlateWindows, InWidget, OutWidgetPath, VisibilityFilter) )
{
const bool bWasFound = FSlateWindowHelper::FindPathToWidget(SlateVirtualWindows, InWidget, OutWidgetPath, VisibilityFilter);
check(bWasFound);
}
}
TSharedPtr<SWindow> FSlateApplication::FindWidgetWindow( TSharedRef< const SWidget > InWidget ) const
{
FWidgetPath WidgetPath;
return FindWidgetWindow( InWidget, WidgetPath );
}
TSharedPtr<SWindow> FSlateApplication::FindWidgetWindow( TSharedRef< const SWidget > InWidget, FWidgetPath& OutWidgetPath ) const
{
// If the user wants a widget path back populate it instead
if ( !FSlateWindowHelper::FindPathToWidget(SlateWindows, InWidget, OutWidgetPath, EVisibility::All) )
{
if ( !FSlateWindowHelper::FindPathToWidget(SlateVirtualWindows, InWidget, OutWidgetPath, EVisibility::All) )
{
return nullptr;
}
}
return OutWidgetPath.TopLevelWindow;
}
void FSlateApplication::ProcessReply( const FWidgetPath& CurrentEventPath, const FReply TheReply, const FWidgetPath* WidgetsUnderMouse, const FPointerEvent* InMouseEvent, const uint32 UserIndex )
{
const TSharedPtr<FDragDropOperation> ReplyDragDropContent = TheReply.GetDragDropContent();
const bool bStartingDragDrop = ReplyDragDropContent.IsValid();
const bool bIsVirtualInteraction = CurrentEventPath.IsValid() ? CurrentEventPath.GetWindow()->IsVirtualWindow() : false;
// Release mouse capture if requested or if we are starting a drag and drop.
// Make sure to only clobber WidgetsUnderCursor if we actually had a mouse capture.
uint32 PointerIndex = InMouseEvent != nullptr ? InMouseEvent->GetPointerIndex() : CursorPointerIndex;
if (MouseCaptor.HasCaptureForPointerIndex(UserIndex,PointerIndex) && (TheReply.ShouldReleaseMouse() || bStartingDragDrop) )
{
WidgetsUnderCursorLastEvent.Add( FUserAndPointer(UserIndex,PointerIndex), MouseCaptor.ToWeakPath(UserIndex,PointerIndex) );
MouseCaptor.InvalidateCaptureForPointer(UserIndex,PointerIndex);
// If mouse capture changes, we should refresh the cursor state.
bQueryCursorRequested = true;
}
// Clear focus is requested.
if (TheReply.ShouldReleaseUserFocus())
{
if (TheReply.AffectsAllUsers())
{
ForEachUser([&] (FSlateUser* User) {
SetUserFocus(User, FWidgetPath(), TheReply.GetFocusCause());
});
}
else
{
SetUserFocus(UserIndex, FWidgetPath(), TheReply.GetFocusCause());
}
}
if (TheReply.ShouldEndDragDrop())
{
CancelDragDrop();
}
if ( bStartingDragDrop )
{
checkf( !this->DragDropContent.IsValid(), TEXT("Drag and Drop already in progress!") );
check( true == TheReply.IsEventHandled() );
check( WidgetsUnderMouse != nullptr );
check( InMouseEvent != nullptr );
DragDropContent = ReplyDragDropContent;
// We have entered drag and drop mode.
// Pretend that the mouse left all the previously hovered widgets, and a drag entered them.
FEventRouter::Route<FNoReply>(this, FEventRouter::FBubblePolicy(*WidgetsUnderMouse), *InMouseEvent, [](const FArrangedWidget& SomeWidget, const FPointerEvent& PointerEvent)
{
SomeWidget.Widget->OnMouseLeave( PointerEvent );
return FNoReply();
});
FEventRouter::Route<FNoReply>(this, FEventRouter::FBubblePolicy(*WidgetsUnderMouse), FDragDropEvent( *InMouseEvent, ReplyDragDropContent ), [](const FArrangedWidget& SomeWidget, const FDragDropEvent& DragDropEvent )
{
SomeWidget.Widget->OnDragEnter( SomeWidget.Geometry, DragDropEvent );
return FNoReply();
});
}
// Setting mouse capture, mouse position, and locking the mouse
// are all operations that we shouldn't do if our application isn't Active (The OS ignores half of it, and we'd be in a half state)
// We do allow the release of capture and lock when deactivated, this is innocuous of some platforms but required on others when
// the Application deactivated before the window. (Mac is an example of this)
if (bAppIsActive || bIsVirtualInteraction)
{
TSharedPtr<SWidget> RequestedMouseCaptor = TheReply.GetMouseCaptor();
// Do not capture the mouse if we are also starting a drag and drop.
if (RequestedMouseCaptor.IsValid() && !bStartingDragDrop)
{
MouseCaptor.SetMouseCaptor(UserIndex, PointerIndex, CurrentEventPath, RequestedMouseCaptor);
// When the cursor capture state changes we need to refresh cursor state.
bQueryCursorRequested = true;
}
if ( !bIsVirtualInteraction && CurrentEventPath.IsValid() && RequestedMouseCaptor.IsValid())
{
// If the mouse is being captured or released, toggle high precision raw input if requested by the reply.
// Raw input is only used with mouse capture
if (TheReply.ShouldUseHighPrecisionMouse())
{
const TSharedRef< SWindow> Window = CurrentEventPath.GetWindow();
PlatformApplication->SetCapture(Window->GetNativeWindow());
PlatformApplication->SetHighPrecisionMouseMode(true, Window->GetNativeWindow());
// When the cursor capture state changes we need to refresh cursor state.
bQueryCursorRequested = true;
}
}
TOptional<FIntPoint> RequestedMousePos = TheReply.GetRequestedMousePos();
if (RequestedMousePos.IsSet())
{
const FVector2D Position = RequestedMousePos.GetValue();
PointerIndexLastPositionMap.Add(CursorPointerIndex, Position);
SetCursorPos(Position);
}
if (TheReply.GetMouseLockWidget().IsValid())
{
// The reply requested mouse lock so tell the native application to lock the mouse to the widget receiving the event
LockCursor(TheReply.GetMouseLockWidget());
}
}
// Releasing high precision mode. @HACKISH We can only support high precision mode on true mouse hardware cursors
// but if the user index isn't 0, there's no way it's the real mouse so we should ignore this if it's not user 0,
// because that means it's a virtual controller.
if ( UserIndex == 0 && !bIsVirtualInteraction )
{
if ( CurrentEventPath.IsValid() && TheReply.ShouldReleaseMouse() && !TheReply.ShouldUseHighPrecisionMouse() )
{
if ( PlatformApplication->IsUsingHighPrecisionMouseMode() )
{
PlatformApplication->SetHighPrecisionMouseMode(false, nullptr);
PlatformApplication->SetCapture(nullptr);
// When the cursor capture state changes we need to refresh cursor state.
bQueryCursorRequested = true;
}
}
}
// Releasing Mouse Lock
if (TheReply.ShouldReleaseMouseLock())
{
LockCursor(nullptr);
}
// If we have a valid Navigation request attempt the navigation.
if (TheReply.GetNavigationDestination().IsValid() || TheReply.GetNavigationType() != EUINavigation::Invalid)
{
FWidgetPath NavigationSource;
if (TheReply.GetNavigationSource() == ENavigationSource::WidgetUnderCursor)
{
NavigationSource = *WidgetsUnderMouse;
}
else if(const FSlateUser* User = GetOrCreateUser(UserIndex))
{
const FUserFocusEntry& UserFocusEntry = User->Focus;
NavigationSource = UserFocusEntry.WidgetPath.ToWidgetPath();
}
if (NavigationSource.IsValid())
{
if (TheReply.GetNavigationDestination().IsValid())
{
ExecuteNavigation(NavigationSource, TheReply.GetNavigationDestination(), UserIndex);
}
else
{
FNavigationEvent NavigationEvent(PlatformApplication->GetModifierKeys(), UserIndex, TheReply.GetNavigationType(), TheReply.GetNavigationGenesis());
FNavigationReply NavigationReply = FNavigationReply::Escape();
for (int32 WidgetIndex = NavigationSource.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
{
FArrangedWidget& SomeWidgetGettingEvent = NavigationSource.Widgets[WidgetIndex];
if (SomeWidgetGettingEvent.Widget->IsEnabled())
{
NavigationReply = SomeWidgetGettingEvent.Widget->OnNavigation(SomeWidgetGettingEvent.Geometry, NavigationEvent).SetHandler(SomeWidgetGettingEvent.Widget);
if (NavigationReply.GetBoundaryRule() != EUINavigationRule::Escape || WidgetIndex == 0)
{
AttemptNavigation(NavigationSource, NavigationEvent, NavigationReply, SomeWidgetGettingEvent);
break;
}
}
}
}
}
}
if ( TheReply.GetDetectDragRequest().IsValid() )
{
checkSlow(InMouseEvent);
DragDetector.StartDragDetection(
WidgetsUnderMouse->GetPathDownTo(TheReply.GetDetectDragRequest().ToSharedRef()),
InMouseEvent->GetUserIndex(),
InMouseEvent->GetPointerIndex(),
TheReply.GetDetectDragRequestButton(),
InMouseEvent->GetScreenSpacePosition());
}
// Set focus if requested.
TSharedPtr<SWidget> RequestedFocusRecepient = TheReply.GetUserFocusRecepient();
if (TheReply.ShouldSetUserFocus() || RequestedFocusRecepient.IsValid())
{
if (TheReply.AffectsAllUsers())
{
ForEachUser([&] (FSlateUser* User) {
SetUserFocus(User->GetUserIndex(), RequestedFocusRecepient, TheReply.GetFocusCause());
});
}
else
{
SetUserFocus(UserIndex, RequestedFocusRecepient, TheReply.GetFocusCause());
}
}
}
void FSlateApplication::LockCursor(const TSharedPtr<SWidget>& Widget)
{
if (PlatformApplication->Cursor.IsValid())
{
if (Widget.IsValid())
{
// Get a path to this widget so we know the position and size of its geometry
FWidgetPath WidgetPath;
const bool bFoundWidget = GeneratePathToWidgetUnchecked(Widget.ToSharedRef(), WidgetPath);
if ( ensureMsgf(bFoundWidget, TEXT("Attempting to LockCursor() to widget but could not find widget %s"), *Widget->ToString()) )
{
LockCursorToPath(WidgetPath);
}
}
else
{
UnlockCursor();
}
}
}
void FSlateApplication::LockCursorToPath(const FWidgetPath& WidgetPath)
{
// The last widget in the path should be the widget we are locking the cursor to
const FArrangedWidget& WidgetGeom = WidgetPath.Widgets[WidgetPath.Widgets.Num() - 1];
TSharedRef<SWindow> Window = WidgetPath.GetWindow();
// Do not attempt to lock the cursor to the window if its not in the foreground. It would cause annoying side effects
if (Window->GetNativeWindow()->IsForegroundWindow())
{
const FSlateRect SlateClipRect = WidgetGeom.Geometry.GetClippingRect();
CursorLock.LastComputedBounds = SlateClipRect;
CursorLock.PathToLockingWidget = WidgetPath;
// Generate a screen space clip rect based on the widgets geometry
// Note: We round the upper left coordinate of the clip rect so we guarantee the rect is inside the geometry of the widget. If we truncated when there is a half pixel we would cause the clip
// rect to be half a pixel larger than the geometry and cause the mouse to go outside of the geometry.
RECT ClipRect;
ClipRect.left = FMath::RoundToInt(SlateClipRect.Left);
ClipRect.top = FMath::RoundToInt(SlateClipRect.Top);
ClipRect.right = FMath::TruncToInt(SlateClipRect.Right);
ClipRect.bottom = FMath::TruncToInt(SlateClipRect.Bottom);
// Lock the mouse to the widget
PlatformApplication->Cursor->Lock(&ClipRect);
}
}
void FSlateApplication::UnlockCursor()
{
// Unlock the mouse
PlatformApplication->Cursor->Lock(nullptr);
CursorLock.PathToLockingWidget = FWeakWidgetPath();
}
void FSlateApplication::UpdateCursorLockRegion()
{
const FWidgetPath PathToWidget = CursorLock.PathToLockingWidget.ToWidgetPath(FWeakWidgetPath::EInterruptedPathHandling::ReturnInvalid);
if (PathToWidget.IsValid())
{
const FSlateRect ComputedClipRect = PathToWidget.Widgets.Last().Geometry.GetClippingRect();
if (ComputedClipRect != CursorLock.LastComputedBounds)
{
LockCursorToPath(PathToWidget);
}
}
}
void FSlateApplication::SetLastUserInteractionTime(double InCurrentTime)
{
if (LastUserInteractionTime != InCurrentTime)
{
LastUserInteractionTime = InCurrentTime;
LastUserInteractionTimeUpdateEvent.Broadcast(LastUserInteractionTime);
}
}
void FSlateApplication::QueryCursor()
{
bQueryCursorRequested = false;
// The slate loading widget thread is not allowed to execute this code
// as it is unsafe to read the hittest grid in another thread
if ( PlatformApplication->Cursor.IsValid() && IsInGameThread() )
{
// drag-drop overrides cursor
FCursorReply CursorReply = FCursorReply::Unhandled();
if ( IsDragDropping() )
{
CursorReply = DragDropContent->OnCursorQuery();
}
if (!CursorReply.IsEventHandled())
{
FWidgetPath WidgetsToQueryForCursor;
const TSharedPtr<SWindow> ActiveModalWindow = GetActiveModalWindow();
const FVector2D CurrentCursorPosition = GetCursorPos();
const FVector2D LastCursorPosition = GetLastCursorPos();
const FPointerEvent CursorEvent(
CursorPointerIndex,
CurrentCursorPosition,
LastCursorPosition,
CurrentCursorPosition - LastCursorPosition,
PressedMouseButtons,
PlatformApplication->GetModifierKeys()
);
// Query widgets with mouse capture for the cursor
if (MouseCaptor.HasCaptureForPointerIndex(CursorUserIndex, CursorPointerIndex))
{
FWidgetPath MouseCaptorPath = MouseCaptor.ToWidgetPath( FWeakWidgetPath::EInterruptedPathHandling::Truncate, &CursorEvent);
if ( MouseCaptorPath.IsValid() )
{
TSharedRef< SWindow > CaptureWindow = MouseCaptorPath.GetWindow();
// Never query the mouse captor path if it is outside an active modal window
if ( !ActiveModalWindow.IsValid() || ( CaptureWindow == ActiveModalWindow || CaptureWindow->IsDescendantOf(ActiveModalWindow) ) )
{
WidgetsToQueryForCursor = MouseCaptorPath;
}
}
}
else
{
WidgetsToQueryForCursor = LocateWindowUnderMouse( GetCursorPos(), GetInteractiveTopLevelWindows() );
}
if (WidgetsToQueryForCursor.IsValid())
{
// Switch worlds for widgets in the current path
FScopedSwitchWorldHack SwitchWorld( WidgetsToQueryForCursor );
for (int32 WidgetIndex = WidgetsToQueryForCursor.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
{
const FArrangedWidget& ArrangedWidget = WidgetsToQueryForCursor.Widgets[WidgetIndex];
CursorReply = ArrangedWidget.Widget->OnCursorQuery(ArrangedWidget.Geometry, CursorEvent);
if (CursorReply.IsEventHandled())
{
if (!CursorReply.GetCursorWidget().IsValid())
{
for (; WidgetIndex >= 0; --WidgetIndex)
{
TOptional<TSharedRef<SWidget>> CursorWidget = WidgetsToQueryForCursor.Widgets[WidgetIndex].Widget->OnMapCursor(CursorReply);
if (CursorWidget.IsSet())
{
CursorReply.SetCursorWidget(WidgetsToQueryForCursor.GetWindow(), CursorWidget.GetValue());
break;
}
}
}
break;
}
}
if (!CursorReply.IsEventHandled() && WidgetsToQueryForCursor.IsValid())
{
// Query was NOT handled, and we are still over a slate window.
CursorReply = FCursorReply::Cursor(EMouseCursor::Default);
}
}
else
{
// Set the default cursor when there isn't an active window under the cursor and the mouse isn't captured
CursorReply = FCursorReply::Cursor(EMouseCursor::Default);
}
}
ProcessCursorReply(CursorReply);
}
}
void FSlateApplication::ProcessCursorReply(const FCursorReply& CursorReply)
{
if (CursorReply.IsEventHandled())
{
CursorWidgetPtr = CursorReply.GetCursorWidget();
if (CursorReply.GetCursorWidget().IsValid())
{
CursorReply.GetCursorWidget()->SetVisibility(EVisibility::HitTestInvisible);
CursorWindowPtr = CursorReply.GetCursorWindow();
PlatformApplication->Cursor->SetType(EMouseCursor::Custom);
}
else
{
CursorWindowPtr.Reset();
PlatformApplication->Cursor->SetType(CursorReply.GetCursorType());
}
}
else
{
CursorWindowPtr.Reset();
CursorWidgetPtr.Reset();
}
}
void FSlateApplication::SpawnToolTip( const TSharedRef<IToolTip>& InToolTip, const FVector2D& InSpawnLocation )
{
// Close existing tool tip, if we have one
CloseToolTip();
// Spawn the new tool tip
{
TSharedPtr< SWindow > NewToolTipWindow( ToolTipWindow.Pin() );
if( !NewToolTipWindow.IsValid() )
{
// Create the tool tip window
NewToolTipWindow = SWindow::MakeToolTipWindow();
// Don't show the window yet. We'll set it up with some content first!
const bool bShowImmediately = false;
AddWindow( NewToolTipWindow.ToSharedRef(), bShowImmediately );
}
NewToolTipWindow->SetContent
(
SNew(SWeakWidget)
.PossiblyNullContent(InToolTip->AsWidget())
);
// Move the window again to recalculate popup window position if necessary (tool tip may spawn outside of the monitors work area)
// and in that case we need to adjust it
DesiredToolTipLocation = InSpawnLocation;
{
// Make sure the desired size is valid
NewToolTipWindow->SlatePrepass(FSlateApplication::Get().GetApplicationScale()*NewToolTipWindow->GetNativeWindow()->GetDPIScaleFactor());
FSlateRect Anchor(DesiredToolTipLocation.X, DesiredToolTipLocation.Y, DesiredToolTipLocation.X, DesiredToolTipLocation.Y);
DesiredToolTipLocation = CalculatePopupWindowPosition( Anchor, NewToolTipWindow->GetDesiredSizeDesktopPixels() );
// MoveWindowTo will adjust the window's position, if needed
NewToolTipWindow->MoveWindowTo( DesiredToolTipLocation );
}
NewToolTipWindow->SetOpacity(0.0f);
// Show the window
NewToolTipWindow->ShowWindow();
// Keep a weak reference to the tool tip window
ToolTipWindow = NewToolTipWindow;
// Keep track of when this tool tip was spawned
ToolTipSummonTime = FPlatformTime::Seconds();
}
}
void FSlateApplication::CloseToolTip()
{
// Notify the source widget that its tooltip is closing
{
TSharedPtr<SWidget> SourceWidget = ActiveToolTipWidgetSource.Pin();
if (SourceWidget.IsValid())
{
SourceWidget->OnToolTipClosing();
}
}
// Notify the active tooltip that it's being closed.
TSharedPtr<IToolTip> StableActiveToolTip = ActiveToolTip.Pin();
if ( StableActiveToolTip.IsValid() )
{
StableActiveToolTip->OnClosed();
}
// If the tooltip had a new window holding it, hide the window.
TSharedPtr< SWindow > PinnedToolTipWindow( ToolTipWindow.Pin() );
if( PinnedToolTipWindow.IsValid() && PinnedToolTipWindow->IsVisible() )
{
// Hide the tool tip window. We don't destroy the window, because we want to reuse it for future tool tips.
PinnedToolTipWindow->HideWindow();
}
ActiveToolTip.Reset();
ActiveToolTipWidgetSource.Reset();
ToolTipOffsetDirection = EToolTipOffsetDirection::Undetermined;
}
void FSlateApplication::UpdateToolTip( bool AllowSpawningOfNewToolTips )
{
const bool bCheckForToolTipChanges =
IsInGameThread() && // We should never allow the slate loading thread to create new windows or interact with the hittest grid
bAllowToolTips && // Tool-tips must be enabled
!IsUsingHighPrecisionMouseMovment() && // If we are using HighPrecision movement then we can't rely on the OS cursor to be accurate
!IsDragDropping(); // We must not currwently be in the middle of a drag-drop action
// We still want to show tooltips for widgets that are disabled
const bool bIgnoreEnabledStatus = true;
float DPIScaleFactor = 1.0f;
FWidgetPath WidgetsToQueryForToolTip;
// We don't show any tooltips when drag and dropping or when another app is active
if (bCheckForToolTipChanges)
{
// Ask each widget under the Mouse if they have a tool tip to show.
FWidgetPath WidgetsUnderMouse = LocateWindowUnderMouse( GetCursorPos(), GetInteractiveTopLevelWindows(), bIgnoreEnabledStatus );
// Don't attempt to show tooltips inside an existing tooltip
if (!WidgetsUnderMouse.IsValid() || WidgetsUnderMouse.GetWindow() != ToolTipWindow.Pin())
{
WidgetsToQueryForToolTip = WidgetsUnderMouse;
if (false)// @na (WidgetsUnderMouse.IsValid())
{
DPIScaleFactor = WidgetsUnderMouse.GetWindow()->GetNativeWindow()->GetDPIScaleFactor();
}
}
}
bool bHaveForceFieldRect = false;
FSlateRect ForceFieldRect;
TSharedPtr<IToolTip> NewToolTip;
TSharedPtr<SWidget> WidgetProvidingNewToolTip;
for ( int32 WidgetIndex=WidgetsToQueryForToolTip.Widgets.Num()-1; WidgetIndex >= 0; --WidgetIndex )
{
FArrangedWidget* CurWidgetGeometry = &WidgetsToQueryForToolTip.Widgets[WidgetIndex];
const TSharedRef<SWidget>& CurWidget = CurWidgetGeometry->Widget;
if( !NewToolTip.IsValid() )
{
TSharedPtr< IToolTip > WidgetToolTip = CurWidget->GetToolTip();
// Make sure the tool-tip currently is displaying something before spawning it.
if( WidgetToolTip.IsValid() && !WidgetToolTip->IsEmpty() )
{
WidgetProvidingNewToolTip = CurWidget;
NewToolTip = WidgetToolTip;
}
}
// Keep track of the root most widget with a tool-tip force field enabled
if( CurWidget->HasToolTipForceField() )
{
if( !bHaveForceFieldRect )
{
bHaveForceFieldRect = true;
ForceFieldRect = CurWidgetGeometry->Geometry.GetClippingRect();
}
else
{
// Grow the rect to encompass this geometry. Usually, the parent's rect should always be inclusive
// of it's child though. Just is kind of just being paranoid.
ForceFieldRect = ForceFieldRect.Expand( CurWidgetGeometry->Geometry.GetClippingRect() );
}
ForceFieldRect = (1.0f / DPIScaleFactor) * ForceFieldRect;
}
}
// Did the tool tip change from last time?
const bool bToolTipChanged = (NewToolTip != ActiveToolTip.Pin());
// Any widgets that wish to handle visualizing the tooltip get a chance here.
TSharedPtr<SWidget> NewTooltipVisualizer;
if (bToolTipChanged)
{
// Remove existing tooltip if there is one.
if (TooltipVisualizerPtr.IsValid())
{
TooltipVisualizerPtr.Pin()->OnVisualizeTooltip( nullptr );
}
// Notify the new tooltip that it's about to be opened.
if ( NewToolTip.IsValid() )
{
NewToolTip->OnOpening();
}
TSharedPtr<SWidget> NewToolTipWidget = NewToolTip.IsValid() ? NewToolTip->AsWidget() : TSharedPtr<SWidget>();
bool bOnVisualizeTooltipHandled = false;
// Some widgets might want to provide an alternative Tooltip Handler.
for ( int32 WidgetIndex=WidgetsToQueryForToolTip.Widgets.Num()-1; !bOnVisualizeTooltipHandled && WidgetIndex >= 0; --WidgetIndex )
{
const FArrangedWidget& CurWidgetGeometry = WidgetsToQueryForToolTip.Widgets[WidgetIndex];
bOnVisualizeTooltipHandled = CurWidgetGeometry.Widget->OnVisualizeTooltip( NewToolTipWidget );
if (bOnVisualizeTooltipHandled)
{
// Someone is taking care of visualizing this tooltip
NewTooltipVisualizer = CurWidgetGeometry.Widget;
}
}
}
// If a widget under the cursor has a tool-tip forcefield active, then go through any menus
// in the menu stack that are above that widget's window, and make sure those windows also
// prevent the tool-tip from encroaching. This prevents tool-tips from drawing over sub-menus
// spawned from menu items in a different window, for example.
if( bHaveForceFieldRect && WidgetsToQueryForToolTip.IsValid() )
{
TSharedPtr<IMenu> MenuInPath = MenuStack.FindMenuInWidgetPath(WidgetsToQueryForToolTip);
if (MenuInPath.IsValid())
{
ForceFieldRect = ForceFieldRect.Expand(MenuStack.GetToolTipForceFieldRect(MenuInPath.ToSharedRef(), WidgetsToQueryForToolTip));
}
}
{
TSharedPtr<IToolTip> ActiveToolTipPtr = ActiveToolTip.Pin();
if ( ( ActiveToolTipPtr.IsValid() && !ActiveToolTipPtr->IsInteractive() ) || ( NewToolTip.IsValid() && NewToolTip != ActiveToolTip.Pin() ) )
{
// Keep track of where we want tool tips to be positioned
DesiredToolTipLocation = GetLastCursorPos() + SlateDefs::ToolTipOffsetFromMouse;
}
}
TSharedPtr< SWindow > ToolTipWindowPtr = ToolTipWindow.Pin();
if ( ToolTipWindowPtr.IsValid() )
{
FSlateRect Anchor(DesiredToolTipLocation.X, DesiredToolTipLocation.Y, DesiredToolTipLocation.X, DesiredToolTipLocation.Y);
DesiredToolTipLocation = CalculatePopupWindowPosition( Anchor, ToolTipWindowPtr->GetDesiredSizeDesktopPixels());
}
// Repel tool-tip from a force field, if necessary
if( bHaveForceFieldRect )
{
FVector2D ToolTipShift;
ToolTipShift.X = ( ForceFieldRect.Right + SlateDefs::ToolTipOffsetFromForceField.X ) - DesiredToolTipLocation.X;
ToolTipShift.Y = ( ForceFieldRect.Bottom + SlateDefs::ToolTipOffsetFromForceField.Y ) - DesiredToolTipLocation.Y;
// Make sure the tool-tip needs to be offset
if( ToolTipShift.X > 0.0f && ToolTipShift.Y > 0.0f )
{
// Find the best edge to move the tool-tip towards
if( ToolTipOffsetDirection == EToolTipOffsetDirection::Right ||
( ToolTipOffsetDirection == EToolTipOffsetDirection::Undetermined && ToolTipShift.X < ToolTipShift.Y ) )
{
// Move right
DesiredToolTipLocation.X += ToolTipShift.X;
ToolTipOffsetDirection = EToolTipOffsetDirection::Right;
}
else
{
// Move down
DesiredToolTipLocation.Y += ToolTipShift.Y;
ToolTipOffsetDirection = EToolTipOffsetDirection::Down;
}
}
}
// The tool tip changed...
if ( bToolTipChanged )
{
// Close any existing tooltips; Unless the current tooltip is interactive and we don't have a valid tooltip to replace it
TSharedPtr<IToolTip> ActiveToolTipPtr = ActiveToolTip.Pin();
if ( NewToolTip.IsValid() || ( ActiveToolTipPtr.IsValid() && !ActiveToolTipPtr->IsInteractive() ) )
{
CloseToolTip();
if (NewTooltipVisualizer.IsValid())
{
TooltipVisualizerPtr = NewTooltipVisualizer;
}
else if( bAllowToolTips && AllowSpawningOfNewToolTips )
{
// Spawn a new one if we have it
if( NewToolTip.IsValid() )
{
SpawnToolTip( NewToolTip.ToSharedRef(), DesiredToolTipLocation );
}
}
else
{
NewToolTip = nullptr;
}
ActiveToolTip = NewToolTip;
ActiveToolTipWidgetSource = WidgetProvidingNewToolTip;
}
}
// Do we have a tool tip window?
if( ToolTipWindow.IsValid() )
{
// Only enable tool-tip transitions if we're running at a decent frame rate
const bool bAllowInstantToolTips = false;
const bool bAllowAnimations = !bAllowInstantToolTips && FSlateApplication::Get().IsRunningAtTargetFrameRate();
// How long since the tool tip was summoned?
const float TimeSinceSummon = FPlatformTime::Seconds() - ToolTipDelay - ToolTipSummonTime;
const float ToolTipOpacity = bAllowInstantToolTips ? 1.0f : FMath::Clamp< float >( TimeSinceSummon / ToolTipFadeInDuration, 0.0f, 1.0f );
// Update window opacity
TSharedRef< SWindow > PinnedToolTipWindow( ToolTipWindow.Pin().ToSharedRef() );
PinnedToolTipWindow->SetOpacity( ToolTipOpacity );
// How far tool tips should slide
const FVector2D SlideDistance( 30.0f, 5.0f );
// Apply steep inbound curve to the movement, so it looks like it quickly decelerating
const float SlideProgress = bAllowAnimations ? FMath::Pow( 1.0f - ToolTipOpacity, 3.0f ) : 0.0f;
FVector2D WindowLocation = DesiredToolTipLocation + SlideProgress * SlideDistance;
if( WindowLocation != PinnedToolTipWindow->GetPositionInScreen() )
{
// Avoid the edges of the desktop
FSlateRect Anchor(WindowLocation.X, WindowLocation.Y, WindowLocation.X, WindowLocation.Y);
WindowLocation = CalculatePopupWindowPosition( Anchor, PinnedToolTipWindow->GetDesiredSizeDesktopPixels());
// Update the tool tip window positioning
// SetCachedScreenPosition is a hack (issue tracked as TTP #347070) which is needed because code in TickWindowAndChildren()/DrawPrepass()
// assumes GetPositionInScreen() to correspond to the new window location in the same tick. This is true on Windows, but other
// OSes (Linux in particular) may not update cached screen position until next time events are polled.
PinnedToolTipWindow->SetCachedScreenPosition( WindowLocation );
PinnedToolTipWindow->MoveWindowTo( WindowLocation );
}
}
}
TArray< TSharedRef<SWindow> > FSlateApplication::GetInteractiveTopLevelWindows()
{
if (ActiveModalWindows.Num() > 0)
{
// If we have modal windows, only the topmost modal window and its children are interactive.
TArray< TSharedRef<SWindow>, TInlineAllocator<1> > OutWindows;
OutWindows.Add( ActiveModalWindows.Last().ToSharedRef() );
return TArray< TSharedRef<SWindow> >(OutWindows);
}
else
{
// No modal windows? All windows are interactive.
return SlateWindows;
}
}
void FSlateApplication::GetAllVisibleWindowsOrdered(TArray< TSharedRef<SWindow> >& OutWindows)
{
for( TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
{
TSharedRef<SWindow> CurrentWindow = *CurrentWindowIt;
if ( CurrentWindow->IsVisible() && !CurrentWindow->IsWindowMinimized() )
{
GetAllVisibleChildWindows(OutWindows, CurrentWindow);
}
}
}
void FSlateApplication::GetAllVisibleChildWindows(TArray< TSharedRef<SWindow> >& OutWindows, TSharedRef<SWindow> CurrentWindow)
{
if ( CurrentWindow->IsVisible() && !CurrentWindow->IsWindowMinimized() )
{
OutWindows.Add(CurrentWindow);
const TArray< TSharedRef<SWindow> >& WindowChildren = CurrentWindow->GetChildWindows();
for (int32 ChildIndex=0; ChildIndex < WindowChildren.Num(); ++ChildIndex)
{
GetAllVisibleChildWindows( OutWindows, WindowChildren[ChildIndex] );
}
}
}
bool FSlateApplication::IsDragDropping() const
{
return DragDropContent.IsValid();
}
TSharedPtr<FDragDropOperation> FSlateApplication::GetDragDroppingContent() const
{
return DragDropContent;
}
void FSlateApplication::CancelDragDrop()
{
for( auto LastWidgetIterator = WidgetsUnderCursorLastEvent.CreateConstIterator(); LastWidgetIterator; ++LastWidgetIterator)
{
FWidgetPath WidgetsToDragLeave = LastWidgetIterator.Value().ToWidgetPath(FWeakWidgetPath::EInterruptedPathHandling::Truncate);
if(WidgetsToDragLeave.IsValid())
{
const FDragDropEvent DragDropEvent(FPointerEvent(), DragDropContent);
for(int32 WidgetIndex = WidgetsToDragLeave.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
{
WidgetsToDragLeave.Widgets[WidgetIndex].Widget->OnDragLeave(DragDropEvent);
}
}
}
WidgetsUnderCursorLastEvent.Empty();
DragDropContent.Reset();
}
void FSlateApplication::EnterDebuggingMode()
{
bRequestLeaveDebugMode = false;
// Note it is ok to hold a reference here as the game viewport should not be destroyed while in debugging mode
TSharedPtr<SViewport> PreviousGameViewport;
// Disable any game viewports while we are in debug mode so that mouse capture is released and the cursor is visible
// We need to retain the keyboard input for debugging purposes, so this is called directly rather than calling UnregisterGameViewport which resets input.
if (GameViewportWidget.IsValid())
{
PreviousGameViewport = GameViewportWidget.Pin();
PreviousGameViewport->SetActive(false);
GameViewportWidget.Reset();
}
Renderer->FlushCommands();
// We are about to start an in stack tick. Make sure the rendering thread isn't already behind
Renderer->Sync();
#if WITH_EDITORONLY_DATA
// Flag that we're about to enter the first frame of intra-frame debugging.
GFirstFrameIntraFrameDebugging = true;
#endif //WITH_EDITORONLY_DATA
// The scissor rect stack must be reset when re-entering the tick loop to avoid graphical artifacts with existing clip rects applied new widgets
TOptional<FShortRect> PreviousScissorRect = GSlateScissorRect;
GSlateScissorRect.Reset();
// Tick slate from here in the event that we should not return until the modal window is closed.
while (!bRequestLeaveDebugMode)
{
// Tick and render Slate
Tick();
// Synchronize the game thread and the render thread so that the render thread doesn't get too far behind.
Renderer->Sync();
#if WITH_EDITORONLY_DATA
// We are done with the first frame
GFirstFrameIntraFrameDebugging = false;
// If we are requesting leaving debugging mode, leave it now.
GIntraFrameDebuggingGameThread = !bRequestLeaveDebugMode;
#endif //WITH_EDITORONLY_DATA
}
bRequestLeaveDebugMode = false;
if ( PreviousGameViewport.IsValid() )
{
check(!GameViewportWidget.IsValid());
// When in single step mode, register the game viewport so we can unregister it later
// but do not do any of the other stuff like locking or capturing the mouse.
if( bLeaveDebugForSingleStep )
{
GameViewportWidget = PreviousGameViewport;
}
else
{
// If we had a game viewport before debugging, re-register it now to capture the mouse and lock the cursor
RegisterGameViewport( PreviousGameViewport.ToSharedRef() );
}
}
// Reset the scissor rect back to what it was before we started debugging
GSlateScissorRect = PreviousScissorRect;
bLeaveDebugForSingleStep = false;
}
void FSlateApplication::LeaveDebuggingMode( bool bLeavingForSingleStep )
{
bRequestLeaveDebugMode = true;
bLeaveDebugForSingleStep = bLeavingForSingleStep;
}
bool FSlateApplication::IsWindowInDestroyQueue(TSharedRef<SWindow> Window) const
{
return WindowDestroyQueue.Contains(Window);
}
void FSlateApplication::SynthesizeMouseMove()
{
SLATE_CYCLE_COUNTER_SCOPE(GSlateSynthesizeMouseMove);
// The slate loading widget thread is not allowed to execute this code
// as it is unsafe to read the hittest grid in another thread
if (PlatformApplication->Cursor.IsValid() && IsInGameThread())
{
// Synthetic mouse events accomplish two goals:
// 1) The UI can change even if the mosue doesn't move.
// Synthesizing a mouse move sends out events.
// In this case, the current and previous position will be the same.
//
// 2) The mouse moves, but the OS decided not to send us an event.
// e.g. Mouse moved outside of our window.
// In this case, the previous and current positions differ.
FPointerEvent MouseEvent
(
CursorPointerIndex,
GetCursorPos(),
GetLastCursorPos(),
PressedMouseButtons,
EKeys::Invalid,
0,
PlatformApplication->GetModifierKeys()
);
ProcessMouseMoveEvent(MouseEvent, true);
}
}
void FSlateApplication::QueueSynthesizedMouseMove()
{
SynthesizeMouseMovePending = 2;
}
void FSlateApplication::OnLogSlateEvent(EEventLog::Type Event, const FString& AdditionalContent)
{
#if LOG_SLATE_EVENTS
if (EventLogger.IsValid())
{
LOG_EVENT_CONTENT(Event, AdditionalContent, TSharedPtr<SWidget>());
}
#endif
}
void FSlateApplication::OnLogSlateEvent(EEventLog::Type Event, const FText& AdditionalContent )
{
#if LOG_SLATE_EVENTS
if (EventLogger.IsValid())
{
LOG_EVENT_CONTENT(Event, AdditionalContent.ToString(), TSharedPtr<SWidget>());
}
#endif
};
void FSlateApplication::SetSlateUILogger(TSharedPtr<IEventLogger> InEventLogger)
{
#if LOG_SLATE_EVENTS
EventLogger = InEventLogger;
#endif
}
void FSlateApplication::SetUnhandledKeyDownEventHandler( const FOnKeyEvent& NewHandler )
{
UnhandledKeyDownEventHandler = NewHandler;
}
float FSlateApplication::GetDragTriggerDistance() const
{
return DragTriggerDistance;
}
void FSlateApplication::SetDragTriggerDistance( float ScreenPixels )
{
DragTriggerDistance = ScreenPixels;
}
void FSlateApplication::SetInputPreProcessor(bool bEnable, TSharedPtr<class IInputProcessor> NewInputProcessor)
{
if (bEnable && NewInputProcessor.IsValid())
{
InputPreProcessor = NewInputProcessor;
}
else
{
InputPreProcessor.Reset();
}
}
void FSlateApplication::SetCursorRadius(float NewRadius)
{
CursorRadius = FMath::Max<float>(0.0f, NewRadius);
}
float FSlateApplication::GetCursorRadius() const
{
return CursorRadius;
}
FVector2D FSlateApplication::CalculatePopupWindowPosition( const FSlateRect& InAnchor, const FVector2D& InSize, const EOrientation Orientation ) const
{
FVector2D CalculatedPopUpWindowPosition( 0, 0 );
FPlatformRect AnchorRect;
AnchorRect.Left = InAnchor.Left;
AnchorRect.Top = InAnchor.Top;
AnchorRect.Right = InAnchor.Right;
AnchorRect.Bottom = InAnchor.Bottom;
EPopUpOrientation::Type PopUpOrientation = EPopUpOrientation::Horizontal;
if ( Orientation == EOrientation::Orient_Vertical )
{
PopUpOrientation = EPopUpOrientation::Vertical;
}
if ( PlatformApplication->TryCalculatePopupWindowPosition( AnchorRect, InSize, PopUpOrientation, /*OUT*/&CalculatedPopUpWindowPosition ) )
{
return CalculatedPopUpWindowPosition;
}
else
{
// Calculate the rectangle around our work area
// Use our own rect. This window as probably doesn't have a size or position yet.
// Use a size of 1 to get the closest monitor to the start point
FPlatformRect WorkAreaFinderRect(AnchorRect);
WorkAreaFinderRect.Left = AnchorRect.Left + 1;
WorkAreaFinderRect.Top = AnchorRect.Top + 1;
const FPlatformRect PlatformWorkArea = PlatformApplication->GetWorkArea(WorkAreaFinderRect);
const FSlateRect WorkAreaRect(
PlatformWorkArea.Left,
PlatformWorkArea.Top,
PlatformWorkArea.Left+(PlatformWorkArea.Right - PlatformWorkArea.Left),
PlatformWorkArea.Top+(PlatformWorkArea.Bottom - PlatformWorkArea.Top) );
// Assume natural left-to-right, top-to-bottom flow; position popup below and to the right.
const FVector2D ProposedPlacement(
Orientation == Orient_Horizontal ? AnchorRect.Right : AnchorRect.Left,
Orientation == Orient_Horizontal ? AnchorRect.Top : AnchorRect.Bottom);
return ComputePopupFitInRect(InAnchor, FSlateRect(ProposedPlacement, ProposedPlacement+InSize), Orientation, WorkAreaRect);
}
}
bool FSlateApplication::IsRunningAtTargetFrameRate() const
{
const float MinimumDeltaTime = 1.0f / TargetFrameRateForResponsiveness.GetValueOnGameThread();
return ( AverageDeltaTimeForResponsiveness <= MinimumDeltaTime ) || !IsNormalExecution();
}
bool FSlateApplication::AreMenuAnimationsEnabled() const
{
return bMenuAnimationsEnabled;
}
void FSlateApplication::EnableMenuAnimations( const bool bEnableAnimations )
{
bMenuAnimationsEnabled = bEnableAnimations;
}
void FSlateApplication::SetAppIcon(const FSlateBrush* const InAppIcon)
{
check(InAppIcon);
AppIcon = InAppIcon;
}
const FSlateBrush* FSlateApplication::GetAppIcon() const
{
return AppIcon;
}
void FSlateApplication::ShowVirtualKeyboard( bool bShow, int32 UserIndex, TSharedPtr<IVirtualKeyboardEntry> TextEntryWidget )
{
SCOPE_CYCLE_COUNTER(STAT_ShowVirtualKeyboard);
if(SlateTextField == nullptr)
{
SlateTextField = new FPlatformTextField();
}
SlateTextField->ShowVirtualKeyboard(bShow, UserIndex, TextEntryWidget);
}
FSlateRect FSlateApplication::GetPreferredWorkArea() const
{
if ( const FSlateUser* User = GetUser(GetUserIndexForKeyboard()) )
{
const FWeakWidgetPath & FocusedWidgetPath = User->Focus.WidgetPath;
// First see if we have a focused widget
if ( FocusedWidgetPath.IsValid() && FocusedWidgetPath.Window.IsValid() )
{
const FVector2D WindowPos = FocusedWidgetPath.Window.Pin()->GetPositionInScreen();
const FVector2D WindowSize = FocusedWidgetPath.Window.Pin()->GetSizeInScreen();
return GetWorkArea(FSlateRect(WindowPos.X, WindowPos.Y, WindowPos.X + WindowSize.X, WindowPos.Y + WindowSize.Y));
}
}
// no focus widget, so use mouse position if there are windows present in the work area
const FVector2D CursorPos = GetCursorPos();
const FSlateRect WorkArea = GetWorkArea(FSlateRect(CursorPos.X, CursorPos.Y, CursorPos.X + 1.0f, CursorPos.Y + 1.0f));
if ( FSlateWindowHelper::CheckWorkAreaForWindows(SlateWindows, WorkArea) )
{
return WorkArea;
}
// If we can't find a window where the cursor is at, try finding a main window.
TSharedPtr<SWindow> ActiveTop = GetActiveTopLevelWindow();
if ( ActiveTop.IsValid() )
{
// Use the current top level windows rect
return GetWorkArea(ActiveTop->GetRectInScreen());
}
// If we can't find a top level window check for an active modal window
TSharedPtr<SWindow> ActiveModal = GetActiveModalWindow();
if ( ActiveModal.IsValid() )
{
// Use the current active modal windows rect
return GetWorkArea(ActiveModal->GetRectInScreen());
}
// no windows on work area - default to primary display
FDisplayMetrics DisplayMetrics;
GetDisplayMetrics(DisplayMetrics);
const FPlatformRect& DisplayRect = DisplayMetrics.PrimaryDisplayWorkAreaRect;
return FSlateRect((float)DisplayRect.Left, (float)DisplayRect.Top, (float)DisplayRect.Right, (float)DisplayRect.Bottom);
}
FSlateRect FSlateApplication::GetWorkArea( const FSlateRect& InRect ) const
{
FPlatformRect InPlatformRect;
InPlatformRect.Left = FMath::TruncToInt(InRect.Left);
InPlatformRect.Top = FMath::TruncToInt(InRect.Top);
InPlatformRect.Right = FMath::TruncToInt(InRect.Right);
InPlatformRect.Bottom = FMath::TruncToInt(InRect.Bottom);
const FPlatformRect OutPlatformRect = PlatformApplication->GetWorkArea( InPlatformRect );
return FSlateRect( OutPlatformRect.Left, OutPlatformRect.Top, OutPlatformRect.Right, OutPlatformRect.Bottom );
}
bool FSlateApplication::SupportsSourceAccess() const
{
if(QuerySourceCodeAccessDelegate.IsBound())
{
return QuerySourceCodeAccessDelegate.Execute();
}
return false;
}
void FSlateApplication::GotoLineInSource(const FString& FileName, int32 LineNumber) const
{
if ( SupportsSourceAccess() )
{
if(SourceCodeAccessDelegate.IsBound())
{
SourceCodeAccessDelegate.Execute(FileName, LineNumber, 0);
}
}
}
void FSlateApplication::ForceRedrawWindow(const TSharedRef<SWindow>& InWindowToDraw)
{
PrivateDrawWindows( InWindowToDraw );
}
bool FSlateApplication::TakeScreenshot(const TSharedRef<SWidget>& Widget, TArray<FColor>&OutColorData, FIntVector& OutSize)
{
return TakeScreenshot(Widget, FIntRect(), OutColorData, OutSize);
}
bool FSlateApplication::TakeScreenshot(const TSharedRef<SWidget>& Widget, const FIntRect& InnerWidgetArea, TArray<FColor>& OutColorData, FIntVector& OutSize)
{
// We can't screenshot the widget unless there's a valid window handle to draw it in.
TSharedPtr<SWindow> WidgetWindow = FSlateApplication::Get().FindWidgetWindow(Widget);
if ( !WidgetWindow.IsValid() )
{
return false;
}
TSharedRef<SWindow> CurrentWindowRef = WidgetWindow.ToSharedRef();
FWidgetPath WidgetPath;
FSlateApplication::Get().GeneratePathToWidgetChecked(Widget, WidgetPath);
FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::NullWidget);
FVector2D Position = ArrangedWidget.Geometry.AbsolutePosition;
FVector2D Size = ArrangedWidget.Geometry.GetDrawSize();
FVector2D WindowPosition = WidgetWindow->GetPositionInScreen();
FIntRect ScreenshotRect = InnerWidgetArea.IsEmpty() ? FIntRect(0, 0, (int32)Size.X, (int32)Size.Y) : InnerWidgetArea;
ScreenshotRect.Min.X += ( Position.X - WindowPosition.X );
ScreenshotRect.Min.Y += ( Position.Y - WindowPosition.Y );
ScreenshotRect.Max.X += ( Position.X - WindowPosition.X );
ScreenshotRect.Max.Y += ( Position.Y - WindowPosition.Y );
Renderer->PrepareToTakeScreenshot(ScreenshotRect, &OutColorData);
PrivateDrawWindows(WidgetWindow);
OutSize.X = ScreenshotRect.Size().X;
OutSize.Y = ScreenshotRect.Size().Y;
return (OutSize.X != 0 && OutSize.Y != 0);
}
TSharedPtr< FSlateWindowElementList > FSlateApplication::GetCachableElementList(const TSharedPtr<SWindow>& CurrentWindow, const ILayoutCache* LayoutCache)
{
TSharedPtr<FCacheElementPools> Pools = CachedElementLists.FindRef(LayoutCache);
if ( !Pools.IsValid() )
{
Pools = MakeShareable( new FCacheElementPools() );
CachedElementLists.Add(LayoutCache, Pools);
}
TSharedPtr< FSlateWindowElementList > NextElementList = Pools->GetNextCachableElementList(CurrentWindow);
return NextElementList;
}
TSharedPtr< FSlateWindowElementList > FSlateApplication::FCacheElementPools::GetNextCachableElementList(const TSharedPtr<SWindow>& CurrentWindow)
{
TSharedPtr< FSlateWindowElementList > NextElementList;
// Move any inactive element lists in the active pool to the inactive pool.
for ( int32 i = ActiveCachedElementListPool.Num() - 1; i >= 0; i-- )
{
if ( ActiveCachedElementListPool[i]->IsCachedRenderDataInUse() == false )
{
InactiveCachedElementListPool.Add(ActiveCachedElementListPool[i]);
ActiveCachedElementListPool.RemoveAtSwap(i, 1, false);
}
}
// Remove inactive lists that don't belong to this window.
for ( int32 i = InactiveCachedElementListPool.Num() - 1; i >= 0; i-- )
{
if ( InactiveCachedElementListPool[i]->GetWindow() != CurrentWindow )
{
InactiveCachedElementListPool.RemoveAtSwap(i, 1, false);
}
}
// Create a new element list if none are available, or use an existing one.
if ( InactiveCachedElementListPool.Num() == 0 )
{
NextElementList = MakeShareable(new FSlateWindowElementList(CurrentWindow));
}
else
{
NextElementList = InactiveCachedElementListPool[0];
NextElementList->ResetBuffers();
InactiveCachedElementListPool.RemoveAtSwap(0, 1, false);
}
ActiveCachedElementListPool.Add(NextElementList);
return NextElementList;
}
bool FSlateApplication::FCacheElementPools::IsInUse() const
{
bool bInUse = false;
for ( TSharedPtr< FSlateWindowElementList > ElementList : InactiveCachedElementListPool )
{
bInUse |= ElementList->IsCachedRenderDataInUse();
}
for ( TSharedPtr< FSlateWindowElementList > ElementList : ActiveCachedElementListPool )
{
bInUse |= ElementList->IsCachedRenderDataInUse();
}
return bInUse;
}
void FSlateApplication::ReleaseResourcesForLayoutCache(const ILayoutCache* LayoutCache)
{
TSharedPtr<FCacheElementPools> Pools = CachedElementLists.FindRef(LayoutCache);
if ( Pools.IsValid() )
{
ReleasedCachedElementLists.Add(Pools);
}
CachedElementLists.Remove(LayoutCache);
// Release the rendering related resources.
Renderer->ReleaseCachingResourcesFor(LayoutCache);
}
TSharedRef<FSlateVirtualUser> FSlateApplication::FindOrCreateVirtualUser(int32 VirtualUserIndex)
{
// Ensure we have a large enough array to add the new virtual user.
if ( VirtualUserIndex >= VirtualUsers.Num() )
{
VirtualUsers.SetNum(VirtualUserIndex + 1);
}
TSharedPtr<FSlateVirtualUser> VirtualUser = VirtualUsers[VirtualUserIndex].Pin();
if ( VirtualUser.IsValid() )
{
return VirtualUser.ToSharedRef();
}
// Register new virtual user with slates standard set of users.
int32 NextVirtualUserIndex = SlateApplicationDefs::MaxHardwareUsers;
while ( GetUser(NextVirtualUserIndex) )
{
NextVirtualUserIndex++;
}
TSharedRef<FSlateUser> NewUser = MakeShareable(new FSlateUser(NextVirtualUserIndex, true));
RegisterUser(NewUser);
// Make a virtual user handle that can be released automatically when all virtual users
// of this same user index are collected.
VirtualUser = MakeShareable(new FSlateVirtualUser(NewUser->GetUserIndex(), VirtualUserIndex));
// Update the virtual user array, so we can get this user back later.
VirtualUsers[VirtualUserIndex] = VirtualUser;
return VirtualUser.ToSharedRef();
}
void FSlateApplication::RegisterUser(TSharedRef<FSlateUser> NewUser)
{
if ( NewUser->UserIndex == -1 )
{
int32 Index = Users.Add(NewUser);
NewUser->UserIndex = Index;
}
else
{
// Ensure we have a large enough array to add the new user.
if ( NewUser->GetUserIndex() >= Users.Num() )
{
Users.SetNum(NewUser->GetUserIndex() + 1);
}
if ( FSlateUser* ExistingUser = Users[NewUser->GetUserIndex()].Get() )
{
// Migrate any state we know about that needs to be maintained if the
// user is replaced.
NewUser->Focus = ExistingUser->Focus;
}
// Replace the user that's at this index with the new user.
Users[NewUser->GetUserIndex()] = NewUser;
}
}
void FSlateApplication::UnregisterUser(int32 UserIndex)
{
if ( UserIndex < Users.Num() )
{
ClearUserFocus(UserIndex, EFocusCause::SetDirectly);
Users[UserIndex].Reset();
}
}
void FSlateApplication::ForEachUser(TFunctionRef<void(FSlateUser*)> InPredicate, bool bIncludeVirtualUsers)
{
for ( int32 UserIndex = 0; UserIndex < Users.Num(); UserIndex++ )
{
if ( FSlateUser* User = Users[UserIndex].Get() )
{
// Ignore virutal users unless told not to.
if ( !bIncludeVirtualUsers && User->IsVirtualUser() )
{
continue;
}
InPredicate(User);
}
}
}
/* FSlateApplicationBase interface
*****************************************************************************/
FVector2D FSlateApplication::GetCursorSize( ) const
{
if ( PlatformApplication->Cursor.IsValid() )
{
int32 X;
int32 Y;
PlatformApplication->Cursor->GetSize( X, Y );
return FVector2D( X, Y );
}
return FVector2D( 1.0f, 1.0f );
}
EVisibility FSlateApplication::GetSoftwareCursorVis( ) const
{
const TSharedPtr<ICursor>& Cursor = PlatformApplication->Cursor;
if (bSoftwareCursorAvailable && Cursor.IsValid() && Cursor->GetType() != EMouseCursor::None)
{
return EVisibility::HitTestInvisible;
}
return EVisibility::Hidden;
}
TSharedPtr< SWidget > FSlateApplication::GetKeyboardFocusedWidget() const
{
if ( const FSlateUser* User = GetUser(GetUserIndexForKeyboard()) )
{
const FUserFocusEntry& UserFocusEntry = User->Focus;
if ( UserFocusEntry.WidgetPath.IsValid() )
{
return UserFocusEntry.WidgetPath.GetLastWidget().Pin();
}
}
return TSharedPtr< SWidget >();
}
TSharedPtr<SWidget> FSlateApplication::GetMouseCaptorImpl() const
{
return MouseCaptor.ToSharedWidget(CursorUserIndex,CursorPointerIndex);
}
bool FSlateApplication::HasAnyMouseCaptor() const
{
return MouseCaptor.HasCapture();
}
bool FSlateApplication::HasUserMouseCapture(int32 UserIndex) const
{
return MouseCaptor.HasCaptureForUser(UserIndex);
}
bool FSlateApplication::DoesWidgetHaveMouseCaptureByUser(const TSharedPtr<const SWidget> Widget, int32 UserIndex, TOptional<int32> PointerIndex) const
{
return MouseCaptor.DoesWidgetHaveMouseCaptureByUser(Widget, UserIndex, PointerIndex);
}
bool FSlateApplication::DoesWidgetHaveMouseCapture(const TSharedPtr<const SWidget> Widget) const
{
return MouseCaptor.DoesWidgetHaveMouseCapture(Widget);
}
TOptional<EFocusCause> FSlateApplication::HasUserFocus(const TSharedPtr<const SWidget> Widget, int32 UserIndex) const
{
if ( const FSlateUser* User = GetUser(UserIndex) )
{
if ( User->GetFocusedWidget() == Widget )
{
TOptional<EFocusCause> FocusReason(User->Focus.FocusCause);
return FocusReason;
}
}
return TOptional<EFocusCause>();
}
TOptional<EFocusCause> FSlateApplication::HasAnyUserFocus(const TSharedPtr<const SWidget> Widget) const
{
for ( int32 UserIndex = 0; UserIndex < Users.Num(); UserIndex++ )
{
if ( const FSlateUser* User = Users[UserIndex].Get() )
{
if ( User->GetFocusedWidget() == Widget )
{
TOptional<EFocusCause> FocusReason(User->Focus.FocusCause);
return FocusReason;
}
}
}
return TOptional<EFocusCause>();
}
bool FSlateApplication::IsWidgetDirectlyHovered(const TSharedPtr<const SWidget> Widget) const
{
for( auto LastWidgetIterator = WidgetsUnderCursorLastEvent.CreateConstIterator(); LastWidgetIterator; ++LastWidgetIterator )
{
const FWeakWidgetPath& WeakPath = LastWidgetIterator.Value();
if( WeakPath.IsValid() && Widget == WeakPath.GetLastWidget().Pin() )
{
return true;
}
}
return false;
}
bool FSlateApplication::ShowUserFocus(const TSharedPtr<const SWidget> Widget) const
{
for ( int32 UserIndex = 0; UserIndex < Users.Num(); UserIndex++ )
{
if ( const FSlateUser* User = Users[UserIndex].Get() )
{
const FUserFocusEntry& UserFocusEntry = User->Focus;
const FWeakWidgetPath & FocusedWidgetPath = UserFocusEntry.WidgetPath;
if ( FocusedWidgetPath.IsValid() && FocusedWidgetPath.GetLastWidget().Pin() == Widget )
{
return UserFocusEntry.ShowFocus;
}
}
}
return false;
}
bool FSlateApplication::HasUserFocusedDescendants(const TSharedRef< const SWidget >& Widget, int32 UserIndex) const
{
if ( const FSlateUser* User = GetUser(UserIndex) )
{
const FUserFocusEntry& UserFocusEntry = User->Focus;
const FWeakWidgetPath & FocusedWidgetPath = UserFocusEntry.WidgetPath;
if ( FocusedWidgetPath.IsValid() && FocusedWidgetPath.GetLastWidget().Pin() != Widget && FocusedWidgetPath.ContainsWidget(Widget) )
{
return true;
}
}
return false;
}
bool FSlateApplication::HasFocusedDescendants( const TSharedRef< const SWidget >& Widget ) const
{
for ( int32 UserIndex = 0; UserIndex < Users.Num(); UserIndex++ )
{
if ( const FSlateUser* User = Users[UserIndex].Get() )
{
const FUserFocusEntry& UserFocusEntry = User->Focus;
const FWeakWidgetPath & FocusedWidgetPath = UserFocusEntry.WidgetPath;
if ( FocusedWidgetPath.IsValid() && FocusedWidgetPath.GetLastWidget().Pin() != Widget && FocusedWidgetPath.ContainsWidget(Widget) )
{
return true;
}
}
}
return false;
}
bool FSlateApplication::IsExternalUIOpened()
{
return bIsExternalUIOpened;
}
TSharedRef<SWidget> FSlateApplication::MakeImage( const TAttribute<const FSlateBrush*>& Image, const TAttribute<FSlateColor>& Color, const TAttribute<EVisibility>& Visibility ) const
{
return SNew(SImage)
.ColorAndOpacity(Color)
.Image(Image)
.Visibility(Visibility);
}
TSharedRef<SWidget> FSlateApplication::MakeWindowTitleBar( const TSharedRef<SWindow>& Window, const TSharedPtr<SWidget>& CenterContent, EHorizontalAlignment CenterContentAlignment, TSharedPtr<IWindowTitleBar>& OutTitleBar ) const
{
TSharedRef<SWindowTitleBar> TitleBar = SNew(SWindowTitleBar, Window, CenterContent, CenterContentAlignment)
.Visibility(EVisibility::SelfHitTestInvisible);
OutTitleBar = TitleBar;
return TitleBar;
}
TSharedRef<IToolTip> FSlateApplication::MakeToolTip(const TAttribute<FText>& ToolTipText)
{
return SNew(SToolTip)
.Text(ToolTipText);
}
TSharedRef<IToolTip> FSlateApplication::MakeToolTip( const FText& ToolTipText )
{
return SNew(SToolTip)
.Text(ToolTipText);
}
/* FGenericApplicationMessageHandler interface
*****************************************************************************/
bool FSlateApplication::ShouldProcessUserInputMessages( const TSharedPtr< FGenericWindow >& PlatformWindow ) const
{
TSharedPtr< SWindow > Window;
if ( PlatformWindow.IsValid() )
{
Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow.ToSharedRef() );
}
if ( ActiveModalWindows.Num() == 0 ||
( Window.IsValid() &&
( Window->IsDescendantOf( GetActiveModalWindow() ) || ActiveModalWindows.Contains( Window ) ) ) )
{
return true;
}
return false;
}
bool FSlateApplication::OnKeyChar( const TCHAR Character, const bool IsRepeat )
{
FCharacterEvent CharacterEvent( Character, PlatformApplication->GetModifierKeys(), 0, IsRepeat );
return ProcessKeyCharEvent( CharacterEvent );
}
bool FSlateApplication::ProcessKeyCharEvent( FCharacterEvent& InCharacterEvent )
{
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyChar);
TScopeCounter<int32> BeginInput(ProcessingInput);
FReply Reply = FReply::Unhandled();
// NOTE: We intentionally don't reset LastUserInteractionTimeForThrottling here so that the UI can be responsive while typing
// Bubble the key event
if ( FSlateUser* User = GetOrCreateUser(InCharacterEvent.GetUserIndex()) )
{
FWidgetPath EventPath = User->Focus.WidgetPath.ToWidgetPath();
// Switch worlds for widgets in the current path
FScopedSwitchWorldHack SwitchWorld(EventPath);
{
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyChar_RouteAlongFocusPath);
Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FBubblePolicy(EventPath), InCharacterEvent, [] (const FArrangedWidget& SomeWidgetGettingEvent, const FCharacterEvent& Event)
{
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyChar_Call_OnKeyChar);
return SomeWidgetGettingEvent.Widget->IsEnabled()
? SomeWidgetGettingEvent.Widget->OnKeyChar(SomeWidgetGettingEvent.Geometry, Event)
: FReply::Unhandled();
});
}
LOG_EVENT_CONTENT(EEventLog::KeyChar, FString::Printf(TEXT("%c"), InCharacterEvent.GetCharacter()), Reply);
}
return Reply.IsEventHandled();
}
bool FSlateApplication::OnKeyDown( const int32 KeyCode, const uint32 CharacterCode, const bool IsRepeat )
{
FKey const Key = FInputKeyManager::Get().GetKeyFromCodes( KeyCode, CharacterCode );
FKeyEvent KeyEvent(Key, PlatformApplication->GetModifierKeys(), GetUserIndexForKeyboard(), IsRepeat, CharacterCode, KeyCode);
return ProcessKeyDownEvent( KeyEvent );
}
bool FSlateApplication::ProcessKeyDownEvent( FKeyEvent& InKeyEvent )
{
TScopeCounter<int32> BeginInput(ProcessingInput);
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyDown);
#if WITH_EDITOR
//Send the key input to all pre input key down listener function
if (OnApplicationPreInputKeyDownListenerEvent.IsBound())
{
OnApplicationPreInputKeyDownListenerEvent.Broadcast(InKeyEvent);
}
#endif //WITH_EDITOR
QueueSynthesizedMouseMove();
// Analog cursor gets first chance at the input
if (InputPreProcessor.IsValid() && InputPreProcessor->HandleKeyDownEvent(*this, InKeyEvent))
{
return true;
}
FReply Reply = FReply::Unhandled();
SetLastUserInteractionTime(this->GetCurrentTime());
if (IsDragDropping() && InKeyEvent.GetKey() == EKeys::Escape)
{
// Pressing ESC while drag and dropping terminates the drag drop.
CancelDragDrop();
Reply = FReply::Handled();
}
else
{
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
#if SLATE_HAS_WIDGET_REFLECTOR
// If we are inspecting, pressing ESC exits inspection mode.
if ( InKeyEvent.GetKey() == EKeys::Escape )
{
TSharedPtr<IWidgetReflector> WidgetReflector = WidgetReflectorPtr.Pin();
const bool bIsWidgetReflectorPicking = WidgetReflector.IsValid() && WidgetReflector->IsInPickingMode();
if ( bIsWidgetReflectorPicking )
{
WidgetReflector->OnWidgetPicked();
Reply = FReply::Handled();
return Reply.IsEventHandled();
}
}
#endif
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// Ctrl+Shift+~ summons the Toolbox.
if (InKeyEvent.GetKey() == EKeys::Tilde && InKeyEvent.IsControlDown() && InKeyEvent.IsShiftDown())
{
IToolboxModule* ToolboxModule = FModuleManager::LoadModulePtr<IToolboxModule>("Toolbox");
if (ToolboxModule)
{
ToolboxModule->SummonToolbox();
}
}
#endif //!(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// Bubble the keyboard event
if ( FSlateUser* User = GetOrCreateUser(InKeyEvent.GetUserIndex()) )
{
FWidgetPath EventPath = User->Focus.WidgetPath.ToWidgetPath();
// Switch worlds for widgets inOnPreviewMouseButtonDown the current path
FScopedSwitchWorldHack SwitchWorld(EventPath);
// Tunnel the keyboard event
Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FTunnelPolicy(EventPath), InKeyEvent, [] (const FArrangedWidget& CurrentWidget, const FKeyEvent& Event)
{
return ( CurrentWidget.Widget->IsEnabled() )
? CurrentWidget.Widget->OnPreviewKeyDown(CurrentWidget.Geometry, Event)
: FReply::Unhandled();
});
// Send out key down events.
if ( !Reply.IsEventHandled() )
{
Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FBubblePolicy(EventPath), InKeyEvent, [] (const FArrangedWidget& SomeWidgetGettingEvent, const FKeyEvent& Event)
{
return ( SomeWidgetGettingEvent.Widget->IsEnabled() )
? SomeWidgetGettingEvent.Widget->OnKeyDown(SomeWidgetGettingEvent.Geometry, Event)
: FReply::Unhandled();
});
}
LOG_EVENT_CONTENT(EEventLog::KeyDown, InKeyEvent.GetKey().ToString(), Reply);
// If the key event was not processed by any widget...
if ( !Reply.IsEventHandled() && UnhandledKeyDownEventHandler.IsBound() )
{
Reply = UnhandledKeyDownEventHandler.Execute(InKeyEvent);
}
}
}
return Reply.IsEventHandled();
}
bool FSlateApplication::OnKeyUp( const int32 KeyCode, const uint32 CharacterCode, const bool IsRepeat )
{
FKey const Key = FInputKeyManager::Get().GetKeyFromCodes( KeyCode, CharacterCode );
FKeyEvent KeyEvent(Key, PlatformApplication->GetModifierKeys(), GetUserIndexForKeyboard(), IsRepeat, CharacterCode, KeyCode);
return ProcessKeyUpEvent( KeyEvent );
}
bool FSlateApplication::ProcessKeyUpEvent( FKeyEvent& InKeyEvent )
{
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyUp);
TScopeCounter<int32> BeginInput(ProcessingInput);
QueueSynthesizedMouseMove();
// Analog cursor gets first chance at the input
if (InputPreProcessor.IsValid() && InputPreProcessor->HandleKeyUpEvent(*this, InKeyEvent))
{
return true;
}
FReply Reply = FReply::Unhandled();
SetLastUserInteractionTime(this->GetCurrentTime());
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
// Bubble the key event
if ( FSlateUser* User = GetOrCreateUser(InKeyEvent.GetUserIndex()) )
{
FWidgetPath EventPath = User->Focus.WidgetPath.ToWidgetPath();
// Switch worlds for widgets in the current path
FScopedSwitchWorldHack SwitchWorld(EventPath);
Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FBubblePolicy(EventPath), InKeyEvent, [] (const FArrangedWidget& SomeWidgetGettingEvent, const FKeyEvent& Event)
{
return ( SomeWidgetGettingEvent.Widget->IsEnabled() )
? SomeWidgetGettingEvent.Widget->OnKeyUp(SomeWidgetGettingEvent.Geometry, Event)
: FReply::Unhandled();
});
LOG_EVENT_CONTENT(EEventLog::KeyUp, InKeyEvent.GetKey().ToString(), Reply);
}
return Reply.IsEventHandled();
}
bool FSlateApplication::ProcessAnalogInputEvent(FAnalogInputEvent& InAnalogInputEvent)
{
SCOPE_CYCLE_COUNTER(STAT_ProcessAnalogInput);
TScopeCounter<int32> BeginInput(ProcessingInput);
QueueSynthesizedMouseMove();
FReply Reply = FReply::Unhandled();
// Analog cursor gets first chance at the input
if (InputPreProcessor.IsValid() && InputPreProcessor->HandleAnalogInputEvent(*this, InAnalogInputEvent))
{
Reply = FReply::Handled();
}
else
{
// Bubble the key event
if ( FSlateUser* User = GetOrCreateUser(InAnalogInputEvent.GetUserIndex()) )
{
FWidgetPath EventPath = User->Focus.WidgetPath.ToWidgetPath();
InAnalogInputEvent.SetEventPath(EventPath);
// Switch worlds for widgets in the current path
FScopedSwitchWorldHack SwitchWorld(EventPath);
Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FBubblePolicy(EventPath), InAnalogInputEvent, [] (const FArrangedWidget& SomeWidgetGettingEvent, const FAnalogInputEvent& Event)
{
return ( SomeWidgetGettingEvent.Widget->IsEnabled() )
? SomeWidgetGettingEvent.Widget->OnAnalogValueChanged(SomeWidgetGettingEvent.Geometry, Event)
: FReply::Unhandled();
});
LOG_EVENT_CONTENT(EEventLog::AnalogInput, InAnalogInputEvent.GetKey().ToString(), Reply);
QueueSynthesizedMouseMove();
}
}
// If no one handled this, it was probably motion in the deadzone. Don't treat it as activity.
if (Reply.IsEventHandled())
{
SetLastUserInteractionTime(this->GetCurrentTime());
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
return true;
}
else
{
return false;
}
}
FKey TranslateMouseButtonToKey( const EMouseButtons::Type Button )
{
FKey Key = EKeys::Invalid;
switch( Button )
{
case EMouseButtons::Left:
Key = EKeys::LeftMouseButton;
break;
case EMouseButtons::Middle:
Key = EKeys::MiddleMouseButton;
break;
case EMouseButtons::Right:
Key = EKeys::RightMouseButton;
break;
case EMouseButtons::Thumb01:
Key = EKeys::ThumbMouseButton;
break;
case EMouseButtons::Thumb02:
Key = EKeys::ThumbMouseButton2;
break;
}
return Key;
}
void FSlateApplication::SetGameIsFakingTouchEvents(const bool bIsFaking, FVector2D* CursorLocation)
{
if (bIsFakingTouched && !bIsFaking && bIsGameFakingTouch && !bIsFakingTouch)
{
OnTouchEnded((CursorLocation ? *CursorLocation : PlatformApplication->Cursor->GetPosition()), 0, 0);
}
bIsGameFakingTouch = bIsFaking;
}
bool FSlateApplication::IsFakingTouchEvents() const
{
return bIsFakingTouch || bIsGameFakingTouch;
}
bool FSlateApplication::OnMouseDown(const TSharedPtr< FGenericWindow >& PlatformWindow, const EMouseButtons::Type Button)
{
return OnMouseDown(PlatformWindow, Button, GetCursorPos());
}
bool FSlateApplication::OnMouseDown( const TSharedPtr< FGenericWindow >& PlatformWindow, const EMouseButtons::Type Button, const FVector2D CursorPos )
{
// convert to touch event if we are faking it
if (bIsFakingTouch || bIsGameFakingTouch)
{
bIsFakingTouched = true;
return OnTouchStarted( PlatformWindow, PlatformApplication->Cursor->GetPosition(), 0, 0 );
}
FKey Key = TranslateMouseButtonToKey( Button );
FPointerEvent MouseEvent(
CursorPointerIndex,
CursorPos,
GetLastCursorPos(),
PressedMouseButtons,
Key,
0,
PlatformApplication->GetModifierKeys()
);
return ProcessMouseButtonDownEvent( PlatformWindow, MouseEvent );
}
bool FSlateApplication::ProcessMouseButtonDownEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, FPointerEvent& MouseEvent )
{
SCOPE_CYCLE_COUNTER(STAT_ProcessMouseButtonDown);
TScopeCounter<int32> BeginInput(ProcessingInput);
#if WITH_EDITOR
//Send the key input to all pre input key down listener function
if (OnApplicationMousePreInputButtonDownListenerEvent.IsBound())
{
OnApplicationMousePreInputButtonDownListenerEvent.Broadcast(MouseEvent);
}
#endif //WITH_EDITOR
QueueSynthesizedMouseMove();
SetLastUserInteractionTime(this->GetCurrentTime());
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
if (PlatformWindow.IsValid())
{
PlatformApplication->SetCapture(PlatformWindow);
}
PressedMouseButtons.Add( MouseEvent.GetEffectingButton() );
// Input preprocessor gets first chance at the input
if( InputPreProcessor.IsValid() && InputPreProcessor->HandleMouseButtonDownEvent( *this, MouseEvent ) )
{
return true;
}
bool bInGame = false;
// Only process mouse down messages if we are not drag/dropping
if ( !IsDragDropping() )
{
FReply Reply = FReply::Unhandled();
if (MouseCaptor.HasCaptureForPointerIndex(MouseEvent.GetUserIndex(), MouseEvent.GetPointerIndex()))
{
FWidgetPath MouseCaptorPath = MouseCaptor.ToWidgetPath( FWeakWidgetPath::EInterruptedPathHandling::Truncate, &MouseEvent );
FArrangedWidget& MouseCaptorWidget = MouseCaptorPath.Widgets.Last();
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld(MouseCaptorPath);
bInGame = FApp::IsGame();
Reply = FEventRouter::Route<FReply>(this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), MouseEvent, [] (const FArrangedWidget& InMouseCaptorWidget, const FPointerEvent& Event)
{
return InMouseCaptorWidget.Widget->OnPreviewMouseButtonDown(InMouseCaptorWidget.Geometry, Event);
});
if ( !Reply.IsEventHandled() )
{
Reply = FEventRouter::Route<FReply>(this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), MouseEvent,
[this] (const FArrangedWidget& InMouseCaptorWidget, const FPointerEvent& Event)
{
FReply TempReply = FReply::Unhandled();
if ( Event.IsTouchEvent() )
{
TempReply = InMouseCaptorWidget.Widget->OnTouchStarted(InMouseCaptorWidget.Geometry, Event);
}
if ( !Event.IsTouchEvent() || ( !TempReply.IsEventHandled() && this->bTouchFallbackToMouse ) )
{
TempReply = InMouseCaptorWidget.Widget->OnMouseButtonDown(InMouseCaptorWidget.Geometry, Event);
}
return TempReply;
});
}
LOG_EVENT(EEventLog::MouseButtonDown, Reply);
}
else
{
FWidgetPath WidgetsUnderCursor = LocateWindowUnderMouse( MouseEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows() );
PopupSupport.SendNotifications( WidgetsUnderCursor );
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld(WidgetsUnderCursor);
bInGame = FApp::IsGame();
Reply = RoutePointerDownEvent(WidgetsUnderCursor, MouseEvent);
}
// See if expensive tasks should be throttled. By default on mouse down expensive tasks are throttled
// to ensure Slate responsiveness in low FPS situations
if (Reply.IsEventHandled() && !bInGame && !MouseEvent.IsTouchEvent())
{
// Enter responsive mode if throttling should occur and its not already happening
if( Reply.ShouldThrottle() && !MouseButtonDownResponsivnessThrottle.IsValid() )
{
MouseButtonDownResponsivnessThrottle = FSlateThrottleManager::Get().EnterResponsiveMode();
}
else if( !Reply.ShouldThrottle() && MouseButtonDownResponsivnessThrottle.IsValid() )
{
// Leave responsive mode if a widget chose not to throttle
FSlateThrottleManager::Get().LeaveResponsiveMode( MouseButtonDownResponsivnessThrottle );
}
}
}
PointerIndexLastPositionMap.Add(MouseEvent.GetPointerIndex(), MouseEvent.GetScreenSpacePosition());
return true;
}
FReply FSlateApplication::RoutePointerDownEvent(FWidgetPath& WidgetsUnderPointer, FPointerEvent& PointerEvent)
{
TScopeCounter<int32> BeginInput(ProcessingInput);
#if PLATFORM_MAC
NSWindow* ActiveWindow = [ NSApp keyWindow ];
const bool bNeedToActivateWindow = ( ActiveWindow == nullptr );
#else
const bool bNeedToActivateWindow = false;
#endif
const TSharedPtr<SWidget> PreviouslyFocusedWidget = GetKeyboardFocusedWidget();
FReply Reply = FEventRouter::Route<FReply>( this, FEventRouter::FTunnelPolicy( WidgetsUnderPointer ), PointerEvent, []( const FArrangedWidget TargetWidget, const FPointerEvent& Event )
{
return TargetWidget.Widget->OnPreviewMouseButtonDown( TargetWidget.Geometry, Event );
} );
if( !Reply.IsEventHandled() )
{
Reply = FEventRouter::Route<FReply>( this, FEventRouter::FBubblePolicy( WidgetsUnderPointer ), PointerEvent, [this]( const FArrangedWidget TargetWidget, const FPointerEvent& Event )
{
FReply ThisReply = FReply::Unhandled();
if( !ThisReply.IsEventHandled() )
{
if( Event.IsTouchEvent() )
{
ThisReply = TargetWidget.Widget->OnTouchStarted( TargetWidget.Geometry, Event );
}
if( !Event.IsTouchEvent() || ( !ThisReply.IsEventHandled() && this->bTouchFallbackToMouse ) )
{
ThisReply = TargetWidget.Widget->OnMouseButtonDown( TargetWidget.Geometry, Event );
}
}
return ThisReply;
} );
}
LOG_EVENT( EEventLog::MouseButtonDown, Reply );
// If none of the widgets requested keyboard focus to be set (or set the keyboard focus explicitly), set it to the leaf-most widget under the mouse.
// On Mac we prevent the OS from activating the window on mouse down, so we have full control and can activate only if there's nothing draggable under the mouse cursor.
const bool bFocusChangedByEventHandler = PreviouslyFocusedWidget != GetKeyboardFocusedWidget();
if( ( !bFocusChangedByEventHandler || bNeedToActivateWindow ) &&
( !Reply.GetUserFocusRecepient().IsValid()
#if PLATFORM_MAC
|| (
PointerEvent.GetEffectingButton() == EKeys::LeftMouseButton &&
!DragDetector.IsDetectingDrag(PointerEvent)
)
#endif
)
)
{
for ( int32 WidgetIndex = WidgetsUnderPointer.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex )
{
FArrangedWidget& CurWidget = WidgetsUnderPointer.Widgets[WidgetIndex];
if ( CurWidget.Widget->SupportsKeyboardFocus() )
{
FWidgetPath NewFocusedWidgetPath = WidgetsUnderPointer.GetPathDownTo(CurWidget.Widget);
SetUserFocus(PointerEvent.GetUserIndex(), NewFocusedWidgetPath, EFocusCause::Mouse);
break;
}
}
#if PLATFORM_MAC
const bool bIsVirtualInteraction = WidgetsUnderPointer.TopLevelWindow.IsValid() ? WidgetsUnderPointer.TopLevelWindow->IsVirtualWindow() : false;
if ( !bIsVirtualInteraction )
{
TSharedPtr<SWindow> TopLevelWindow = WidgetsUnderPointer.TopLevelWindow;
if ( bNeedToActivateWindow || ( TopLevelWindow.IsValid() && TopLevelWindow->GetNativeWindow()->GetOSWindowHandle() != ActiveWindow ) )
{
// Clicking on a context menu should not activate anything
// @todo: This needs to be updated when we have window type in SWindow and we no longer have to guess if WidgetsUnderCursor.TopLevelWindow is a menu
const bool bIsContextMenu = TopLevelWindow.IsValid() && !TopLevelWindow->IsRegularWindow() && TopLevelWindow->HasMinimizeBox() && TopLevelWindow->HasMaximizeBox();
if ( !bIsContextMenu && PointerEvent.GetEffectingButton() == EKeys::LeftMouseButton && !DragDetector.IsDetectingDrag(PointerEvent) && ActiveWindow == [NSApp keyWindow] )
{
MouseCaptorHelper Captor = MouseCaptor;
FPlatformMisc::ActivateApplication();
if ( TopLevelWindow.IsValid() )
{
TopLevelWindow->BringToFront(true);
}
MouseCaptor = Captor;
}
}
}
#endif
}
return Reply;
}
FReply FSlateApplication::RoutePointerUpEvent(FWidgetPath& WidgetsUnderPointer, FPointerEvent& PointerEvent)
{
TScopeCounter<int32> BeginInput(ProcessingInput);
FReply Reply = FReply::Unhandled();
#if PLATFORM_MAC
NSWindow* ActiveNativeWindow = [NSApp keyWindow];
TSharedPtr<SWindow> TopLevelWindow;
#endif
// Update the drag detector, this release may stop a drag detection.
DragDetector.OnPointerRelease(PointerEvent);
if (MouseCaptor.HasCaptureForPointerIndex(PointerEvent.GetUserIndex(), PointerEvent.GetPointerIndex()))
{
//FWidgetPath MouseCaptorPath = MouseCaptor.ToWidgetPath(PointerEvent.GetPointerIndex());
FWidgetPath MouseCaptorPath = MouseCaptor.ToWidgetPath( FWeakWidgetPath::EInterruptedPathHandling::Truncate, &PointerEvent );
if ( ensureMsgf(MouseCaptorPath.Widgets.Num() > 0, TEXT("A window had a widget with mouse capture. That entire window has been dismissed before the mouse up could be processed.")) )
{
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld( MouseCaptorPath );
Reply =
FEventRouter::Route<FReply>( this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), PointerEvent, [this]( const FArrangedWidget& TargetWidget, const FPointerEvent& Event )
{
FReply TempReply = FReply::Unhandled();
if (Event.IsTouchEvent())
{
TempReply = TargetWidget.Widget->OnTouchEnded( TargetWidget.Geometry, Event );
}
if (!Event.IsTouchEvent() || (!TempReply.IsEventHandled() && this->bTouchFallbackToMouse))
{
TempReply = TargetWidget.Widget->OnMouseButtonUp( TargetWidget.Geometry, Event );
}
return TempReply;
});
#if PLATFORM_MAC
TopLevelWindow = MouseCaptorPath.TopLevelWindow;
#endif
LOG_EVENT( EEventLog::MouseButtonUp, Reply );
}
}
else
{
FWidgetPath LocalWidgetsUnderCursor = WidgetsUnderPointer.IsValid() ? WidgetsUnderPointer : LocateWindowUnderMouse( PointerEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows() );
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld( LocalWidgetsUnderCursor );
// Cache the drag drop content and reset the pointer in case OnMouseButtonUpMessage re-enters as a result of OnDrop
const bool bIsDragDropping = IsDragDropping();
TSharedPtr< FDragDropOperation > LocalDragDropContent = DragDropContent;
DragDropContent.Reset();
Reply = FEventRouter::Route<FReply>( this, FEventRouter::FBubblePolicy(LocalWidgetsUnderCursor), PointerEvent, [&](const FArrangedWidget& CurWidget, const FPointerEvent& Event)
{
if (bIsDragDropping)
{
return CurWidget.Widget->OnDrop(CurWidget.Geometry, FDragDropEvent(Event, LocalDragDropContent));
}
FReply TempReply = FReply::Unhandled();
if (Event.IsTouchEvent())
{
TempReply = CurWidget.Widget->OnTouchEnded( CurWidget.Geometry, Event );
}
if (!Event.IsTouchEvent() || (!TempReply.IsEventHandled() && bTouchFallbackToMouse))
{
TempReply = CurWidget.Widget->OnMouseButtonUp( CurWidget.Geometry, Event );
}
return TempReply;
});
LOG_EVENT( bIsDragDropping ? EEventLog::DragDrop : EEventLog::MouseButtonUp, Reply );
// If we were dragging, notify the content
if ( bIsDragDropping )
{
// @todo slate : depending on SetEventPath() is not ideal.
PointerEvent.SetEventPath( LocalWidgetsUnderCursor );
LocalDragDropContent->OnDrop( Reply.IsEventHandled(), PointerEvent );
WidgetsUnderCursorLastEvent.Remove( FUserAndPointer( PointerEvent.GetUserIndex(), PointerEvent.GetPointerIndex() ) );
}
#if PLATFORM_MAC
else if (ActiveNativeWindow == nullptr) // activate only if the app is in the background
{
TopLevelWindow = LocalWidgetsUnderCursor.TopLevelWindow;
}
#endif
}
#if PLATFORM_MAC
// Activate a window under the mouse if it's inactive and mouse up didn't bring any window to front
TSharedPtr<SWindow> ActiveWindow = GetActiveTopLevelWindow();
if ( PointerEvent.GetEffectingButton() == EKeys::LeftMouseButton && TopLevelWindow.IsValid() && ActiveWindow != TopLevelWindow
&& ActiveNativeWindow == [NSApp keyWindow] && ![(NSWindow*)TopLevelWindow->GetNativeWindow()->GetOSWindowHandle() isMiniaturized] )
{
FPlatformMisc::ActivateApplication();
if ( !TopLevelWindow->IsVirtualWindow() )
{
TopLevelWindow->BringToFront(true);
}
}
#endif
return Reply;
}
bool FSlateApplication::RoutePointerMoveEvent(const FWidgetPath& WidgetsUnderPointer, FPointerEvent& PointerEvent, bool bIsSynthetic)
{
TScopeCounter<int32> BeginInput(ProcessingInput);
bool bHandled = false;
FWeakWidgetPath LastWidgetsUnderCursor;
// User asked us to detect a drag.
bool bDragDetected = false;
bool bShouldStartDetectingDrag = true;
//@TODO VREDITOR - Remove and move to interaction component
if (OnDragDropCheckOverride.IsBound())
{
bShouldStartDetectingDrag = OnDragDropCheckOverride.Execute();
}
if ( !bIsSynthetic && bShouldStartDetectingDrag )
{
FWeakWidgetPath* DetectDragForWidget;
bDragDetected = DragDetector.DetectDrag(PointerEvent, GetDragTriggerDistance(), DetectDragForWidget);
if ( bDragDetected )
{
FWidgetPath DragDetectPath = DetectDragForWidget->ToWidgetPath(FWeakWidgetPath::EInterruptedPathHandling::ReturnInvalid);
const TSharedPtr<SWidget> DragDetectRequestor = DetectDragForWidget->GetLastWidget().Pin();
if ( DragDetectPath.IsValid() && DragDetectRequestor.IsValid() )
{
FWidgetAndPointer DetectDragForMe = DragDetectPath.FindArrangedWidgetAndCursor(DragDetectRequestor.ToSharedRef()).Get(FWidgetAndPointer());
// A drag has been triggered. The cursor exited some widgets as a result.
// This assignment ensures that we will send OnLeave notifications to those widgets.
LastWidgetsUnderCursor = *DetectDragForWidget;
DragDetector.ResetDetection();
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld(DragDetectPath);
// Send an OnDragDetected to the widget that requested drag-detection.
FReply Reply = FEventRouter::Route<FReply>(this, FEventRouter::FDirectPolicy(DetectDragForMe, DragDetectPath), PointerEvent, [] (const FArrangedWidget& InDetectDragForMe, const FPointerEvent& TranslatedMouseEvent)
{
return InDetectDragForMe.Widget->OnDragDetected(InDetectDragForMe.Geometry, TranslatedMouseEvent);
});
LOG_EVENT(EEventLog::DragDetected, Reply);
}
else
{
bDragDetected = false;
}
}
}
if (bDragDetected)
{
// When a drag was detected, we pretend that the widgets under the mouse last time around.
// We have set LastWidgetsUnderCursor accordingly when the drag was detected above.
}
else
{
// No Drag Detection
LastWidgetsUnderCursor = WidgetsUnderCursorLastEvent.FindRef( FUserAndPointer( PointerEvent.GetUserIndex(), PointerEvent.GetPointerIndex() ) );
}
FWidgetPath MouseCaptorPath;
if ( MouseCaptor.HasCaptureForPointerIndex(PointerEvent.GetUserIndex(), PointerEvent.GetPointerIndex()) )
{
//MouseCaptorPath = MouseCaptor.ToWidgetPath(MouseEvent.GetPointerIndex(), FWeakWidgetPath::EInterruptedPathHandling::ReturnInvalid);
MouseCaptorPath = MouseCaptor.ToWidgetPath(FWeakWidgetPath::EInterruptedPathHandling::ReturnInvalid, &PointerEvent);
}
// Send out mouse leave events
// If we are doing a drag and drop, we will send this event instead.
{
FDragDropEvent DragDropEvent( PointerEvent, DragDropContent );
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld( LastWidgetsUnderCursor.Window.Pin() );
for ( int32 WidgetIndex = LastWidgetsUnderCursor.Widgets.Num()-1; WidgetIndex >=0; --WidgetIndex )
{
// Guards for cases where WidgetIndex can become invalid due to MouseMove being re-entrant.
while ( WidgetIndex >= LastWidgetsUnderCursor.Widgets.Num() )
{
WidgetIndex--;
}
if ( WidgetIndex >= 0 )
{
const TSharedPtr<SWidget>& SomeWidgetPreviouslyUnderCursor = LastWidgetsUnderCursor.Widgets[WidgetIndex].Pin();
if( SomeWidgetPreviouslyUnderCursor.IsValid() )
{
TOptional<FArrangedWidget> FoundWidget = WidgetsUnderPointer.FindArrangedWidget( SomeWidgetPreviouslyUnderCursor.ToSharedRef() );
const bool bWidgetNoLongerUnderMouse = !FoundWidget.IsSet();
if ( bWidgetNoLongerUnderMouse )
{
// Widget is no longer under cursor, so send a MouseLeave.
// The widget might not even be in the hierarchy any more!
// Thus, we cannot translate the PointerPosition into the appropriate space for this event.
if ( IsDragDropping() )
{
// Note that the event's pointer position is not translated.
SomeWidgetPreviouslyUnderCursor->OnDragLeave( DragDropEvent );
LOG_EVENT( EEventLog::DragLeave, SomeWidgetPreviouslyUnderCursor );
// Reset the cursor override
DragDropEvent.GetOperation()->SetCursorOverride( TOptional<EMouseCursor::Type>() );
}
else
{
// Only fire mouse leave events for widgets inside the captor path, or whoever if there is no captor path.
if ( MouseCaptorPath.IsValid() == false || MouseCaptorPath.ContainsWidget(SomeWidgetPreviouslyUnderCursor.ToSharedRef()) )
{
// Note that the event's pointer position is not translated.
SomeWidgetPreviouslyUnderCursor->OnMouseLeave(PointerEvent);
LOG_EVENT(EEventLog::MouseLeave, SomeWidgetPreviouslyUnderCursor);
}
}
}
}
}
}
}
if (MouseCaptorPath.IsValid())
{
if ( !bIsSynthetic )
{
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld( MouseCaptorPath );
FEventRouter::Route<FNoReply>(this, FEventRouter::FBubblePolicy(WidgetsUnderPointer), PointerEvent, [&MouseCaptorPath, &LastWidgetsUnderCursor] (const FArrangedWidget& WidgetUnderCursor, const FPointerEvent& Event)
{
if ( !LastWidgetsUnderCursor.ContainsWidget(WidgetUnderCursor.Widget) )
{
if ( MouseCaptorPath.ContainsWidget(WidgetUnderCursor.Widget) )
{
WidgetUnderCursor.Widget->OnMouseEnter(WidgetUnderCursor.Geometry, Event);
}
}
return FNoReply();
});
FReply Reply = FEventRouter::Route<FReply>( this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), PointerEvent, [this]( const FArrangedWidget& MouseCaptorWidget, const FPointerEvent& Event )
{
FReply TempReply = FReply::Unhandled();
if (Event.IsTouchEvent())
{
TempReply = MouseCaptorWidget.Widget->OnTouchMoved( MouseCaptorWidget.Geometry, Event );
}
if (!Event.IsTouchEvent() || (!TempReply.IsEventHandled() && this->bTouchFallbackToMouse))
{
TempReply = MouseCaptorWidget.Widget->OnMouseMove( MouseCaptorWidget.Geometry, Event );
}
return TempReply;
} );
bHandled = Reply.IsEventHandled();
}
}
else
{
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld( WidgetsUnderPointer );
// Send out mouse enter events.
if (IsDragDropping())
{
FDragDropEvent DragDropEvent( PointerEvent, DragDropContent );
FEventRouter::Route<FNoReply>( this, FEventRouter::FBubblePolicy(WidgetsUnderPointer), DragDropEvent, [&LastWidgetsUnderCursor](const FArrangedWidget& WidgetUnderCursor, const FDragDropEvent& InDragDropEvent)
{
if ( !LastWidgetsUnderCursor.ContainsWidget(WidgetUnderCursor.Widget) )
{
WidgetUnderCursor.Widget->OnDragEnter( WidgetUnderCursor.Geometry, InDragDropEvent );
}
return FNoReply();
} );
}
else
{
FEventRouter::Route<FNoReply>(this, FEventRouter::FBubblePolicy(WidgetsUnderPointer), PointerEvent, [&LastWidgetsUnderCursor] (const FArrangedWidget& WidgetUnderCursor, const FPointerEvent& Event)
{
if ( !LastWidgetsUnderCursor.ContainsWidget(WidgetUnderCursor.Widget) )
{
WidgetUnderCursor.Widget->OnMouseEnter( WidgetUnderCursor.Geometry, Event );
}
return FNoReply();
} );
}
// Bubble the MouseMove event.
FReply Reply = FEventRouter::Route<FReply>(this, FEventRouter::FBubblePolicy(WidgetsUnderPointer), PointerEvent, [&](const FArrangedWidget& CurWidget, const FPointerEvent& Event)
{
FReply TempReply = FReply::Unhandled();
if (Event.IsTouchEvent())
{
TempReply = CurWidget.Widget->OnTouchMoved( CurWidget.Geometry, Event );
}
if (!TempReply.IsEventHandled())
{
TempReply = (IsDragDropping())
? CurWidget.Widget->OnDragOver( CurWidget.Geometry, FDragDropEvent( Event, DragDropContent ) )
: CurWidget.Widget->OnMouseMove( CurWidget.Geometry, Event );
}
return TempReply;
});
LOG_EVENT( IsDragDropping() ? EEventLog::DragOver : EEventLog::MouseMove, Reply )
bHandled = Reply.IsEventHandled();
}
// Give the current drag drop operation a chance to do something
// custom (e.g. update the Drag/Drop preview based on content)
if (IsDragDropping())
{
FDragDropEvent DragDropEvent( PointerEvent, DragDropContent );
//@TODO VREDITOR - Remove and move to interaction component
if (OnDragDropCheckOverride.IsBound() && DragDropEvent.GetOperation().IsValid())
{
DragDropEvent.GetOperation()->SetDecoratorVisibility(false);
DragDropEvent.GetOperation()->SetCursorOverride(EMouseCursor::None);
DragDropContent->SetCursorOverride(EMouseCursor::None);
}
FScopedSwitchWorldHack SwitchWorld( WidgetsUnderPointer );
DragDropContent->OnDragged( DragDropEvent );
// Update the window we're under for rendering the drag drop operation if
// it's a windowless drag drop operation.
if ( WidgetsUnderPointer.IsValid() )
{
DragDropWindowPtr = WidgetsUnderPointer.GetWindow();
}
else
{
DragDropWindowPtr = nullptr;
}
// Don't update the cursor for the platform if we don't have a valid cursor on this platform
if ( PlatformApplication->Cursor.IsValid() )
{
FCursorReply CursorReply = DragDropContent->OnCursorQuery();
if ( !CursorReply.IsEventHandled() )
{
// Set the default cursor when there isn't an active window under the cursor and the mouse isn't captured
CursorReply = FCursorReply::Cursor(EMouseCursor::Default);
}
ProcessCursorReply(CursorReply);
}
}
else
{
DragDropWindowPtr = nullptr;
}
WidgetsUnderCursorLastEvent.Add( FUserAndPointer( PointerEvent.GetUserIndex(), PointerEvent.GetPointerIndex() ), FWeakWidgetPath( WidgetsUnderPointer ) );
PointerIndexLastPositionMap.Add(PointerEvent.GetPointerIndex(), PointerEvent.GetScreenSpacePosition());
return bHandled;
}
bool FSlateApplication::OnMouseDoubleClick( const TSharedPtr< FGenericWindow >& PlatformWindow, const EMouseButtons::Type Button )
{
return OnMouseDoubleClick(PlatformWindow, Button, GetCursorPos());
}
bool FSlateApplication::OnMouseDoubleClick( const TSharedPtr< FGenericWindow >& PlatformWindow, const EMouseButtons::Type Button, const FVector2D CursorPos )
{
if (bIsFakingTouch || bIsGameFakingTouch)
{
bIsFakingTouched = true;
return OnTouchStarted(PlatformWindow, PlatformApplication->Cursor->GetPosition(), 0, 0);
}
FKey Key = TranslateMouseButtonToKey( Button );
FPointerEvent MouseEvent(
CursorPointerIndex,
CursorPos,
GetLastCursorPos(),
PressedMouseButtons,
Key,
0,
PlatformApplication->GetModifierKeys()
);
return ProcessMouseButtonDoubleClickEvent( PlatformWindow, MouseEvent );
}
bool FSlateApplication::ProcessMouseButtonDoubleClickEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, FPointerEvent& InMouseEvent )
{
SCOPE_CYCLE_COUNTER(STAT_ProcessMouseButtonDoubleClick);
QueueSynthesizedMouseMove();
SetLastUserInteractionTime(this->GetCurrentTime());
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
PlatformApplication->SetCapture( PlatformWindow );
PressedMouseButtons.Add( InMouseEvent.GetEffectingButton() );
if (MouseCaptor.HasCaptureForPointerIndex(InMouseEvent.GetUserIndex(), InMouseEvent.GetPointerIndex()))
{
// If a widget has mouse capture, we've opted to simply treat this event as a mouse down
return ProcessMouseButtonDownEvent(PlatformWindow, InMouseEvent);
}
FWidgetPath WidgetsUnderCursor = LocateWindowUnderMouse(InMouseEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows());
FReply Reply = RoutePointerDoubleClickEvent( WidgetsUnderCursor, InMouseEvent );
PointerIndexLastPositionMap.Add(InMouseEvent.GetPointerIndex(), InMouseEvent.GetScreenSpacePosition());
return Reply.IsEventHandled();
}
FReply FSlateApplication::RoutePointerDoubleClickEvent(FWidgetPath& WidgetsUnderPointer, FPointerEvent& PointerEvent)
{
TScopeCounter<int32> BeginInput(ProcessingInput);
FReply Reply = FReply::Unhandled();
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld( WidgetsUnderPointer );
Reply = FEventRouter::Route<FReply>( this, FEventRouter::FBubblePolicy( WidgetsUnderPointer ), PointerEvent, []( const FArrangedWidget& TargetWidget, const FPointerEvent& Event )
{
return TargetWidget.Widget->OnMouseButtonDoubleClick( TargetWidget.Geometry, Event );
} );
LOG_EVENT( EEventLog::MouseButtonDoubleClick, Reply );
return Reply;
}
bool FSlateApplication::OnMouseUp( const EMouseButtons::Type Button )
{
return OnMouseUp(Button, GetCursorPos());
}
bool FSlateApplication::OnMouseUp( const EMouseButtons::Type Button, const FVector2D CursorPos )
{
// convert to touch event if we are faking it
if (bIsFakingTouch || bIsGameFakingTouch)
{
bIsFakingTouched = false;
return OnTouchEnded(PlatformApplication->Cursor->GetPosition(), 0, 0);
}
FKey Key = TranslateMouseButtonToKey( Button );
FPointerEvent MouseEvent(
CursorPointerIndex,
CursorPos,
GetLastCursorPos(),
PressedMouseButtons,
Key,
0,
PlatformApplication->GetModifierKeys()
);
return ProcessMouseButtonUpEvent( MouseEvent );
}
bool FSlateApplication::ProcessMouseButtonUpEvent( FPointerEvent& MouseEvent )
{
SCOPE_CYCLE_COUNTER(STAT_ProcessMouseButtonUp);
QueueSynthesizedMouseMove();
SetLastUserInteractionTime(this->GetCurrentTime());
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
PressedMouseButtons.Remove( MouseEvent.GetEffectingButton() );
// Input preprocessor gets first chance at the input
if( InputPreProcessor.IsValid() && InputPreProcessor->HandleMouseButtonUpEvent( *this, MouseEvent ) )
{
return true;
}
// An empty widget path is passed in. As an optimization, one will be generated only if a captured mouse event isn't routed
FWidgetPath EmptyPath;
const bool bHandled = RoutePointerUpEvent( EmptyPath, MouseEvent ).IsEventHandled();
// If in responsive mode throttle, leave it on mouse up.
if( MouseButtonDownResponsivnessThrottle.IsValid() )
{
FSlateThrottleManager::Get().LeaveResponsiveMode( MouseButtonDownResponsivnessThrottle );
}
if ( PressedMouseButtons.Num() == 0 )
{
// Release Capture
PlatformApplication->SetCapture( nullptr );
}
return bHandled;
}
bool FSlateApplication::OnMouseWheel( const float Delta )
{
return OnMouseWheel(Delta, GetCursorPos());
}
bool FSlateApplication::OnMouseWheel( const float Delta, const FVector2D CursorPos )
{
FPointerEvent MouseWheelEvent(
CursorPointerIndex,
CursorPos,
CursorPos,
PressedMouseButtons,
EKeys::Invalid,
Delta,
PlatformApplication->GetModifierKeys()
);
return ProcessMouseWheelOrGestureEvent( MouseWheelEvent, nullptr );
}
bool FSlateApplication::ProcessMouseWheelOrGestureEvent( FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent )
{
SCOPE_CYCLE_COUNTER(STAT_ProcessMouseWheelGesture);
QueueSynthesizedMouseMove();
const bool bShouldProcessEvent = InWheelEvent.GetWheelDelta() != 0 ||
(InGestureEvent != nullptr && InGestureEvent->GetGestureDelta() != FVector2D::ZeroVector);
if ( !bShouldProcessEvent )
{
return false;
}
SetLastUserInteractionTime(this->GetCurrentTime());
// NOTE: We intentionally don't reset LastUserInteractionTimeForThrottling here so that the UI can be responsive while scrolling
FWidgetPath EventPath = LocateWindowUnderMouse(InWheelEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows());
return RouteMouseWheelOrGestureEvent(EventPath, InWheelEvent, InGestureEvent).IsEventHandled();
}
FReply FSlateApplication::RouteMouseWheelOrGestureEvent(const FWidgetPath& WidgetsUnderPointer, const FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent)
{
TScopeCounter<int32> BeginInput(ProcessingInput);
FWidgetPath MouseCaptorPath;
if ( MouseCaptor.HasCaptureForPointerIndex(InWheelEvent.GetUserIndex(), InWheelEvent.GetPointerIndex()) )
{
MouseCaptorPath = MouseCaptor.ToWidgetPath(FWeakWidgetPath::EInterruptedPathHandling::ReturnInvalid, &InWheelEvent);
}
const FWidgetPath& EventPath = MouseCaptorPath.IsValid() ? MouseCaptorPath : WidgetsUnderPointer;
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld(EventPath);
FReply Reply = FEventRouter::Route<FReply>(this, FEventRouter::FBubblePolicy(EventPath), InWheelEvent, [&InGestureEvent] (const FArrangedWidget& CurWidget, const FPointerEvent& Event)
{
FReply TempReply = FReply::Unhandled();
// Gesture event gets first shot, if slate doesn't respond to it, we'll try the wheel event.
if ( InGestureEvent != nullptr && InGestureEvent->GetGestureDelta() != FVector2D::ZeroVector )
{
TempReply = CurWidget.Widget->OnTouchGesture(CurWidget.Geometry, *InGestureEvent);
}
// Send the mouse wheel event if we haven't already handled the gesture version of this event.
if ( !TempReply.IsEventHandled() && Event.GetWheelDelta() != 0 )
{
TempReply = CurWidget.Widget->OnMouseWheel(CurWidget.Geometry, Event);
}
return TempReply;
});
LOG_EVENT(InGestureEvent ? EEventLog::TouchGesture : EEventLog::MouseWheel, Reply);
return Reply;
}
bool FSlateApplication::OnMouseMove()
{
// convert to touch event if we are faking it
if (bIsFakingTouched)
{
return OnTouchMoved(PlatformApplication->Cursor->GetPosition(), 0, 0);
}
else if (!bIsGameFakingTouch && bIsFakingTouch)
{
return false;
}
bool Result = true;
const FVector2D CurrentCursorPosition = GetCursorPos();
const FVector2D LastCursorPosition = GetLastCursorPos();
if ( LastCursorPosition != CurrentCursorPosition )
{
LastMouseMoveTime = GetCurrentTime();
FPointerEvent MouseEvent(
CursorPointerIndex,
CurrentCursorPosition,
LastCursorPosition,
PressedMouseButtons,
EKeys::Invalid,
0,
PlatformApplication->GetModifierKeys()
);
if (InputPreProcessor.IsValid() && InputPreProcessor->HandleMouseMoveEvent(*this, MouseEvent))
{
return true;
}
Result = ProcessMouseMoveEvent( MouseEvent );
}
return Result;
}
bool FSlateApplication::OnRawMouseMove( const int32 X, const int32 Y )
{
if (bIsFakingTouched)
{
return OnTouchMoved(GetCursorPos(), 0, 0);
}
if ( X != 0 || Y != 0 )
{
FPointerEvent MouseEvent(
CursorPointerIndex,
GetCursorPos(),
GetLastCursorPos(),
FVector2D( X, Y ),
PressedMouseButtons,
PlatformApplication->GetModifierKeys()
);
if (InputPreProcessor.IsValid() && InputPreProcessor->HandleMouseMoveEvent(*this, MouseEvent))
{
return true;
}
ProcessMouseMoveEvent(MouseEvent);
}
return true;
}
bool FSlateApplication::ProcessMouseMoveEvent( FPointerEvent& MouseEvent, bool bIsSynthetic )
{
SCOPE_CYCLE_COUNTER(STAT_ProcessMouseMove);
if ( !bIsSynthetic )
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_ProcessMouseMove_Tooltip);
QueueSynthesizedMouseMove();
// Detecting a mouse move of zero delta is our way of filtering out synthesized move events
const bool AllowSpawningOfToolTips = true;
UpdateToolTip( AllowSpawningOfToolTips );
// Guard against synthesized mouse moves and only track user interaction if the cursor pos changed
SetLastUserInteractionTime(this->GetCurrentTime());
}
// When the event came from the OS, we are guaranteed to be over a slate window.
// Otherwise, we are synthesizing a MouseMove ourselves, and must verify that the
// cursor is indeed over a Slate window.
const bool bOverSlateWindow = !bIsSynthetic || PlatformApplication->IsCursorDirectlyOverSlateWindow();
FWidgetPath WidgetsUnderCursor = bOverSlateWindow
? LocateWindowUnderMouse(MouseEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows())
: FWidgetPath();
bool bResult;
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_ProcessMouseMove_RoutePointerMoveEvent);
bResult = RoutePointerMoveEvent(WidgetsUnderCursor, MouseEvent, bIsSynthetic);
}
return bResult;
}
bool FSlateApplication::OnCursorSet()
{
bQueryCursorRequested = true;
return true;
}
void FSlateApplication::NavigateToWidget(const uint32 UserIndex, const TSharedPtr<SWidget>& NavigationDestination, ENavigationSource NavigationSource)
{
if (NavigationDestination.IsValid())
{
FWidgetPath NavigationSourceWP;
if (NavigationSource == ENavigationSource::WidgetUnderCursor)
{
NavigationSourceWP = LocateWindowUnderMouse(GetCursorPos(), GetInteractiveTopLevelWindows());
}
else if (const FSlateUser* User = GetOrCreateUser(UserIndex))
{
const FUserFocusEntry& UserFocusEntry = User->Focus;
NavigationSourceWP = UserFocusEntry.WidgetPath.ToWidgetPath();
}
if (NavigationSourceWP.IsValid())
{
ExecuteNavigation(NavigationSourceWP, NavigationDestination, UserIndex);
}
}
}
bool FSlateApplication::AttemptNavigation(const FWidgetPath& NavigationSource, const FNavigationEvent& NavigationEvent, const FNavigationReply& NavigationReply, const FArrangedWidget& BoundaryWidget)
{
if ( !NavigationSource.IsValid() )
{
return false;
}
TSharedPtr<SWidget> DestinationWidget = TSharedPtr<SWidget>();
EUINavigation NavigationType = NavigationEvent.GetNavigationType();
if ( NavigationReply.GetBoundaryRule() == EUINavigationRule::Explicit )
{
DestinationWidget = NavigationReply.GetFocusRecipient();
}
else if ( NavigationReply.GetBoundaryRule() == EUINavigationRule::Custom )
{
const FNavigationDelegate& FocusDelegate = NavigationReply.GetFocusDelegate();
if ( FocusDelegate.IsBound() )
{
DestinationWidget = FocusDelegate.Execute(NavigationType);
}
}
else
{
// Find the next widget
if (NavigationType == EUINavigation::Next || NavigationType == EUINavigation::Previous)
{
// Fond the next widget
FWeakWidgetPath WeakNavigationSource(NavigationSource);
FWidgetPath NewFocusedWidgetPath = WeakNavigationSource.ToNextFocusedPath(NavigationType, NavigationReply, BoundaryWidget);
// Resolve the Widget Path
FArrangedWidget& NewFocusedArrangedWidget = NewFocusedWidgetPath.Widgets.Last();
DestinationWidget = NewFocusedArrangedWidget.Widget;
}
else
{
// Resolve the Widget Path
const FArrangedWidget& FocusedArrangedWidget = NavigationSource.Widgets.Last();
// Switch worlds for widgets in the current path
FScopedSwitchWorldHack SwitchWorld(NavigationSource);
DestinationWidget = NavigationSource.GetWindow()->GetHittestGrid()->FindNextFocusableWidget(FocusedArrangedWidget, NavigationType, NavigationReply, BoundaryWidget);
}
}
return ExecuteNavigation(NavigationSource, DestinationWidget, NavigationEvent.GetUserIndex());
}
bool FSlateApplication::ExecuteNavigation(const FWidgetPath& NavigationSource, TSharedPtr<SWidget> DestinationWidget, const uint32 UserIndex)
{
bool bHandled = false;
// Give the custom viewport navigation event handler a chance to handle the navigation if the NavigationSource is contained within it.
TSharedPtr<ISlateViewport> Viewport = NavigationSource.GetWindow()->GetViewport();
if (Viewport.IsValid())
{
TSharedPtr<SWidget> ViewportWidget = Viewport->GetWidget().Pin();
if (ViewportWidget.IsValid())
{
if (NavigationSource.ContainsWidget(ViewportWidget.ToSharedRef()))
{
bHandled = Viewport->HandleNavigation(UserIndex, DestinationWidget);
}
}
}
// Set controller focus if the navigation hasn't been handled have a valid widget
if (!bHandled && DestinationWidget.IsValid())
{
SetUserFocus(UserIndex, DestinationWidget, EFocusCause::Navigation);
bHandled = true;
}
return bHandled;
}
bool FSlateApplication::OnControllerAnalog( FGamepadKeyNames::Type KeyName, int32 ControllerId, float AnalogValue )
{
FKey Key(KeyName);
check(Key.IsValid());
int32 UserIndex = GetUserIndexForController(ControllerId);
FAnalogInputEvent AnalogInputEvent(Key, PlatformApplication->GetModifierKeys(), UserIndex, false, 0, 0, AnalogValue);
return ProcessAnalogInputEvent(AnalogInputEvent);
}
bool FSlateApplication::OnControllerButtonPressed( FGamepadKeyNames::Type KeyName, int32 ControllerId, bool IsRepeat )
{
FKey Key(KeyName);
check(Key.IsValid());
int32 UserIndex = GetUserIndexForController(ControllerId);
FKeyEvent KeyEvent(Key, PlatformApplication->GetModifierKeys(), UserIndex, IsRepeat, 0, 0);
return ProcessKeyDownEvent(KeyEvent);
}
bool FSlateApplication::OnControllerButtonReleased( FGamepadKeyNames::Type KeyName, int32 ControllerId, bool IsRepeat )
{
FKey Key(KeyName);
check(Key.IsValid());
int32 UserIndex = GetUserIndexForController(ControllerId);
FKeyEvent KeyEvent(Key, PlatformApplication->GetModifierKeys(),UserIndex, IsRepeat, 0, 0);
return ProcessKeyUpEvent(KeyEvent);
}
bool FSlateApplication::OnTouchGesture( EGestureEvent::Type GestureType, const FVector2D &Delta, const float MouseWheelDelta, bool bIsDirectionInvertedFromDevice )
{
const FVector2D CurrentCursorPosition = GetCursorPos();
FPointerEvent GestureEvent(
CurrentCursorPosition,
CurrentCursorPosition,
PressedMouseButtons,
PlatformApplication->GetModifierKeys(),
GestureType,
Delta,
bIsDirectionInvertedFromDevice
);
FPointerEvent MouseWheelEvent(
CursorPointerIndex,
CurrentCursorPosition,
CurrentCursorPosition,
PressedMouseButtons,
EKeys::Invalid,
MouseWheelDelta,
PlatformApplication->GetModifierKeys()
);
return ProcessMouseWheelOrGestureEvent( MouseWheelEvent, &GestureEvent );
}
bool FSlateApplication::OnTouchStarted( const TSharedPtr< FGenericWindow >& PlatformWindow, const FVector2D& Location, int32 TouchIndex, int32 ControllerId )
{
FPointerEvent PointerEvent(
ControllerId,
TouchIndex,
Location,
Location,
true);
ProcessTouchStartedEvent( PlatformWindow, PointerEvent );
return true;
}
void FSlateApplication::ProcessTouchStartedEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, FPointerEvent& PointerEvent )
{
ProcessMouseButtonDownEvent( PlatformWindow, PointerEvent );
}
bool FSlateApplication::OnTouchMoved( const FVector2D& Location, int32 TouchIndex, int32 ControllerId )
{
FVector2D LastLocation = PointerIndexLastPositionMap.Contains(TouchIndex) ?
PointerIndexLastPositionMap[TouchIndex] : Location;
FPointerEvent PointerEvent(
ControllerId,
TouchIndex,
Location,
LastLocation,
true);
ProcessTouchMovedEvent(PointerEvent);
return true;
}
void FSlateApplication::ProcessTouchMovedEvent( FPointerEvent& PointerEvent )
{
ProcessMouseMoveEvent(PointerEvent);
}
bool FSlateApplication::OnTouchEnded( const FVector2D& Location, int32 TouchIndex, int32 ControllerId )
{
FPointerEvent PointerEvent(
ControllerId,
TouchIndex,
Location,
Location,
true);
ProcessTouchEndedEvent(PointerEvent);
return true;
}
void FSlateApplication::ProcessTouchEndedEvent( FPointerEvent& PointerEvent )
{
ProcessMouseButtonUpEvent(PointerEvent);
}
bool FSlateApplication::OnMotionDetected(const FVector& Tilt, const FVector& RotationRate, const FVector& Gravity, const FVector& Acceleration, int32 ControllerId)
{
FMotionEvent MotionEvent(
ControllerId,
Tilt,
RotationRate,
Gravity,
Acceleration
);
ProcessMotionDetectedEvent(MotionEvent);
return true;
}
void FSlateApplication::ProcessMotionDetectedEvent( FMotionEvent& MotionEvent )
{
QueueSynthesizedMouseMove();
SetLastUserInteractionTime(this->GetCurrentTime());
if ( FSlateUser* User = GetOrCreateUser(MotionEvent.GetUserIndex()) )
{
if (InputPreProcessor.IsValid() && InputPreProcessor->HandleMotionDetectedEvent(*this, MotionEvent))
{
return;
}
if ( User->Focus.WidgetPath.IsValid() )
{
/* Get the controller focus target for this user */
const FWidgetPath PathToWidget = User->Focus.WidgetPath.ToWidgetPath();
FScopedSwitchWorldHack SwitchWorld(PathToWidget);
FReply Reply = FEventRouter::Route<FReply>(this, FEventRouter::FBubblePolicy(PathToWidget), MotionEvent, [] (const FArrangedWidget& SomeWidget, const FMotionEvent& InMotionEvent)
{
return SomeWidget.Widget->OnMotionDetected(SomeWidget.Geometry, InMotionEvent);
});
}
}
}
bool FSlateApplication::OnSizeChanged( const TSharedRef< FGenericWindow >& PlatformWindow, const int32 Width, const int32 Height, bool bWasMinimized )
{
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
if ( Window.IsValid() )
{
Window->SetCachedSize( FVector2D( Width, Height ) );
Renderer->RequestResize( Window, Width, Height );
if ( !bWasMinimized && Window->IsRegularWindow() && !Window->HasOSWindowBorder() && Window->IsVisible() && Window->IsDrawingEnabled() )
{
PrivateDrawWindows( Window );
}
if( !bWasMinimized && Window->IsVisible() && Window->IsRegularWindow() && Window->IsAutosized() )
{
// Reduces flickering due to one frame lag when windows are resized automatically
Renderer->FlushCommands();
}
// Inform the notification manager we have activated a window - it may want to force notifications
// back to the front of the z-order
FSlateNotificationManager::Get().ForceNotificationsInFront( Window.ToSharedRef() );
}
return true;
}
void FSlateApplication::OnOSPaint( const TSharedRef< FGenericWindow >& PlatformWindow )
{
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
PrivateDrawWindows( Window );
Renderer->FlushCommands();
}
FWindowSizeLimits FSlateApplication::GetSizeLimitsForWindow(const TSharedRef<FGenericWindow>& Window) const
{
TSharedPtr<SWindow> SlateWindow = FSlateWindowHelper::FindWindowByPlatformWindow(SlateWindows, Window);
if (SlateWindow.IsValid())
{
return SlateWindow->GetSizeLimits();
}
else
{
return FWindowSizeLimits();
}
}
void FSlateApplication::OnResizingWindow( const TSharedRef< FGenericWindow >& PlatformWindow )
{
// Flush the rendering command queue to ensure that there aren't pending viewport draw commands for the old viewport size.
Renderer->FlushCommands();
}
bool FSlateApplication::BeginReshapingWindow( const TSharedRef< FGenericWindow >& PlatformWindow )
{
if(!IsExternalUIOpened())
{
if (!ThrottleHandle.IsValid())
{
ThrottleHandle = FSlateThrottleManager::Get().EnterResponsiveMode();
}
return true;
}
return false;
}
void FSlateApplication::FinishedReshapingWindow( const TSharedRef< FGenericWindow >& PlatformWindow )
{
if (ThrottleHandle.IsValid())
{
FSlateThrottleManager::Get().LeaveResponsiveMode(ThrottleHandle);
}
}
void FSlateApplication::OnMovedWindow( const TSharedRef< FGenericWindow >& PlatformWindow, const int32 X, const int32 Y )
{
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
if ( Window.IsValid() )
{
Window->SetCachedScreenPosition( FVector2D( X, Y ) );
}
}
FWindowActivateEvent::EActivationType TranslationWindowActivationMessage( const EWindowActivation::Type ActivationType )
{
FWindowActivateEvent::EActivationType Result = FWindowActivateEvent::EA_Activate;
switch( ActivationType )
{
case EWindowActivation::Activate:
Result = FWindowActivateEvent::EA_Activate;
break;
case EWindowActivation::ActivateByMouse:
Result = FWindowActivateEvent::EA_ActivateByMouse;
break;
case EWindowActivation::Deactivate:
Result = FWindowActivateEvent::EA_Deactivate;
break;
default:
check( false );
}
return Result;
}
bool FSlateApplication::OnWindowActivationChanged( const TSharedRef< FGenericWindow >& PlatformWindow, const EWindowActivation::Type ActivationType )
{
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
if ( !Window.IsValid() )
{
return false;
}
FWindowActivateEvent::EActivationType TranslatedActivationType = TranslationWindowActivationMessage( ActivationType );
FWindowActivateEvent WindowActivateEvent( TranslatedActivationType, Window.ToSharedRef() );
return ProcessWindowActivatedEvent( WindowActivateEvent );
}
bool FSlateApplication::ProcessWindowActivatedEvent( const FWindowActivateEvent& ActivateEvent )
{
//UE_LOG(LogSlate, Warning, TEXT("Window being %s: %p"), ActivateEvent.GetActivationType() == FWindowActivateEvent::EA_Deactivate ? TEXT("Deactivated") : TEXT("Activated"), &(ActivateEvent.GetAffectedWindow().Get()));
TSharedPtr<SWindow> ActiveModalWindow = GetActiveModalWindow();
if ( ActivateEvent.GetActivationType() != FWindowActivateEvent::EA_Deactivate )
{
const bool bActivatedByMouse = ActivateEvent.GetActivationType() == FWindowActivateEvent::EA_ActivateByMouse;
// Only window activate by mouse is considered a user interaction
if (bActivatedByMouse)
{
SetLastUserInteractionTime(this->GetCurrentTime());
}
// Widgets that happen to be under the mouse need to update if activation changes
// This also serves as a force redraw which is needed when restoring a window that was previously inactive.
QueueSynthesizedMouseMove();
// NOTE: The window is brought to front even when a modal window is active and this is not the modal window one of its children
// The reason for this is so that the Slate window order is in sync with the OS window order when a modal window is open. This is important so that when the modal window closes the proper window receives input from Slate.
// If you change this be sure to test windows are activated properly and receive input when they are opened when a modal dialog is open.
FSlateWindowHelper::BringWindowToFront(SlateWindows, ActivateEvent.GetAffectedWindow());
// Do not process activation messages unless we have no modal windows or the current window is modal
if( !ActiveModalWindow.IsValid() || ActivateEvent.GetAffectedWindow() == ActiveModalWindow || ActivateEvent.GetAffectedWindow()->IsDescendantOf(ActiveModalWindow) )
{
// Window being ACTIVATED
{
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld( ActivateEvent.GetAffectedWindow() );
ActivateEvent.GetAffectedWindow()->OnIsActiveChanged( ActivateEvent );
}
if ( ActivateEvent.GetAffectedWindow()->IsRegularWindow() )
{
ActiveTopLevelWindow = ActivateEvent.GetAffectedWindow();
}
// A Slate window was activated
bSlateWindowActive = true;
{
FScopedSwitchWorldHack SwitchWorld( ActivateEvent.GetAffectedWindow() );
// let the menu stack know of new window being activated. We may need to close menus as a result
MenuStack.OnWindowActivated( ActivateEvent.GetAffectedWindow() );
}
// Inform the notification manager we have activated a window - it may want to force notifications
// back to the front of the z-order
FSlateNotificationManager::Get().ForceNotificationsInFront( ActivateEvent.GetAffectedWindow() );
// As we've just been activated, attempt to restore the resolution that the engine previously cached.
// This allows us to force ourselves back to the correct resolution after alt-tabbing out of a fullscreen
// window and then going back in again.
Renderer->RestoreSystemResolution(ActivateEvent.GetAffectedWindow());
// Synthesize mouse move to resume rendering in the next tick if Slate is sleeping
QueueSynthesizedMouseMove();
}
else
{
// An attempt is being made to activate another window when a modal window is running
ActiveModalWindow->BringToFront();
ActiveModalWindow->FlashWindow();
}
TSharedRef<SWindow> Window = ActivateEvent.GetAffectedWindow();
TSharedPtr<ISlateViewport> Viewport = Window->GetViewport();
if (Viewport.IsValid())
{
TSharedPtr<SWidget> ViewportWidgetPtr = Viewport->GetWidget().Pin();
if (ViewportWidgetPtr.IsValid())
{
TArray< TSharedRef<SWindow> > JustThisWindow;
JustThisWindow.Add(Window);
FWidgetPath PathToViewport;
if (FSlateWindowHelper::FindPathToWidget(JustThisWindow, ViewportWidgetPtr.ToSharedRef(), PathToViewport, EVisibility::All))
{
// Activate the viewport and process the reply
FReply ViewportActivatedReply = Viewport->OnViewportActivated(ActivateEvent);
if (ViewportActivatedReply.IsEventHandled())
{
ProcessReply(PathToViewport, ViewportActivatedReply, nullptr, nullptr);
}
}
}
}
}
else
{
// Window being DEACTIVATED
// If our currently-active top level window was deactivated, take note of that
if ( ActivateEvent.GetAffectedWindow()->IsRegularWindow() &&
ActivateEvent.GetAffectedWindow() == ActiveTopLevelWindow.Pin() )
{
ActiveTopLevelWindow.Reset();
}
// A Slate window was deactivated. Currently there is no active Slate window
bSlateWindowActive = false;
// Switch worlds for the activated window
FScopedSwitchWorldHack SwitchWorld( ActivateEvent.GetAffectedWindow() );
ActivateEvent.GetAffectedWindow()->OnIsActiveChanged( ActivateEvent );
TSharedRef<SWindow> Window = ActivateEvent.GetAffectedWindow();
TSharedPtr<ISlateViewport> Viewport = Window->GetViewport();
if (Viewport.IsValid())
{
Viewport->OnViewportDeactivated(ActivateEvent);
}
// A window was deactivated; mouse capture should be cleared
ResetToDefaultPointerInputSettings();
}
return true;
}
bool FSlateApplication::OnApplicationActivationChanged( const bool IsActive )
{
ProcessApplicationActivationEvent( IsActive );
return true;
}
void FSlateApplication::ProcessApplicationActivationEvent(bool InAppActivated)
{
const bool UserSwitchedAway = bAppIsActive && !InAppActivated;
bAppIsActive = InAppActivated;
// If the user switched to a different application then we should dismiss our pop-ups. In the case
// where a user clicked on a different Slate window, OnWindowActivatedMessage() will be call MenuStack.OnWindowActivated()
// to destroy any windows in our stack that are no longer appropriate to be displayed.
if (UserSwitchedAway)
{
// Close pop-up menus
DismissAllMenus();
// Close tool-tips
CloseToolTip();
// No slate window is active when our entire app becomes inactive
bSlateWindowActive = false;
// If we have a slate-only drag-drop occurring, stop the drag drop.
if (IsDragDropping() && !DragDropContent->IsExternalOperation())
{
DragDropContent.Reset();
}
}
else
{
//Ensure that slate ticks/renders next frame
QueueSynthesizedMouseMove();
}
OnApplicationActivationStateChanged().Broadcast(InAppActivated);
}
void FSlateApplication::SetNavigationConfig(TSharedRef<FNavigationConfig> Config)
{
NavigationConfig = Config;
}
bool FSlateApplication::OnConvertibleLaptopModeChanged()
{
EConvertibleLaptopMode NewMode = FPlatformMisc::GetConvertibleLaptopMode();
// Notify that we want the mobile experience when in tablet mode, otherwise use mouse and keyboard
if (!(FParse::Param(FCommandLine::Get(), TEXT("simmobile")) || FParse::Param(FCommandLine::Get(), TEXT("faketouches"))))
{
// Not sure what the correct long-term strategy is. Use bIsFakingTouch for now to get things going.
if (NewMode == EConvertibleLaptopMode::Tablet)
{
bIsFakingTouch = true;
}
else
{
bIsFakingTouch = false;
}
}
FCoreDelegates::PlatformChangedLaptopMode.Broadcast(NewMode);
return true;
}
EWindowZone::Type FSlateApplication::GetWindowZoneForPoint( const TSharedRef< FGenericWindow >& PlatformWindow, const int32 X, const int32 Y )
{
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
if ( Window.IsValid() )
{
return Window->GetCurrentWindowZone( FVector2D( X, Y ) );
}
return EWindowZone::NotInWindow;
}
void FSlateApplication::PrivateDestroyWindow( const TSharedRef<SWindow>& DestroyedWindow )
{
// Notify the window that it is going to be destroyed. The window must be completely intact when this is called
// because delegates are allowed to leave Slate here
DestroyedWindow->NotifyWindowBeingDestroyed();
// Release rendering resources.
// This MUST be done before destroying the native window as the native window is required to be valid before releasing rendering resources with some API's
Renderer->OnWindowDestroyed( DestroyedWindow );
// Destroy the native window
DestroyedWindow->DestroyWindowImmediately();
// Remove the window and all its children from the Slate window list
FSlateWindowHelper::RemoveWindowFromList(SlateWindows, DestroyedWindow);
// Shutdown the application if there are no more windows
{
bool bAnyRegularWindows = false;
for( auto WindowIter( SlateWindows.CreateConstIterator() ); WindowIter; ++WindowIter )
{
auto Window = *WindowIter;
if( Window->IsRegularWindow() )
{
bAnyRegularWindows = true;
break;
}
}
if (!bAnyRegularWindows)
{
OnExitRequested.ExecuteIfBound();
}
}
}
void FSlateApplication::OnWindowClose( const TSharedRef< FGenericWindow >& PlatformWindow )
{
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
if ( Window.IsValid() )
{
bool bCanCloseWindow = true;
TSharedPtr< SViewport > CurrentGameViewportWidget = GameViewportWidget.Pin();
if (CurrentGameViewportWidget.IsValid())
{
TSharedPtr< ISlateViewport > SlateViewport = CurrentGameViewportWidget->GetViewportInterface().Pin();
if (SlateViewport.IsValid())
{
bCanCloseWindow = !SlateViewport->OnRequestWindowClose().bIsHandled;
}
}
if (bCanCloseWindow)
{
Window->RequestDestroyWindow();
}
}
}
EDropEffect::Type FSlateApplication::OnDragEnterText( const TSharedRef< FGenericWindow >& Window, const FString& Text )
{
const TSharedPtr< FExternalDragOperation > DragDropOperation = FExternalDragOperation::NewText( Text );
const TSharedPtr< SWindow > EffectingWindow = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, Window );
EDropEffect::Type Result = EDropEffect::None;
if ( DragDropOperation.IsValid() && EffectingWindow.IsValid() )
{
Result = OnDragEnter( EffectingWindow.ToSharedRef(), DragDropOperation.ToSharedRef() );
}
return Result;
}
EDropEffect::Type FSlateApplication::OnDragEnterFiles( const TSharedRef< FGenericWindow >& Window, const TArray< FString >& Files )
{
const TSharedPtr< FExternalDragOperation > DragDropOperation = FExternalDragOperation::NewFiles( Files );
const TSharedPtr< SWindow > EffectingWindow = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, Window );
EDropEffect::Type Result = EDropEffect::None;
if ( DragDropOperation.IsValid() && EffectingWindow.IsValid() )
{
Result = OnDragEnter( EffectingWindow.ToSharedRef(), DragDropOperation.ToSharedRef() );
}
return Result;
}
EDropEffect::Type FSlateApplication::OnDragEnterExternal( const TSharedRef< FGenericWindow >& Window, const FString& Text, const TArray< FString >& Files )
{
const TSharedPtr< FExternalDragOperation > DragDropOperation = FExternalDragOperation::NewOperation( Text, Files );
const TSharedPtr< SWindow > EffectingWindow = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, Window );
EDropEffect::Type Result = EDropEffect::None;
if ( DragDropOperation.IsValid() && EffectingWindow.IsValid() )
{
Result = OnDragEnter( EffectingWindow.ToSharedRef(), DragDropOperation.ToSharedRef() );
}
return Result;
}
EDropEffect::Type FSlateApplication::OnDragEnter( const TSharedRef< SWindow >& Window, const TSharedRef<FExternalDragOperation>& DragDropOperation )
{
// We are encountering a new drag and drop operation.
// Assume we cannot handle it.
DragIsHandled = false;
const FVector2D CurrentCursorPosition = GetCursorPos();
const FVector2D LastCursorPosition = GetLastCursorPos();
// Tell slate to enter drag and drop mode.
// Make a faux mouse event for slate, so we can initiate a drag and drop.
FDragDropEvent DragDropEvent(
FPointerEvent(
CursorPointerIndex,
CurrentCursorPosition,
LastCursorPosition,
PressedMouseButtons,
EKeys::Invalid,
0,
PlatformApplication->GetModifierKeys() ),
DragDropOperation
);
ProcessDragEnterEvent( Window, DragDropEvent );
return EDropEffect::None;
}
bool FSlateApplication::ProcessDragEnterEvent( TSharedRef<SWindow> WindowEntered, FDragDropEvent& DragDropEvent )
{
SetLastUserInteractionTime(this->GetCurrentTime());
FWidgetPath WidgetsUnderCursor = LocateWindowUnderMouse( DragDropEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows() );
// Switch worlds for widgets in the current path
FScopedSwitchWorldHack SwitchWorld( WidgetsUnderCursor );
FReply TriggerDragDropReply = FReply::Handled().BeginDragDrop( DragDropEvent.GetOperation().ToSharedRef() );
ProcessReply( WidgetsUnderCursor, TriggerDragDropReply, &WidgetsUnderCursor, &DragDropEvent );
PointerIndexLastPositionMap.Add(DragDropEvent.GetPointerIndex(), DragDropEvent.GetScreenSpacePosition());
return true;
}
EDropEffect::Type FSlateApplication::OnDragOver( const TSharedPtr< FGenericWindow >& Window )
{
EDropEffect::Type Result = EDropEffect::None;
if ( IsDragDropping() )
{
bool MouseMoveHandled = true;
FVector2D CursorMovementDelta( 0, 0 );
const FVector2D CurrentCursorPosition = GetCursorPos();
const FVector2D LastCursorPosition = GetLastCursorPos();
if ( LastCursorPosition != CurrentCursorPosition )
{
FPointerEvent MouseEvent(
CursorPointerIndex,
CurrentCursorPosition,
LastCursorPosition,
PressedMouseButtons,
EKeys::Invalid,
0,
PlatformApplication->GetModifierKeys()
);
MouseMoveHandled = ProcessMouseMoveEvent( MouseEvent );
CursorMovementDelta = MouseEvent.GetCursorDelta();
}
// Slate is now in DragAndDrop mode. It is tracking the payload.
// We just need to convey mouse movement.
if ( CursorMovementDelta.SizeSquared() > 0 )
{
DragIsHandled = MouseMoveHandled;
}
if ( DragIsHandled )
{
Result = EDropEffect::Copy;
}
}
return Result;
}
void FSlateApplication::OnDragLeave( const TSharedPtr< FGenericWindow >& Window )
{
DragDropContent.Reset();
}
EDropEffect::Type FSlateApplication::OnDragDrop( const TSharedPtr< FGenericWindow >& Window )
{
EDropEffect::Type Result = EDropEffect::None;
if ( IsDragDropping() )
{
FPointerEvent MouseEvent(
CursorPointerIndex,
GetCursorPos(),
GetLastCursorPos(),
PressedMouseButtons,
EKeys::LeftMouseButton,
0,
PlatformApplication->GetModifierKeys()
);
// User dropped into a Slate window. Slate is already in drag and drop mode.
// It knows what to do based on a mouse up.
if ( ProcessMouseButtonUpEvent( MouseEvent ) )
{
Result = EDropEffect::Copy;
}
}
return Result;
}
bool FSlateApplication::OnWindowAction( const TSharedRef< FGenericWindow >& PlatformWindow, const EWindowAction::Type InActionType)
{
// Return false to tell the OS layer that it should ignore the action
if (IsExternalUIOpened())
{
return false;
}
bool bResult = true;
for (int32 Index = 0; Index < OnWindowActionNotifications.Num(); Index++)
{
if (OnWindowActionNotifications[Index].IsBound())
{
if (OnWindowActionNotifications[Index].Execute(PlatformWindow, InActionType))
{
// If the delegate returned true, it means that it wants the OS layer to stop processing the action
bResult = false;
}
}
}
return bResult;
}
void FSlateApplication::OnVirtualDesktopSizeChanged(const FDisplayMetrics& NewDisplayMetric)
{
const FPlatformRect& VirtualDisplayRect = NewDisplayMetric.VirtualDisplayRect;
VirtualDesktopRect = FSlateRect(
VirtualDisplayRect.Left,
VirtualDisplayRect.Top,
VirtualDisplayRect.Right,
VirtualDisplayRect.Bottom);
}
/*
*****************************************************************************/
TSharedRef<FSlateApplication> FSlateApplication::InitializeAsStandaloneApplication(const TSharedRef<FSlateRenderer>& PlatformRenderer)
{
return InitializeAsStandaloneApplication(PlatformRenderer, MakeShareable(FPlatformMisc::CreateApplication()));
}
TSharedRef<FSlateApplication> FSlateApplication::InitializeAsStandaloneApplication(const TSharedRef< class FSlateRenderer >& PlatformRenderer, const TSharedRef<class GenericApplication>& InPlatformApplication)
{
// create the platform slate application (what FSlateApplication::Get() returns)
TSharedRef<FSlateApplication> Slate = FSlateApplication::Create(InPlatformApplication);
// initialize renderer
FSlateApplication::Get().InitializeRenderer(PlatformRenderer);
// set the normal UE4 GIsRequestingExit when outer frame is closed
FSlateApplication::Get().SetExitRequestedHandler(FSimpleDelegate::CreateStatic(&OnRequestExit));
return Slate;
}
void FSlateApplication::SetWidgetReflector(const TSharedRef<IWidgetReflector>& WidgetReflector)
{
if ( SourceCodeAccessDelegate.IsBound() )
{
WidgetReflector->SetSourceAccessDelegate(SourceCodeAccessDelegate);
}
if ( AssetAccessDelegate.IsBound() )
{
WidgetReflector->SetAssetAccessDelegate(AssetAccessDelegate);
}
WidgetReflectorPtr = WidgetReflector;
}
void FSlateApplication::NavigateFromWidgetUnderCursor(const uint32 InUserIndex, EUINavigation InNavigationType, TSharedRef<SWindow> InWindow)
{
if (InNavigationType != EUINavigation::Invalid)
{
FWidgetPath PathToLocatedWidget = LocateWidgetInWindow(GetCursorPos(), InWindow, false);
if (PathToLocatedWidget.IsValid())
{
TSharedPtr<SWidget> WidgetToNavFrom = PathToLocatedWidget.Widgets.Last().Widget;
if (WidgetToNavFrom.IsValid())
{
FSlateApplication::Get().ProcessReply(PathToLocatedWidget, FReply::Handled().SetNavigation(InNavigationType, ENavigationGenesis::User, ENavigationSource::WidgetUnderCursor), &PathToLocatedWidget, nullptr, InUserIndex);
}
}
}
}
#undef SLATE_HAS_WIDGET_REFLECTOR