You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 2973846 on 2016/05/11 by Jamie.Dale Exposed FConfigValue::ExpandValue and added FConfigValue::CollapseValue These are both static and can be used to expand or collapse the macros used in our config files (mostly when dealing with paths), in code that has to deal with the config system, but isn't internal to the config system (mostly things that deal with default configs outside of UObjects). The old non-static version of FConfigValue::ExpandValue is now FConfigValue::ExpandValueInternal, which just calls FConfigValue::ExpandValue on SavedValue and ExpandedValue. This also changes some code that was using FString.Replace to use FString.ReplaceInline. This reduces allocations, and also allows us to avoid another string comparison to see whether the strings are identical (as ReplaceInline returns the number of replacements that were made). Change 2973847 on 2016/05/11 by Jamie.Dale Changing the loading phase in the localization dashboard now writes to the default config #jira UE-30482 Change 2973866 on 2016/05/11 by Jamie.Dale Deprecated some functions that were taking an unused position. These unused parameters caused confusion and lead to UE-30276. The old versions have been deprecated, and new versions without those parameters have been added. Existing code has been updated to call the non-deprecated version. - FViewportFrame::ResizeFrame - FSceneViewport::ResizeFrame - FSceneViewport::ResizeViewport Change 2974505 on 2016/05/11 by Nick.Darnell PR #2309: Added Combobox styling (Contributed by Chris528) Change 2975241 on 2016/05/12 by Richard.TalbotWatkin Made sRGB Preview the default in the Color Picker. Change 2975390 on 2016/05/12 by Jamie.Dale Made sure that en-US-POSIX is in our list of available cultures Some people use machine tags as their native text, so they need an invariant machine like culture to use as their native culture. en-US-POSIX is perfect for this. Change 2975411 on 2016/05/12 by Jamie.Dale PR #2237: Fixed formatting of Error_TooManyMaterials message (Contributed by pfranz) Change 2975559 on 2016/05/12 by Jamie.Dale Dialogue Wave VO direction can now be localized This is gathered as editor-only data. #jira UE-28715 Change 2975710 on 2016/05/12 by Jamie.Dale Implemented UObject::IsLocalizedResource to test whether the object belongs to a localized package Change 2975728 on 2016/05/12 by Jamie.Dale Exported dialogue scripts now include a column that says whether they have a localized recording of that line of dialogue #jira UETOOL-794 Change 2975763 on 2016/05/12 by Jamie.Dale We no longer warn if asked to check out a UNC path when running the GatherText commandlets #jira UE-25833 Change 2975766 on 2016/05/12 by Jamie.Dale Resolved some loc key conflicts #jira UE-25833 Change 2975774 on 2016/05/12 by Jamie.Dale PO files now only contain a single entry in the case of a native translation being exported They used to contain the original entry, as well as an entry for the native translation, however the original entry would never be used. This change also cleans up some directory walking code that was looking for archive files, and replaces it with code to load the specific archive file. Change 2975776 on 2016/05/12 by Jamie.Dale Downgraded a PO file import warning that isn't really an issue #jira UE-25833 Change 2976675 on 2016/05/13 by Jamie.Dale Fixed some more fallout from changes to use the window position when changing the game viewport mode - FSceneViewport::ResizeFrame: - Fixed the HMD monitor info setting the wrong variables. - Fixed SetWindowMode and ResizeViewport potentially being passed two different modes. - We now only move the window if we need to (this avoids issues with WindowedFullscreen window positioning). - FWindowsWindow::MoveWindowTo: - Now treats the screen space position it's given as relative to the top-left of the window, rather than the top-left of the windows' client area. - FWindowsApplication: - WM_MOVE was passing a screen space position relative to the top-left of the windows' client area, rather than its window area like Slate expected. #jira UE-30276 #jira UE-30677 Change 2976804 on 2016/05/13 by Jamie.Dale Slight optimization to FICUInternationalization::FindOrMakeCulture to avoid hitting the filesystem until we know we need to Change 2976967 on 2016/05/13 by Alexis.Matte #jira UE-30687 Cannot import a skeletal mesh scale to zero Change 2977042 on 2016/05/13 by Alexis.Matte #jira UE-29952 log a warning if fbx exceed the maximum number of LOD. #2326 Github PR #code review matt.kuhlenschmidt Change 2977074 on 2016/05/13 by Jamie.Dale Follow up to CL# 2976804 to avoid a potential change in behavior Change 2977076 on 2016/05/13 by Jamie.Dale Some tidy up and optimization to SCulturePicker Change 2977327 on 2016/05/13 by Alex.Delesky Now deleting the Redirector package on Redirector Fix Up rather than simply removing it from the Content Browser. #jira UE-30423 Change 2977499 on 2016/05/13 by Alexis.Matte #jira UE-29475 Enable UStruct child property to be favorite Change 2978415 on 2016/05/16 by Jamie.Dale We now pre-load all the culture data when starting the editor to avoid a UI hitch later Change 2978517 on 2016/05/16 by Alex.Delesky #jira UE-29406 Creating a static mesh from a geometry brush and then attempting to reimport the mesh will no longer crash the editor. Change 2978518 on 2016/05/16 by Alex.Delesky #jira UE-28210 The FBX Importer no longer runs cleanup upon failing to import an FBX file and won't crash the engine the next time an FBX is imported within the same editor session. Change 2978556 on 2016/05/16 by Alexis.Matte Fbx tests automation #jira UE-29635 Change 2978797 on 2016/05/16 by Alexis.Matte #jira UE-30774 - prevent baking the pivot if we transform the vertex with the absolute transform. - Also make sure we set the identity for the Max puivot in case we dont bake the pivot and we dont transform the vertex with the absolute transform. #code review matt.kuhlenschmidt Change 2978965 on 2016/05/16 by Alexis.Matte FBX importer, fix the socket rotation. #jira UE-30094 Change 2980613 on 2016/05/17 by Jamie.Dale Moved the XLOC UAT localization provider to be publicly accessible Change 2980614 on 2016/05/17 by Jamie.Dale Reference update for project move Change 2980633 on 2016/05/17 by Jamie.Dale Made the culture mapping used between XLOC and UE4 configurable on a per-project basis You can now override GetEpicCultureToXLocLanguageId in your custom localization provider in order to change the default mappings. Change 2980836 on 2016/05/17 by Jamie.Dale Added -LocalizationSteps flag to allow you to only run a subset of the UAT "Localise" command You can pass any of the following steps: Download, Gather, Import, Export, Compile, GenerateReports, Upload Change 2982700 on 2016/05/18 by Jamie.Dale Fixed the loc package gather potentially adding the same source location multiple times Change 2983906 on 2016/05/19 by Jamie.Dale Slight cleanup of the way we register localization gatherer callbacks Change 2984356 on 2016/05/19 by Chris.Wood Removed temporary analytics API change needed for earlier hot fix [UE-31005] - Undo temp Hardware Survey API change from 4.10 - CL 2782817 Change 2986679 on 2016/05/23 by Alex.Delesky #jira UE-24747 - Importing FBX files that contain meshes that do not have non-degenerate triangles will no longer crash the editor on import, and will warn the user that the meshes are bad. Change 2986798 on 2016/05/23 by Alex.Delesky #jira UE-31136 - Chord Input fields will no longer display the blinking edit cursor if they do not have focus. Change 2987106 on 2016/05/23 by Alexis.Matte Fbx importer, fail import must not create a package in the content browser #jira UE-31154 Change 2987563 on 2016/05/23 by Alex.Delesky #jira UE-30988 - Changed the default window mode when launching a game from the .uproject file to Windowed Change 2987564 on 2016/05/23 by Alex.Delesky #jira UE-28856 - Fixed a crash that could potentially occur when starting up PIE while dragging objects like widgets in the editor. Change 2988321 on 2016/05/24 by Jamie.Dale Added a way to backup and restore the selection state of a level (its actors and components) in a way that can be reapplied even if the level is reloaded Change 2988708 on 2016/05/24 by Jamie.Dale Fix for crash when missing the fallback/last resort font Change 2988782 on 2016/05/24 by Jamie.Dale Added the ability to version each localized string individually when loaded into the localization manager The single 32-bit global history has now been replaced with two 16-bit histories. One is global, and is updated whenever the culture is changed (or a LocRes file is loaded), and the other is local to each string, and is updated if the display string is changed outside of a culture update (to handle cases where the display string is changed, but the key is preserved). Changing the global history will reset all local histories. Because of the change from an int32 to a uint16, 0, rather than INDEX_NONE, is now considered the "unset" value for a history. Change 2988856 on 2016/05/24 by Jamie.Dale Added a way to get the package(s) of the object(s) being edited by a property panel Typically the package is just the outermost of the object being edited, however there are some cases where this may not be the case: - UMG widgets edit a transient copy of the real data, so we use the SetObjectPackageOverrides to override the package these objects should use to be the real asset package. - Structs (UDS, Data Table, etc) don't have a way to get to their package, so you have to specify it on their FStructOnScope instance (see FStructOnScope::GetPackage and FStructOnScope::SetPackage). This has been hooked up for the UDS and Data Table editors. Change 2988955 on 2016/05/24 by Alex.Delesky #jira UE-30645 - Adding in support for splash images to support .png and .jpg files. In general, this adds multi-extension support for external image references and external image picker modules. Git Request #2376 Change 2989418 on 2016/05/25 by Jamie.Dale Added a way to count text references within a package that match the given search criteria This can be used to detect whether a localization ID is unique within its package. The following search modes are available: - MatchId: Detect a reference if it matches the given ID (ignoring the source text) - MatchSource: Detect a reference if it matches the given ID and source string - MismatchSource: Detect a reference if it matches the given ID but has a different source string Change 2989436 on 2016/05/25 by Jamie.Dale Added "root-level" meta-data (meta-data associated with the package rather than an object within it) Change 2989471 on 2016/05/25 by Alexis.Matte Fbx scene importer, fix naming clash when creating package we now also look in memory to find existing package not just on disk Change 2989639 on 2016/05/25 by Jamie.Dale Added static version of FName::IsValidXName This allows you to verify name-like strings without having to convert them to an FName (and thus add them to the name table) Change 2989716 on 2016/05/25 by Alex.Delesky #jira UE-30828 - The Standalone Session Frontend will now render the names of automation tests correctly instead of as solid white blocks. Change 2990100 on 2016/05/25 by Alexis.Matte Fix crash when reimporting a mesh that originaly exceed the maximum number of LOD #jira UE-30907 Change 2991442 on 2016/05/26 by Bob.Tellez #UE4 Fix components in world not rendering when saved without a physics scene. Change 2991736 on 2016/05/26 by Bob.Tellez #UE4 Fix duplicated worlds not being initialized when inactive. Re-enabled duplication of worlds in the content browser. Change 2991942 on 2016/05/26 by Alex.Delesky #jira UE-31012 - Setting a Decimal Grid Interval value to 0 and using it will no longer crash the editor or cause an editor crash on startup. Change 2991994 on 2016/05/26 by Alex.Delesky #jira UE-31177 - Attempting to export an entire level as an object file and choosing to export all materials as images will no longer crash the editor. Change 2994037 on 2016/05/30 by Alexis.Matte Add Fbx Automation Tests - static mesh import reimport (sections and materials) - skeletal mesh import and reimport (sections and materials also bone position) - static/skeletal mesh LODs (import, add, reimport) - rigid mesh (import, reimport) Change 2994253 on 2016/05/31 by Alexis.Matte Mikkt crash when computing the normals if there is more vertex then the number of wedge #jira UE-29143 Change 2994260 on 2016/05/31 by Alexis.Matte Make sure we cannot modify fbx test plan when json file is read only Change 2994431 on 2016/05/31 by Alex.Delesky #jira UE-21900 - The scale widget should now render all axes when using an orthographic camera. Change 2994432 on 2016/05/31 by Alex.Delesky #jira UE-31328 - New objects dragged into the scene will now comply with the Surface Snapping option in the viewport, and will not use the Surface Offset if snapping is disabled. Change 2994537 on 2016/05/31 by Richard.TalbotWatkin Fixed potential crash in the Mesh Paint tool when non-transactable actors are in the SelectedActors list following a Redo. #jira UE-31172 - Crash related to Vertex Painting - MeshPaint!CastChecked<AActor,UObject>() Change 2994983 on 2016/05/31 by Richard.TalbotWatkin Added some guard code to protect against a crash when editing geometry. Repro currently unknown, ensure was added in order to try to get more information. #jira UE-30820 - UT EDITOR: CRASH: Crash in Public Release CL#2973693 Change 2995022 on 2016/05/31 by Jamie.Dale PR #2428: Added missing END_OPTIMIZATION macro to SOutputLog (Contributed by MatzeOGH) Change 2995027 on 2016/05/31 by Jamie.Dale PR #2409: fixed a small typo in GraphEditor.h (Contributed by MatzeOGH) Change 2995963 on 2016/06/01 by Alex.Delesky #jira UE-31317 - The transform gizmo will no longer block the placement of a material onto a mesh. Change 2997002 on 2016/06/01 by Cody.Albert Fix to ensure ActiveTopLevelWindow is properly set after a window is destroyed #jira UE-31448 Change 2998013 on 2016/06/02 by Alexis.Matte Prevent static mesh materials array to grow when using the reset button in the staticmesh editor. #jira UE-12931 Change 2998370 on 2016/06/02 by Alexis.Matte Fbx Automation, add some import LOD test in case the options are not ok Change 2999709 on 2016/06/03 by Jamie.Dale Fixed some issues with gathering text from BP bytecode Bytecode in Blueprints is very volatile, and can only be safely gathered after it's been compiled (which is not guaranteed to have happened by the time we save the package). This change avoids caching any assets that contain scripts (non-data-only Blueprints), and instead will always load them to perform a gather (which will ensure the Blueprint bytecode is up-to-date due to compile-on-load). Change 2999755 on 2016/06/03 by Richard.TalbotWatkin Fixes to Spline Mesh collision generation. - Fixed a serious issue with DDC ID generation, in that the static mesh wasn't forming a part of the key, hence any two spline meshes with identical properties but different meshes would yield the same cache entry. - Fixed how different collision boxes are transformed when rebuilding physics meshes. Convex collision transforms are now correctly taken into account, and spherical and capsule collision now gets correctly translated when a scale is applied to the start or end of the spline mesh. - Optimized physics rebuilding. A new BodySetup object is now only created when needed, otherwise it is reused. #jira UE-31361 - Splines handle box collision and collision from other shapes differently Change 2999973 on 2016/06/03 by Jamie.Dale We now skip bulk data when detecting text references #jira UE-31596 Change 3000159 on 2016/06/03 by Alex.Delesky #jira UE-30244 - Added a safeguard against a potential crash when editing BSP brushes before placing another BSP brush into the level. Change 3001814 on 2016/06/06 by Alexis.Matte Make sure the staticmesh Materials list dont grow when we reimport or override a LOD other then the base mesh. Add a fbx test to make sure the problem is flag by automation test #jira UE-1394 Change 3001820 on 2016/06/06 by Alex.Delesky #jira UE-19079 - Widget Blueprints should no longer crash when dragging widgets from one blueprint to a second and then compiling the second blueprint. Change 3001915 on 2016/06/06 by Alexis.Matte Make sure we check attribute type before checking attribute unique ID in case of unique id clash. #jira UE-31214 Change 3002026 on 2016/06/06 by Alexis.Matte Importing morph target should not import textures like materials since the base mesh already import thoses. UDN Question: https://udn.unrealengine.com/questions/293973/does-importing-an-fbx-with-morph-targets-cause-a-m.html Change 3002623 on 2016/06/06 by Jamie.Dale Fixing more loc conflicts Change 3002883 on 2016/06/06 by Jamie.Dale Adding retry when dealing with OneSky This is attempting to compensate for some timeouts with OneSky, which were also noticed when testing UE-31413 Change 3003004 on 2016/06/06 by Trung.Le #jira UE-13101 - Make "Description" field for a BluePrint Function multiline Change3003859on 2016/06/07 by Alexis.Matte #jira UE-30436 Refresh the property editor when a array element is added, remove, insert, delete and the property is favorite Change 3004132 on 2016/06/07 by Jamie.Dale Fixed a hash conflict that could occur when both the case-sensitive and case-insensitive FName hashes were identical This resulted in the case-preserving FName being added to the head of the linked list for the bucket, which caused any subsequent name lookups to return that name index for the comparison index (since it matched an insensitive string comparison), rather than the name index of the first case-variant of that name that was added to the bucket. This change has new entries be inserted at the tail of the list, which ensures that enumeration for a case-insensitive name will always find the same entry in the bucket (the first one that was ever added) and will continue to compare correctly. Change 3004286 on 2016/06/07 by Jamie.Dale Ensured that assignments that publish new names to the bucket are atomic Change 3004310 on 2016/06/07 by Jamie.Dale Ensured FName internal hashes are returned as uint16 Change 3004381 on 2016/06/07 by Jamie.Dale FAsyncPackage now creates the meta-data before processing the remaining exports This matches the behavior of FLinkerLoad::LoadAllObjects, as other objects may depend on the meta-data being loaded before them. Change 3004765 on 2016/06/07 by Alex.Delesky #jira UE-31498 - Material thumbnails will now render the full sphere rather than an extreme close-up of the material. Change 3005754 on 2016/06/08 by Trung.Le Allow whitespace for meta class names #jira UE-31668 Change 3005755 on 2016/06/08 by Stephan.Jiang UMGSequencePlayer implements GetPlaybackContext() and return UserWidget->GetWorld() if it's valid #jira UE-31299 Change 3006512 on 2016/06/08 by Alex.Delesky #jira UE-31572 - The "All Classes" tab in the Modes panel will now refresh when a placeable asset is created, renamed, or deleted without needed to navigate away from the tab first. Change 3006760 on 2016/06/08 by Jamie.Dale Added support for stable localization keys This feature adds support for preserving the existing key of an FText property when editing the source string, providing that it is the only reference to that string within the package. A side effect of this is that you're now able to specify custom keys for FText properties since we can now verify that the custom key won't cause an identity conflict. In order to limit the search domain for uniqueness to a single package, we've added the concept of a "localization namespace" to packages (stored in the meta-data). Each package is given a unique namespace, which is appended to the user-defined namespace of the text when it is modified, saved, or duplicated. This package namespace ensures that the same user-defined namespace and key may be used in different packages without causing an identity conflict. In order to access the package namespace within the Core code that hosts FText (which doesn't know about UPackage), FArchive now provides a GetLocalizationNamespace function to access the package namespace within the Core code, and a SetLocalizationNamespace function for CoreUObject and Engine code to pass down the package namespace from their packages. If you have an archive that handles duplicating objects into a different package, or duplicating packages themselves, then you'll want to make sure it's setting the package namespace correctly. FObjectReader and FObjectWriter have been updated to do this, and serve as a good example. FDuplicateDataReader (used by StaticDuplicateObject), and FCopyPropertiesArchiveObjectWriter (used when compiling Blueprints) have also been updated to set the package namespace, as they both handle copying objects between packages. TextNamespaceUtil provides a suite of functions for getting at (or setting) the namespace for a package. Keys will start to stabilize naturally over time once this feature is enabled, however the StabilizeLocalizationKeys commandlet may also be used to stabilize all the keys for a game at once. Running it for a game under source control would look something like this: MyGame -run=StabilizeLocalizationKeys -IncludeGame -NativeCulture=en -EnableSCC This commandlet also updates your localization archives to use the new text identities, however you'll still need to run a localization gather and localization compile before the updated translations will be available for your game. Note: This feature is currently disabled via the USE_STABLE_LOCALIZATION_KEYS define. It will be enabled at a later date. #jira UETOOL-796 Change 3007501 on 2016/06/09 by Trung.Le #jira UE-31722 Fix MaterialFunctions crash when editing text in Libraries Category Text field. Solution: Removed PredEdit and PostEdit from IEditableTextProperty, its derived types and other code that was calling them. The new SetText method already calls NotifyPreChange and NotifyPostChange to properly create/destroy ScopedTransaction. Change 3007524 on 2016/06/09 by Jamie.Dale Added some additional checks to avoid re-keying text when duplicating for PIE Change 3007564 on 2016/06/09 by Jamie.Dale PR #2401: DataTable import/export improvements (Contributed by bozaro) Change 3007653 on 2016/06/09 by Jamie.Dale PR #2459: Generate JSON for nested structs in DataTable rows (Contributed by jorgenpt) Change 3008019 on 2016/06/09 by Jamie.Dale Updated structs to export as JSON when displaying them in the Data Table editor This produces much cleaner results than using the text export method (which will use the internal names for user defined structs). This also cleans up the FDataTableExporterCSV and FDataTableExporterJSON APIs so that you don't need to pass in a UDataTable if you're not going to use it. #jira UE-29958 Change 3008052 on 2016/06/09 by Jamie.Dale Fixed bug importing an array inside a JSON Data Table This was noticed when testing a GitHub PR, but the JSON importer for a Data Table was appending the new data to the array rather than replacing it. It now clears the array prior to importing. Change 3008875 on 2016/06/10 by Jamie.Dale PR #2406: Git plugin: Fix for Git diff not working in UE 4.12 (and master) (Contributed by SRombauts) Change 3008879 on 2016/06/10 by Jamie.Dale PR #2484: Git Plugin: fix the Submit To Source Control menu broken by new "migrate" support in 4.12 (and master) (Contributed by SRombauts) Change 3008990 on 2016/06/10 by Alex.Delesky #jira UE-15699 - Submitting to source control via the editor should now check for current asset status before prompting the user to submit their changes. This should prevent files that had been previously deleted from being readded to source. Change 3008991 on 2016/06/10 by Alex.Delesky #jira UE-31688 - The Output Log will now automatically anchor to the bottom of the scroll bar when the user scrolls all the way down using the mouse wheel or clicking and dragging the content window. Change 3010856 on 2016/06/13 by Alexis.Matte #jira UE-31713 Fix a serialize issue for skeletal mesh with apex cloth. Change 3011736 on 2016/06/13 by Jamie.Dale Adding missing plurals.res file This is needed to get plural form information from ICU. #jira UETOOL-875 Change 3012387 on 2016/06/14 by Richard.TalbotWatkin Disabled the Paste context menu action if the property is marked as EditConst. #jira UE-27469 - User is able to paste values into a read-only setting Change 3012971 on 2016/06/14 by Stephan.Jiang Editor Preferences->Widget Designer now have two options to toggle the visibilities of widgets created from Engine content folder and Developers folder. By default, visibility for engine content is off and developers is on #jira UE-31657 Change 3013111 on 2016/06/14 by Jamie.Dale Unified the number, percentage, and currency formatting between the ICU and Legacy text implementations Removed all the old legacy number formatting code, and removed the calls to the ICU specific number formatting. Everything is now using FastDecimalFormat as this will allow some optimizations later when formatting numbers in FText::Format. Change 3015438 on 2016/06/15 by Cody.Albert Fixing ScrollBy function to calculate new scroll offset based on the current scroll offset and not the current desired scroll offset (which may not be the same during an animation) #jira UE-32082 Change 3016782 on 2016/06/16 by Richard.TalbotWatkin Corrected ConvexHull2D so that it returns an empty set of indices when passed an empty points array. Change 3016949 on 2016/06/16 by Jamie.Dale Added FastDecimalFormat overloads to write into an existing string This helps avoid an extra allocation if you already have a pre-sized string that you're writing the number to (as is the case in FText::Format). Change 3016952 on 2016/06/16 by Jamie.Dale Changed an Add for an Emplace to avoid moving a temporary Change 3016954 on 2016/06/16 by Jamie.Dale Updated some FText code to avoid creating temporary objects just to move data through a hierarchy There was some code in FText and its internal types that were using pass-by-value as a marshaller to move data through a hierarchy. This resulted in temporary objects being created and destroyed to facilitate the movement of data. This change has all the internal FText code (private FText constructors, internal text data, and internal text history) take its movable types as an r-value reference. This avoids the temporary objects, but also makes it impossible to accidentally copy a construction argument when you meant to move it (you can still copy, but the copy must be explicit). In addition to this, FText::FromString and FText::AsCultureInvariant now have two overloads, const FString& and FString&&, to avoid them creating a temporary when you're invoking a move. FText::ChangeKey now takes its parameters by const& as their data wasn't being moved further down the chain, so the by-value copy was wasteful. Change 3019021 on 2016/06/19 by Richard.TalbotWatkin When deleting a brush, ensure geometry is rebuilt before updating the details panel according to the selection change, so that the old Surface Properties don't continue to appear. #jira UE-8966 - Surface Properties of a BSP remain in the details panel after the BSP is deleted Change 3019022 on 2016/06/19 by Richard.TalbotWatkin Fixed issue where the Surface Properties category in the Details panel doesn't appear after selecting a surface on a Brush which has just been placed. #jira UE-31916 - Selecting an edge of BSP geometry then a face does not show Surface Properties while in Place mode #jira UE-31915 - Selecting BSP face does not show Surface Properties in Details Change 3019025 on 2016/06/19 by Richard.TalbotWatkin Fixed issue which was stopping 'Cancel' from correctly returning a 'Cancelled' result during P4 asynchronous ops. #jira UE-28595 - Submit to Source Control: "Checking for assets to check in..." cancel button does not cancel operation, editor becomes unresponsive Change 3020050 on 2016/06/20 by Cody.Albert Changed window centering logic to correctly work when monitor 1 isn't set to primary monitor. #jira UE-32173 Change 3021145 on 2016/06/21 by Jamie.Dale Added support for text format argument modifiers These can be used to mutate a format argument before appending it to the resultant formatted string, and are applied to the preceding argument via a pipe, eg) "{Arg}|plural(one=is,other=are)". We provide a few of these by default: - |plural(key=val,...) - |ordinal(key=val,...) Provides support for cardinal and ordinal plural forms, where key may be any of "one", "two", "few", "many", or "other", and val may be any optionally quoted string. - |gender(masculine,feminine,[neuter]) Provides support for gender forms, where the 0th item is the masculine version, the 1st item is the feminine version, and the 2nd item is an optional neuter version. The values may be any optionally quoted string. - |hpp(consonant,vowel) Provides support for Hangul post-positions, where the 0th item is the consonant suffix, and the 1st item is the verb suffix. The values may be any optionally quoted string. Major changes: - Exposed the ICU plural form handling via FCulture::GetPluralForm. - Updated the FText formatting code to use an expression evaluator (to support the more complex expressions needed for the argument modifiers). - Added FTextFormat to store a pre-compiled format expression. Re-using one of these if you're performing a lot of formats with the same FText will increase your performance (as around half of the FText::Format cost can be compilation, via an implicit construction of FTextFormat). - Updated the FText::Format(...) family of functions to take their format string as FTextFormat, and take their arguments as FFormatArgumentValue. This allows us access to the real numeric types within the format code, but doesn't break the existing API as these types are implicitly constructible from the old parameters (FText). - Converted text history to store their format string as an FTextFormat in-case they need to perform a re-format (this is still saved as an FText). Breaking changes: - The rules for the escape token have been simplified, and there is an incredibly unlikely chance that this may affect some text: - The ` character will now only escape a valid character (producing only the escaped character in the final string), or it will be ignored and inserted as a literal character, eg) "`{F" -> "{F", and "`F" -> "`F". - Previously it would also remove the escape character when it followed { or }, eg) "{`" -> "{" and "}`" -> "}", rather than "{`" and "}`" like you might expect. It would also have previously removed a ` at the end of a string due to a parser bug. Change 3021156 on 2016/06/21 by Jamie.Dale Updated LinuxToolChain to use the same output delegate for all of its actions when cross-compiling This avoids the compile and link actions being split into different batches. Change 3021280 on 2016/06/21 by Richard.TalbotWatkin Fixed bug in parsing LOD in UStaticMeshComponent::ImportCustomProperties (thanks to Aurelien Cordonnier). #jira UE-31937 - UDN code submission for UStaticMeshComponent::ImportCustomProperties parsing bug Change 3022949 on 2016/06/22 by Alex.Delesky #jira UE-31944 - Upgrading Subversion binaries to version 1.9.4. Change 3023092 on 2016/06/22 by Jamie.Dale Downgraded some checks to ensures and added an early out #jira UE-32009 Change 3023154 on 2016/06/22 by Jamie.Dale Ported over CL# 3018771 to the UE automation This fixes an issue where a downloaded PO file smaller than the one already on disk leaving a mix of both files on disk (rather than the existing file on disk being truncated). Change 3023579 on 2016/06/22 by Jamie.Dale Expanded the Blueprint FormatText node to support numeric and gender types These are needed to correctly support the new plural and gender forms that can be used in format strings, as these require actual numeric/enum data to be passed into the format arguments, rather than pre-formatted text. Major changes: - The FormatText node for Blueprints now uses PC_Wildcard as its pin type for format arguments instead of PC_Text. - Any existing literal text argument data in the pin is hoisted out into a "Make Literal Text" node which is then connected to the pin. - FFormatArgumentData has been updated to be variant on the data needed by Blueprints. It's now a less comprehensive and non-unioned version of FFormatArgumentValue. - The version of FText::Format taking FFormatArgumentData has been deprecated as its usage was internal to Blueprints and we have much better ways to format text in C++. Any existing C++ using that (of which we have none internally) should be updated to use FFormatArgumentValue instead. Change 3023915 on 2016/06/22 by Jamie.Dale Cleaned up some of the UK2Node_FormatText expansion code to avoid unchecked literals Change 3024813 on 2016/06/23 by Jamie.Dale Renamed FContext to FManifestContext to better reflect its purpose and avoid naming conflicts with other code Change 3024852 on 2016/06/23 by Nick.Darnell FBX - Updating automation tests with the changes to chunk and chunk index removal and them being merged with sections. Change 3024994 on 2016/06/23 by Nick.Darnell UMG - Removing the DesignerWidgetTree, instead going to directly inject the widget tree into the partially constructed UUserWidget during design time, when refreshing the preview. This avoids doing something a little dangerous and sketchy like updating the living class instance with a new designer tree that all new instances will begin biasing using. Also making the preview widget explictly non-transactional as there's no reason to track changes to the preview, all the changes that need to be tracked should be on the template widget. This should fix the crash in the widget designer when you Undo just after compiling the widget blueprint. #jira UE-31155 Change 3025194 on 2016/06/23 by Alex.Delesky #jira UE-31155 - Compilation error fix. Change 3025255 on 2016/06/23 by Alex.Delesky #jira UE-21900 - Redoing changes done in CL 2994431 since it got stomped. Reinstates the grabber handles and ensures consistent scaling on the scale widget in orthographic viewports. Change 3025460 on 2016/06/23 by Cody.Albert Fixed issue where widget components would misalign when aspect ratio was being constrained #jira UE-29637 Change 3025508 on 2016/06/23 by Cody.Albert Adding support for adjusting animation playback speed #jira UE-32222 Change 3026444 on 2016/06/24 by Jamie.Dale Fixed crash caused by bad access of shared this when closing an active IME context This was only needed to get the owner window, which we now cache when the IME context is created. #jira UE-32240 Change 3028358 on 2016/06/27 by Jamie.Dale Fixed IMEs not working due to no window being cached #jira UE-32240 Change 3028464 on 2016/06/27 by Alex.Delesky #jira UE-31873 - A single "Files need check-out" notification will now be shown instead of multiple notifications if multiple files need to be checked out, and updated as more files need to be checked out. Change 3028524 on 2016/06/27 by Chris.Wood Switched off uploads to legacy Crash Report Receiver. [UE-31252] - Switch off deprecated CRR upload in Crash Report Client Also added CRC version string, added to crash context from CRC config Change 3028840 on 2016/06/27 by Alexis.Matte #jira UE-32306 replace material bad name character by an underscore when doing a scen import. Change 3028924 on 2016/06/27 by Alexis.Matte #jira UE-32125 Make sure we can add a plan when a fbx file is drop in the fbx automation test folder Change 3029044 on 2016/06/27 by Alex.Delesky #jira UE-31944 - Updating SVN binaries for Mac to 1.9.4 Change 3029276 on 2016/06/27 by Alex.Delesky #jira UE-31531 - A user can now select the base class when creating a new physical material. PR #2462: added dialog, which enables picking base class for asset (Contributed by iniside) Change3029459on 2016/06/27 by Alexis.Matte #jira UE-32354 Make sure we set all blueprint component to the correct mobility set in the scene import options. Change 3030577 on 2016/06/28 by Nick.Darnell PR #2531: Git plugin: fix wrong status icons (Contributed by SRombauts) Change 3030587 on 2016/06/28 by Alexis.Matte #jira UE-32251 add missing body setup variables when restoring the body setup value after a re-import of a staticmesh Change 3030946 on 2016/06/28 by Alexis.Matte #jira UE-32515 prevent crash when re-import staticmesh userdata Change 3031115 on 2016/06/28 by Jamie.Dale The DDC builder now gives the shader compile worker a chance to catch up when it pauses to run a GC pass This prevents an issue where the shader backlog could cause massive amounts of memory to be consumed. Change3031146on 2016/06/28 by Jamie.Dale Fixed errors when building with USE_STABLE_LOCALIZATION_KEYS enabled caused by UEdGraphPin no longer being a UObject Change 3031357 on 2016/06/28 by Nick.Darnell PR #2431: Add plugin support to the editor class wizard. (Contributed by Koderz) Change 3031515 on 2016/06/28 by Jamie.Dale Fixed game targets not being able to depend on other game targets Change 3031520 on 2016/06/28 by Jamie.Dale Localization compilation now specifies an ArchiveName to use Change 3031671 on 2016/06/28 by Nick.Darnell Editor - Checking to see if a weak variable is valid before using it in the editor build window. Change 3032013 on 2016/06/28 by Matt.Kuhlenschmidt Added ability to invert the Y axis in editor viewports for mouse look and orbit Change 3032495 on 2016/06/29 by Jamie.Dale Fixed some measuring issues with bi-directional text within a right-flowed document There were three main issues: 1) Measuring blocks was measuring visual glyphs rather than logical glyphs (this caused bad measures/wrapping and overlapped rendering). 2) The text layout would consider blocks visually contiguous without making sure the block flow direction matched the line flow direction (this caused bad highlights). 3) The text layout would fail to compensate for a non-contiguous block that had a flow direction different to the line flow direction (it was hard-coded for RTL in LTR, so broke for LTR in RTL - this caused bad highlights). #jira UE-32526 Change 3032533 on 2016/06/29 by Nick.Darnell UMG - The widget component now extends from UMeshComponent, it can have a custom material applied to it, in order to achieve cooler effects - like ignoring the depth buffer. Users who use this option are encouraged to start with the widget components default material and work from there. The widget component now offers the ability to automatically size the render target to be the desired size of the widget - note that this can go real bad if your widget wants to be really big. Change3032855on 2016/06/29 by Alexis.Matte #jira UE-32508 Remove the cachewindow from the FTextInputMethodContext constructor since it will be cache only when the IME is activated #test please re-test also UE-32240 Change 3033145 on 2016/06/29 by Alex.Delesky #jira UE-32239 - The PropertyEditorModule will no longer cause a crash on editor shutdown if a SDetailsView widget tries to force refresh itself when the Slate application is no longer initialized. Change 3033147 on 2016/06/29 by Alex.Delesky #jira UE-32326 - Clicking on the "Install {compiler}" button when trying to create a new code class or code project will now not crash the engine if it fails to open the installation file for write, nor will it create multiple notifications if the button is pressed repeatedly. This also addresses a potential issue with static initialization order when it comes to adding TickableEditorObjects to its corresponding array, since it was wholly possible for a statically initialized TickableEditorObject to initialize itself and add itself to the tickable objects arra before the tickable objects array was initialized, causing that object to not get ticked at runtime and causing a crash when the editor was closed. Change 3033162 on 2016/06/29 by Alex.Delesky #jira UE-31827 - Undo/redo now works in the Material function editor. Change 3033391 on 2016/06/29 by Matt.Kuhlenschmidt Fix post process settings blendable picker not being readable in the details panel Change 3033498 on 2016/06/29 by Matt.Kuhlenschmidt Fixed huge number of redundant calls to CanEditChange and DiffersFromDefault that were causing massive performance loss when thousands of objects are selected. CanEditChange and DiffersFromDefault are now cached each time a property value changes. Fixed redundant calls for getting visualizers for each selected object. This is now cached on selection Change 3033504 on 2016/06/29 by Matt.Kuhlenschmidt Fix Mass customization on the body instance not working with undo/redo or reset to default Change 3034357 on 2016/06/30 by Alex.Delesky #jira UE-31184 - Renamed the multiple collision components in the cascade particle system to more accurately reflect what they represent. Change 3035915 on 2016/07/01 by Richard.TalbotWatkin Fix to SListPanel so that those with horizontal arrangement (i.e. from STileView) use the number of desired items instead of the number of actual items in order to calculate the desired size of the geometry. This fixes the case where an STileView is contained within an SScrollBox. #jira UE-32195 - STileView no longer works correctly when placed inside of a SScrollBox Change 3035951 on 2016/07/01 by Richard.TalbotWatkin Fixed issue when importing a brush, so that the brush is always validated (relinked), whether it be a static or dynamic brush. This is because the process of rebuilding a dynamic brush sets the link indices to signify FBspSurf indices from the UModel instead of FPoly indices (the FPoly::iLink member is overloaded in its meaning). Always forcing a relink correctly sets the linked list of coplanars. #jira UE-32087 - Crash occurs when creating Static Mesh from Trigger Volume Change 3036991 on 2016/07/04 by Alexis.Matte #jira UETOOL-901 Scene importer now support the rigid mesh animation Change 3037037 on 2016/07/04 by Jamie.Dale Fixed regression in editable text box alignment Text was no longer vertically aligned center since SEditableText was converted to use a text layout. This vertical alignment is now handled by the outer SEditableTextBox instead. Change 3037057 on 2016/07/04 by Richard.TalbotWatkin Fixed screenshots when running automation tests so that they are saved locally when a FAutomationWorkerScreenMessage is received. #jira UE-29815 - In-game screenshot isn't working under certain circumstances Change 3037082 on 2016/07/04 by Chris.Wood Added detection of asserts and passing assert flag and crash type string to crash reports. [UE-30592] - Crash Reporter should determine crash type on client and pass string to server Reviewe by Steve with reservations about the static variable for setting asserted state. While not thread-aware, this is probably accurate enough for the purpose of crash reporting, certainly for now. I'm submitting it like this because the work required to add fully thread-aware fix is not necessary at this point. Change 3037095 on 2016/07/04 by Alexis.Matte Fix the bone name when duplicating a socket. Change 3037453 on 2016/07/05 by Stephan.Jiang Adding ability to animate the root wigdet #2 FHierarchyRoot adds the preview widget instead of CDO to selectedobjects in widgetblueprint the properties are then migrated back to the CDO #UE 31810 Change 3037487 on 2016/07/05 by Jamie.Dale Fixed crash caused by stale BP pointer #jira UE-32325 Change 3037488 on 2016/07/05 by Jamie.Dale Fixed a crash that could occur when a class and a folder had the same name Change 3037526 on 2016/07/05 by Jamie.Dale Speculative fix for a potential race condition when shutting down the editor while a "launch" was in progress The launch-thread could potentially queue up a request after the game-thread had requested it cancel, and cleared out any queued tasks. This change has the game-thread wait for the launch-thread to acknowledge its cancellation before continuing with editor shutdown. #jira UE-17688 Change 3037557 on 2016/07/05 by Alex.Delesky #jira UE-32424 - Added a safeguard to ensure that renaming a world that was duplicated from another world would not crash the editor if both worlds' lightmaps and shadowmaps were still active in memory, due to the editor attempting to rename identical textures from different packages to the same location. The actual fix to this issue was performed in an earlier CL, but this should prevent the editor from crashing if the issue returns. Change 3037558 on 2016/07/05 by Alex.Delesky #jira UE-32285 - Importing assets to the Content Browser via drag and drop operations are no longer permitted while the UI file picker dialog is opened. Change 3037559 on 2016/07/05 by Alex.Delesky #jira UE-32075 - The user can no longer attempt to import non-FBX and non-OBJ files when importing into a level. Change 3037593 on 2016/07/05 by Stephan.Jiang GitHub #2549: Add function for setting the playback rate of UMG animations original code shelved in CL 3033449 #UE-32653 Change 3037605 on 2016/07/05 by Jamie.Dale Fixed infinite recursion that could happen when gather loc from an object with a custom callback #jira UE-32670 Change 3037649 on 2016/07/05 by Nick.Darnell PR #2538: [WidgetBlueprintLibrary] GetAllWidgetsOfClass, Added META ~ DeterminesOutputType, DynamicOutputParam, removes the need for extra cast, Rama (Contributed by EverNewJoy) Change 3037652 on 2016/07/05 by Nick.Darnell Clean - Removing commented out code. Change 3037658 on 2016/07/05 by Matt.Kuhlenschmidt Fix initial hitch when dragging around in a color picker opened from a material expression node. Change 3037679 on 2016/07/05 by Nick.Darnell Engine - Texture2D no longer forces the MIP level to 0 for TextureGroup_UI textures. Change 3037757 on 2016/07/05 by Nick.Darnell PR #2447: WebBrowser widget: Added GetUrl method and OnUrlChanged property (Contributed by nelbok) Change 3037840 on 2016/07/05 by Nick.Darnell UMG - Now allowing for spirtes to be used just like textures and materials on UMG widgets anywhere that takes a brush, can now also take a Sprite. There is now a ISlateTextureAtlasInterface interface that any UObject may now implement if it wishes to integrate with UMG to provide its atlas data in a form Slate can understand. Change 3037924 on 2016/07/05 by Jamie.Dale Re-ordered variable initialization to appease a warning on Mac Change 3037981 on 2016/07/05 by Jamie.Dale Fixed crash where FColorStructCustomization could call SetPerObjectValues with an empty array #jira UE-32639 Change 3038075 on 2016/07/05 by Cody.Albert Removed misleading error message in HandleCECommand #jira 28007 Change 3038231 on 2016/07/05 by Alexis.Matte #jira UE-30694 We set the section collision only if there is an imported collision or a generated one. If there is no collision we do not set the collision flag. Change 3038275 on 2016/07/05 by Alex.Delesky #jira UE-32689 - "Game Gets Mouse Control" will now override the Capture Mouse on Launch setting when launching the game from within a Level Viewport (i.e., within the editor window itself). Change 3039310 on 2016/07/06 by Trung.Le #jira UE-25005 Change PIE Key Bindings - Removed Shift+F1 and Esc from BaseInput.ini - Created new customizable key binding for + Shift+F1: same functionality. + Esc: now will pause the play session and bring back the mouse cursor. Clicking the mouse on the viewport should resume play session. + Shift+Esc: now will stop the play session Change 3039458 on 2016/07/06 by Trung.Le Removed unused code in StaticMeshLight.cpp Change 3039827 on 2016/07/06 by Frank.Fella FString - Fix divide overload path concatenation for empty paths since there are several places in the engine that expect using that doing { path / "" } will append a / onto path. #jira UE-31959 Change 3041094 on 2016/07/07 by Nick.Darnell WebBrowser - Fixing an issue where the web browser widget plugin wasn't loading soon enough to be properly loaded in time if it was referenced by game nessesary content thatloads in the Default stage of the pipeline, so moving it to PreDefault. #jira UE-32694 Change 3041110 on 2016/07/07 by Matt.Kuhlenschmidt Fix visualizers on blueprint actors not working when the internal components are trashed and replaced Change 3041302 on 2016/07/07 by Chris.Wood Increased buffer size for crash uploads. [UE-32151] - High number of crashes read from S3 by Crash Report Process are failing to unpack Trivial change in dev branch - no code review Change3041969on 2016/07/07 by Nick.Darnell UMG - Input Key Selector now no longer adds a bogus Selected Key property to the details panel. Change 3041971 on 2016/07/07 by Nick.Darnell UMG - Not using separate settings for the Engine/Developer folders visible in the UMG palette, now just using the same setting that powers the content browser. Change 3042612 on 2016/07/08 by Trung.Le #jira UE-25005, set Shift+Esc defaults to toggle play/pause and Esc remains defaults to quit Change 3042732 on 2016/07/08 by mitchell.wilson Adding test content for UMG Paper 2d Atlas test Change 3042780 on 2016/07/08 by mitchell.wilson Updating UMG_Paper2d test content for UMG Paper 2d Atlas testing Change 3042870 on 2016/07/08 by mitchell.wilson Renaming UMG_Paper2d to UMG_Sprite Change 3044104 on 2016/07/10 by Nick.Darnell PR #2104: Improved widget input support (Contributed by projectgheist) Change 3044107 on 2016/07/10 by Nick.Darnell Slate - Fixing the slider handle rendering to no longer run off the edge and get cut off. #jira UE-25750 Change 3044377 on 2016/07/11 by Chris.Wood Add Slack messaging module - Epic Friday Change 3044536 on 2016/07/11 by Alex.Delesky #jira UE-7293 - Mouse locking to viewport is now determined off an enum instead of a boolean, to allow for more flexibility when upgrading with new features. Change 3044922 on 2016/07/11 by Nick.Darnell Slate/UMG - Working on better support for VR interactions with Slate widgets. This change fixes a lot of issues with the way interaction works with slate widgets rendered in the virtual world. Breakages, direct mouse interaction with widgets in the virtual world is no longer supported. Those kinds of interactions must all use the WidgetInteractionComponent now, which by default works similar to the lasers in VREditor for interaction. However - you can disable automatic hittesting, and instead provide a custom hitresult instead if you want to use screen tracing and act like you're just a mouse cursor that is supported. Menu anchors now properly function inside of widgets in the virtual world. Performance improvements - the viewport no longer arranges all 3d widgets every frame. Additionally, Widget Components now support a whole bunch of methods for reducing how often they redraw to help control performance, they also support manual refresh. This automatically works in tandem with the widget interaction component to request refresh whenever the widget interaction component is interacting with the widget, thus giving you a simple way to only redraw widgets that the user is hovering on top of. Unrelated - this change also fixes Stop navigation commands not working with Next/Prev navigation - Wrap is still unsupported. Change 3045157 on 2016/07/11 by Nick.Darnell Slate - Always consume the bottom face button of the analog cursor, even if it's a repeat. Change 3045355 on 2016/07/11 by Matt.Kuhlenschmidt Added logging for unreproducible top 10 crash in matinee when a track ends up not being able to add a keyframe Change 3045358 on 2016/07/11 by Alex.Delesky #jira UE-31179 - The editor should now log additional information and hit an assertion if the editor tries to construct FObjectOrAssetData using invalid data. This doesn't stop the crash, but should help get some extra info when it does break. Change 3045371 on 2016/07/11 by Matt.Kuhlenschmidt Enable the widget reflector from the editor console by typing "widgetreflector" Change 3045387 on 2016/07/11 by Stephan.Jiang Stripping off 'b' in the propertyname so that "Is Enabled" is animated properly. #UE-31874 Change 3046093 on 2016/07/12 by Nick.Darnell UMG - The Slider now exposes the IsFocusable option from Slate. #jira UE-32960 Change 3046094 on 2016/07/12 by Alexis.Matte #jira UE-32807 scene re-import blueprint hierarchy kept some part of old blueprint component value. Change 3046104 on 2016/07/12 by Stephan.Jiang typo "Syc" causing the "Sync" button doesn't show Slateicon #UE-31409 Change 3046142 on 2016/07/12 by Nick.Darnell Orion - Upgrading more code to use the new input mode functions and not the deprecated ones. Change 3046165 on 2016/07/12 by Nick.Darnell UMG - Fixing a crash on the widget component if the render target is null when reapplied through widget component data. #jira UE-32844 Change 3046255 on 2016/07/12 by Nick.Darnell UT - More build warning fixes for the new Input Mode methods. Change 3046604 on 2016/07/12 by Richard.Hinckley Adding a template file and code to support creating a UInterface directly from the New C++ Class wizard. Change 3047071 on 2016/07/12 by Matt.Kuhlenschmidt Better way of summoning the widget reflector from the console Change 3047842 on 2016/07/13 by Matt.Kuhlenschmidt Mark Subdivision surface setting as advanced since it is experimental and definitely for advanced users only Change 3048754 on 2016/07/13 by Trung.Le #jira UE-32159 Automatically regain focus after user gets mouse control during PIE session so we can continue process PIE keybinding commands Change 3048756 on 2016/07/13 by Trung.Le Removed default toggle pause/play keybinding from BaseInput.ini, instead we should use the action defined in DebuggerCommands that is customizable Change 3048865 on 2016/07/13 by Trung.Le #jira UE-32159 SGlobalPlayWorldActions widget shouldn't clear out active widget pointer when it's being handled properly Change 3048892 on 2016/07/13 by Nick.Darnell UMG - Fixing a problem with the interaction component, it now does some basic intelligent ignoring of anything it's attached to - excluding widget components. So it's easier to attach it to things that might be inside of a say a player collision capsule. Also removing the 'Max Interaction Distance' from the widget component as that is no longer the arbitor of interaction distance. #jira UE-33250 Change 3049096 on 2016/07/13 by Trung.Le Wrap SGlobalPlayActions around ViewportWidget instead of making it a child of ViewportWidget. This was causing PIE to stop working when there are other UMG in game. #jira UE-33259 Change 3049177 on 2016/07/13 by Stephan.Jiang Fixing the "No Animation Selected" tag shows up after switching back from Graph to Designer. #UE-33016 Change 3049726 on 2016/07/14 by Stephan.Jiang Adding icons for terrain mirror tool #UE-20588 Change 3049957 on 2016/07/14 by Nick.Darnell Slate - Fixing a small bug in the virtual user function - was preventing getting the same virtual user multiple times if it had already been created. Adding an option to the widget component to control the focusabilty of the underlying slate window that's created to host the widget content. Adding an option to the widget interaction component to control if it should be simulating mouse input at all - use this to effectively disable hit testing, and changing hover states and the like. Change 3049994 on 2016/07/14 by Stephan.Jiang Set viewed animtion to current animtion after switching from Graph to Designer (This is for "No Animation Selected" showing up when switching) #UE-33016 Change 3050194 on 2016/07/14 by Stephan.Jiang Added ability to replace the widget the track is currently bound to Also includes changes in WidgetBlueprintEditor to send delegate to AnimationtabSummoner when switching from Graph to Designer #UE-31809 [CL 3050870 by Matt Kuhlenschmidt in Main branch]
3901 lines
131 KiB
C++
3901 lines
131 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "LevelEditor.h"
|
|
#include "SLevelViewport.h"
|
|
#include "SLevelViewportToolBar.h"
|
|
#include "Editor/UnrealEd/Public/STransformViewportToolbar.h"
|
|
#include "LevelViewportLayout.h"
|
|
#include "LevelViewportTabContent.h"
|
|
#include "Runtime/Engine/Public/Slate/SceneViewport.h"
|
|
#include "EditorShowFlags.h"
|
|
#include "LevelViewportActions.h"
|
|
#include "SLevelEditor.h"
|
|
#include "AssetSelection.h"
|
|
#include "LevelEditorActions.h"
|
|
#include "Editor/UnrealEd/Public/Kismet2/DebuggerCommands.h"
|
|
#include "Layers/ILayers.h"
|
|
#include "Editor/UnrealEd/Public/DragAndDrop/ClassDragDropOp.h"
|
|
#include "Editor/UnrealEd/Public/DragAndDrop/AssetDragDropOp.h"
|
|
#include "Editor/UnrealEd/Public/DragAndDrop/ExportTextDragDropOp.h"
|
|
#include "Editor/UnrealEd/Public/DragAndDrop/BrushBuilderDragDropOp.h"
|
|
#include "Editor/SceneOutliner/Public/SceneOutliner.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "LevelUtils.h"
|
|
#include "HighresScreenshotUI.h"
|
|
#include "SCaptureRegionWidget.h"
|
|
#include "ISettingsModule.h"
|
|
#include "BufferVisualizationData.h"
|
|
#include "EditorViewportCommands.h"
|
|
#include "Runtime/Engine/Classes/Engine/UserInterfaceSettings.h"
|
|
#include "Runtime/Engine/Classes/Engine/RendererSettings.h"
|
|
#include "SNotificationList.h"
|
|
#include "NotificationManager.h"
|
|
#include "SLevelViewportControlsPopup.h"
|
|
#include "Camera/CameraActor.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "Engine/LocalPlayer.h"
|
|
#include "Engine/Selection.h"
|
|
#include "GameFramework/PlayerInput.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "SActorPilotViewportToolbar.h"
|
|
#include "SGameLayerManager.h"
|
|
#include "FoliageType.h"
|
|
|
|
static const FName LevelEditorName("LevelEditor");
|
|
|
|
#define LOCTEXT_NAMESPACE "LevelViewport"
|
|
|
|
// @todo Slate Hack: Disallow game UI to be used in play in viewport until GWorld problem is fixed
|
|
// Currently Slate has no knowledge of a world and cannot switch it before input events,etc
|
|
#define ALLOW_PLAY_IN_VIEWPORT_GAMEUI 1
|
|
|
|
namespace SLevelViewportPIEAnimation
|
|
{
|
|
float const MouseControlLabelFadeout = 5.0f;
|
|
}
|
|
|
|
class FLevelViewportDropContextMenuImpl
|
|
{
|
|
public:
|
|
/**
|
|
* Fills in menu options for the actor add/replacement submenu
|
|
*
|
|
* @param bReplace true if we want to add a replace menu instead of add
|
|
* @param MenuBuilder The menu to add items to
|
|
*/
|
|
static void FillDropAddReplaceActorMenu( bool bReplace, class FMenuBuilder& MenuBuilder );
|
|
};
|
|
|
|
|
|
SLevelViewport::SLevelViewport()
|
|
: HighResScreenshotDialog( NULL )
|
|
, ViewTransitionType( EViewTransition::None )
|
|
, bViewTransitionAnimPending( false )
|
|
, DeviceProfile("Default")
|
|
, PIEOverlaySlotIndex(0)
|
|
, bPIEHasFocus(false)
|
|
, bPIEContainsFocus(false)
|
|
, UserAllowThrottlingValue(0)
|
|
{
|
|
}
|
|
|
|
SLevelViewport::~SLevelViewport()
|
|
{
|
|
// Clean up any actor preview viewports
|
|
for (FViewportActorPreview& ActorPreview : ActorPreviews)
|
|
{
|
|
ActorPreview.bIsPinned = false;
|
|
}
|
|
PreviewActors( TArray< AActor* >() );
|
|
|
|
FLevelViewportCommands::NewStatCommandDelegate.RemoveAll(this);
|
|
|
|
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
|
|
LevelEditor.OnRedrawLevelEditingViewports().RemoveAll( this );
|
|
LevelEditor.OnTakeHighResScreenShots().RemoveAll( this );
|
|
LevelEditor.OnActorSelectionChanged().RemoveAll( this );
|
|
LevelEditor.OnMapChanged().RemoveAll( this );
|
|
GEngine->OnLevelActorDeleted().RemoveAll( this );
|
|
|
|
GetMutableDefault<ULevelEditorViewportSettings>()->OnSettingChanged().RemoveAll( this );
|
|
|
|
// If this viewport has a high res screenshot window attached to it, close it
|
|
if (HighResScreenshotDialog.IsValid())
|
|
{
|
|
HighResScreenshotDialog.Pin()->RequestDestroyWindow();
|
|
HighResScreenshotDialog.Reset();
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::HandleViewportSettingChanged(FName PropertyName)
|
|
{
|
|
if ( PropertyName == TEXT("bPreviewSelectedCameras") )
|
|
{
|
|
OnPreviewSelectedCamerasChange();
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::IsVisible() const
|
|
{
|
|
// The viewport is visible if we don't have a parent layout (likely a floating window) or this viewport is visible in the parent layout
|
|
return IsInForegroundTab() && SEditorViewport::IsVisible();
|
|
}
|
|
|
|
bool SLevelViewport::IsInForegroundTab() const
|
|
{
|
|
if (ViewportWidget.IsValid() && ParentLayout.IsValid() && !ConfigKey.IsEmpty())
|
|
{
|
|
return ParentLayout.Pin()->IsLevelViewportVisible(*ConfigKey);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SLevelViewport::Construct(const FArguments& InArgs)
|
|
{
|
|
GetMutableDefault<ULevelEditorViewportSettings>()->OnSettingChanged().AddRaw(this, &SLevelViewport::HandleViewportSettingChanged);
|
|
|
|
ParentLayout = InArgs._ParentLayout;
|
|
ParentLevelEditor = StaticCastSharedRef<SLevelEditor>( InArgs._ParentLevelEditor.Pin().ToSharedRef() );
|
|
ConfigKey = InArgs._ConfigKey;
|
|
|
|
// Store border brushes for differentiating between active and inactive viewports
|
|
ActiveBorder = FEditorStyle::GetBrush( "LevelViewport.ActiveViewportBorder" );
|
|
NoBorder = FEditorStyle::GetBrush( "LevelViewport.NoViewportBorder" );
|
|
DebuggingBorder = FEditorStyle::GetBrush( "LevelViewport.DebugBorder" );
|
|
BlackBackground = FEditorStyle::GetBrush( "LevelViewport.BlackBackground" );
|
|
StartingPlayInEditorBorder = FEditorStyle::GetBrush( "LevelViewport.StartingPlayInEditorBorder" );
|
|
StartingSimulateBorder = FEditorStyle::GetBrush( "LevelViewport.StartingSimulateBorder" );
|
|
ReturningToEditorBorder = FEditorStyle::GetBrush( "LevelViewport.ReturningToEditorBorder" );
|
|
|
|
|
|
ConstructLevelEditorViewportClient( InArgs );
|
|
|
|
SEditorViewport::Construct( SEditorViewport::FArguments() );
|
|
|
|
ActiveViewport = SceneViewport;
|
|
|
|
ConstructViewportOverlayContent();
|
|
|
|
|
|
// If a map has already been loaded, this will test for it and copy the correct camera location out
|
|
OnMapChanged( GWorld, EMapChangeType::LoadMap );
|
|
|
|
// Important: We use raw bindings here because we are releasing our binding in our destructor (where a weak pointer would be invalid)
|
|
// It's imperative that our delegate is removed in the destructor for the level editor module to play nicely with reloading.
|
|
|
|
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
|
|
LevelEditor.OnRedrawLevelEditingViewports().AddRaw( this, &SLevelViewport::RedrawViewport );
|
|
LevelEditor.OnTakeHighResScreenShots().AddRaw( this, &SLevelViewport::TakeHighResScreenShot );
|
|
|
|
// Tell the level editor we want to be notified when selection changes
|
|
LevelEditor.OnActorSelectionChanged().AddRaw( this, &SLevelViewport::OnActorSelectionChanged );
|
|
|
|
// Tell the level editor we want to be notified when selection changes
|
|
LevelEditor.OnMapChanged().AddRaw( this, &SLevelViewport::OnMapChanged );
|
|
|
|
GEngine->OnLevelActorDeleted().AddRaw( this, &SLevelViewport::OnLevelActorsRemoved );
|
|
}
|
|
|
|
void SLevelViewport::ConstructViewportOverlayContent()
|
|
{
|
|
PIEViewportOverlayWidget = SNew( SOverlay );
|
|
|
|
int32 SlotIndex = 0;
|
|
#if ALLOW_PLAY_IN_VIEWPORT_GAMEUI
|
|
ViewportOverlay->AddSlot( SlotIndex )
|
|
[
|
|
SAssignNew(GameLayerManager, SGameLayerManager)
|
|
.SceneViewport(this, &SLevelViewport::GetGameSceneViewport)
|
|
[
|
|
PIEViewportOverlayWidget.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
++SlotIndex;
|
|
#endif
|
|
|
|
ViewportOverlay->AddSlot( SlotIndex )
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SAssignNew( ActorPreviewHorizontalBox, SHorizontalBox )
|
|
];
|
|
|
|
ViewportOverlay->AddSlot(SlotIndex)
|
|
.VAlign(VAlign_Bottom)
|
|
.HAlign(HAlign_Left)
|
|
.Padding(5.0f)
|
|
[
|
|
SNew(SLevelViewportControlsPopup)
|
|
.Visibility(this, &SLevelViewport::GetViewportControlsVisibility)
|
|
];
|
|
|
|
ViewportOverlay->AddSlot( SlotIndex )
|
|
.VAlign( VAlign_Bottom )
|
|
.HAlign( HAlign_Right )
|
|
.Padding( 5.0f )
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility(this, &SLevelViewport::GetCurrentFeatureLevelPreviewTextVisibility)
|
|
// Current level label
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SLevelViewport::GetCurrentFeatureLevelPreviewText, true)
|
|
.Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font")))
|
|
.ShadowOffset(FVector2D(1, 1))
|
|
]
|
|
|
|
// Current level
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(4.0f, 1.0f, 2.0f, 1.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SLevelViewport::GetCurrentFeatureLevelPreviewText, false)
|
|
.Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font")))
|
|
.ColorAndOpacity(FLinearColor(0.4f, 1.0f, 1.0f))
|
|
.ShadowOffset(FVector2D(1, 1))
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility(this, &SLevelViewport::GetCurrentLevelTextVisibility)
|
|
// Current level label
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SLevelViewport::GetCurrentLevelText, true)
|
|
.Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font")))
|
|
.ShadowOffset(FVector2D(1, 1))
|
|
]
|
|
|
|
// Current level
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(4.0f, 1.0f, 2.0f, 1.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SLevelViewport::GetCurrentLevelText, false)
|
|
.Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font")))
|
|
.ColorAndOpacity(FLinearColor(0.4f, 1.0f, 1.0f))
|
|
.ShadowOffset(FVector2D(1, 1))
|
|
]
|
|
]
|
|
];
|
|
|
|
// Add highres screenshot region capture editing widget
|
|
ViewportOverlay->AddSlot(SlotIndex)
|
|
.VAlign( VAlign_Fill )
|
|
.HAlign( HAlign_Fill )
|
|
.Padding( 0 )
|
|
[
|
|
SAssignNew(CaptureRegionWidget, SCaptureRegionWidget)
|
|
];
|
|
}
|
|
|
|
void SLevelViewport::ConstructLevelEditorViewportClient( const FArguments& InArgs )
|
|
{
|
|
if (InArgs._LevelEditorViewportClient.IsValid())
|
|
{
|
|
LevelViewportClient = InArgs._LevelEditorViewportClient;
|
|
}
|
|
else
|
|
{
|
|
LevelViewportClient = MakeShareable( new FLevelEditorViewportClient(SharedThis(this)) );
|
|
}
|
|
|
|
// Default level viewport client values for settings that could appear in layout config ini
|
|
FLevelEditorViewportInstanceSettings ViewportInstanceSettings;
|
|
ViewportInstanceSettings.ViewportType = InArgs._ViewportType;
|
|
ViewportInstanceSettings.PerspViewModeIndex = VMI_Lit;
|
|
ViewportInstanceSettings.OrthoViewModeIndex = VMI_BrushWireframe;
|
|
ViewportInstanceSettings.bIsRealtime = InArgs._Realtime;
|
|
|
|
FEngineShowFlags EditorShowFlags(ESFIM_Editor);
|
|
FEngineShowFlags GameShowFlags(ESFIM_Game);
|
|
|
|
// Use config key if it exists to set up the level viewport client
|
|
if(!ConfigKey.IsEmpty())
|
|
{
|
|
const FLevelEditorViewportInstanceSettings* const ViewportInstanceSettingsPtr = GetDefault<ULevelEditorViewportSettings>()->GetViewportInstanceSettings(ConfigKey);
|
|
ViewportInstanceSettings = (ViewportInstanceSettingsPtr) ? *ViewportInstanceSettingsPtr : LoadLegacyConfigFromIni(ConfigKey, ViewportInstanceSettings);
|
|
|
|
if(!ViewportInstanceSettings.EditorShowFlagsString.IsEmpty())
|
|
{
|
|
EditorShowFlags.SetFromString(*ViewportInstanceSettings.EditorShowFlagsString);
|
|
}
|
|
|
|
if(!ViewportInstanceSettings.GameShowFlagsString.IsEmpty())
|
|
{
|
|
GameShowFlags.SetFromString(*ViewportInstanceSettings.GameShowFlagsString);
|
|
}
|
|
|
|
if(!GetBufferVisualizationData().GetMaterial(ViewportInstanceSettings.BufferVisualizationMode))
|
|
{
|
|
ViewportInstanceSettings.BufferVisualizationMode = NAME_None;
|
|
}
|
|
}
|
|
|
|
if(ViewportInstanceSettings.ViewportType == LVT_Perspective)
|
|
{
|
|
ApplyViewMode(ViewportInstanceSettings.PerspViewModeIndex, true, EditorShowFlags);
|
|
ApplyViewMode(ViewportInstanceSettings.PerspViewModeIndex, true, GameShowFlags);
|
|
}
|
|
else
|
|
{
|
|
ApplyViewMode(ViewportInstanceSettings.OrthoViewModeIndex, false, EditorShowFlags);
|
|
ApplyViewMode(ViewportInstanceSettings.OrthoViewModeIndex, false, GameShowFlags);
|
|
}
|
|
|
|
// Disabling some features for orthographic views.
|
|
if(ViewportInstanceSettings.ViewportType != LVT_Perspective)
|
|
{
|
|
EditorShowFlags.MotionBlur = 0;
|
|
EditorShowFlags.Fog = 0;
|
|
EditorShowFlags.SetDepthOfField(false);
|
|
GameShowFlags.MotionBlur = 0;
|
|
GameShowFlags.Fog = 0;
|
|
GameShowFlags.SetDepthOfField(false);
|
|
}
|
|
|
|
EditorShowFlags.SetSnap(1);
|
|
GameShowFlags.SetSnap(1);
|
|
|
|
// Create level viewport client
|
|
LevelViewportClient->ParentLevelEditor = ParentLevelEditor.Pin();
|
|
LevelViewportClient->ViewportType = ViewportInstanceSettings.ViewportType;
|
|
LevelViewportClient->bSetListenerPosition = false;
|
|
LevelViewportClient->EngineShowFlags = EditorShowFlags;
|
|
LevelViewportClient->LastEngineShowFlags = GameShowFlags;
|
|
LevelViewportClient->CurrentBufferVisualizationMode = ViewportInstanceSettings.BufferVisualizationMode;
|
|
LevelViewportClient->ExposureSettings = ViewportInstanceSettings.ExposureSettings;
|
|
if(InArgs._ViewportType == LVT_Perspective)
|
|
{
|
|
LevelViewportClient->SetViewLocation( EditorViewportDefs::DefaultPerspectiveViewLocation );
|
|
LevelViewportClient->SetViewRotation( EditorViewportDefs::DefaultPerspectiveViewRotation );
|
|
LevelViewportClient->SetAllowCinematicPreview(true);
|
|
}
|
|
LevelViewportClient->SetRealtime(ViewportInstanceSettings.bIsRealtime);
|
|
LevelViewportClient->SetShowStats(ViewportInstanceSettings.bShowStats);
|
|
if (ViewportInstanceSettings.bShowFPS_DEPRECATED)
|
|
{
|
|
GetMutableDefault<ULevelEditorViewportSettings>()->bSaveEngineStats = true;
|
|
ViewportInstanceSettings.EnabledStats.AddUnique(TEXT("FPS"));
|
|
}
|
|
if (GetDefault<ULevelEditorViewportSettings>()->bSaveEngineStats)
|
|
{
|
|
GEngine->SetEngineStats(GetWorld(), LevelViewportClient.Get(), ViewportInstanceSettings.EnabledStats, true);
|
|
}
|
|
LevelViewportClient->VisibilityDelegate.BindSP( this, &SLevelViewport::IsVisible );
|
|
LevelViewportClient->ImmersiveDelegate.BindSP( this, &SLevelViewport::IsImmersive );
|
|
LevelViewportClient->bDrawBaseInfo = true;
|
|
LevelViewportClient->bDrawVertices = true;
|
|
LevelViewportClient->ViewFOV = LevelViewportClient->FOVAngle = ViewportInstanceSettings.FOVAngle;
|
|
LevelViewportClient->OverrideFarClipPlane( ViewportInstanceSettings.FarViewPlane );
|
|
|
|
// Set the selection outline flag based on preferences
|
|
LevelViewportClient->EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline);
|
|
|
|
// Always composite editor objects after post processing in the editor
|
|
LevelViewportClient->EngineShowFlags.SetCompositeEditorPrimitives(true);
|
|
|
|
LevelViewportClient->SetViewModes(ViewportInstanceSettings.PerspViewModeIndex, ViewportInstanceSettings.OrthoViewModeIndex );
|
|
|
|
bShowFullToolbar = ViewportInstanceSettings.bShowFullToolbar;
|
|
}
|
|
|
|
const FSceneViewport* SLevelViewport::GetGameSceneViewport() const
|
|
{
|
|
return ActiveViewport.Get();
|
|
}
|
|
|
|
FReply SLevelViewport::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
|
|
{
|
|
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
if( HasPlayInEditorViewport() || LevelViewportClient->IsSimulateInEditorViewport() )
|
|
{
|
|
// Only process commands for pie when a play world is active
|
|
FPlayWorldCommands::GlobalPlayWorldActions->ProcessCommandBindings( InKeyEvent );
|
|
|
|
// Always handle commands in pie so they arent bubbled to editor only widgets
|
|
Reply = FReply::Handled();
|
|
}
|
|
|
|
if( !IsPlayInEditorViewportActive() )
|
|
{
|
|
Reply = SEditorViewport::OnKeyDown(MyGeometry,InKeyEvent);
|
|
|
|
|
|
// If we are in immersive mode and the event was not handled, we will check to see if the the
|
|
// optional parent level editor is set. If it is, we give it a chance to handle the key event.
|
|
// This command forwarding is currently only needed when in immersive mode because in that case
|
|
// the SLevelEditor is not a direct parent of the viewport.
|
|
if ( this->IsImmersive() && !Reply.IsEventHandled() )
|
|
{
|
|
TSharedPtr<ILevelEditor> ParentLevelEditorSharedPtr = ParentLevelEditor.Pin();
|
|
if( ParentLevelEditorSharedPtr.IsValid() )
|
|
{
|
|
Reply = ParentLevelEditorSharedPtr->OnKeyDownInViewport( MyGeometry, InKeyEvent );
|
|
}
|
|
}
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
void SLevelViewport::OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
// Prevent OnDragEnter from reentering because it will affect the drop preview placement and management.
|
|
// This may happen currently if an unloaded class is dragged from the class viewer and a slow task is triggered,
|
|
// which re-ticks slate and triggers another mouse move.
|
|
static bool bDragEnterReentranceGuard = false;
|
|
if ( !bDragEnterReentranceGuard )
|
|
{
|
|
bDragEnterReentranceGuard = true;
|
|
// Don't execute the dragdrop op if the current level is locked.
|
|
// This prevents duplicate warning messages firing on DragEnter and Placement.
|
|
ULevel* CurrentLevel = (GetWorld()) ? GetWorld()->GetCurrentLevel() : NULL;
|
|
|
|
if ( CurrentLevel && !FLevelUtils::IsLevelLocked(CurrentLevel) )
|
|
{
|
|
if ( HandleDragObjects(MyGeometry, DragDropEvent) )
|
|
{
|
|
if ( HandlePlaceDraggedObjects(MyGeometry, DragDropEvent, /*bCreateDropPreview=*/true) )
|
|
{
|
|
DragDropEvent.GetOperation()->SetDecoratorVisibility(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
bDragEnterReentranceGuard = false;
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::OnDragLeave( const FDragDropEvent& DragDropEvent )
|
|
{
|
|
if ( LevelViewportClient->HasDropPreviewActors() )
|
|
{
|
|
LevelViewportClient->DestroyDropPreviewActors();
|
|
}
|
|
|
|
if (DragDropEvent.GetOperation().IsValid())
|
|
{
|
|
DragDropEvent.GetOperation()->SetDecoratorVisibility(true);
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::HandleDragObjects(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
|
|
{
|
|
bool bValidDrag = false;
|
|
TArray<FAssetData> SelectedAssetDatas;
|
|
|
|
TSharedPtr< FDragDropOperation > Operation = DragDropEvent.GetOperation();
|
|
if (!Operation.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Operation->IsOfType<FClassDragDropOp>())
|
|
{
|
|
auto ClassOperation = StaticCastSharedPtr<FClassDragDropOp>( Operation );
|
|
|
|
bValidDrag = true;
|
|
|
|
for (int32 DroppedAssetIdx = 0; DroppedAssetIdx < ClassOperation->ClassesToDrop.Num(); ++DroppedAssetIdx)
|
|
{
|
|
new(SelectedAssetDatas)FAssetData(ClassOperation->ClassesToDrop[DroppedAssetIdx].Get());
|
|
}
|
|
}
|
|
else if (Operation->IsOfType<FUnloadedClassDragDropOp>())
|
|
{
|
|
bValidDrag = true;
|
|
}
|
|
else if (Operation->IsOfType<FExportTextDragDropOp>())
|
|
{
|
|
bValidDrag = true;
|
|
}
|
|
else if (Operation->IsOfType<FBrushBuilderDragDropOp>())
|
|
{
|
|
bValidDrag = true;
|
|
|
|
auto BrushOperation = StaticCastSharedPtr<FBrushBuilderDragDropOp>( Operation );
|
|
|
|
new(SelectedAssetDatas) FAssetData(BrushOperation->GetBrushBuilder().Get());
|
|
}
|
|
else
|
|
{
|
|
SelectedAssetDatas = AssetUtil::ExtractAssetDataFromDrag( DragDropEvent );
|
|
|
|
if ( SelectedAssetDatas.Num() > 0 )
|
|
{
|
|
bValidDrag = true;
|
|
}
|
|
}
|
|
|
|
// Update cached mouse position
|
|
if ( bValidDrag )
|
|
{
|
|
// Grab viewport to offset click position correctly
|
|
FIntPoint ViewportOrigin, ViewportSize;
|
|
LevelViewportClient->GetViewportDimensions(ViewportOrigin, ViewportSize);
|
|
|
|
// Save off the local mouse position from the drop point for potential use later (with Drag Drop context menu)
|
|
CachedOnDropLocalMousePos = MyGeometry.AbsoluteToLocal( DragDropEvent.GetScreenSpacePosition() );
|
|
CachedOnDropLocalMousePos.X -= ViewportOrigin.X;
|
|
CachedOnDropLocalMousePos.Y -= ViewportOrigin.Y;
|
|
}
|
|
|
|
// Update the currently dragged actor if it exists
|
|
bool bDroppedObjectsVisible = true;
|
|
if (LevelViewportClient->UpdateDropPreviewActors(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y, DroppedObjects, bDroppedObjectsVisible))
|
|
{
|
|
// if dragged actors were hidden, show decorator
|
|
Operation->SetDecoratorVisibility(! bDroppedObjectsVisible);
|
|
}
|
|
|
|
Operation->SetCursorOverride(TOptional<EMouseCursor::Type>());
|
|
|
|
FText HintText;
|
|
|
|
// Determine if we can drop the assets
|
|
for ( auto InfoIt = SelectedAssetDatas.CreateConstIterator(); InfoIt; ++InfoIt )
|
|
{
|
|
const FAssetData& AssetData = *InfoIt;
|
|
|
|
// Ignore invalid assets
|
|
if ( !AssetData.IsValid() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FDropQuery DropResult = LevelViewportClient->CanDropObjectsAtCoordinates(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y, AssetData);
|
|
|
|
if ( !DropResult.bCanDrop )
|
|
{
|
|
// At least one of the assets can't be dropped.
|
|
Operation->SetCursorOverride(EMouseCursor::SlashedCircle);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if ( HintText.IsEmpty() )
|
|
{
|
|
HintText = DropResult.HintText;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( Operation->IsOfType<FAssetDragDropOp>() )
|
|
{
|
|
auto AssetOperation = StaticCastSharedPtr<FAssetDragDropOp>(DragDropEvent.GetOperation());
|
|
AssetOperation->SetToolTip(HintText, NULL);
|
|
}
|
|
|
|
return bValidDrag;
|
|
}
|
|
|
|
FReply SLevelViewport::OnDragOver( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
if ( HandleDragObjects(MyGeometry, DragDropEvent) )
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
bool SLevelViewport::HandlePlaceDraggedObjects(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent, bool bCreateDropPreview)
|
|
{
|
|
bool bAllAssetWereLoaded = false;
|
|
bool bValidDrop = false;
|
|
UActorFactory* ActorFactory = NULL;
|
|
|
|
TSharedPtr< FDragDropOperation > Operation = DragDropEvent.GetOperation();
|
|
if (!Operation.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Don't handle the placement if we couldn't handle the drag
|
|
if (!HandleDragObjects(MyGeometry, DragDropEvent))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Operation->IsOfType<FClassDragDropOp>())
|
|
{
|
|
auto ClassOperation = StaticCastSharedPtr<FClassDragDropOp>( Operation );
|
|
|
|
DroppedObjects.Empty();
|
|
|
|
// Check if the asset is loaded, used to see if the context menu should be available
|
|
bAllAssetWereLoaded = true;
|
|
|
|
for (int32 DroppedAssetIdx = 0; DroppedAssetIdx < ClassOperation->ClassesToDrop.Num(); ++DroppedAssetIdx)
|
|
{
|
|
UObject* Object = ClassOperation->ClassesToDrop[DroppedAssetIdx].Get();
|
|
|
|
if(Object)
|
|
{
|
|
DroppedObjects.Add(Object);
|
|
}
|
|
else
|
|
{
|
|
bAllAssetWereLoaded = false;
|
|
}
|
|
}
|
|
|
|
bValidDrop = true;
|
|
}
|
|
else if (Operation->IsOfType<FUnloadedClassDragDropOp>())
|
|
{
|
|
TSharedPtr<FUnloadedClassDragDropOp> DragDropOp = StaticCastSharedPtr<FUnloadedClassDragDropOp>( Operation );
|
|
|
|
DroppedObjects.Empty();
|
|
|
|
// Check if the asset is loaded, used to see if the context menu should be available
|
|
bAllAssetWereLoaded = true;
|
|
|
|
TArray< FClassPackageData >& AssetArray = *(DragDropOp->AssetsToDrop.Get());
|
|
for (int32 DroppedAssetIdx = 0; DroppedAssetIdx < AssetArray.Num(); ++DroppedAssetIdx)
|
|
{
|
|
bValidDrop = true;
|
|
|
|
FString& AssetName = AssetArray[DroppedAssetIdx].AssetName;
|
|
|
|
// Check to see if the asset can be found, otherwise load it.
|
|
UObject* Object = FindObject<UObject>(NULL, *AssetName);
|
|
if(Object == NULL)
|
|
{
|
|
// Check to see if the dropped asset was a blueprint
|
|
const FString& PackageName = AssetArray[DroppedAssetIdx].GeneratedPackageName;
|
|
Object = FindObject<UObject>(NULL, *FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName));
|
|
|
|
if ( Object == NULL )
|
|
{
|
|
// Load the package.
|
|
GWarn->BeginSlowTask( LOCTEXT("OnDrop_FullyLoadPackage", "Fully Loading Package For Drop"), true, false );
|
|
UPackage* Package = LoadPackage(NULL, *PackageName, LOAD_NoRedirects );
|
|
if (Package)
|
|
{
|
|
Package->FullyLoad();
|
|
}
|
|
GWarn->EndSlowTask();
|
|
|
|
Object = FindObject<UObject>(Package, *AssetName);
|
|
}
|
|
}
|
|
|
|
// Check again if it has been loaded, if not, mark that all were not loaded and move on.
|
|
if(Object)
|
|
{
|
|
DroppedObjects.Add(Object);
|
|
}
|
|
else
|
|
{
|
|
bAllAssetWereLoaded = false;
|
|
}
|
|
}
|
|
}
|
|
else if (Operation->IsOfType<FAssetDragDropOp>())
|
|
{
|
|
bValidDrop = true;
|
|
DroppedObjects.Empty();
|
|
|
|
TSharedPtr<FAssetDragDropOp> DragDropOp = StaticCastSharedPtr<FAssetDragDropOp>( Operation );
|
|
|
|
ActorFactory = DragDropOp->ActorFactory.Get();
|
|
|
|
bAllAssetWereLoaded = true;
|
|
for (int32 AssetIdx = 0; AssetIdx < DragDropOp->AssetData.Num(); ++AssetIdx)
|
|
{
|
|
const FAssetData& AssetData = DragDropOp->AssetData[AssetIdx];
|
|
|
|
UObject* Asset = AssetData.GetAsset();
|
|
if ( Asset != NULL )
|
|
{
|
|
DroppedObjects.Add( Asset );
|
|
}
|
|
else
|
|
{
|
|
bAllAssetWereLoaded = false;
|
|
}
|
|
}
|
|
}
|
|
// OLE drops are blocking which causes problem when positioning and maintaining the drop preview
|
|
// Drop preview is disabled when dragging from external sources
|
|
else if ( !bCreateDropPreview && Operation->IsOfType<FExternalDragOperation>() )
|
|
{
|
|
bValidDrop = true;
|
|
DroppedObjects.Empty();
|
|
|
|
TArray<FAssetData> DroppedAssetDatas = AssetUtil::ExtractAssetDataFromDrag(DragDropEvent);
|
|
|
|
bAllAssetWereLoaded = true;
|
|
for (int32 AssetIdx = 0; AssetIdx < DroppedAssetDatas.Num(); ++AssetIdx)
|
|
{
|
|
const FAssetData& AssetData = DroppedAssetDatas[AssetIdx];
|
|
|
|
UObject* Asset = AssetData.GetAsset();
|
|
if ( Asset != NULL )
|
|
{
|
|
DroppedObjects.Add( Asset );
|
|
}
|
|
else
|
|
{
|
|
bAllAssetWereLoaded = false;
|
|
}
|
|
}
|
|
}
|
|
else if ( Operation->IsOfType<FExportTextDragDropOp>() )
|
|
{
|
|
bValidDrop = true;
|
|
|
|
TSharedPtr<FExportTextDragDropOp> DragDropOp = StaticCastSharedPtr<FExportTextDragDropOp>( Operation );
|
|
|
|
// Check if the asset is loaded, used to see if the context menu should be available
|
|
bAllAssetWereLoaded = true;
|
|
DroppedObjects.Empty();
|
|
|
|
// Create a container object to hold the export text and pass it into the actor placement code
|
|
UExportTextContainer* NewContainer = NewObject<UExportTextContainer>();
|
|
NewContainer->ExportText = DragDropOp->ActorExportText;
|
|
DroppedObjects.Add(NewContainer);
|
|
}
|
|
else if ( Operation->IsOfType<FBrushBuilderDragDropOp>() )
|
|
{
|
|
bValidDrop = true;
|
|
DroppedObjects.Empty();
|
|
|
|
TSharedPtr<FBrushBuilderDragDropOp> DragDropOp = StaticCastSharedPtr<FBrushBuilderDragDropOp>( Operation );
|
|
|
|
if(DragDropOp->GetBrushBuilder().IsValid())
|
|
{
|
|
DroppedObjects.Add(DragDropOp->GetBrushBuilder().Get());
|
|
}
|
|
}
|
|
|
|
if ( bValidDrop )
|
|
{
|
|
// Grab the hit proxy, used for the (potential) context menu
|
|
HHitProxy* HitProxy = LevelViewportClient->Viewport->GetHitProxy(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y);
|
|
|
|
// If Ctrl is down, pop in the context menu
|
|
const bool bShowDropContextMenu = !bCreateDropPreview && DragDropEvent.IsControlDown() && ( !HitProxy || !( HitProxy->IsA( HWidgetAxis::StaticGetType() ) ) );
|
|
bool bDropSuccessful = false;
|
|
|
|
// Make sure the drop preview is destroyed
|
|
LevelViewportClient->DestroyDropPreviewActors();
|
|
|
|
if( !bShowDropContextMenu || !bCreateDropPreview )
|
|
{
|
|
// Otherwise just attempt to drop the object(s)
|
|
TArray< AActor* > TemporaryActors;
|
|
// Only select actor on drop
|
|
const bool SelectActor = !bCreateDropPreview;
|
|
bDropSuccessful = LevelViewportClient->DropObjectsAtCoordinates(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y, DroppedObjects, TemporaryActors, false, bCreateDropPreview, SelectActor, ActorFactory);
|
|
}
|
|
else if ( bAllAssetWereLoaded && DroppedObjects.Num() > 0 )
|
|
{
|
|
FWidgetPath WidgetPath = DragDropEvent.GetEventPath() != nullptr ? *DragDropEvent.GetEventPath() : FWidgetPath();
|
|
|
|
FSlateApplication::Get().PushMenu(
|
|
SharedThis( this ),
|
|
WidgetPath,
|
|
BuildViewportDragDropContextMenu(),
|
|
DragDropEvent.GetScreenSpacePosition(),
|
|
FPopupTransitionEffect( FPopupTransitionEffect::ContextMenu ) );
|
|
|
|
bDropSuccessful = true;
|
|
}
|
|
|
|
// Give the editor focus (quick Undo/Redo support after a drag drop operation)
|
|
if(ParentLevelEditor.IsValid())
|
|
{
|
|
FGlobalTabmanager::Get()->DrawAttentionToTabManager(ParentLevelEditor.Pin()->GetTabManager());
|
|
}
|
|
|
|
if(bDropSuccessful)
|
|
{
|
|
SetKeyboardFocusToThisViewport();
|
|
}
|
|
|
|
return bDropSuccessful;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FReply SLevelViewport::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
ULevel* CurrentLevel = (GetWorld()) ? GetWorld()->GetCurrentLevel() : NULL;
|
|
|
|
if (CurrentLevel && !FLevelUtils::IsLevelLocked(CurrentLevel))
|
|
{
|
|
return HandlePlaceDraggedObjects(MyGeometry, DragDropEvent, /*bCreateDropPreview=*/false) ? FReply::Handled() : FReply::Unhandled();
|
|
}
|
|
else
|
|
{
|
|
FNotificationInfo Info(LOCTEXT("Error_OperationDisallowedOnLockedLevel", "The requested operation could not be completed because the level is locked."));
|
|
Info.ExpireDuration = 3.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info);
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
|
|
|
|
void SLevelViewport::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
SEditorViewport::Tick( AllottedGeometry, InCurrentTime, InDeltaTime );
|
|
|
|
const bool bContainsFocus = HasFocusedDescendants();
|
|
|
|
// When we have focus we update the 'Allow Throttling' option in slate to be disabled so that interactions in the
|
|
// viewport with Slate widgets that are part of the game, don't throttle.
|
|
if ( GEditor->PlayWorld != nullptr && bPIEContainsFocus != bContainsFocus )
|
|
{
|
|
// We can arrive at this point before creating throttling manager (which registers the cvar), so create it explicitly.
|
|
static const FSlateThrottleManager & ThrottleManager = FSlateThrottleManager::Get();
|
|
static IConsoleVariable* AllowThrottling = IConsoleManager::Get().FindConsoleVariable(TEXT("Slate.bAllowThrottling"));
|
|
check(AllowThrottling);
|
|
|
|
if ( bContainsFocus )
|
|
{
|
|
UserAllowThrottlingValue = AllowThrottling->GetInt();
|
|
AllowThrottling->Set(0);
|
|
}
|
|
else
|
|
{
|
|
AllowThrottling->Set(UserAllowThrottlingValue);
|
|
}
|
|
|
|
bPIEContainsFocus = bContainsFocus;
|
|
}
|
|
|
|
// We defer starting animation playback because very often there may be a large hitch after the frame in which
|
|
// the animation was triggered, and we don't want to start animating until after that hitch. Otherwise, the
|
|
// user could miss part of the animation, or even the whole thing!
|
|
if( bViewTransitionAnimPending )
|
|
{
|
|
ViewTransitionAnim.Play(this->AsShared());
|
|
bViewTransitionAnimPending = false;
|
|
}
|
|
|
|
// If we've completed a transition, then start animating back to our regular border. We
|
|
// do this so that we can avoid a popping artifact after PIE/SIE ends.
|
|
if( !ViewTransitionAnim.IsPlaying() && ViewTransitionType != EViewTransition::None )
|
|
{
|
|
if(ViewTransitionType == EViewTransition::StartingPlayInEditor)
|
|
{
|
|
if(PIEOverlaySlotIndex)
|
|
{
|
|
PIEOverlayAnim = FCurveSequence(0.0f, SLevelViewportPIEAnimation::MouseControlLabelFadeout, ECurveEaseFunction::CubicInOut);
|
|
PIEOverlayAnim.Play(this->AsShared());
|
|
}
|
|
}
|
|
ViewTransitionType = EViewTransition::None;
|
|
ViewTransitionAnim = FCurveSequence( 0.0f, 0.25f, ECurveEaseFunction::QuadOut );
|
|
ViewTransitionAnim.PlayReverse(this->AsShared());
|
|
}
|
|
|
|
if(IsPlayInEditorViewportActive() && bPIEHasFocus != ActiveViewport->HasMouseCapture())
|
|
{
|
|
bPIEHasFocus = ActiveViewport->HasMouseCapture();
|
|
PIEOverlayAnim = FCurveSequence(0.0f, SLevelViewportPIEAnimation::MouseControlLabelFadeout, ECurveEaseFunction::CubicInOut);
|
|
PIEOverlayAnim.Play(this->AsShared());
|
|
}
|
|
|
|
// Update actor preview viewports, if we have any
|
|
UpdateActorPreviewViewports();
|
|
|
|
#if STATS
|
|
// Check to see if there are any new stat group which need registering with the viewports
|
|
extern CORE_API void CheckForRegisteredStatGroups();
|
|
CheckForRegisteredStatGroups();
|
|
#endif
|
|
}
|
|
|
|
|
|
TSharedRef< SWidget > SLevelViewport::BuildViewportDragDropContextMenu()
|
|
{
|
|
// Get all menu extenders for this context menu from the level editor module
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
|
|
TArray<FLevelEditorModule::FLevelViewportMenuExtender_SelectedObjects> MenuExtenderDelegates = LevelEditorModule.GetAllLevelViewportDragDropContextMenuExtenders();
|
|
|
|
TArray<TSharedPtr<FExtender>> Extenders;
|
|
for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i)
|
|
{
|
|
if (MenuExtenderDelegates[i].IsBound())
|
|
{
|
|
Extenders.Add(MenuExtenderDelegates[i].Execute(CommandList.ToSharedRef(), DroppedObjects));
|
|
}
|
|
}
|
|
TSharedPtr<FExtender> MenuExtender = FExtender::Combine(Extenders);
|
|
|
|
// Builds a context menu used to perform specific actions on actors selected within the editor
|
|
const bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder ViewportContextMenuBuilder( bShouldCloseWindowAfterMenuSelection, CommandList, MenuExtender );
|
|
{
|
|
FLevelViewportDropContextMenuImpl::FillDropAddReplaceActorMenu( false, ViewportContextMenuBuilder );
|
|
|
|
// If any actors are in the current editor selection, add submenu for swapping out those actors with an asset from the chosen factory
|
|
if( GEditor->GetSelectedActorCount() > 0 && !AssetSelectionUtils::IsBuilderBrushSelected() )
|
|
{
|
|
FLevelViewportDropContextMenuImpl::FillDropAddReplaceActorMenu( true, ViewportContextMenuBuilder );
|
|
}
|
|
|
|
if(DroppedObjects.Num() > 0)
|
|
{
|
|
// Grab the hit proxy, used for determining which object we're potentially targeting
|
|
const HHitProxy* DroppedUponProxy = LevelViewportClient->Viewport->GetHitProxy(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y);
|
|
UObject* FirstDroppedObject = DroppedObjects[0];
|
|
|
|
// If we're using a material asset, check if the apply material option(s) should be added
|
|
if(DroppedUponProxy && Cast<UMaterialInterface>(FirstDroppedObject) && LevelViewportClient->CanApplyMaterialToHitProxy(DroppedUponProxy))
|
|
{
|
|
ViewportContextMenuBuilder.BeginSection("ApplyMaterial");
|
|
{
|
|
ViewportContextMenuBuilder.AddMenuEntry( FLevelViewportCommands::Get().ApplyMaterialToActor );
|
|
}
|
|
ViewportContextMenuBuilder.EndSection();
|
|
}
|
|
}
|
|
}
|
|
|
|
return ViewportContextMenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void SLevelViewport::OnMapChanged( UWorld* World, EMapChangeType MapChangeType )
|
|
{
|
|
if( World && ( ( World == GetWorld() ) || ( World->EditorViews[LevelViewportClient->ViewportType].CamUpdated ) ) )
|
|
{
|
|
if( MapChangeType == EMapChangeType::LoadMap )
|
|
{
|
|
if (World->EditorViews[LevelViewportClient->ViewportType].CamOrthoZoom == 0.0f)
|
|
{
|
|
World->EditorViews[LevelViewportClient->ViewportType].CamOrthoZoom = DEFAULT_ORTHOZOOM;
|
|
}
|
|
|
|
ResetNewLevelViewFlags();
|
|
LevelViewportClient->ResetCamera();
|
|
|
|
bool bInitializedOrthoViewport = false;
|
|
for (int32 ViewportType = 0; ViewportType < LVT_MAX; ViewportType++)
|
|
{
|
|
if (ViewportType == LVT_Perspective || !bInitializedOrthoViewport)
|
|
{
|
|
LevelViewportClient->SetInitialViewTransform(
|
|
static_cast<ELevelViewportType>(ViewportType),
|
|
World->EditorViews[ViewportType].CamPosition,
|
|
World->EditorViews[ViewportType].CamRotation,
|
|
World->EditorViews[ViewportType].CamOrthoZoom);
|
|
|
|
if (ViewportType != LVT_Perspective)
|
|
{
|
|
bInitializedOrthoViewport = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if( MapChangeType == EMapChangeType::SaveMap )
|
|
{
|
|
//@todo there could potentially be more than one of the same viewport type. This effectively takes the last one of a specific type
|
|
World->EditorViews[LevelViewportClient->ViewportType] =
|
|
FLevelViewportInfo(
|
|
LevelViewportClient->GetViewLocation(),
|
|
LevelViewportClient->GetViewRotation(),
|
|
LevelViewportClient->GetOrthoZoom() );
|
|
}
|
|
else if( MapChangeType == EMapChangeType::NewMap )
|
|
{
|
|
|
|
ResetNewLevelViewFlags();
|
|
|
|
LevelViewportClient->ResetViewForNewMap();
|
|
}
|
|
World->EditorViews[LevelViewportClient->ViewportType].CamUpdated = false;
|
|
|
|
RedrawViewport(true);
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::OnLevelActorsRemoved(AActor* InActor)
|
|
{
|
|
// Kill any existing actor previews that have expired
|
|
for( int32 PreviewIndex = 0; PreviewIndex < ActorPreviews.Num(); ++PreviewIndex )
|
|
{
|
|
AActor* ExistingActor = ActorPreviews[PreviewIndex].Actor.Get();
|
|
if ( !ExistingActor || ExistingActor == InActor )
|
|
{
|
|
// decrement index so we don't miss next preview after deleting
|
|
RemoveActorPreview( PreviewIndex-- );
|
|
}
|
|
}
|
|
}
|
|
|
|
void FLevelViewportDropContextMenuImpl::FillDropAddReplaceActorMenu( bool bReplace, FMenuBuilder& MenuBuilder )
|
|
{
|
|
// Builds a submenu for the Drag Drop context menu used to replace all actors in the current editor selection with a different asset
|
|
TArray<FAssetData> SelectedAssets;
|
|
AssetSelectionUtils::GetSelectedAssets( SelectedAssets );
|
|
|
|
FAssetData TargetAssetData;
|
|
if ( SelectedAssets.Num() > 0 )
|
|
{
|
|
TargetAssetData = SelectedAssets.Top();
|
|
}
|
|
|
|
TArray< FActorFactoryAssetProxy::FMenuItem > SelectedAssetMenuOptions;
|
|
FActorFactoryAssetProxy::GenerateActorFactoryMenuItems( TargetAssetData, &SelectedAssetMenuOptions, false );
|
|
|
|
if(SelectedAssetMenuOptions.Num() > 0)
|
|
{
|
|
FText AddReplaceTitle = (bReplace)? FText::GetEmpty() : LOCTEXT("DragDropContext_AddAsType", "Add As Type");
|
|
|
|
MenuBuilder.BeginSection("AddReplace", AddReplaceTitle);
|
|
{
|
|
for( int32 ItemIndex = 0; ItemIndex < SelectedAssetMenuOptions.Num(); ++ItemIndex )
|
|
{
|
|
const FActorFactoryAssetProxy::FMenuItem& MenuItem = SelectedAssetMenuOptions[ItemIndex];
|
|
|
|
if ( bReplace )
|
|
{
|
|
FUIAction Action( FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::ReplaceActors_Clicked, MenuItem.FactoryToUse, MenuItem.AssetData ) );
|
|
|
|
FText MenuEntryName = FText::Format( NSLOCTEXT("LevelEditor", "ReplaceActorMenuFormat", "Replace with {0}"), MenuItem.FactoryToUse->GetDisplayName() );
|
|
if ( MenuItem.AssetData.IsValid() )
|
|
{
|
|
MenuEntryName = FText::Format( NSLOCTEXT("LevelEditor", "ReplaceActorUsingAssetMenuFormat", "Replace with {0}: {1}"),
|
|
MenuItem.FactoryToUse->GetDisplayName(),
|
|
FText::FromName( MenuItem.AssetData.AssetName ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FUIAction Action( FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::AddActor_Clicked, MenuItem.FactoryToUse, MenuItem.AssetData, false ) );
|
|
|
|
FText MenuEntryName = FText::Format( NSLOCTEXT("SLevelViewport", "AddActorMenuFormat", "Add {0}"), MenuItem.FactoryToUse->GetDisplayName() );
|
|
if ( MenuItem.AssetData.IsValid() )
|
|
{
|
|
MenuEntryName = FText::Format( NSLOCTEXT("SLevelViewport", "AddActorUsingAssetMenuFormat", "Add {0}: {1}"),
|
|
MenuItem.FactoryToUse->GetDisplayName(),
|
|
FText::FromName( MenuItem.AssetData.AssetName ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bound event Triggered via FLevelViewportCommands::ApplyMaterialToActor, attempts to apply a material selected in the content browser
|
|
* to an actor being hovered over in the Editor viewport.
|
|
*/
|
|
void SLevelViewport::OnApplyMaterialToViewportTarget()
|
|
{
|
|
if(DroppedObjects.Num() > 0)
|
|
{
|
|
// Grab the hit proxy, used for determining which object we're potentially targeting
|
|
const HHitProxy* DroppedUponProxy = LevelViewportClient->Viewport->GetHitProxy(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y);
|
|
UObject* FirstDroppedObject = DroppedObjects[0];
|
|
|
|
// Ensure we're dropping a material asset and our target is an acceptable receiver
|
|
if(DroppedUponProxy && Cast<UMaterialInterface>(FirstDroppedObject) && LevelViewportClient->CanApplyMaterialToHitProxy(DroppedUponProxy))
|
|
{
|
|
// Drop the object, but ensure we're only affecting the target actor, not whatever may be in the current selection
|
|
TArray< AActor* > TemporaryActors;
|
|
LevelViewportClient->DropObjectsAtCoordinates(CachedOnDropLocalMousePos.X,CachedOnDropLocalMousePos.Y, DroppedObjects, TemporaryActors, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::BindCommands()
|
|
{
|
|
SEditorViewport::BindCommands();
|
|
|
|
FUICommandList& UICommandListRef = *CommandList;
|
|
|
|
BindOptionCommands( UICommandListRef );
|
|
BindViewCommands( UICommandListRef );
|
|
BindShowCommands( UICommandListRef );
|
|
BindDropCommands( UICommandListRef );
|
|
|
|
if ( ParentLevelEditor.IsValid() )
|
|
{
|
|
UICommandListRef.Append(ParentLevelEditor.Pin()->GetLevelEditorActions().ToSharedRef());
|
|
}
|
|
|
|
UICommandListRef.SetCanProduceActionForCommand( FUICommandList::FCanProduceActionForCommand::CreateSP(this, &SLevelViewport::CanProduceActionForCommand) );
|
|
}
|
|
|
|
void SLevelViewport::BindOptionCommands( FUICommandList& OutCommandList )
|
|
{
|
|
const FLevelViewportCommands& ViewportActions = FLevelViewportCommands::Get();
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.AdvancedSettings,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnAdvancedSettings ) );
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ToggleMaximize,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleMaximizeMode ),
|
|
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanToggleMaximizeMode ) );
|
|
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ToggleGameView,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleGameView ),
|
|
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanToggleGameView ),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsInGameView ) );
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ToggleImmersive,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleImmersive),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsImmersive ) );
|
|
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ToggleCinematicPreview,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllowCinematicPreview ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::AllowsCinematicPreview )
|
|
);
|
|
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.CreateCamera,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnCreateCameraActor ),
|
|
FCanExecuteAction(),
|
|
FCanExecuteAction::CreateSP( this, &SLevelViewport::IsPerspectiveViewport )
|
|
);
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.HighResScreenshot,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnTakeHighResScreenshot ),
|
|
FCanExecuteAction()
|
|
);
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ToggleActorPilotCameraView,
|
|
FExecuteAction::CreateSP(this, &SLevelViewport::ToggleActorPilotCameraView),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsLockedCameraViewEnabled )
|
|
);
|
|
|
|
// Map each bookmark action
|
|
for( int32 BookmarkIndex = 0; BookmarkIndex < AWorldSettings::MAX_BOOKMARK_NUMBER; ++BookmarkIndex )
|
|
{
|
|
OutCommandList.MapAction(
|
|
ViewportActions.JumpToBookmarkCommands[BookmarkIndex],
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnJumpToBookmark, BookmarkIndex )
|
|
);
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.SetBookmarkCommands[BookmarkIndex],
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetBookmark, BookmarkIndex )
|
|
);
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ClearBookmarkCommands[BookmarkIndex],
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnClearBookMark, BookmarkIndex )
|
|
);
|
|
}
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ClearAllBookMarks,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnClearAllBookMarks )
|
|
);
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ToggleViewportToolbar,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleShowFullToolbar ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::ShouldShowFullToolbar )
|
|
);
|
|
}
|
|
|
|
void SLevelViewport::BindViewCommands( FUICommandList& OutCommandList )
|
|
{
|
|
const FLevelViewportCommands& ViewportActions = FLevelViewportCommands::Get();
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.FindInLevelScriptBlueprint,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::FindSelectedInLevelScript ),
|
|
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanFindSelectedInLevelScript )
|
|
);
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.EjectActorPilot,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnActorUnlock ),
|
|
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanExecuteActorUnlock )
|
|
);
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.PilotSelectedActor,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnActorLockSelected ),
|
|
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanExecuteActorLockSelected )
|
|
);
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_OnePane,
|
|
FExecuteAction::CreateSP(this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::OnePane),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::OnePane));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_TwoPanesH,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::TwoPanesHoriz ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::TwoPanesHoriz ));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_TwoPanesV,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::TwoPanesVert ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::TwoPanesVert ));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_ThreePanesLeft,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::ThreePanesLeft ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::ThreePanesLeft ));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_ThreePanesRight,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::ThreePanesRight ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::ThreePanesRight ));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_ThreePanesTop,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::ThreePanesTop ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::ThreePanesTop ));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_ThreePanesBottom,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::ThreePanesBottom ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::ThreePanesBottom ));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_FourPanesLeft,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::FourPanesLeft ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::FourPanesLeft ));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_FourPanesRight,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::FourPanesRight ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::FourPanesRight ));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_FourPanesTop,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::FourPanesTop ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::FourPanesTop ));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_FourPanesBottom,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::FourPanesBottom ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::FourPanesBottom ));
|
|
|
|
OutCommandList.MapAction(
|
|
ViewportActions.ViewportConfig_FourPanes2x2,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnSetViewportConfiguration, LevelViewportConfigurationNames::FourPanes2x2 ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportConfigurationSet, LevelViewportConfigurationNames::FourPanes2x2 ));
|
|
|
|
auto ProcessViewportTypeActions = [&](FName InViewportTypeName, const FViewportTypeDefinition& InDefinition){
|
|
if (InDefinition.ActivationCommand.IsValid())
|
|
{
|
|
OutCommandList.MapAction(InDefinition.ActivationCommand, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SLevelViewport::ToggleViewportTypeActivationWithinLayout, InViewportTypeName),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SLevelViewport::IsViewportTypeWithinLayoutEqual, InViewportTypeName)
|
|
));
|
|
}
|
|
};
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
|
|
LevelEditorModule.IterateViewportTypes(ProcessViewportTypeActions);
|
|
|
|
// Map Buffer visualization mode actions
|
|
for (FLevelViewportCommands::TBufferVisualizationModeCommandMap::TConstIterator It = ViewportActions.BufferVisualizationModeCommands.CreateConstIterator(); It; ++It)
|
|
{
|
|
const FLevelViewportCommands::FBufferVisualizationRecord& Record = It.Value();
|
|
OutCommandList.MapAction(
|
|
Record.Command,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::ChangeBufferVisualizationMode, Record.Name ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsBufferVisualizationModeSelected, Record.Name ) );
|
|
}
|
|
}
|
|
|
|
|
|
void SLevelViewport::BindShowCommands( FUICommandList& OutCommandList )
|
|
{
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().UseDefaultShowFlags,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnUseDefaultShowFlags, false ) );
|
|
|
|
const TArray<FShowFlagData>& ShowFlagData = GetShowFlagMenuItems();
|
|
|
|
// Bind each show flag to the same delegate. We use the delegate payload system to figure out what show flag we are dealing with
|
|
for( int32 ShowFlag = 0; ShowFlag < ShowFlagData.Num(); ++ShowFlag )
|
|
{
|
|
const FShowFlagData& SFData = ShowFlagData[ShowFlag];
|
|
|
|
// NOTE: There should be one command per show flag so using ShowFlag as the index to ShowFlagCommands is acceptable
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().ShowFlagCommands[ ShowFlag ].ShowMenuItem,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleShowFlag, SFData.EngineShowFlagIndex ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsShowFlagEnabled, SFData.EngineShowFlagIndex ) );
|
|
}
|
|
|
|
// Show Volumes
|
|
{
|
|
// Map 'Show All' and 'Hide All' commands
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().ShowAllVolumes,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllVolumeActors, true ) );
|
|
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().HideAllVolumes,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllVolumeActors, false ) );
|
|
|
|
// Get all known volume classes
|
|
TArray< UClass* > VolumeClasses;
|
|
UUnrealEdEngine::GetSortedVolumeClasses(&VolumeClasses);
|
|
|
|
for( int32 VolumeClassIndex = 0; VolumeClassIndex < VolumeClasses.Num(); ++VolumeClassIndex )
|
|
{
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().ShowVolumeCommands[ VolumeClassIndex ].ShowMenuItem,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleShowVolumeClass, VolumeClassIndex ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsVolumeVisible, VolumeClassIndex ) );
|
|
}
|
|
}
|
|
|
|
// Show Layers
|
|
{
|
|
// Map 'Show All' and 'Hide All' commands
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().ShowAllLayers,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllLayers, true ) );
|
|
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().HideAllLayers,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllLayers, false ) );
|
|
}
|
|
|
|
// Show Sprite Categories
|
|
{
|
|
// Map 'Show All' and 'Hide All' commands
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().ShowAllSprites,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllSpriteCategories, true ) );
|
|
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().HideAllSprites,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllSpriteCategories, false ) );
|
|
|
|
// Bind each show flag to the same delegate. We use the delegate payload system to figure out what show flag we are dealing with
|
|
for( int32 CategoryIndex = 0; CategoryIndex < GUnrealEd->SpriteIDToIndexMap.Num(); ++CategoryIndex )
|
|
{
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().ShowSpriteCommands[ CategoryIndex ].ShowMenuItem,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleSpriteCategory, CategoryIndex ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SLevelViewport::IsSpriteCategoryVisible, CategoryIndex ) );
|
|
}
|
|
}
|
|
|
|
// Show Stat Categories
|
|
{
|
|
// Map 'Hide All' command
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().HideAllStats,
|
|
FExecuteAction::CreateSP(this, &SLevelViewport::OnToggleAllStatCommands, false));
|
|
|
|
for (auto StatCatIt = FLevelViewportCommands::Get().ShowStatCatCommands.CreateConstIterator(); StatCatIt; ++StatCatIt)
|
|
{
|
|
const TArray< FLevelViewportCommands::FShowMenuCommand >& ShowStatCommands = StatCatIt.Value();
|
|
for (int32 StatIndex = 0; StatIndex < ShowStatCommands.Num(); ++StatIndex)
|
|
{
|
|
const FLevelViewportCommands::FShowMenuCommand& StatCommand = ShowStatCommands[StatIndex];
|
|
BindStatCommand(StatCommand.ShowMenuItem, StatCommand.LabelOverride.ToString());
|
|
}
|
|
}
|
|
|
|
// Bind a listener here for any additional stat commands that get registered later.
|
|
FLevelViewportCommands::NewStatCommandDelegate.AddRaw(this, &SLevelViewport::BindStatCommand);
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::BindDropCommands( FUICommandList& OutCommandList )
|
|
{
|
|
OutCommandList.MapAction(
|
|
FLevelViewportCommands::Get().ApplyMaterialToActor,
|
|
FExecuteAction::CreateSP( this, &SLevelViewport::OnApplyMaterialToViewportTarget ) );
|
|
}
|
|
|
|
|
|
void SLevelViewport::BindStatCommand(const TSharedPtr<FUICommandInfo> InMenuItem, const FString& InCommandName)
|
|
{
|
|
CommandList->MapAction(
|
|
InMenuItem,
|
|
FExecuteAction::CreateSP(this, &SLevelViewport::ToggleStatCommand, InCommandName),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SLevelViewport::IsStatCommandVisible, InCommandName));
|
|
}
|
|
|
|
const FSlateBrush* SLevelViewport::OnGetViewportBorderBrush() const
|
|
{
|
|
const FSlateBrush* BorderBrush = NULL;
|
|
if( FSlateApplication::Get().IsNormalExecution() )
|
|
{
|
|
// Only show the active border if we have a valid client, its the current client being edited and we arent in immersive (in immersive there is only one visible viewport)
|
|
if( LevelViewportClient.IsValid() && LevelViewportClient.Get() == GCurrentLevelEditingViewportClient && !IsImmersive() )
|
|
{
|
|
BorderBrush = ActiveBorder;
|
|
}
|
|
else
|
|
{
|
|
BorderBrush = NoBorder;
|
|
}
|
|
|
|
// If a PIE/SIE/Editor transition just completed, then we'll draw a border effect to draw attention to it
|
|
if( ViewTransitionAnim.IsPlaying() )
|
|
{
|
|
switch( ViewTransitionType )
|
|
{
|
|
case EViewTransition::FadingIn:
|
|
BorderBrush = BlackBackground;
|
|
break;
|
|
|
|
case EViewTransition::StartingPlayInEditor:
|
|
BorderBrush = StartingPlayInEditorBorder;
|
|
break;
|
|
|
|
case EViewTransition::StartingSimulate:
|
|
BorderBrush = StartingSimulateBorder;
|
|
break;
|
|
|
|
case EViewTransition::ReturningToEditor:
|
|
BorderBrush = ReturningToEditorBorder;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BorderBrush = DebuggingBorder;
|
|
}
|
|
|
|
return BorderBrush;
|
|
}
|
|
|
|
FSlateColor SLevelViewport::OnGetViewportBorderColorAndOpacity() const
|
|
{
|
|
FLinearColor ViewportBorderColorAndOpacity = FLinearColor::White;
|
|
if( FSlateApplication::Get().IsNormalExecution() )
|
|
{
|
|
if( ViewTransitionAnim.IsPlaying() )
|
|
{
|
|
ViewportBorderColorAndOpacity = FLinearColor( 1.0f, 1.0f, 1.0f, 1.0f - ViewTransitionAnim.GetLerp() );
|
|
}
|
|
}
|
|
return ViewportBorderColorAndOpacity;
|
|
}
|
|
|
|
EVisibility SLevelViewport::OnGetViewportContentVisibility() const
|
|
{
|
|
// Do not show any of the viewports inner slate content (active viewport borders, etc) when we are playing in editor and in immersive mode
|
|
// as they are meaningless in that situation
|
|
EVisibility BaseVisibility = SEditorViewport::OnGetViewportContentVisibility();
|
|
if (BaseVisibility != EVisibility::Visible)
|
|
{
|
|
return BaseVisibility;
|
|
}
|
|
return IsPlayInEditorViewportActive() && IsImmersive() ? EVisibility::Collapsed : EVisibility::Visible;
|
|
}
|
|
|
|
EVisibility SLevelViewport::GetToolBarVisibility() const
|
|
{
|
|
// Do not show the toolbar if this viewport has a play in editor session
|
|
return IsPlayInEditorViewportActive() ? EVisibility::Collapsed : OnGetViewportContentVisibility();
|
|
}
|
|
|
|
EVisibility SLevelViewport::GetMaximizeToggleVisibility() const
|
|
{
|
|
bool bIsMaximizeSupported = false;
|
|
bool bShowMaximizeToggle = false;
|
|
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
|
|
if (LayoutPinned.IsValid())
|
|
{
|
|
bIsMaximizeSupported = LayoutPinned->IsMaximizeSupported();
|
|
bShowMaximizeToggle = !LayoutPinned->IsTransitioning();
|
|
}
|
|
|
|
// Do not show the maximize/minimize toggle when in immersive mode
|
|
return (!bIsMaximizeSupported || IsImmersive()) ? EVisibility::Collapsed : (bShowMaximizeToggle ? EVisibility::Visible : EVisibility::Hidden);
|
|
}
|
|
|
|
EVisibility SLevelViewport::GetCloseImmersiveButtonVisibility() const
|
|
{
|
|
// Do not show the Immersive toggle button when not in immersive mode
|
|
return IsImmersive() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SLevelViewport::GetTransformToolbarVisibility() const
|
|
{
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
|
|
TSharedPtr<ILevelViewport> ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport();
|
|
|
|
// Am I the ActiveLevelViewport?
|
|
if( ActiveLevelViewport.Get() == this )
|
|
{
|
|
// Only return visible if we are/were the active viewport.
|
|
return EVisibility::Visible;
|
|
}
|
|
|
|
return EVisibility::Hidden;
|
|
}
|
|
|
|
bool SLevelViewport::IsMaximized() const
|
|
{
|
|
if( ParentLayout.IsValid() && !ConfigKey.IsEmpty() )
|
|
{
|
|
return ParentLayout.Pin()->IsViewportMaximized( *ConfigKey );
|
|
}
|
|
|
|
// Assume the viewport is always maximized if we have no layout for some reason
|
|
return true;
|
|
}
|
|
|
|
TSharedRef<FEditorViewportClient> SLevelViewport::MakeEditorViewportClient()
|
|
{
|
|
return LevelViewportClient.ToSharedRef();
|
|
}
|
|
|
|
TSharedPtr<SWidget> SLevelViewport::MakeViewportToolbar()
|
|
{
|
|
|
|
// Build our toolbar level toolbar
|
|
TSharedRef< SLevelViewportToolBar > ToolBar =
|
|
SNew( SLevelViewportToolBar )
|
|
.Viewport( SharedThis( this ) )
|
|
.Visibility( this, &SLevelViewport::GetToolBarVisibility )
|
|
.IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() );
|
|
|
|
|
|
return
|
|
SNew(SVerticalBox)
|
|
.Visibility( EVisibility::SelfHitTestInvisible )
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
ToolBar
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.VAlign(VAlign_Top)
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(SActorPilotViewportToolbar)
|
|
.Viewport( SharedThis( this ) )
|
|
.Visibility(this, &SLevelViewport::GetLockedIconVisibility)
|
|
];
|
|
}
|
|
|
|
void SLevelViewport::OnUndo()
|
|
{
|
|
GUnrealEd->Exec( GetWorld(), TEXT("TRANSACTION UNDO") );
|
|
}
|
|
|
|
void SLevelViewport::OnRedo()
|
|
{
|
|
GUnrealEd->Exec( GetWorld(), TEXT("TRANSACTION REDO") );
|
|
}
|
|
|
|
bool SLevelViewport::CanExecuteUndo() const
|
|
{
|
|
return GUnrealEd->Trans->CanUndo() && FSlateApplication::Get().IsNormalExecution();
|
|
}
|
|
|
|
bool SLevelViewport::CanExecuteRedo() const
|
|
{
|
|
return GUnrealEd->Trans->CanRedo() && FSlateApplication::Get().IsNormalExecution();
|
|
}
|
|
|
|
void SLevelViewport::OnAdvancedSettings()
|
|
{
|
|
FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer("Editor", "LevelEditor", "Viewport");
|
|
}
|
|
|
|
void SLevelViewport::OnToggleImmersive()
|
|
{
|
|
if( ParentLayout.IsValid() )
|
|
{
|
|
bool bWantImmersive = !IsImmersive();
|
|
bool bWantMaximize = IsMaximized();
|
|
|
|
// We always want to animate in response to user-interactive toggling of maximized state
|
|
const bool bAllowAnimation = true;
|
|
|
|
FName ViewportName = *ConfigKey;
|
|
if (!ViewportName.IsNone())
|
|
{
|
|
ParentLayout.Pin()->RequestMaximizeViewport( ViewportName, bWantMaximize, bWantImmersive, bAllowAnimation );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::IsImmersive() const
|
|
{
|
|
if( ParentLayout.IsValid() && !ConfigKey.IsEmpty() )
|
|
{
|
|
return ParentLayout.Pin()->IsViewportImmersive( *ConfigKey );
|
|
}
|
|
|
|
// Assume the viewport is not immersive if we have no layout for some reason
|
|
return false;
|
|
}
|
|
|
|
void SLevelViewport::OnCreateCameraActor()
|
|
{
|
|
// Find the perspective viewport we were using
|
|
FViewport* pViewPort = GEditor->GetActiveViewport();
|
|
FLevelEditorViewportClient* ViewportClient = NULL;
|
|
for( int32 iView = 0; iView < GEditor->LevelViewportClients.Num(); iView++ )
|
|
{
|
|
if( GEditor->LevelViewportClients[ iView ]->IsPerspective() && GEditor->LevelViewportClients[ iView ]->Viewport == pViewPort )
|
|
{
|
|
ViewportClient = GEditor->LevelViewportClients[ iView ];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( ViewportClient == NULL )
|
|
{
|
|
// May fail to find viewport if shortcut key was pressed on an ortho viewport, if so early out.
|
|
// This function only works on perspective viewports so new camera can match perspective camera.
|
|
return;
|
|
}
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("LevelViewport", "CreateCameraHere", "Create Camera Here"));
|
|
|
|
// Set new camera to match viewport
|
|
ACameraActor* pNewCamera = ViewportClient->GetWorld()->SpawnActor<ACameraActor>();
|
|
pNewCamera->SetActorLocation( ViewportClient->GetViewLocation(), false );
|
|
pNewCamera->SetActorRotation( ViewportClient->GetViewRotation() );
|
|
pNewCamera->CameraComponent->FieldOfView = ViewportClient->ViewFOV;
|
|
|
|
// Deselect any currently selected actors
|
|
GUnrealEd->SelectNone( true, true );
|
|
GEditor->GetSelectedActors()->DeselectAll();
|
|
GEditor->GetSelectedObjects()->DeselectAll();
|
|
|
|
// Select newly created Camera
|
|
TArray<UObject *> SelectedActors;
|
|
GEditor->SelectActor( pNewCamera, true, false );
|
|
SelectedActors.Add( pNewCamera );
|
|
|
|
// Send notification about actors that may have changed
|
|
ULevel::LevelDirtiedEvent.Broadcast();
|
|
|
|
// Update the details window with the actors we have just selected
|
|
GUnrealEd->UpdateFloatingPropertyWindowsFromActorList( SelectedActors );
|
|
|
|
// Redraw viewports to show new camera
|
|
GEditor->RedrawAllViewports();
|
|
}
|
|
|
|
bool SLevelViewport::IsPerspectiveViewport() const
|
|
{
|
|
bool bIsPerspective = false;
|
|
FViewport* pViewPort = GEditor->GetActiveViewport();
|
|
if( pViewPort && pViewPort->GetClient()->IsOrtho() == false )
|
|
{
|
|
bIsPerspective = true;
|
|
}
|
|
return bIsPerspective;
|
|
}
|
|
|
|
void SLevelViewport::OnTakeHighResScreenshot()
|
|
{
|
|
HighResScreenshotDialog = SHighResScreenshotDialog::OpenDialog(ActiveViewport, CaptureRegionWidget);
|
|
}
|
|
|
|
void SLevelViewport::ToggleGameView()
|
|
{
|
|
if( LevelViewportClient->IsPerspective() )
|
|
{
|
|
bool bGameViewEnable = !LevelViewportClient->IsInGameView();
|
|
|
|
LevelViewportClient->SetGameView(bGameViewEnable);
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::CanToggleGameView() const
|
|
{
|
|
return LevelViewportClient->IsPerspective();
|
|
}
|
|
|
|
bool SLevelViewport::IsInGameView() const
|
|
{
|
|
return LevelViewportClient->IsInGameView();
|
|
}
|
|
|
|
|
|
void SLevelViewport::ChangeBufferVisualizationMode( FName InName )
|
|
{
|
|
LevelViewportClient->SetViewMode(VMI_VisualizeBuffer);
|
|
LevelViewportClient->CurrentBufferVisualizationMode = InName;
|
|
}
|
|
|
|
bool SLevelViewport::IsBufferVisualizationModeSelected( FName InName ) const
|
|
{
|
|
return LevelViewportClient->IsViewModeEnabled( VMI_VisualizeBuffer ) && LevelViewportClient->CurrentBufferVisualizationMode == InName;
|
|
}
|
|
|
|
void SLevelViewport::OnToggleAllVolumeActors( bool bVisible )
|
|
{
|
|
// Reinitialize the volume actor visibility flags to the new state. All volumes should be visible if "Show All" was selected and hidden if it was not selected.
|
|
LevelViewportClient->VolumeActorVisibility.Init( bVisible, LevelViewportClient->VolumeActorVisibility.Num() );
|
|
|
|
// Update visibility based on the new state
|
|
// All volume actor types should be taken since the user clicked on show or hide all to get here
|
|
GUnrealEd->UpdateVolumeActorVisibility( NULL, LevelViewportClient.Get() );
|
|
}
|
|
|
|
/** Called when the user toggles a volume visibility from Volumes sub-menu. **/
|
|
void SLevelViewport::ToggleShowVolumeClass( int32 VolumeID )
|
|
{
|
|
TArray< UClass* > VolumeClasses;
|
|
UUnrealEdEngine::GetSortedVolumeClasses(&VolumeClasses);
|
|
|
|
// Get the corresponding volume class for the clicked menu item.
|
|
UClass *SelectedVolumeClass = VolumeClasses[ VolumeID ];
|
|
|
|
LevelViewportClient->VolumeActorVisibility[ VolumeID ] = !LevelViewportClient->VolumeActorVisibility[ VolumeID ];
|
|
|
|
// Update the found actors visibility based on the new bitfield
|
|
GUnrealEd->UpdateVolumeActorVisibility( SelectedVolumeClass, LevelViewportClient.Get() );
|
|
}
|
|
|
|
/** Called to determine if vlume class is visible. **/
|
|
bool SLevelViewport::IsVolumeVisible( int32 VolumeID ) const
|
|
{
|
|
return LevelViewportClient->VolumeActorVisibility[ VolumeID ];
|
|
}
|
|
|
|
/** Called when a user selects show or hide all from the layers visibility menu. **/
|
|
void SLevelViewport::OnToggleAllLayers( bool bVisible )
|
|
{
|
|
if (bVisible)
|
|
{
|
|
// clear all hidden layers
|
|
LevelViewportClient->ViewHiddenLayers.Empty();
|
|
}
|
|
else
|
|
{
|
|
// hide them all
|
|
TArray<FName> AllLayerNames;
|
|
GEditor->Layers->AddAllLayerNamesTo(AllLayerNames);
|
|
LevelViewportClient->ViewHiddenLayers = AllLayerNames;
|
|
}
|
|
|
|
// update actor visibility for this view
|
|
GEditor->Layers->UpdatePerViewVisibility(LevelViewportClient.Get());
|
|
|
|
LevelViewportClient->Invalidate();
|
|
}
|
|
|
|
/** Called when the user toggles a layer from Layers sub-menu. **/
|
|
void SLevelViewport::ToggleShowLayer( FName LayerName )
|
|
{
|
|
int32 HiddenIndex = LevelViewportClient->ViewHiddenLayers.Find(LayerName);
|
|
if ( HiddenIndex == INDEX_NONE )
|
|
{
|
|
LevelViewportClient->ViewHiddenLayers.Add(LayerName);
|
|
}
|
|
else
|
|
{
|
|
LevelViewportClient->ViewHiddenLayers.RemoveAt(HiddenIndex);
|
|
}
|
|
|
|
// update actor visibility for this view
|
|
GEditor->Layers->UpdatePerViewVisibility(LevelViewportClient.Get(), LayerName);
|
|
|
|
LevelViewportClient->Invalidate();
|
|
}
|
|
|
|
/** Called to determine if a layer is visible. **/
|
|
bool SLevelViewport::IsLayerVisible( FName LayerName ) const
|
|
{
|
|
return LevelViewportClient->ViewHiddenLayers.Find(LayerName) == INDEX_NONE;
|
|
}
|
|
|
|
void SLevelViewport::ToggleShowFoliageType(TWeakObjectPtr<UFoliageType> InFoliageType)
|
|
{
|
|
UFoliageType* FoliageType = InFoliageType.Get();
|
|
if (FoliageType)
|
|
{
|
|
FoliageType->HiddenEditorViews^= (1ull << LevelViewportClient->ViewIndex);
|
|
// Notify UFoliageType that things have changed
|
|
FoliageType->OnHiddenEditorViewMaskChanged(GetWorld());
|
|
|
|
// Make sure to redraw viewport when user toggles foliage
|
|
LevelViewportClient->Invalidate();
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::ToggleAllFoliageTypes(bool bVisible)
|
|
{
|
|
UWorld* CurrentWorld = GetWorld();
|
|
TArray<UFoliageType*> AllFoliageTypes = GEditor->GetFoliageTypesInWorld(CurrentWorld);
|
|
if (AllFoliageTypes.Num())
|
|
{
|
|
const uint64 ViewMask = (1ull << LevelViewportClient->ViewIndex);
|
|
|
|
for (UFoliageType* FoliageType : AllFoliageTypes)
|
|
{
|
|
if (bVisible)
|
|
{
|
|
FoliageType->HiddenEditorViews&= ~ViewMask;
|
|
}
|
|
else
|
|
{
|
|
FoliageType->HiddenEditorViews|= ViewMask;
|
|
}
|
|
|
|
FoliageType->OnHiddenEditorViewMaskChanged(CurrentWorld);
|
|
}
|
|
|
|
// Make sure to redraw viewport when user toggles meshes
|
|
LevelViewportClient->Invalidate();
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::IsFoliageTypeVisible(TWeakObjectPtr<UFoliageType> InFoliageType) const
|
|
{
|
|
const UFoliageType* FoliageType = InFoliageType.Get();
|
|
if (FoliageType)
|
|
{
|
|
return (FoliageType->HiddenEditorViews & (1ull << LevelViewportClient->ViewIndex)) == 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FViewport* SLevelViewport::GetActiveViewport()
|
|
{
|
|
return ActiveViewport->GetViewport();
|
|
}
|
|
|
|
void SLevelViewport::OnFocusViewportToSelection()
|
|
{
|
|
GUnrealEd->Exec( GetWorld(), TEXT("CAMERA ALIGN ACTIVEVIEWPORTONLY") );
|
|
}
|
|
|
|
/** Called when the user selects show or hide all from the sprite sub-menu. **/
|
|
void SLevelViewport::OnToggleAllSpriteCategories( bool bVisible )
|
|
{
|
|
LevelViewportClient->SetAllSpriteCategoryVisibility( bVisible );
|
|
LevelViewportClient->Invalidate();
|
|
}
|
|
|
|
/** Called when the user toggles a category from the sprite sub-menu. **/
|
|
void SLevelViewport::ToggleSpriteCategory( int32 CategoryID )
|
|
{
|
|
LevelViewportClient->SetSpriteCategoryVisibility( CategoryID, !LevelViewportClient->GetSpriteCategoryVisibility( CategoryID ) );
|
|
LevelViewportClient->Invalidate();
|
|
}
|
|
|
|
/** Called to determine if a category from the sprite sub-menu is visible. **/
|
|
bool SLevelViewport::IsSpriteCategoryVisible( int32 CategoryID ) const
|
|
{
|
|
return LevelViewportClient->GetSpriteCategoryVisibility( CategoryID );
|
|
}
|
|
|
|
void SLevelViewport::OnToggleAllStatCommands( bool bVisible )
|
|
{
|
|
check(bVisible == 0);
|
|
// If it's in the array, it's visible so just toggle it again
|
|
const TArray<FString>* EnabledStats = LevelViewportClient->GetEnabledStats();
|
|
check(EnabledStats);
|
|
while (EnabledStats->Num() > 0)
|
|
{
|
|
const FString& CommandName = EnabledStats->Last();
|
|
ToggleStatCommand(CommandName);
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::OnUseDefaultShowFlags(bool bUseSavedDefaults)
|
|
{
|
|
// cache off the current viewmode as it gets trashed when applying FEngineShowFlags()
|
|
const EViewModeIndex CachedViewMode = LevelViewportClient->GetViewMode();
|
|
|
|
// Setting show flags to the defaults should not stomp on the current viewmode settings.
|
|
LevelViewportClient->SetGameView(false);
|
|
|
|
// Get default save flags
|
|
FEngineShowFlags EditorShowFlags(ESFIM_Editor);
|
|
FEngineShowFlags GameShowFlags(ESFIM_Game);
|
|
|
|
if (bUseSavedDefaults && !ConfigKey.IsEmpty())
|
|
{
|
|
FLevelEditorViewportInstanceSettings ViewportInstanceSettings;
|
|
ViewportInstanceSettings.ViewportType = LevelViewportClient->ViewportType;
|
|
|
|
// Get saved defaults if specified
|
|
const FLevelEditorViewportInstanceSettings* const ViewportInstanceSettingsPtr = GetDefault<ULevelEditorViewportSettings>()->GetViewportInstanceSettings(ConfigKey);
|
|
ViewportInstanceSettings = ViewportInstanceSettingsPtr ? *ViewportInstanceSettingsPtr : LoadLegacyConfigFromIni(ConfigKey, ViewportInstanceSettings);
|
|
|
|
if (!ViewportInstanceSettings.EditorShowFlagsString.IsEmpty())
|
|
{
|
|
EditorShowFlags.SetFromString(*ViewportInstanceSettings.EditorShowFlagsString);
|
|
}
|
|
|
|
if (!ViewportInstanceSettings.GameShowFlagsString.IsEmpty())
|
|
{
|
|
GameShowFlags.SetFromString(*ViewportInstanceSettings.GameShowFlagsString);
|
|
}
|
|
}
|
|
|
|
// this trashes the current viewmode!
|
|
LevelViewportClient->EngineShowFlags = EditorShowFlags;
|
|
// Restore the state of SelectionOutline based on user settings
|
|
LevelViewportClient->EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline);
|
|
LevelViewportClient->LastEngineShowFlags = GameShowFlags;
|
|
|
|
// re-apply the cached viewmode, as it was trashed with FEngineShowFlags()
|
|
ApplyViewMode(CachedViewMode, LevelViewportClient->IsPerspective(), LevelViewportClient->EngineShowFlags);
|
|
ApplyViewMode(CachedViewMode, LevelViewportClient->IsPerspective(), LevelViewportClient->LastEngineShowFlags);
|
|
|
|
// set volume / layer / sprite visibility defaults
|
|
if (!bUseSavedDefaults)
|
|
{
|
|
LevelViewportClient->InitializeVisibilityFlags();
|
|
GUnrealEd->UpdateVolumeActorVisibility(NULL, LevelViewportClient.Get());
|
|
GEditor->Layers->UpdatePerViewVisibility(LevelViewportClient.Get());
|
|
}
|
|
|
|
LevelViewportClient->Invalidate();
|
|
}
|
|
|
|
|
|
void SLevelViewport::SetKeyboardFocusToThisViewport()
|
|
{
|
|
if( ensure( ViewportWidget.IsValid() ) )
|
|
{
|
|
// Set keyboard focus directly
|
|
FSlateApplication::Get().SetKeyboardFocus( ViewportWidget.ToSharedRef() );
|
|
}
|
|
}
|
|
|
|
|
|
void SLevelViewport::SaveConfig(const FString& ConfigName)
|
|
{
|
|
// When we startup the editor we always start it up in IsInGameView()=false mode
|
|
FEngineShowFlags& EditorShowFlagsToSave = LevelViewportClient->IsInGameView() ? LevelViewportClient->LastEngineShowFlags : LevelViewportClient->EngineShowFlags;
|
|
FEngineShowFlags& GameShowFlagsToSave = LevelViewportClient->IsInGameView() ? LevelViewportClient->EngineShowFlags : LevelViewportClient->LastEngineShowFlags;
|
|
|
|
FLevelEditorViewportInstanceSettings ViewportInstanceSettings;
|
|
ViewportInstanceSettings.ViewportType = LevelViewportClient->ViewportType;
|
|
ViewportInstanceSettings.PerspViewModeIndex = LevelViewportClient->GetPerspViewMode();
|
|
ViewportInstanceSettings.OrthoViewModeIndex = LevelViewportClient->GetOrthoViewMode();
|
|
ViewportInstanceSettings.EditorShowFlagsString = EditorShowFlagsToSave.ToString();
|
|
ViewportInstanceSettings.GameShowFlagsString = GameShowFlagsToSave.ToString();
|
|
ViewportInstanceSettings.BufferVisualizationMode = LevelViewportClient->CurrentBufferVisualizationMode;
|
|
ViewportInstanceSettings.ExposureSettings = LevelViewportClient->ExposureSettings;
|
|
ViewportInstanceSettings.FOVAngle = LevelViewportClient->FOVAngle;
|
|
ViewportInstanceSettings.bIsRealtime = LevelViewportClient->IsRealtime();
|
|
ViewportInstanceSettings.bShowStats = LevelViewportClient->ShouldShowStats();
|
|
ViewportInstanceSettings.FarViewPlane = LevelViewportClient->GetFarClipPlaneOverride();
|
|
ViewportInstanceSettings.bShowFullToolbar = bShowFullToolbar;
|
|
|
|
if (GetDefault<ULevelEditorViewportSettings>()->bSaveEngineStats)
|
|
{
|
|
const TArray<FString>* EnabledStats = NULL;
|
|
|
|
// If the selected viewport is currently hosting a PIE session, we need to make sure we copy to stats from the active viewport
|
|
// Note: This happens if you close the editor while it's running because SwapStatCommands gets called after the config save when shutting down.
|
|
if (IsPlayInEditorViewportActive())
|
|
{
|
|
EnabledStats = ActiveViewport->GetClient()->GetEnabledStats();
|
|
}
|
|
else
|
|
{
|
|
EnabledStats = LevelViewportClient->GetEnabledStats();
|
|
}
|
|
|
|
check(EnabledStats);
|
|
ViewportInstanceSettings.EnabledStats = *EnabledStats;
|
|
}
|
|
GetMutableDefault<ULevelEditorViewportSettings>()->SetViewportInstanceSettings(ConfigName, ViewportInstanceSettings);
|
|
}
|
|
|
|
|
|
FLevelEditorViewportInstanceSettings SLevelViewport::LoadLegacyConfigFromIni(const FString& InConfigKey, const FLevelEditorViewportInstanceSettings& InDefaultSettings)
|
|
{
|
|
FLevelEditorViewportInstanceSettings ViewportInstanceSettings = InDefaultSettings;
|
|
|
|
const FString& IniSection = FLayoutSaveRestore::GetAdditionalLayoutConfigIni();
|
|
|
|
{
|
|
int32 ViewportTypeAsInt = ViewportInstanceSettings.ViewportType;
|
|
GConfig->GetInt(*IniSection, *(InConfigKey + TEXT(".Type")), ViewportTypeAsInt, GEditorPerProjectIni);
|
|
ViewportInstanceSettings.ViewportType = (ViewportTypeAsInt == -1 || ViewportTypeAsInt == 255) ? LVT_None : static_cast<ELevelViewportType>(ViewportTypeAsInt); // LVT_None used to be -1 or 255
|
|
|
|
if(ViewportInstanceSettings.ViewportType == LVT_None)
|
|
{
|
|
ViewportInstanceSettings.ViewportType = LVT_Perspective;
|
|
}
|
|
}
|
|
|
|
GConfig->GetString(*IniSection, *(InConfigKey + TEXT(".EditorShowFlags")), ViewportInstanceSettings.EditorShowFlagsString, GEditorPerProjectIni);
|
|
GConfig->GetString(*IniSection, *(InConfigKey + TEXT(".GameShowFlags")), ViewportInstanceSettings.GameShowFlagsString, GEditorPerProjectIni);
|
|
|
|
// A single view mode index has been deprecated in favor of separate perspective and orthographic settings
|
|
EViewModeIndex LegacyViewModeIndex = VMI_Unknown;
|
|
{
|
|
int32 LegacyVMIAsInt = VMI_Unknown;
|
|
GConfig->GetInt(*IniSection, *(InConfigKey+TEXT(".ViewModeIndex")), LegacyVMIAsInt, GEditorPerProjectIni);
|
|
LegacyViewModeIndex = (LegacyVMIAsInt == -1) ? VMI_Unknown : static_cast<EViewModeIndex>(LegacyVMIAsInt); // VMI_Unknown used to be -1
|
|
}
|
|
|
|
if(!GConfig->GetInt(*IniSection, *(InConfigKey+TEXT(".PerspViewModeIndex")), (int32&)ViewportInstanceSettings.PerspViewModeIndex, GEditorPerProjectIni))
|
|
{
|
|
if(ViewportInstanceSettings.ViewportType == LVT_Perspective)
|
|
{
|
|
// This viewport may pre-date the ViewModeIndex setting (VMI_Unknown), if so, try to be backward compatible
|
|
ViewportInstanceSettings.PerspViewModeIndex = (LegacyViewModeIndex == VMI_Unknown) ? FindViewMode(LevelViewportClient->EngineShowFlags) : LegacyViewModeIndex;
|
|
}
|
|
else
|
|
{
|
|
// Default to Lit for a perspective viewport
|
|
ViewportInstanceSettings.PerspViewModeIndex = VMI_Lit;
|
|
}
|
|
}
|
|
|
|
if(!GConfig->GetInt(*IniSection, *(InConfigKey+TEXT(".OrthoViewModeIndex")), (int32&)ViewportInstanceSettings.OrthoViewModeIndex, GEditorPerProjectIni))
|
|
{
|
|
// Default to Brush Wireframe for an orthographic viewport
|
|
ViewportInstanceSettings.OrthoViewModeIndex = (ViewportInstanceSettings.ViewportType != LVT_Perspective && LegacyViewModeIndex != VMI_Unknown) ? LegacyViewModeIndex : VMI_BrushWireframe;
|
|
}
|
|
|
|
{
|
|
FString BufferVisualizationModeString;
|
|
if(GConfig->GetString(*IniSection, *(InConfigKey+TEXT(".BufferVisualizationMode")), BufferVisualizationModeString, GEditorPerProjectIni))
|
|
{
|
|
ViewportInstanceSettings.BufferVisualizationMode = *BufferVisualizationModeString;
|
|
}
|
|
}
|
|
|
|
{
|
|
FString ExposureSettingsString;
|
|
if(GConfig->GetString(*IniSection, *(InConfigKey + TEXT(".ExposureSettings")), ExposureSettingsString, GEditorPerProjectIni))
|
|
{
|
|
ViewportInstanceSettings.ExposureSettings.SetFromString(*ExposureSettingsString);
|
|
}
|
|
}
|
|
|
|
GConfig->GetBool(*IniSection, *(InConfigKey + TEXT(".bIsRealtime")), ViewportInstanceSettings.bIsRealtime, GEditorPerProjectIni);
|
|
GConfig->GetBool(*IniSection, *(InConfigKey + TEXT(".bWantStats")), ViewportInstanceSettings.bShowStats, GEditorPerProjectIni);
|
|
GConfig->GetBool(*IniSection, *(InConfigKey + TEXT(".bWantFPS")), ViewportInstanceSettings.bShowFPS_DEPRECATED, GEditorPerProjectIni);
|
|
GConfig->GetFloat(*IniSection, *(InConfigKey + TEXT(".FOVAngle")), ViewportInstanceSettings.FOVAngle, GEditorPerProjectIni);
|
|
|
|
return ViewportInstanceSettings;
|
|
}
|
|
|
|
|
|
void SLevelViewport::OnSetBookmark( int32 BookmarkIndex )
|
|
{
|
|
GLevelEditorModeTools().SetBookmark( BookmarkIndex, LevelViewportClient.Get() );
|
|
}
|
|
|
|
void SLevelViewport::OnJumpToBookmark( int32 BookmarkIndex )
|
|
{
|
|
const bool bShouldRestoreLevelVisibility = true;
|
|
GLevelEditorModeTools().JumpToBookmark( BookmarkIndex, bShouldRestoreLevelVisibility, LevelViewportClient.Get() );
|
|
}
|
|
|
|
void SLevelViewport::OnClearBookMark( int32 BookmarkIndex )
|
|
{
|
|
GLevelEditorModeTools().ClearBookmark( BookmarkIndex, LevelViewportClient.Get() );
|
|
}
|
|
|
|
void SLevelViewport::OnClearAllBookMarks()
|
|
{
|
|
GLevelEditorModeTools().ClearAllBookmarks( LevelViewportClient.Get() );
|
|
}
|
|
|
|
void SLevelViewport::OnToggleAllowCinematicPreview()
|
|
{
|
|
// Reset the FOV of Viewport for cases where we have been previewing the matinee with a changing FOV
|
|
LevelViewportClient->ViewFOV = LevelViewportClient->AllowsCinematicPreview() ? LevelViewportClient->ViewFOV : LevelViewportClient->FOVAngle;
|
|
|
|
LevelViewportClient->SetAllowCinematicPreview( !LevelViewportClient->AllowsCinematicPreview() );
|
|
LevelViewportClient->Invalidate( false );
|
|
}
|
|
|
|
bool SLevelViewport::AllowsCinematicPreview() const
|
|
{
|
|
return LevelViewportClient->AllowsCinematicPreview();
|
|
}
|
|
|
|
void SLevelViewport::OnIncrementPositionGridSize()
|
|
{
|
|
GEditor->GridSizeIncrement();
|
|
GEditor->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void SLevelViewport::OnDecrementPositionGridSize()
|
|
{
|
|
GEditor->GridSizeDecrement();
|
|
GEditor->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void SLevelViewport::OnIncrementRotationGridSize()
|
|
{
|
|
GEditor->RotGridSizeIncrement();
|
|
GEditor->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void SLevelViewport::OnDecrementRotationGridSize()
|
|
{
|
|
GEditor->RotGridSizeDecrement();
|
|
GEditor->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void SLevelViewport::OnActorLockToggleFromMenu(AActor* Actor)
|
|
{
|
|
if (Actor != NULL)
|
|
{
|
|
const bool bLockNewActor = Actor != LevelViewportClient->GetActiveActorLock().Get();
|
|
|
|
// Lock the new actor if it wasn't the same actor that we just unlocked
|
|
if (bLockNewActor)
|
|
{
|
|
// Unlock the previous actor
|
|
OnActorUnlock();
|
|
|
|
LockActorInternal(Actor);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::IsActorLocked(const TWeakObjectPtr<AActor> Actor) const
|
|
{
|
|
return LevelViewportClient->IsActorLocked(Actor);
|
|
}
|
|
|
|
bool SLevelViewport::IsAnyActorLocked() const
|
|
{
|
|
return LevelViewportClient->IsAnyActorLocked();
|
|
}
|
|
|
|
void SLevelViewport::ToggleActorPilotCameraView()
|
|
{
|
|
LevelViewportClient->bLockedCameraView = !LevelViewportClient->bLockedCameraView;
|
|
}
|
|
|
|
bool SLevelViewport::IsLockedCameraViewEnabled() const
|
|
{
|
|
return LevelViewportClient->bLockedCameraView;
|
|
}
|
|
|
|
void SLevelViewport::FindSelectedInLevelScript()
|
|
{
|
|
GUnrealEd->FindSelectedActorsInLevelScript();
|
|
}
|
|
|
|
bool SLevelViewport::CanFindSelectedInLevelScript() const
|
|
{
|
|
AActor* Actor = GEditor->GetSelectedActors()->GetTop<AActor>();
|
|
return (Actor != NULL);
|
|
}
|
|
|
|
void SLevelViewport::OnActorUnlock()
|
|
{
|
|
if (AActor* LockedActor = LevelViewportClient->GetActiveActorLock().Get())
|
|
{
|
|
// Check to see if the locked actor was previously overriding the camera settings
|
|
if (CanGetCameraInformationFromActor(LockedActor))
|
|
{
|
|
// Reset the settings
|
|
LevelViewportClient->ViewFOV = LevelViewportClient->FOVAngle;
|
|
}
|
|
|
|
LevelViewportClient->SetActorLock(nullptr);
|
|
|
|
// remove roll and pitch from camera when unbinding from actors
|
|
GEditor->RemovePerspectiveViewRotation(true, true, false);
|
|
|
|
// If we had a camera actor locked, and it was selected, then we should re-show the inset preview
|
|
OnPreviewSelectedCamerasChange();
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::CanExecuteActorUnlock() const
|
|
{
|
|
return IsAnyActorLocked();
|
|
}
|
|
|
|
void SLevelViewport::OnActorLockSelected()
|
|
{
|
|
USelection* ActorSelection = GEditor->GetSelectedActors();
|
|
if (1 == ActorSelection->Num())
|
|
{
|
|
AActor* Actor = CastChecked<AActor>(ActorSelection->GetSelectedObject(0));
|
|
LockActorInternal(Actor);
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::CanExecuteActorLockSelected() const
|
|
{
|
|
USelection* ActorSelection = GEditor->GetSelectedActors();
|
|
if (1 == ActorSelection->Num())
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SLevelViewport::IsSelectedActorLocked() const
|
|
{
|
|
USelection* ActorSelection = GEditor->GetSelectedActors();
|
|
if (1 == ActorSelection->Num() && IsAnyActorLocked())
|
|
{
|
|
AActor* Actor = CastChecked<AActor>(ActorSelection->GetSelectedObject(0));
|
|
if (LevelViewportClient->GetActiveActorLock().Get() == Actor)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
float SLevelViewport::GetActorLockSceneOutlinerColumnWidth()
|
|
{
|
|
return 18.0f; // 16.0f for the icons and 2.0f padding
|
|
}
|
|
|
|
TSharedRef< ISceneOutlinerColumn > SLevelViewport::CreateActorLockSceneOutlinerColumn( ISceneOutliner& SceneOutliner ) const
|
|
{
|
|
/**
|
|
* A custom column for the SceneOutliner which shows whether an actor is locked to a viewport
|
|
*/
|
|
class FCustomColumn : public ISceneOutlinerColumn
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor
|
|
*/
|
|
FCustomColumn( const SLevelViewport* const InViewport )
|
|
: Viewport(InViewport)
|
|
{
|
|
}
|
|
|
|
virtual ~FCustomColumn()
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Begin ISceneOutlinerColumn Implementation
|
|
|
|
virtual FName GetColumnID() override
|
|
{
|
|
return FName( "LockedToViewport" );
|
|
}
|
|
|
|
virtual SHeaderRow::FColumn::FArguments ConstructHeaderRowColumn() override
|
|
{
|
|
return SHeaderRow::Column( GetColumnID() )
|
|
.FixedWidth(SLevelViewport::GetActorLockSceneOutlinerColumnWidth())
|
|
[
|
|
SNew( SSpacer )
|
|
];
|
|
}
|
|
|
|
virtual const TSharedRef< SWidget > ConstructRowWidget( SceneOutliner::FTreeItemRef TreeItem, const STableRow<SceneOutliner::FTreeItemPtr>& InRow ) override
|
|
{
|
|
struct FConstructWidget : SceneOutliner::FColumnGenerator
|
|
{
|
|
const SLevelViewport* Viewport;
|
|
FConstructWidget(const SLevelViewport* InViewport) : Viewport(InViewport) {}
|
|
|
|
virtual TSharedRef<SWidget> GenerateWidget(SceneOutliner::FActorTreeItem& ActorItem) const override
|
|
{
|
|
AActor* Actor = ActorItem.Actor.Get();
|
|
if (!Actor)
|
|
{
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
const bool bLocked = Viewport->IsActorLocked(Actor);
|
|
|
|
return SNew(SBox)
|
|
.WidthOverride(SLevelViewport::GetActorLockSceneOutlinerColumnWidth())
|
|
.Padding(FMargin(2.0f, 0.0f, 0.0f, 0.0f))
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush(bLocked ? "PropertyWindow.Locked" : "PropertyWindow.Unlocked"))
|
|
.ColorAndOpacity(bLocked ? FLinearColor::White : FLinearColor(1.0f, 1.0f, 1.0f, 0.5f))
|
|
];
|
|
}
|
|
};
|
|
|
|
FConstructWidget Visitor(Viewport);
|
|
TreeItem->Visit(Visitor);
|
|
|
|
if (Visitor.Widget.IsValid())
|
|
{
|
|
return Visitor.Widget.ToSharedRef();
|
|
}
|
|
else
|
|
{
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
}
|
|
|
|
// End ISceneOutlinerColumn Implementation
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
private:
|
|
const SLevelViewport* Viewport;
|
|
};
|
|
|
|
return MakeShareable( new FCustomColumn( this ) );
|
|
}
|
|
|
|
void SLevelViewport::RedrawViewport( bool bInvalidateHitProxies )
|
|
{
|
|
if ( bInvalidateHitProxies )
|
|
{
|
|
// Invalidate hit proxies and display pixels.
|
|
LevelViewportClient->Viewport->Invalidate();
|
|
|
|
// Also update preview viewports
|
|
for( auto ActorPreviewIt = ActorPreviews.CreateConstIterator(); ActorPreviewIt; ++ActorPreviewIt )
|
|
{
|
|
auto& CurActorPreview = *ActorPreviewIt;
|
|
|
|
CurActorPreview.LevelViewportClient->Viewport->Invalidate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Invalidate only display pixels.
|
|
LevelViewportClient->Viewport->InvalidateDisplay();
|
|
|
|
// Also update preview viewports
|
|
for( auto ActorPreviewIt = ActorPreviews.CreateConstIterator(); ActorPreviewIt; ++ActorPreviewIt )
|
|
{
|
|
auto& CurActorPreview = *ActorPreviewIt;
|
|
|
|
CurActorPreview.LevelViewportClient->Viewport->InvalidateDisplay();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::CanToggleMaximizeMode() const
|
|
{
|
|
TSharedPtr<FLevelViewportLayout> ParentLayoutPinned = ParentLayout.Pin();
|
|
return (ParentLayoutPinned.IsValid() && ParentLayoutPinned->IsMaximizeSupported() && !ParentLayoutPinned->IsTransitioning());
|
|
}
|
|
|
|
void SLevelViewport::OnToggleMaximizeMode()
|
|
{
|
|
OnToggleMaximize();
|
|
}
|
|
|
|
FReply SLevelViewport::OnToggleMaximize()
|
|
{
|
|
TSharedPtr<FLevelViewportLayout> ParentLayoutPinned = ParentLayout.Pin();
|
|
if (ParentLayoutPinned.IsValid() && ParentLayoutPinned->IsMaximizeSupported())
|
|
{
|
|
OnFloatingButtonClicked();
|
|
|
|
bool bWantImmersive = IsImmersive();
|
|
bool bWantMaximize = IsMaximized();
|
|
|
|
//When in Immersive mode we always want to toggle back to normal editing mode while retaining the previous maximized state
|
|
if( bWantImmersive )
|
|
{
|
|
bWantImmersive = false;
|
|
}
|
|
else
|
|
{
|
|
bWantMaximize = !bWantMaximize;
|
|
}
|
|
|
|
// We always want to animate in response to user-interactive toggling of maximized state
|
|
const bool bAllowAnimation = true;
|
|
|
|
|
|
FName ViewportName = *ConfigKey;
|
|
if (!ViewportName.IsNone())
|
|
{
|
|
ParentLayout.Pin()->RequestMaximizeViewport( ViewportName, bWantMaximize, bWantImmersive, bAllowAnimation );
|
|
}
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
|
|
void SLevelViewport::MakeImmersive( const bool bWantImmersive, const bool bAllowAnimation )
|
|
{
|
|
if( ensure( ParentLayout.IsValid() ) )
|
|
{
|
|
const bool bWantMaximize = IsMaximized();
|
|
|
|
FName ViewportName = *ConfigKey;
|
|
if (!ViewportName.IsNone())
|
|
{
|
|
ParentLayout.Pin()->RequestMaximizeViewport( ViewportName, bWantMaximize, bWantImmersive, bAllowAnimation );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers a game viewport with the Slate application so that specific messages can be routed directly to this level viewport if it is an active PIE viewport
|
|
*/
|
|
void SLevelViewport::RegisterGameViewportIfPIE()
|
|
{
|
|
if(ActiveViewport->IsPlayInEditorViewport())
|
|
{
|
|
FSlateApplication::Get().RegisterGameViewport(ViewportWidget.ToSharedRef());
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::HasPlayInEditorViewport() const
|
|
{
|
|
return ActiveViewport->IsPlayInEditorViewport() || ( InactiveViewport.IsValid() && InactiveViewport->IsPlayInEditorViewport() );
|
|
}
|
|
|
|
bool SLevelViewport::IsPlayInEditorViewportActive() const
|
|
{
|
|
return ActiveViewport->IsPlayInEditorViewport();
|
|
}
|
|
|
|
void SLevelViewport::OnActorSelectionChanged(const TArray<UObject*>& NewSelection, bool bForceRefresh)
|
|
{
|
|
// On the first actor selection after entering Game View, enable the selection show flag
|
|
if (IsVisible() && IsInGameView() && NewSelection.Num() != 0)
|
|
{
|
|
if( LevelViewportClient->bAlwaysShowModeWidgetAfterSelectionChanges )
|
|
{
|
|
LevelViewportClient->EngineShowFlags.SetModeWidgets(true);
|
|
}
|
|
LevelViewportClient->EngineShowFlags.SetSelection(true);
|
|
LevelViewportClient->EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline);
|
|
}
|
|
|
|
|
|
// Check to see if we have any actors that we should preview. Only do this if we're the active level viewport client.
|
|
// NOTE: We don't actively monitor which viewport is "current" and remove views, etc. This ends up OK though because
|
|
// the camera PIP views will feel "sticky" in the viewport that was active when you last selected objects
|
|
// to preview!
|
|
if (GetDefault<ULevelEditorViewportSettings>()->bPreviewSelectedCameras && GCurrentLevelEditingViewportClient == LevelViewportClient.Get())
|
|
{
|
|
PreviewSelectedCameraActors();
|
|
}
|
|
else
|
|
{
|
|
// We're no longer the active viewport client, so remove any existing previewed actors
|
|
PreviewActors(TArray<AActor*>());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void SLevelViewport::PreviewSelectedCameraActors()
|
|
{
|
|
TArray<AActor*> ActorsToPreview;
|
|
|
|
for (FSelectionIterator SelectionIt( *GEditor->GetSelectedActors()); SelectionIt; ++SelectionIt)
|
|
{
|
|
AActor* SelectedActor = CastChecked<AActor>( *SelectionIt );
|
|
|
|
if (LevelViewportClient->IsLockedToActor(SelectedActor))
|
|
{
|
|
// If this viewport is already locked to the specified camera, then we don't need to do anything
|
|
}
|
|
else if (CanGetCameraInformationFromActor(SelectedActor))
|
|
{
|
|
ActorsToPreview.Add(SelectedActor);
|
|
}
|
|
}
|
|
|
|
PreviewActors( ActorsToPreview );
|
|
}
|
|
|
|
|
|
class SActorPreview : public SCompoundWidget
|
|
{
|
|
|
|
public:
|
|
|
|
~SActorPreview();
|
|
|
|
SLATE_BEGIN_ARGS( SActorPreview )
|
|
: _ViewportWidth( 240 ),
|
|
_ViewportHeight( 180 ) {}
|
|
|
|
/** Width of the viewport */
|
|
SLATE_ARGUMENT( int32, ViewportWidth )
|
|
|
|
/** Height of the viewport */
|
|
SLATE_ARGUMENT( int32, ViewportHeight )
|
|
|
|
/** Actor being previewed.*/
|
|
SLATE_ARGUMENT( TWeakObjectPtr< AActor >, PreviewActor )
|
|
|
|
/** Parent Viewport this preview is part of.*/
|
|
SLATE_ARGUMENT( TWeakPtr<SLevelViewport>, ParentViewport )
|
|
|
|
SLATE_END_ARGS()
|
|
|
|
/** Called by Slate to construct this widget */
|
|
void Construct( const FArguments& InArgs );
|
|
|
|
|
|
/** @return Returns this actor preview's viewport widget */
|
|
const TSharedRef< SViewport > GetViewportWidget() const
|
|
{
|
|
return ViewportWidget.ToSharedRef();
|
|
}
|
|
|
|
|
|
/** SWidget overrides */
|
|
virtual void OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override;
|
|
virtual void OnMouseLeave( const FPointerEvent& MouseEvent ) override;
|
|
|
|
/** Highlight this preview window by flashing the border. Will replay the curve sequence if it is already in the middle of a highlight. */
|
|
void Highlight();
|
|
|
|
private:
|
|
|
|
/** Called when an actor in the world is selected */
|
|
void OnActorSelected(UObject* InActor);
|
|
|
|
/** @return Returns the color and opacity to use for this widget */
|
|
FLinearColor GetColorAndOpacity() const;
|
|
|
|
/** @return Returns the border color and opacity to use for this widget (FSlateColor version) */
|
|
FSlateColor GetBorderColorAndOpacity() const;
|
|
|
|
/** @return Gets the name of the preview actor.*/
|
|
FText OnReadText() const;
|
|
|
|
/** @return Gets the Width of the preview viewport.*/
|
|
FOptionalSize OnReadWidth() const;
|
|
|
|
/** @return Gets the Height of the preview viewport.*/
|
|
FOptionalSize OnReadHeight() const;
|
|
|
|
/** @return Get the Width to wrap the preview actor name at.*/
|
|
float OnReadTextWidth() const;
|
|
|
|
/** Called when the pin preview button is clicked */
|
|
FReply OnTogglePinnedButtonClicked();
|
|
|
|
/** Swap between the pinned and unpinned icons */
|
|
const FSlateBrush* GetPinButtonIconBrush() const;
|
|
|
|
/** @return the tooltip to display when hovering over the pin button */
|
|
FText GetPinButtonToolTipText() const;
|
|
|
|
/** Viewport widget for this actor preview */
|
|
TSharedPtr< SViewport > ViewportWidget;
|
|
|
|
/** Actor being previewed.*/
|
|
TWeakObjectPtr< AActor > PreviewActorPtr;
|
|
|
|
/** Parent Viewport this preview is part of.*/
|
|
TWeakPtr<SLevelViewport> ParentViewport;
|
|
|
|
/** Curve sequence for fading in and out */
|
|
FCurveSequence FadeSequence;
|
|
|
|
/** Curve sequence for flashing the border (highlighting) when a pinned preview is re-selected */
|
|
FCurveSequence HighlightSequence;
|
|
|
|
/** Padding around the preview actor name */
|
|
static const float PreviewTextPadding;
|
|
};
|
|
|
|
const float SActorPreview::PreviewTextPadding = 3.0f;
|
|
|
|
SActorPreview::~SActorPreview()
|
|
{
|
|
USelection::SelectObjectEvent.RemoveAll(this);
|
|
}
|
|
|
|
void SActorPreview::Construct( const FArguments& InArgs )
|
|
{
|
|
const int32 HorizSpacingBetweenViewports = 18;
|
|
const int32 PaddingBeforeBorder = 6;
|
|
|
|
USelection::SelectObjectEvent.AddRaw(this, &SActorPreview::OnActorSelected);
|
|
|
|
// We don't want the border to be hit testable, since it would just get in the way of other
|
|
// widgets that are added to the viewport overlay.
|
|
this->SetVisibility(EVisibility::SelfHitTestInvisible);
|
|
|
|
this->ChildSlot
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(0)
|
|
|
|
.Visibility(EVisibility::SelfHitTestInvisible)
|
|
|
|
.BorderImage(FEditorStyle::GetBrush("NoBorder"))
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Bottom)
|
|
.Padding(FMargin(0, 0, PaddingBeforeBorder, PaddingBeforeBorder))
|
|
[
|
|
SNew( SOverlay )
|
|
+SOverlay::Slot()
|
|
[
|
|
SNew( SBorder )
|
|
// We never want the user to be able to interact with this viewport. Clicks should go right though it!
|
|
.Visibility( EVisibility::HitTestInvisible )
|
|
|
|
.Padding( 16.0f )
|
|
.BorderImage( FEditorStyle::GetBrush( "UniformShadow_Tint" ) )
|
|
|
|
.BorderBackgroundColor( this, &SActorPreview::GetBorderColorAndOpacity )
|
|
.ColorAndOpacity( this, &SActorPreview::GetColorAndOpacity )
|
|
|
|
[
|
|
SNew( SBox )
|
|
.WidthOverride( this, &SActorPreview::OnReadWidth )
|
|
.HeightOverride(this, &SActorPreview::OnReadHeight )
|
|
[
|
|
SNew( SOverlay )
|
|
+SOverlay::Slot()
|
|
[
|
|
SAssignNew( ViewportWidget, SViewport )
|
|
.RenderDirectlyToWindow( false )
|
|
.IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() )
|
|
.EnableGammaCorrection( false ) // Scene rendering handles gamma correction
|
|
.EnableBlending( true )
|
|
]
|
|
|
|
+SOverlay::Slot()
|
|
.Padding(PreviewTextPadding)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( this, &SActorPreview::OnReadText )
|
|
.Font( FSlateFontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 10 ) )
|
|
.ShadowOffset( FVector2D::UnitVector )
|
|
.WrapTextAt( this, &SActorPreview::OnReadTextWidth )
|
|
]
|
|
]
|
|
]
|
|
|
|
]
|
|
+SOverlay::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.VAlign(VAlign_Bottom)
|
|
.Padding( 24.0f )
|
|
[
|
|
// Create a button to pin/unpin this viewport
|
|
SNew( SButton )
|
|
.ContentPadding(0)
|
|
.ForegroundColor( FSlateColor::UseForeground() )
|
|
.ButtonStyle( FEditorStyle::Get(), "ToggleButton" )
|
|
|
|
.IsFocusable(false)
|
|
[
|
|
SNew( SImage )
|
|
.Visibility( EVisibility::Visible )
|
|
.Image( this, &SActorPreview::GetPinButtonIconBrush )
|
|
]
|
|
|
|
// Bind the button's "on clicked" event to our object's method for this
|
|
.OnClicked( this, &SActorPreview::OnTogglePinnedButtonClicked )
|
|
.Visibility( EVisibility::Visible )
|
|
|
|
// Pass along the block's tool-tip string
|
|
.ToolTipText( this, &SActorPreview::GetPinButtonToolTipText )
|
|
]
|
|
]
|
|
];
|
|
|
|
// Setup animation curve for fading in and out. Note that we add a bit of lead-in time on the fade-in
|
|
// to avoid hysteresis as the user moves the mouse over the view
|
|
{
|
|
/** The amount of time to wait before fading in after the mouse leaves */
|
|
const float TimeBeforeFadingIn = 0.5f;
|
|
|
|
/** The amount of time spent actually fading in or out */
|
|
const float FadeTime = 0.25f;
|
|
|
|
FadeSequence = FCurveSequence( TimeBeforeFadingIn, FadeTime );
|
|
|
|
// Start fading in!
|
|
FadeSequence.Play(this->AsShared(), false, TimeBeforeFadingIn); // Skip the initial time delay and just fade straight in
|
|
}
|
|
|
|
HighlightSequence = FCurveSequence(0.f, 0.5f, ECurveEaseFunction::Linear);
|
|
|
|
PreviewActorPtr = InArgs._PreviewActor;
|
|
ParentViewport = InArgs._ParentViewport;
|
|
}
|
|
|
|
FReply SActorPreview::OnTogglePinnedButtonClicked()
|
|
{
|
|
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
|
|
|
|
if (ParentViewportPtr.IsValid())
|
|
{
|
|
ParentViewportPtr->ToggleActorPreviewIsPinned(PreviewActorPtr);
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
const FSlateBrush* SActorPreview::GetPinButtonIconBrush() const
|
|
{
|
|
const FSlateBrush* IconBrush = NULL;
|
|
|
|
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
|
|
|
|
if (ParentViewportPtr.IsValid())
|
|
{
|
|
if ( ParentViewportPtr->IsActorPreviewPinned(PreviewActorPtr) )
|
|
{
|
|
IconBrush = FEditorStyle::GetBrush( "ViewportActorPreview.Pinned" );
|
|
}
|
|
else
|
|
{
|
|
IconBrush = FEditorStyle::GetBrush( "ViewportActorPreview.Unpinned" );
|
|
}
|
|
|
|
}
|
|
|
|
return IconBrush;
|
|
}
|
|
|
|
FText SActorPreview::GetPinButtonToolTipText() const
|
|
{
|
|
FText CurrentToolTipText = LOCTEXT("PinPreviewActorTooltip", "Pin Preview");
|
|
|
|
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
|
|
|
|
if (ParentViewportPtr.IsValid())
|
|
{
|
|
if ( ParentViewportPtr->IsActorPreviewPinned(PreviewActorPtr) )
|
|
{
|
|
CurrentToolTipText = LOCTEXT("UnpinPreviewActorTooltip", "Unpin Preview");
|
|
}
|
|
}
|
|
|
|
return CurrentToolTipText;
|
|
}
|
|
|
|
|
|
void SActorPreview::OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
SCompoundWidget::OnMouseEnter( MyGeometry, MouseEvent );
|
|
|
|
// The viewport could potentially be moved around inside the toolbar when the mouse is captured
|
|
// If that is the case we do not play the fade transition
|
|
if( !FSlateApplication::Get().IsUsingHighPrecisionMouseMovment() )
|
|
{
|
|
if( FadeSequence.IsPlaying() )
|
|
{
|
|
if( FadeSequence.IsForward() )
|
|
{
|
|
// Fade in is already playing so just force the fade out curve to the end so we don't have a "pop"
|
|
// effect from quickly resetting the alpha
|
|
FadeSequence.JumpToStart();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FadeSequence.PlayReverse(this->AsShared());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SActorPreview::OnMouseLeave( const FPointerEvent& MouseEvent )
|
|
{
|
|
SCompoundWidget::OnMouseLeave( MouseEvent );
|
|
|
|
// The viewport could potentially be moved around inside the toolbar when the mouse is captured
|
|
// If that is the case we do not play the fade transition
|
|
if( !FSlateApplication::Get().IsUsingHighPrecisionMouseMovment() )
|
|
{
|
|
if( FadeSequence.IsPlaying() )
|
|
{
|
|
if( FadeSequence.IsInReverse() )
|
|
{
|
|
FadeSequence.Reverse();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FadeSequence.Play(this->AsShared());
|
|
}
|
|
}
|
|
|
|
// Now is a good time to check if we need to remove any PreviewActors that might have been un-pinned
|
|
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
|
|
if (ParentViewportPtr.IsValid())
|
|
{
|
|
ParentViewportPtr->OnPreviewSelectedCamerasChange();
|
|
}
|
|
}
|
|
|
|
|
|
FLinearColor SActorPreview::GetColorAndOpacity() const
|
|
{
|
|
FLinearColor Color = FLinearColor::White;
|
|
|
|
const float HoveredOpacity = 0.4f;
|
|
const float NonHoveredOpacity = 1.0f;
|
|
|
|
Color.A = FMath::Lerp( HoveredOpacity, NonHoveredOpacity, FadeSequence.GetLerp() );
|
|
|
|
return Color;
|
|
}
|
|
|
|
void SActorPreview::OnActorSelected(UObject* InActor)
|
|
{
|
|
if (InActor && InActor == PreviewActorPtr && InActor->IsSelected())
|
|
{
|
|
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
|
|
const bool bIsPreviewPinned = ParentViewportPtr.IsValid() && ParentViewportPtr->IsActorPreviewPinned(PreviewActorPtr);
|
|
|
|
if (bIsPreviewPinned)
|
|
{
|
|
Highlight();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SActorPreview::Highlight()
|
|
{
|
|
HighlightSequence.JumpToStart();
|
|
HighlightSequence.Play(this->AsShared());
|
|
}
|
|
|
|
FSlateColor SActorPreview::GetBorderColorAndOpacity() const
|
|
{
|
|
FLinearColor Color(0.f, 0.f, 0.f, 0.5f);
|
|
|
|
if (HighlightSequence.IsPlaying())
|
|
{
|
|
static const FName SelectionColorName("SelectionColor");
|
|
const FLinearColor SelectionColor = FEditorStyle::Get().GetSlateColor(SelectionColorName).GetSpecifiedColor().CopyWithNewOpacity(0.5f);
|
|
|
|
const float Interp = FMath::Sin(HighlightSequence.GetLerp()*6*PI) / 2 + 1;
|
|
Color = FMath::Lerp(SelectionColor, Color, Interp);
|
|
}
|
|
|
|
return Color;
|
|
}
|
|
|
|
FText SActorPreview::OnReadText() const
|
|
{
|
|
if( PreviewActorPtr.IsValid() )
|
|
{
|
|
return FText::FromString(PreviewActorPtr.Get()->GetActorLabel());
|
|
}
|
|
else
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
}
|
|
|
|
FOptionalSize SActorPreview::OnReadWidth() const
|
|
{
|
|
const float PreviewHeight = OnReadHeight().Get();
|
|
|
|
// See if the preview actor wants to constrain the aspect ratio first
|
|
if (AActor* PreviewActor = PreviewActorPtr.Get())
|
|
{
|
|
FMinimalViewInfo CameraInfo;
|
|
if (SLevelViewport::GetCameraInformationFromActor(PreviewActor, /*out*/ CameraInfo))
|
|
{
|
|
if (CameraInfo.bConstrainAspectRatio && (CameraInfo.AspectRatio > 0.0f))
|
|
{
|
|
return PreviewHeight * CameraInfo.AspectRatio;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise try to match the parent viewport's aspect ratio
|
|
if ( ParentViewport.IsValid() )
|
|
{
|
|
return PreviewHeight * ParentViewport.Pin()->GetActiveViewport()->GetDesiredAspectRatio();
|
|
}
|
|
|
|
return PreviewHeight * 1.7777f;
|
|
}
|
|
|
|
FOptionalSize SActorPreview::OnReadHeight() const
|
|
{
|
|
const float MinimumHeight = 32;
|
|
// Also used as parent height in case valid parent viewport is not set
|
|
const float MaximumHeight = 428;
|
|
// Used to make sure default viewport scale * parent viewport height = roughly same size as original windows
|
|
const float PreviewScalingFactor = 0.06308f;
|
|
|
|
float ParentHeight = MaximumHeight;
|
|
if ( ParentViewport.IsValid() )
|
|
{
|
|
ParentHeight = ParentViewport.Pin()->GetActiveViewport()->GetSizeXY().Y;
|
|
}
|
|
return FMath::Clamp( GetDefault<ULevelEditorViewportSettings>()->CameraPreviewSize * ParentHeight * PreviewScalingFactor, MinimumHeight, MaximumHeight );
|
|
}
|
|
|
|
float SActorPreview::OnReadTextWidth() const
|
|
{
|
|
return OnReadWidth().Get() - (PreviewTextPadding*2.0f);
|
|
}
|
|
|
|
void SLevelViewport::PreviewActors( const TArray< AActor* >& ActorsToPreview )
|
|
{
|
|
TArray< AActor* > NewActorsToPreview;
|
|
TArray< AActor* > ActorsToStopPreviewing;
|
|
|
|
// Look for actors that we no longer want to preview
|
|
for( auto ActorPreviewIt = ActorPreviews.CreateConstIterator(); ActorPreviewIt; ++ActorPreviewIt )
|
|
{
|
|
auto ExistingActor = ActorPreviewIt->Actor.Get();
|
|
if( ExistingActor != NULL )
|
|
{
|
|
auto bShouldKeepActor = false;
|
|
for( auto ActorIt = ActorsToPreview.CreateConstIterator(); ActorIt; ++ActorIt )
|
|
{
|
|
auto CurActor = *ActorIt;
|
|
if( CurActor != NULL && CurActor == ExistingActor )
|
|
{
|
|
bShouldKeepActor = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !bShouldKeepActor )
|
|
{
|
|
// We were asked to stop previewing this actor
|
|
ActorsToStopPreviewing.AddUnique( ExistingActor );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for any new actors that we aren't previewing already
|
|
for( auto ActorIt = ActorsToPreview.CreateConstIterator(); ActorIt; ++ActorIt )
|
|
{
|
|
auto CurActor = *ActorIt;
|
|
|
|
// Check to see if we're already previewing this actor. If we are, we'll just skip it
|
|
auto bIsAlreadyPreviewed = false;
|
|
for( auto ExistingPreviewIt = ActorPreviews.CreateConstIterator(); ExistingPreviewIt; ++ExistingPreviewIt )
|
|
{
|
|
// There could be null actors in this list as we haven't actually removed them yet.
|
|
auto ExistingActor = ExistingPreviewIt->Actor.Get();
|
|
if( ExistingActor != NULL && CurActor == ExistingActor )
|
|
{
|
|
// Already previewing this actor. Ignore it.
|
|
bIsAlreadyPreviewed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !bIsAlreadyPreviewed )
|
|
{
|
|
// This is a new actor that we want to preview. Let's set that up.
|
|
NewActorsToPreview.Add( CurActor );
|
|
}
|
|
}
|
|
|
|
|
|
// Kill any existing actor previews that we don't want or have expired
|
|
for( int32 PreviewIndex = 0; PreviewIndex < ActorPreviews.Num(); ++PreviewIndex )
|
|
{
|
|
AActor* ExistingActor = ActorPreviews[PreviewIndex].Actor.Get();
|
|
if ( ExistingActor == NULL )
|
|
{
|
|
// decrement index so we don't miss next preview after deleting
|
|
RemoveActorPreview( PreviewIndex-- );
|
|
}
|
|
else
|
|
{
|
|
if ( !ActorPreviews[PreviewIndex].bIsPinned )
|
|
{
|
|
for( auto ActorIt = ActorsToStopPreviewing.CreateConstIterator(); ActorIt; ++ActorIt )
|
|
{
|
|
auto CurActor = *ActorIt;
|
|
if( ExistingActor == CurActor )
|
|
{
|
|
// Remove this preview!
|
|
// decrement index so we don't miss next preview after deleting
|
|
RemoveActorPreview( PreviewIndex-- );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create previews for any actors that we need to
|
|
if( NewActorsToPreview.Num() > 0 )
|
|
{
|
|
for( auto ActorIt = NewActorsToPreview.CreateConstIterator(); ActorIt; ++ActorIt )
|
|
{
|
|
auto CurActor = *ActorIt;
|
|
|
|
TSharedPtr< FLevelEditorViewportClient > ActorPreviewLevelViewportClient = MakeShareable( new FLevelEditorViewportClient(SharedThis(this)) );
|
|
{
|
|
// NOTE: We don't bother setting ViewLocation, ViewRotation, etc, here. This is because we'll call
|
|
// PushControllingActorDataToViewportClient() below which will do this!
|
|
|
|
// ParentLevelEditor is used for summoning context menus, which should never happen for these preview
|
|
// viewports, but we'll keep the relationship intact anyway.
|
|
ActorPreviewLevelViewportClient->ParentLevelEditor = ParentLevelEditor.Pin();
|
|
|
|
ActorPreviewLevelViewportClient->ViewportType = LVT_Perspective;
|
|
ActorPreviewLevelViewportClient->bSetListenerPosition = false; // Preview viewports never be a listener
|
|
|
|
// Never draw the axes indicator in these small viewports
|
|
ActorPreviewLevelViewportClient->bDrawAxes = false;
|
|
|
|
// Default to "game" show flags for camera previews
|
|
// Still draw selection highlight though
|
|
ActorPreviewLevelViewportClient->EngineShowFlags = FEngineShowFlags(ESFIM_Game);
|
|
ActorPreviewLevelViewportClient->EngineShowFlags.SetSelection(true);
|
|
ActorPreviewLevelViewportClient->LastEngineShowFlags = FEngineShowFlags(ESFIM_Editor);
|
|
|
|
// We don't use view modes for preview viewports
|
|
ActorPreviewLevelViewportClient->SetViewMode( VMI_Unknown );
|
|
|
|
// User should never be able to interact with this viewport
|
|
ActorPreviewLevelViewportClient->bDisableInput = true;
|
|
|
|
// Never allow Matinee to possess these views
|
|
ActorPreviewLevelViewportClient->SetAllowCinematicPreview( false );
|
|
|
|
// Our preview viewport is always visible if our owning SLevelViewport is visible, so we hook up
|
|
// to the same IsVisible method
|
|
ActorPreviewLevelViewportClient->VisibilityDelegate.BindSP( this, &SLevelViewport::IsVisible );
|
|
|
|
// Push actor transform to view. From here on out, this will happen automatically in FLevelEditorViewportClient::Tick.
|
|
// The reason we allow the viewport client to update this is to avoid off-by-one-frame issues when dragging actors around.
|
|
ActorPreviewLevelViewportClient->SetActorLock( CurActor );
|
|
ActorPreviewLevelViewportClient->UpdateViewForLockedActor();
|
|
}
|
|
|
|
TSharedPtr< SActorPreview > ActorPreviewWidget = SNew(SActorPreview)
|
|
.PreviewActor(CurActor)
|
|
.ParentViewport(SharedThis(this));
|
|
|
|
auto ActorPreviewViewportWidget = ActorPreviewWidget->GetViewportWidget();
|
|
|
|
TSharedPtr< FSceneViewport > ActorPreviewSceneViewport = MakeShareable( new FSceneViewport( ActorPreviewLevelViewportClient.Get(), ViewportWidget ) );
|
|
{
|
|
ActorPreviewLevelViewportClient->Viewport = ActorPreviewSceneViewport.Get();
|
|
ActorPreviewViewportWidget->SetViewportInterface( ActorPreviewSceneViewport.ToSharedRef() );
|
|
}
|
|
|
|
FViewportActorPreview& NewActorPreview = *new( ActorPreviews ) FViewportActorPreview;
|
|
NewActorPreview.Actor = CurActor;
|
|
NewActorPreview.LevelViewportClient = ActorPreviewLevelViewportClient;
|
|
NewActorPreview.SceneViewport = ActorPreviewSceneViewport;
|
|
NewActorPreview.PreviewWidget = ActorPreviewWidget;
|
|
NewActorPreview.bIsPinned = false;
|
|
|
|
// Add our new widget to our viewport's overlay
|
|
// @todo camerapip: Consider using a canvas instead of an overlay widget -- our viewports get SQUASHED when the view shrinks!
|
|
ActorPreviewHorizontalBox->AddSlot()
|
|
.AutoWidth()
|
|
[
|
|
ActorPreviewWidget.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
// OK, at least one new preview viewport was added, so update settings for all views immediately.
|
|
// This will also be repeated every time the SLevelViewport is ticked, just to make sure that
|
|
// feature such as "real-time" mode stay in sync.
|
|
UpdateActorPreviewViewports();
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::ToggleActorPreviewIsPinned(TWeakObjectPtr<AActor> ActorToTogglePinned)
|
|
{
|
|
if (ActorToTogglePinned.IsValid())
|
|
{
|
|
AActor* ActorToTogglePinnedPtr = ActorToTogglePinned.Get();
|
|
|
|
for (FViewportActorPreview& ActorPreview : ActorPreviews)
|
|
{
|
|
if ( ActorPreview.Actor.IsValid() )
|
|
{
|
|
if ( ActorToTogglePinnedPtr == ActorPreview.Actor.Get() )
|
|
{
|
|
ActorPreview.ToggleIsPinned();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool SLevelViewport::IsActorPreviewPinned( TWeakObjectPtr<AActor> PreviewActor )
|
|
{
|
|
if (PreviewActor.IsValid())
|
|
{
|
|
AActor* PreviewActorPtr = PreviewActor.Get();
|
|
|
|
for (FViewportActorPreview& ActorPreview : ActorPreviews)
|
|
{
|
|
if ( ActorPreview.Actor.IsValid() )
|
|
{
|
|
if ( PreviewActorPtr == ActorPreview.Actor.Get() )
|
|
{
|
|
return ActorPreview.bIsPinned;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SLevelViewport::UpdateActorPreviewViewports()
|
|
{
|
|
// Remove any previews that are locked to the same actor as the level viewport client's actor lock
|
|
for( int32 PreviewIndex = 0; PreviewIndex < ActorPreviews.Num(); ++PreviewIndex )
|
|
{
|
|
AActor* ExistingActor = ActorPreviews[PreviewIndex].Actor.Get();
|
|
if (ExistingActor && LevelViewportClient->IsActorLocked(ExistingActor))
|
|
{
|
|
RemoveActorPreview( PreviewIndex-- );
|
|
}
|
|
}
|
|
|
|
// Look for actors that we no longer want to preview
|
|
for( auto ActorPreviewIt = ActorPreviews.CreateConstIterator(); ActorPreviewIt; ++ActorPreviewIt )
|
|
{
|
|
auto& CurActorPreview = *ActorPreviewIt;
|
|
|
|
CurActorPreview.LevelViewportClient->SetRealtime( LevelViewportClient->IsRealtime() );
|
|
CurActorPreview.LevelViewportClient->bDrawBaseInfo = LevelViewportClient->bDrawBaseInfo;
|
|
CurActorPreview.LevelViewportClient->bDrawVertices = LevelViewportClient->bDrawVertices;
|
|
CurActorPreview.LevelViewportClient->EngineShowFlags.SetSelectionOutline(LevelViewportClient->EngineShowFlags.SelectionOutline);
|
|
CurActorPreview.LevelViewportClient->EngineShowFlags.SetCompositeEditorPrimitives(LevelViewportClient->EngineShowFlags.CompositeEditorPrimitives);
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::OnPreviewSelectedCamerasChange()
|
|
{
|
|
// Check to see if previewing selected cameras is enabled and if we're the active level viewport client.
|
|
if (GetDefault<ULevelEditorViewportSettings>()->bPreviewSelectedCameras && GCurrentLevelEditingViewportClient == LevelViewportClient.Get())
|
|
{
|
|
PreviewSelectedCameraActors();
|
|
}
|
|
else
|
|
{
|
|
// We're either not the active viewport client or preview selected cameras option is disabled, so remove any existing previewed actors
|
|
PreviewActors(TArray<AActor*>());
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::SetDeviceProfileString( const FString& ProfileName )
|
|
{
|
|
DeviceProfile = ProfileName;
|
|
}
|
|
|
|
bool SLevelViewport::IsDeviceProfileStringSet( FString ProfileName ) const
|
|
{
|
|
return DeviceProfile == ProfileName;
|
|
}
|
|
|
|
FString SLevelViewport::GetDeviceProfileString( ) const
|
|
{
|
|
return DeviceProfile;
|
|
}
|
|
|
|
FText SLevelViewport::GetCurrentFeatureLevelPreviewText( bool bDrawOnlyLabel ) const
|
|
{
|
|
FText LabelName;
|
|
FText FeatureLevelText;
|
|
|
|
if (bDrawOnlyLabel)
|
|
{
|
|
LabelName = LOCTEXT("FeatureLevelLabel", "Feature Level:");
|
|
}
|
|
else
|
|
{
|
|
auto* World = GetWorld();
|
|
if (World != nullptr)
|
|
{
|
|
const auto FeatureLevel = World->FeatureLevel;
|
|
if (FeatureLevel != GMaxRHIFeatureLevel)
|
|
{
|
|
FName FeatureLevelName;
|
|
GetFeatureLevelName(FeatureLevel, FeatureLevelName);
|
|
FeatureLevelText = FText::Format(LOCTEXT("FeatureLevel", "{0}"), FText::FromName(FeatureLevelName));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bDrawOnlyLabel)
|
|
{
|
|
return LabelName;
|
|
}
|
|
|
|
return FeatureLevelText;
|
|
}
|
|
|
|
FText SLevelViewport::GetCurrentLevelText( bool bDrawOnlyLabel ) const
|
|
{
|
|
// Display the current level and current level grid volume in the status bar
|
|
FText LabelName;
|
|
FText CurrentLevelName;
|
|
|
|
|
|
if( ActiveViewport.IsValid() && (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) && GetWorld() && GetWorld()->GetCurrentLevel() != nullptr )
|
|
{
|
|
if( ActiveViewport->GetPlayInEditorIsSimulate() || !ActiveViewport->GetClient()->GetWorld()->IsGameWorld() )
|
|
{
|
|
if(bDrawOnlyLabel)
|
|
{
|
|
LabelName = LOCTEXT("CurrentLevelLabel", "Level:");
|
|
}
|
|
else
|
|
{
|
|
// Get the level name (without the number at the end)
|
|
FText ActualLevelName = FText::FromString(FPackageName::GetShortFName(GetWorld()->GetCurrentLevel()->GetOutermost()->GetFName()).GetPlainNameString());
|
|
|
|
if(GetWorld()->GetCurrentLevel() == GetWorld()->PersistentLevel)
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("ActualLevelName"), ActualLevelName);
|
|
CurrentLevelName = FText::Format(LOCTEXT("LevelName", "{0} (Persistent)"), ActualLevelName);
|
|
}
|
|
else
|
|
{
|
|
CurrentLevelName = ActualLevelName;
|
|
}
|
|
}
|
|
|
|
if(bDrawOnlyLabel)
|
|
{
|
|
return LabelName;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CurrentLevelName;
|
|
}
|
|
|
|
EVisibility SLevelViewport::GetCurrentLevelTextVisibility() const
|
|
{
|
|
EVisibility ContentVisibility = OnGetViewportContentVisibility();
|
|
if (ContentVisibility == EVisibility::Visible)
|
|
{
|
|
ContentVisibility = EVisibility::SelfHitTestInvisible;
|
|
}
|
|
return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) ? ContentVisibility : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SLevelViewport::GetCurrentFeatureLevelPreviewTextVisibility() const
|
|
{
|
|
if (GetWorld())
|
|
{
|
|
return (GetWorld()->FeatureLevel != GMaxRHIFeatureLevel) ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed;
|
|
}
|
|
else
|
|
{
|
|
return EVisibility::Collapsed;
|
|
}
|
|
}
|
|
|
|
EVisibility SLevelViewport::GetViewportControlsVisibility() const
|
|
{
|
|
// Do not show the controls if this viewport has a play in editor session
|
|
// or is not the current viewport
|
|
return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient && !IsPlayInEditorViewportActive()) ? OnGetViewportContentVisibility() : EVisibility::Collapsed;
|
|
}
|
|
|
|
void SLevelViewport::OnSetViewportConfiguration(FName ConfigurationName)
|
|
{
|
|
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
|
|
if (LayoutPinned.IsValid())
|
|
{
|
|
TSharedPtr<FLevelViewportTabContent> ViewportTabPinned = LayoutPinned->GetParentTabContent().Pin();
|
|
if (ViewportTabPinned.IsValid())
|
|
{
|
|
// Viewport clients are going away. Any current one is invalid.
|
|
GCurrentLevelEditingViewportClient = nullptr;
|
|
ViewportTabPinned->SetViewportConfiguration(ConfigurationName);
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::IsViewportConfigurationSet(FName ConfigurationName) const
|
|
{
|
|
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
|
|
if (LayoutPinned.IsValid())
|
|
{
|
|
TSharedPtr<FLevelViewportTabContent> ViewportTabPinned = LayoutPinned->GetParentTabContent().Pin();
|
|
if (ViewportTabPinned.IsValid())
|
|
{
|
|
return ViewportTabPinned->IsViewportConfigurationSet(ConfigurationName);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FName SLevelViewport::GetViewportTypeWithinLayout() const
|
|
{
|
|
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
|
|
if (LayoutPinned.IsValid() && !ConfigKey.IsEmpty())
|
|
{
|
|
TSharedPtr<IViewportLayoutEntity> Entity = LayoutPinned->GetViewports().FindRef(*ConfigKey);
|
|
if (Entity.IsValid())
|
|
{
|
|
return Entity->GetType();
|
|
}
|
|
}
|
|
return "Default";
|
|
}
|
|
|
|
void SLevelViewport::SetViewportTypeWithinLayout(FName InLayoutType)
|
|
{
|
|
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
|
|
if (LayoutPinned.IsValid() && !ConfigKey.IsEmpty())
|
|
{
|
|
// Important - RefreshViewportConfiguration does not save config values. We save its state first, to ensure that .TypeWithinLayout (below) doesn't get overwritten
|
|
TSharedPtr<FLevelViewportTabContent> ViewportTabPinned = LayoutPinned->GetParentTabContent().Pin();
|
|
if (ViewportTabPinned.IsValid())
|
|
{
|
|
ViewportTabPinned->SaveConfig();
|
|
}
|
|
|
|
const FString& IniSection = FLayoutSaveRestore::GetAdditionalLayoutConfigIni();
|
|
GConfig->SetString( *IniSection, *( ConfigKey + TEXT(".TypeWithinLayout") ), *InLayoutType.ToString(), GEditorPerProjectIni );
|
|
|
|
// Force a refresh of the tab content
|
|
// Viewport clients are going away. Any current one is invalid.
|
|
GCurrentLevelEditingViewportClient = nullptr;
|
|
ViewportTabPinned->RefreshViewportConfiguration();
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::ToggleViewportTypeActivationWithinLayout(FName InLayoutType)
|
|
{
|
|
if (GetViewportTypeWithinLayout() != InLayoutType)
|
|
{
|
|
SetViewportTypeWithinLayout(InLayoutType);
|
|
}
|
|
}
|
|
|
|
bool SLevelViewport::IsViewportTypeWithinLayoutEqual(FName InLayoutType)
|
|
{
|
|
return GetViewportTypeWithinLayout() == InLayoutType;
|
|
}
|
|
|
|
void SLevelViewport::StartPlayInEditorSession(UGameViewportClient* PlayClient, const bool bInSimulateInEditor)
|
|
{
|
|
check( !HasPlayInEditorViewport() );
|
|
|
|
check( !InactiveViewport.IsValid() );
|
|
|
|
// Ensure our active viewport is for level editing
|
|
check( ActiveViewport->GetClient() == LevelViewportClient.Get() );
|
|
// Save camera settings that may be adversely affected by PIE, so that they may be restored later
|
|
LevelViewportClient->PrepareCameraForPIE();
|
|
|
|
// Here we will swap the editor viewport client out for the client for the play in editor session
|
|
InactiveViewport = ActiveViewport;
|
|
// Store the content in the viewport widget (editor tool bar etc) so we can show the game UI content if it has any
|
|
InactiveViewportWidgetEditorContent = ViewportWidget->GetContent();
|
|
|
|
// Remove keyboard focus to send a focus lost message to the widget to clean up any saved state from the viewport interface thats about to be swapped out
|
|
// Focus will be set when the game viewport is registered
|
|
FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::SetDirectly);
|
|
|
|
// Attach global play world actions widget to view port
|
|
ActiveViewport = MakeShareable( new FSceneViewport( PlayClient, ViewportWidget) );
|
|
ActiveViewport->SetPlayInEditorViewport( true );
|
|
|
|
// Whether to start with the game taking mouse control or leaving it shown in the editor
|
|
ActiveViewport->SetPlayInEditorGetsMouseControl(GetDefault<ULevelEditorPlaySettings>()->GameGetsMouseControl);
|
|
ActiveViewport->SetPlayInEditorIsSimulate(bInSimulateInEditor);
|
|
|
|
ActiveViewport->OnPlayWorldViewportSwapped( *InactiveViewport );
|
|
|
|
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared());
|
|
PlayClient->SetViewportOverlayWidget(ParentWindow, PIEViewportOverlayWidget.ToSharedRef());
|
|
PlayClient->SetGameLayerManager(GameLayerManager);
|
|
|
|
// Our viewport widget should start rendering the new viewport for the play in editor scene
|
|
ViewportWidget->SetViewportInterface( ActiveViewport.ToSharedRef() );
|
|
|
|
// Let the viewport client know what viewport it is associated with
|
|
PlayClient->Viewport = ActiveViewport.Get();
|
|
|
|
// Register the new viewport widget with Slate for viewport specific message routing.
|
|
FSlateApplication::Get().RegisterGameViewport(ViewportWidget.ToSharedRef() );
|
|
|
|
// Kick off a quick transition effect (border graphics)
|
|
ViewTransitionType = EViewTransition::StartingPlayInEditor;
|
|
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
|
|
bViewTransitionAnimPending = true;
|
|
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/StartPlayInEditor_Cue.StartPlayInEditor_Cue" ) );
|
|
|
|
bPIEHasFocus = ActiveViewport->HasMouseCapture();
|
|
|
|
ULevelEditorPlaySettings const* EditorPlayInSettings = GetDefault<ULevelEditorPlaySettings>();
|
|
check(EditorPlayInSettings);
|
|
|
|
if(EditorPlayInSettings->ShowMouseControlLabel)
|
|
{
|
|
ELabelAnchorMode AnchorMode = EditorPlayInSettings->MouseControlLabelPosition.GetValue();
|
|
|
|
ShowMouseCaptureLabel(AnchorMode);
|
|
}
|
|
|
|
GEngine->BroadcastLevelActorListChanged();
|
|
}
|
|
|
|
EVisibility SLevelViewport::GetMouseCaptureLabelVisibility() const
|
|
{
|
|
if (GEditor->PlayWorld)
|
|
{
|
|
// Show the label if the local player's PC isn't set to show the cursor
|
|
auto const TargetPlayer = GEngine->GetLocalPlayerFromControllerId(GEditor->PlayWorld, 0);
|
|
if (TargetPlayer && TargetPlayer->PlayerController && !TargetPlayer->PlayerController->bShowMouseCursor)
|
|
{
|
|
return EVisibility::HitTestInvisible;
|
|
}
|
|
}
|
|
|
|
return EVisibility::Collapsed;
|
|
}
|
|
|
|
FLinearColor SLevelViewport::GetMouseCaptureLabelColorAndOpacity() const
|
|
{
|
|
static const FName DefaultForegroundName("DefaultForeground");
|
|
|
|
FSlateColor SlateColor = FEditorStyle::GetSlateColor(DefaultForegroundName);
|
|
FLinearColor Col = SlateColor.IsColorSpecified() ? SlateColor.GetSpecifiedColor() : FLinearColor::White;
|
|
|
|
float Alpha = 0.0f;
|
|
|
|
if(ViewTransitionAnim.IsPlaying() && ViewTransitionType == EViewTransition::StartingPlayInEditor)
|
|
{
|
|
Alpha = ViewTransitionAnim.GetLerp();
|
|
}
|
|
else if(PIEOverlayAnim.IsPlaying())
|
|
{
|
|
Alpha = 1.0f - PIEOverlayAnim.GetLerp();
|
|
}
|
|
|
|
return Col.CopyWithNewOpacity(Alpha);
|
|
}
|
|
|
|
FText SLevelViewport::GetMouseCaptureLabelText() const
|
|
{
|
|
if(ActiveViewport->HasMouseCapture())
|
|
{
|
|
// Default Shift+F1 if a valid chord is not found
|
|
static FInputChord Chord(EKeys::F1, EModifierKey::Shift);
|
|
TSharedPtr<FUICommandInfo> UICommand = FInputBindingManager::Get().FindCommandInContext(TEXT("PlayWorld"), TEXT("GetMouseControl"));
|
|
if (UICommand.IsValid())
|
|
{
|
|
TSharedRef<const FInputChord> ActiveChord = UICommand->GetActiveChord();
|
|
Chord = ActiveChord.Get();
|
|
}
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("InputText"), Chord.GetInputText());
|
|
return FText::Format( LOCTEXT("ShowMouseCursorLabel", "{InputText} for Mouse Cursor"), Args );
|
|
}
|
|
else
|
|
{
|
|
return LOCTEXT("GameMouseControlLabel", "Click for Mouse Control");
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::ShowMouseCaptureLabel(ELabelAnchorMode AnchorMode)
|
|
{
|
|
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopLeft / 3) + 1) == EVerticalAlignment::VAlign_Top && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopLeft % 3) + 1) == EHorizontalAlignment::HAlign_Left, "Alignment from ELabelAnchorMode error.");
|
|
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopCenter / 3) + 1) == EVerticalAlignment::VAlign_Top && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopCenter % 3) + 1) == EHorizontalAlignment::HAlign_Center, "Alignment from ELabelAnchorMode error.");
|
|
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopRight / 3) + 1) == EVerticalAlignment::VAlign_Top && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_TopRight % 3) + 1) == EHorizontalAlignment::HAlign_Right, "Alignment from ELabelAnchorMode error.");
|
|
|
|
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_CenterLeft / 3) + 1) == EVerticalAlignment::VAlign_Center && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_CenterLeft % 3) + 1) == EHorizontalAlignment::HAlign_Left, "Alignment from ELabelAnchorMode error.");
|
|
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_Centered / 3) + 1) == EVerticalAlignment::VAlign_Center && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_Centered % 3) + 1) == EHorizontalAlignment::HAlign_Center, "Alignment from ELabelAnchorMode error.");
|
|
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_CenterRight / 3) + 1) == EVerticalAlignment::VAlign_Center && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_CenterRight % 3) + 1) == EHorizontalAlignment::HAlign_Right, "Alignment from ELabelAnchorMode error.");
|
|
|
|
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomLeft / 3) + 1) == EVerticalAlignment::VAlign_Bottom && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomLeft % 3) + 1) == EHorizontalAlignment::HAlign_Left, "Alignment from ELabelAnchorMode error.");
|
|
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomCenter / 3) + 1) == EVerticalAlignment::VAlign_Bottom && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomCenter % 3) + 1) == EHorizontalAlignment::HAlign_Center, "Alignment from ELabelAnchorMode error.");
|
|
static_assert((EVerticalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomRight / 3) + 1) == EVerticalAlignment::VAlign_Bottom && (EHorizontalAlignment)((ELabelAnchorMode::LabelAnchorMode_BottomRight % 3) + 1) == EHorizontalAlignment::HAlign_Right, "Alignment from ELabelAnchorMode error.");
|
|
|
|
EVerticalAlignment VAlign = (EVerticalAlignment)((AnchorMode/3)+1);
|
|
EHorizontalAlignment HAlign = (EHorizontalAlignment)((AnchorMode%3)+1);
|
|
|
|
SOverlay::FOverlaySlot& Slot = ViewportOverlay->AddSlot();
|
|
PIEOverlaySlotIndex = Slot.ZOrder;
|
|
|
|
Slot.HAlign(HAlign)
|
|
.VAlign(VAlign)
|
|
[
|
|
SNew( SBorder )
|
|
.BorderImage( FEditorStyle::GetBrush("NoBorder") )
|
|
.Visibility(this, &SLevelViewport::GetMouseCaptureLabelVisibility)
|
|
.ColorAndOpacity( this, &SLevelViewport::GetMouseCaptureLabelColorAndOpacity )
|
|
.ForegroundColor( FLinearColor::White )
|
|
.Padding(15.0f)
|
|
[
|
|
SNew( SButton )
|
|
.ButtonStyle( FEditorStyle::Get(), "EditorViewportToolBar.MenuButton" )
|
|
.IsFocusable(false)
|
|
.ButtonColorAndOpacity( FSlateColor(FLinearColor::Black) )
|
|
.ForegroundColor( FLinearColor::White )
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+ SHorizontalBox::Slot()
|
|
.MaxWidth(32.f)
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0.0f, 2.0f, 2.0f, 2.0f)
|
|
[
|
|
SNew( SVerticalBox )
|
|
+ SVerticalBox::Slot()
|
|
.MaxHeight(16.f)
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("LevelViewport.CursorIcon"))
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.Padding(2.0f, 2.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SLevelViewport::GetMouseCaptureLabelText)
|
|
.Font( FSlateFontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 9 ) )
|
|
.ColorAndOpacity(FLinearColor::White)
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
void SLevelViewport::HideMouseCaptureLabel()
|
|
{
|
|
ViewportOverlay->RemoveSlot(PIEOverlaySlotIndex);
|
|
PIEOverlaySlotIndex = 0;
|
|
}
|
|
|
|
void SLevelViewport::ResetNewLevelViewFlags()
|
|
{
|
|
const bool bUseSavedDefaults = true;
|
|
OnUseDefaultShowFlags(bUseSavedDefaults);
|
|
}
|
|
|
|
void SLevelViewport::EndPlayInEditorSession()
|
|
{
|
|
check( HasPlayInEditorViewport() );
|
|
|
|
FSlateApplication::Get().UnregisterGameViewport();
|
|
|
|
check( InactiveViewport.IsValid() );
|
|
|
|
if( IsPlayInEditorViewportActive() )
|
|
{
|
|
ActiveViewport->OnPlayWorldViewportSwapped(*InactiveViewport);
|
|
|
|
// Play in editor viewport was active, swap back to our level editor viewport
|
|
ActiveViewport->SetViewportClient( nullptr );
|
|
|
|
// We should be the only thing holding on to viewports
|
|
check( ActiveViewport.IsUnique() );
|
|
|
|
ActiveViewport = InactiveViewport;
|
|
|
|
// Ensure our active viewport is for level editing
|
|
check( ActiveViewport->GetClient() == LevelViewportClient.Get() );
|
|
|
|
// Restore camera settings that may be adversely affected by PIE
|
|
LevelViewportClient->RestoreCameraFromPIE();
|
|
RedrawViewport(true);
|
|
}
|
|
else
|
|
{
|
|
InactiveViewport->SetViewportClient( nullptr );
|
|
}
|
|
|
|
// Remove camera roll from any PIE camera applied in this viewport. A rolled camera is hard to use for editing
|
|
LevelViewportClient->RemoveCameraRoll();
|
|
|
|
// Reset the inactive viewport
|
|
InactiveViewport.Reset();
|
|
|
|
// Viewport widget should begin drawing the editor viewport
|
|
ViewportWidget->SetViewportInterface( ActiveViewport.ToSharedRef() );
|
|
ViewportWidget->SetContent( InactiveViewportWidgetEditorContent );
|
|
|
|
// No longer need to store the content
|
|
InactiveViewportWidgetEditorContent.Reset();
|
|
|
|
if(PIEOverlaySlotIndex)
|
|
{
|
|
HideMouseCaptureLabel();
|
|
}
|
|
|
|
// Kick off a quick transition effect (border graphics)
|
|
ViewTransitionType = EViewTransition::ReturningToEditor;
|
|
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
|
|
bViewTransitionAnimPending = true;
|
|
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/EndPlayInEditor_Cue.EndPlayInEditor_Cue" ) );
|
|
|
|
GEngine->BroadcastLevelActorListChanged();
|
|
}
|
|
|
|
void SLevelViewport::SwapViewportsForSimulateInEditor()
|
|
{
|
|
// Ensure our active viewport was the play in editor viewport
|
|
check( IsPlayInEditorViewportActive() );
|
|
|
|
// Remove the mouse control label - not relevant for SIE
|
|
if(PIEOverlaySlotIndex)
|
|
{
|
|
HideMouseCaptureLabel();
|
|
}
|
|
|
|
// Unregister the game viewport with slate which will release mouse capture and lock
|
|
FSlateApplication::Get().UnregisterGameViewport();
|
|
|
|
// Swap between the active and inactive viewport
|
|
TSharedPtr<FSceneViewport> TempViewport = ActiveViewport;
|
|
ActiveViewport = InactiveViewport;
|
|
InactiveViewport = TempViewport;
|
|
|
|
ViewportWidget->SetContent( InactiveViewportWidgetEditorContent );
|
|
|
|
// Resize the viewport to be the same size the previously active viewport
|
|
// When starting in immersive mode its possible that the viewport has not been resized yet
|
|
ActiveViewport->OnPlayWorldViewportSwapped( *InactiveViewport );
|
|
|
|
ViewportWidget->SetViewportInterface( ActiveViewport.ToSharedRef() );
|
|
|
|
// Kick off a quick transition effect (border graphics)
|
|
ViewTransitionType = EViewTransition::StartingSimulate;
|
|
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
|
|
bViewTransitionAnimPending = true;
|
|
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/PossessPlayer_Cue.PossessPlayer_Cue" ) );
|
|
}
|
|
|
|
|
|
void SLevelViewport::SwapViewportsForPlayInEditor()
|
|
{
|
|
// Ensure our inactive viewport was the play in editor viewport
|
|
check( !IsPlayInEditorViewportActive() && HasPlayInEditorViewport() );
|
|
|
|
// Put the mouse control label up again.
|
|
ULevelEditorPlaySettings const* EditorPlayInSettings = GetDefault<ULevelEditorPlaySettings>();
|
|
check(EditorPlayInSettings);
|
|
|
|
if(EditorPlayInSettings->ShowMouseControlLabel)
|
|
{
|
|
ELabelAnchorMode AnchorMode = EditorPlayInSettings->MouseControlLabelPosition.GetValue();
|
|
|
|
ShowMouseCaptureLabel(AnchorMode);
|
|
}
|
|
|
|
// Swap between the active and inactive viewport
|
|
TSharedPtr<FSceneViewport> TempViewport = ActiveViewport;
|
|
ActiveViewport = InactiveViewport;
|
|
InactiveViewport = TempViewport;
|
|
|
|
// Resize the viewport to be the same size the previously active viewport
|
|
// When starting in immersive mode its possible that the viewport has not been resized yet
|
|
ActiveViewport->OnPlayWorldViewportSwapped( *InactiveViewport );
|
|
|
|
InactiveViewportWidgetEditorContent = ViewportWidget->GetContent();
|
|
ViewportWidget->SetViewportInterface( ActiveViewport.ToSharedRef() );
|
|
|
|
// Register the game viewport with slate which will capture the mouse and lock it to the viewport
|
|
FSlateApplication::Get().RegisterGameViewport( ViewportWidget.ToSharedRef() );
|
|
|
|
// Kick off a quick transition effect (border graphics)
|
|
ViewTransitionType = EViewTransition::StartingPlayInEditor;
|
|
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
|
|
bViewTransitionAnimPending = true;
|
|
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/EjectFromPlayer_Cue.EjectFromPlayer_Cue" ) );
|
|
}
|
|
|
|
|
|
void SLevelViewport::OnSimulateSessionStarted()
|
|
{
|
|
// Kick off a quick transition effect (border graphics)
|
|
ViewTransitionType = EViewTransition::StartingSimulate;
|
|
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
|
|
bViewTransitionAnimPending = true;
|
|
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/StartSimulate_Cue.StartSimulate_Cue" ) );
|
|
|
|
// Make sure the viewport's hit proxies are invalidated. If not done, clicking in the viewport could select an editor world actor
|
|
ActiveViewport->InvalidateHitProxy();
|
|
}
|
|
|
|
|
|
void SLevelViewport::OnSimulateSessionFinished()
|
|
{
|
|
// Kick off a quick transition effect (border graphics)
|
|
ViewTransitionType = EViewTransition::ReturningToEditor;
|
|
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
|
|
bViewTransitionAnimPending = true;
|
|
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/EndSimulate_Cue.EndSimulate_Cue" ) );
|
|
|
|
// Make sure the viewport's hit proxies are invalidated. If not done, clicking in the viewport could select a pie world actor
|
|
ActiveViewport->InvalidateHitProxy();
|
|
}
|
|
|
|
EVisibility SLevelViewport::GetLockedIconVisibility() const
|
|
{
|
|
return IsAnyActorLocked() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
FText SLevelViewport::GetLockedIconToolTip() const
|
|
{
|
|
FText ToolTipText;
|
|
if (IsAnyActorLocked())
|
|
{
|
|
ToolTipText = FText::Format( LOCTEXT("ActorLockedIcon_ToolTip", "Viewport Locked to {0}"), FText::FromString( LevelViewportClient->GetActiveActorLock().Get()->GetActorLabel() ) );
|
|
}
|
|
|
|
return ToolTipText;
|
|
}
|
|
|
|
UWorld* SLevelViewport::GetWorld() const
|
|
{
|
|
return ParentLevelEditor.IsValid() ? ParentLevelEditor.Pin()->GetWorld() : NULL;
|
|
}
|
|
|
|
void SLevelViewport::RemoveActorPreview( int32 PreviewIndex )
|
|
{
|
|
// Remove widget from viewport overlay
|
|
ActorPreviewHorizontalBox->RemoveSlot( ActorPreviews[PreviewIndex].PreviewWidget.ToSharedRef() );
|
|
|
|
// Clean up our level viewport client
|
|
if( ActorPreviews[PreviewIndex].LevelViewportClient.IsValid() )
|
|
{
|
|
ActorPreviews[PreviewIndex].LevelViewportClient->Viewport = NULL;
|
|
}
|
|
|
|
// Remove from our list of actor previews. This will destroy our level viewport client and viewport widget.
|
|
ActorPreviews.RemoveAt( PreviewIndex );
|
|
}
|
|
|
|
void SLevelViewport::AddOverlayWidget(TSharedRef<SWidget> OverlaidWidget)
|
|
{
|
|
ViewportOverlay->AddSlot()
|
|
[
|
|
OverlaidWidget
|
|
];
|
|
}
|
|
|
|
void SLevelViewport::RemoveOverlayWidget(TSharedRef<SWidget> OverlaidWidget)
|
|
{
|
|
ViewportOverlay->RemoveSlot(OverlaidWidget);
|
|
}
|
|
|
|
bool SLevelViewport::CanProduceActionForCommand(const TSharedRef<const FUICommandInfo>& Command) const
|
|
{
|
|
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
|
|
TSharedPtr<ILevelViewport> ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport();
|
|
if ( ActiveLevelViewport.IsValid() )
|
|
{
|
|
return ActiveLevelViewport == SharedThis(this);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SLevelViewport::LockActorInternal(AActor* NewActorToLock)
|
|
{
|
|
if (NewActorToLock != NULL)
|
|
{
|
|
LevelViewportClient->SetActorLock(NewActorToLock);
|
|
if (LevelViewportClient->IsPerspective() && LevelViewportClient->GetActiveActorLock().IsValid())
|
|
{
|
|
LevelViewportClient->MoveCameraToLockedActor();
|
|
}
|
|
}
|
|
|
|
// Make sure the inset preview is closed if we are locking a camera that was already part of the selection set and thus being previewed.
|
|
OnPreviewSelectedCamerasChange();
|
|
}
|
|
|
|
bool SLevelViewport::GetCameraInformationFromActor(AActor* Actor, FMinimalViewInfo& out_CameraInfo)
|
|
{
|
|
// @todo camerapip: Could support actors other than cameras too! (Character views?)
|
|
//@TODO: CAMERA: Support richer camera interactions in SIE; this may shake out naturally if everything uses camera components though
|
|
TArray<UCameraComponent*> CamComps;
|
|
Actor->GetComponents<UCameraComponent>(CamComps);
|
|
for (UCameraComponent* CamComp : CamComps)
|
|
{
|
|
if (CamComp->bIsActive)
|
|
{
|
|
// first active camera, use it and be done
|
|
CamComp->GetCameraView(0.0f, out_CameraInfo);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// see if any actors are attached to us, directly or indirectly, that have an active camera component we might want to use
|
|
// #note: assumption here that attachment cannot be circular
|
|
TArray<AActor*> AttachedActors;
|
|
Actor->GetAttachedActors(AttachedActors);
|
|
for (AActor* AttachedActor : AttachedActors)
|
|
{
|
|
if (GetCameraInformationFromActor(AttachedActor, out_CameraInfo))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// no active cameras
|
|
return false;
|
|
}
|
|
|
|
bool SLevelViewport::CanGetCameraInformationFromActor(AActor* Actor)
|
|
{
|
|
FMinimalViewInfo CameraInfo;
|
|
|
|
return GetCameraInformationFromActor(Actor, /*out*/ CameraInfo);
|
|
}
|
|
|
|
void SLevelViewport::TakeHighResScreenShot()
|
|
{
|
|
if( LevelViewportClient.IsValid() )
|
|
{
|
|
LevelViewportClient->TakeHighResScreenShot();
|
|
}
|
|
}
|
|
|
|
void SLevelViewport::OnFloatingButtonClicked()
|
|
{
|
|
// if one of the viewports floating buttons has been clicked, update the global viewport ptr
|
|
LevelViewportClient->SetLastKeyViewport();
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|