Files
UnrealEngine/Engine/Source/Editor/LevelEditor/Private/SLevelViewport.cpp
2025-05-18 13:04:45 +08:00

5592 lines
188 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SLevelViewport.h"
#include "Materials/MaterialInterface.h"
#include "Engine/Selection.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandList.h"
#include "Subsystems/EditorAssetSubsystem.h"
#include "Widgets/Text/STextBlock.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/Docking/TabManager.h"
#include "Engine/GameViewportClient.h"
#include "EngineGlobals.h"
#include "ActorFactories/ActorFactory.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/FeedbackContext.h"
#include "Modules/ModuleManager.h"
#include "GameFramework/PlayerController.h"
#include "Application/ThrottleManager.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SWidgetSwitcher.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Views/SHeaderRow.h"
#include "Framework/Docking/LayoutService.h"
#include "Styling/CoreStyle.h"
#include "Styling/AppStyle.h"
#include "Editor/UnrealEdEngine.h"
#include "Editor/Transactor.h"
#include "Exporters/ExportTextContainer.h"
#include "Camera/CameraActor.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/WorldSettings.h"
#include "LevelEditorViewport.h"
#include "UnrealEdMisc.h"
#include "UnrealEdGlobals.h"
#include "LevelEditor.h"
#include "SLevelViewportToolBar.h"
#include "LevelViewportActions.h"
#include "LevelEditorActions.h"
#include "SceneView.h"
#include "Slate/SceneViewport.h"
#include "EditorShowFlags.h"
#include "SLevelEditor.h"
#include "AssetSelection.h"
#include "Kismet2/DebuggerCommands.h"
#include "Layers/LayersSubsystem.h"
#include "DragAndDrop/ClassDragDropOp.h"
#include "DragAndDrop/AssetDragDropOp.h"
#include "DragAndDrop/ExportTextDragDropOp.h"
#include "LevelUtils.h"
#include "DragAndDrop/BrushBuilderDragDropOp.h"
#include "ISceneOutlinerColumn.h"
#include "ActorTreeItem.h"
#include "ScopedTransaction.h"
#include "SCaptureRegionWidget.h"
#include "HighresScreenshotUI.h"
#include "ISettingsModule.h"
#include "BufferVisualizationData.h"
#include "NaniteVisualizationData.h"
#include "LumenVisualizationData.h"
#include "SubstrateVisualizationData.h"
#include "GroomVisualizationData.h"
#include "VirtualShadowMapVisualizationData.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "SActorPilotViewportToolbar.h"
#include "Engine/LocalPlayer.h"
#include "Slate/SGameLayerManager.h"
#include "FoliageType.h"
#include "IVREditorModule.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "BufferVisualizationMenuCommands.h"
#include "NaniteVisualizationMenuCommands.h"
#include "LumenVisualizationMenuCommands.h"
#include "SubstrateVisualizationMenuCommands.h"
#include "VirtualShadowMapVisualizationMenuCommands.h"
#include "VirtualTextureVisualizationMenuCommands.h"
#include "EditorLevelUtils.h"
#include "Engine/LevelStreaming.h"
#include "WorldBrowserModule.h"
#include "Bookmarks/IBookmarkTypeTools.h"
#include "ToolMenus.h"
#include "Bookmarks/IBookmarkTypeTools.h"
#include "Editor/EditorPerformanceSettings.h"
#include "UnrealWidget.h"
#include "WorldPartition/WorldPartitionSubsystem.h"
#include "WorldPartition/WorldPartition.h"
#include "WorldPartition/DataLayer/WorldDataLayers.h"
#include "WorldPartition/DataLayer/DataLayerInstance.h"
#include "DataLayer/DataLayerEditorSubsystem.h"
#include "SInViewportDetails.h"
#include "Viewports/InViewportUIDragOperation.h"
#include "SActorEditorContext.h"
#include "Settings/LevelEditorPlaySettings.h"
#include "SWorldPartitionViewportWidget.h"
#include "LevelViewportLayout.h"
#include "EditorViewportTabContent.h"
#include "EditorViewportCommands.h"
#include "FunctionalUIScreenshotTest.h"
#include "GPUSkinCache.h"
#include "GPUSkinCacheVisualizationMenuCommands.h"
#include "SActionableMessageViewportWidget.h"
#include "ShowFlagMenuCommands.h"
#include "SkeletalRenderPublic.h"
#include "ViewportToolBarContext.h"
#include "Subsystems/PanelExtensionSubsystem.h"
#include "ViewportToolbar/LevelEditorSubmenus.h"
#include "ViewportToolbar/LevelViewportContext.h"
#include "ViewportToolbar/UnrealEdViewportToolbar.h"
static const FName LevelEditorName("LevelEditor");
static FAutoConsoleCommand EnableInViewportMenu(TEXT("Editor.EnableInViewportMenu"), TEXT("Enables the new in-viewport property menu"), FConsoleCommandDelegate::CreateStatic(&SLevelViewport::EnableInViewportMenu));
bool SLevelViewport::bInViewportMenuEnabled = false;
#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;
}
namespace UE::SLevelViewport::Internal
{
bool SaveViewportInfo(UWorld* World, FLevelEditorViewportClient* LevelEditorViewportClient, ULevelEditorViewportSettings* LevelEditorViewportSettings)
{
if (!World || !LevelEditorViewportClient || !LevelEditorViewportSettings)
{
return false;
}
// there could potentially be more than one of the same viewport type. This effectively takes the last one of a specific type
World->EditorViews[LevelEditorViewportClient->ViewportType] =
FLevelViewportInfo(
LevelEditorViewportClient->GetViewLocation(),
LevelEditorViewportClient->GetViewRotation(),
LevelEditorViewportClient->GetOrthoZoom());
LevelEditorViewportSettings->EditorViews.FindOrAdd(World).LevelViewportsInfo = World->EditorViews;
LevelEditorViewportSettings->SaveConfig();
return true;
}
bool LoadViewportInfo(UWorld* World, FLevelEditorViewportClient* LevelEditorViewportClient, ULevelEditorViewportSettings* LevelEditorViewportSettings)
{
if (!World || !LevelEditorViewportClient || !LevelEditorViewportSettings)
{
return false;
}
if (FLevelEditorViewporEditorViews* PerUserEditorViews = LevelEditorViewportSettings->EditorViews.Find(World))
{
World->EditorViews = PerUserEditorViews->LevelViewportsInfo;
}
LevelEditorViewportClient->ResetCamera();
bool bInitializedOrthoViewport = false;
for (int32 ViewportType = 0; ViewportType < LVT_MAX; ViewportType++)
{
float& CamOrthoZoom = World->EditorViews[ViewportType].CamOrthoZoom;
if (CamOrthoZoom < MIN_ORTHOZOOM || CamOrthoZoom > MAX_ORTHOZOOM)
{
CamOrthoZoom = DEFAULT_ORTHOZOOM;
}
if (ViewportType == LVT_Perspective || !bInitializedOrthoViewport)
{
LevelEditorViewportClient->SetInitialViewTransform(
static_cast<ELevelViewportType>(ViewportType),
World->EditorViews[ViewportType].CamPosition,
World->EditorViews[ViewportType].CamRotation,
World->EditorViews[ViewportType].CamOrthoZoom);
if (ViewportType != LVT_Perspective)
{
bInitializedOrthoViewport = true;
}
}
}
return true;
}
// Clears existing selection, then selects the specified actor
void SelectActor(AActor* InActor)
{
check(InActor);
const FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>(LevelEditorName);
FEditorModeTools& EditorModeManager = LevelEditorModule.GetFirstLevelEditor()->GetEditorModeManager();
// Deselect any currently selected actors
EditorModeManager.SelectNone();
EditorModeManager.GetSelectedActors()->DeselectAll();
EditorModeManager.GetSelectedObjects()->DeselectAll();
EditorModeManager.GetSelectedActors()->Select(InActor, true);
EditorModeManager.ActorSelectionChangeNotify();
}
}
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( nullptr )
, ViewTransitionType( EViewTransition::None )
, bViewTransitionAnimPending( false )
, DeviceProfile("Default")
, bPIEHasFocus(false)
, bPIEContainsFocus(false)
, UserAllowThrottlingValue(0)
{
}
SLevelViewport::~SLevelViewport()
{
// Clean up any actor preview viewports
for (FViewportActorPreview& ActorPreview : ActorPreviews)
{
ActorPreview.bIsPinned = false;
}
const bool bPreviewInDesktopViewport = !IVREditorModule::Get().IsVREditorModeActive();
PreviewActors( TArray< AActor* >(), bPreviewInDesktopViewport);
FLevelViewportCommands::NewStatCommandDelegate.RemoveAll(this);
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorName );
LevelEditor.OnRedrawLevelEditingViewports().RemoveAll( this );
LevelEditor.OnActorSelectionChanged().RemoveAll( this );
LevelEditor.OnElementSelectionChanged().RemoveAll( this );
LevelEditor.OnMapChanged().RemoveAll( this );
if(UObjectInitialized())
{
GEngine->OnLevelActorDeleted().RemoveAll(this);
GEngine->OnEditorClose().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.IsNone())
{
return ParentLayout.Pin()->IsLevelViewportVisible(ConfigKey);
}
return false;
}
void SLevelViewport::Construct(const FArguments& InArgs, const FAssetEditorViewportConstructionArgs& InConstructionArguments)
{
GetMutableDefault<ULevelEditorViewportSettings>()->OnSettingChanged().AddRaw(this, &SLevelViewport::HandleViewportSettingChanged);
ParentLayout = StaticCastSharedPtr<FLevelViewportLayout>(InConstructionArguments.ParentLayout);
ParentLevelEditor = StaticCastSharedRef<SLevelEditor>( InArgs._ParentLevelEditor.Pin().ToSharedRef() );
ConfigKey = InConstructionArguments.ConfigKey;
LevelViewportClient = InArgs._LevelEditorViewportClient;
DebuggingBorder = FAppStyle::Get().GetBrush( "LevelViewport.DebugBorder" );
BlackBackground = FAppStyle::Get().GetBrush( "LevelViewport.BlackBackground" );
StartingPlayInEditorBorder = FAppStyle::Get().GetBrush( "LevelViewport.StartingPlayInEditorBorder" );
StartingSimulateBorder = FAppStyle::Get().GetBrush( "LevelViewport.StartingSimulateBorder" );
ReturningToEditorBorder = FAppStyle::Get().GetBrush( "LevelViewport.ReturningToEditorBorder" );
NonMaximizedBorder = FAppStyle::Get().GetBrush("LevelViewport.NonMaximizedBorder");
// Default level viewport client values for settings that could appear in layout config ini
FLevelEditorViewportInstanceSettings ViewportInstanceSettings;
ViewportInstanceSettings.ViewportType = InConstructionArguments.ViewportType;
ViewportInstanceSettings.PerspViewModeIndex = VMI_Lit;
ViewportInstanceSettings.OrthoViewModeIndex = VMI_BrushWireframe;
ViewportInstanceSettings.bIsRealtime = InConstructionArguments.bRealtime;
ConstructLevelEditorViewportClient(ViewportInstanceSettings);
SEditorViewport::Construct(SEditorViewport::FArguments()
.ViewportSize(MakeAttributeSP(this, &SLevelViewport::GetSViewportSize))
);
TSharedRef<SWidget> EditorViewportWidget = ChildSlot.GetChildAt(0);
ChildSlot
[
SNew(SScaleBox)
.Stretch(this, &SLevelViewport::OnGetScaleBoxStretch)
.HAlign(EHorizontalAlignment::HAlign_Center)
.VAlign(EVerticalAlignment::VAlign_Center)
.StretchDirection(EStretchDirection::Both)
[
EditorViewportWidget
]
];
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 );
// 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.OnElementSelectionChanged().AddRaw( this, &SLevelViewport::OnElementSelectionChanged );
// Tell the level editor we want to be notified when selection changes
LevelEditor.OnMapChanged().AddRaw( this, &SLevelViewport::OnMapChanged );
GEngine->OnLevelActorDeleted().AddRaw( this, &SLevelViewport::OnLevelActorsRemoved );
GEngine->OnEditorClose().AddRaw( this, &SLevelViewport::OnEditorClose );
FEditorDelegates::PostPIEStarted.AddSP(this, &SLevelViewport::TransitionToPIE);
FEditorDelegates::PrePIEEnded.AddSP(this, &SLevelViewport::TransitionFromPIE);
bIsInViewportMenuShowing = false;
bIsInViewportMenuInitialized = false;
}
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
// Add widget to show viewport warnings from the ActionableMessageSubsystem.
{
TAttribute<FMargin> Padding = TAttribute<FMargin>::CreateLambda(
[]()
{
const float TopPadding = UE::UnrealEd::ShowOldViewportToolbars() ? 37.0f : 8.0f;
return FMargin(0.f, TopPadding, 10.f, 0.f);
}
);
// clang-format off
ViewportOverlay->AddSlot(SlotIndex)
.Padding(Padding)
.VAlign(VAlign_Top)
.HAlign(HAlign_Right)
[
SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("ActionableMessage.Border"))
[
SAssignNew(ActionableMessageViewportWidget, SActionableMessageViewportWidget)
.Visibility_Lambda(
[this]()
{
if (!IsViewportToolbarVisible())
{
return EVisibility::Collapsed;
}
if (GetToolBarVisibility() == EVisibility::Collapsed)
{
return EVisibility::Collapsed;
}
if (!IsActiveLevelViewport())
{
return EVisibility::Collapsed;
}
return ActionableMessageViewportWidget->GetVisibility();
}
)
]
];
// clang-format on
}
ViewportOverlay->AddSlot( SlotIndex )
.HAlign(HAlign_Right)
.Padding(0.0f, 0.0f, 0.0f, 35.0f)
[
SAssignNew( ActorPreviewHorizontalBox, SHorizontalBox )
];
auto GetCombinedVisibility = [this]()
{
if (GetCurrentScreenPercentageVisibility() == EVisibility::Collapsed &&
GetCurrentFeatureLevelPreviewTextVisibility() == EVisibility::Collapsed &&
GetSelectedActorsCurrentLevelTextVisibility() == EVisibility::Collapsed &&
!IsActorEditorContextVisible())
{
return EVisibility::Collapsed;
}
return EVisibility::Visible;
};
ViewportOverlay->AddSlot( SlotIndex )
.VAlign( VAlign_Bottom )
.HAlign( HAlign_Right )
.Padding( 5.0f )
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("FloatingBorder"))
[
SNew(SVerticalBox)
.Visibility_Lambda(GetCombinedVisibility)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
[
SNew(SHorizontalBox)
.Visibility(this, &SLevelViewport::GetCurrentScreenPercentageVisibility)
// Current screen percentage label
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("ScreenPercentageLabel", "Screen Percentage"))
.ShadowOffset(FVector2D(1, 1))
]
// Current screen percentage
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f, 1.0f, 2.0f, 1.0f)
[
SNew(STextBlock)
.Text(this, &SLevelViewport::GetCurrentScreenPercentageText)
.ShadowOffset(FVector2D(1, 1))
]
]
// add feature level widget
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
[
BuildFeatureLevelWidget()
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
[
SNew(SVerticalBox)
.Visibility(this, &SLevelViewport::GetSelectedActorsCurrentLevelTextVisibility)
// Current level label
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
[
SNew(STextBlock)
.Text(this, &SLevelViewport::GetSelectedActorsCurrentLevelText, true)
.ShadowOffset(FVector2D(1, 1))
]
// Current level
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(4.0f, 1.0f, 2.0f, 1.0f)
[
SNew(STextBlock)
.Text(this, &SLevelViewport::GetSelectedActorsCurrentLevelText, false)
.ShadowOffset(FVector2D(1, 1))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SActorEditorContext)
.World(GetWorld())
.Visibility_Lambda([this]() { return IsActorEditorContextVisible() ? OnGetViewportContentVisibility() : EVisibility::Collapsed; })
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 4.0f, 0.0f, 0.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("FloatingBorder"))
[
SAssignNew(WorldPartitionViewportWidget, SWorldPartitionViewportWidget)
.Clickable(true)
.Visibility_Lambda([this]()
{
return (!ActiveViewport.IsValid() || IsPlayInEditorViewportActive()) ? EVisibility::Collapsed : WorldPartitionViewportWidget->GetVisibility(GetWorld());
})
]
]
];
// Add highres screenshot region capture editing widget
ViewportOverlay->AddSlot(SlotIndex)
.VAlign( VAlign_Fill )
.HAlign( HAlign_Fill )
.Padding( 0.f )
[
SAssignNew(CaptureRegionWidget, SCaptureRegionWidget)
];
}
bool SLevelViewport::IsActorEditorContextVisible() const
{
return GetDefault<ULevelEditorViewportSettings>()->bShowActorEditorContext &&
GetWorld() &&
GetWorld()->GetCurrentLevel() &&
(&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) &&
ActiveViewport.IsValid() &&
(ActiveViewport->GetPlayInEditorIsSimulate() || !ActiveViewport->GetClient()->GetWorld()->IsGameWorld()) &&
SActorEditorContext::IsVisible(GetWorld());
}
void SLevelViewport::ConstructLevelEditorViewportClient(FLevelEditorViewportInstanceSettings& ViewportInstanceSettings)
{
if (!LevelViewportClient.IsValid())
{
LevelViewportClient = MakeShareable( new FLevelEditorViewportClient(SharedThis(this)) );
}
FEngineShowFlags EditorShowFlags(ESFIM_Editor);
FEngineShowFlags GameShowFlags(ESFIM_Game);
// Use config key if it exists to set up the level viewport client
if(!ConfigKey.IsNone())
{
FString ConfigKeyAsString = ConfigKey.ToString();
const FLevelEditorViewportInstanceSettings* const ViewportInstanceSettingsPtr = GetDefault<ULevelEditorViewportSettings>()->GetViewportInstanceSettings(ConfigKeyAsString);
ViewportInstanceSettings = (ViewportInstanceSettingsPtr) ? *ViewportInstanceSettingsPtr : LoadLegacyConfigFromIni(ConfigKeyAsString, 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->CurrentNaniteVisualizationMode = ViewportInstanceSettings.NaniteVisualizationMode;
LevelViewportClient->CurrentLumenVisualizationMode = ViewportInstanceSettings.LumenVisualizationMode;
LevelViewportClient->CurrentSubstrateVisualizationMode = ViewportInstanceSettings.SubstrateVisualizationMode;
LevelViewportClient->CurrentGroomVisualizationMode = ViewportInstanceSettings.GroomVisualizationMode;
LevelViewportClient->CurrentVirtualShadowMapVisualizationMode = ViewportInstanceSettings.VirtualShadowMapVisualizationMode;
LevelViewportClient->CurrentVirtualTextureVisualizationMode = ViewportInstanceSettings.VirtualTextureVisualizationMode;
LevelViewportClient->CurrentRayTracingDebugVisualizationMode = ViewportInstanceSettings.RayTracingDebugVisualizationMode;
LevelViewportClient->CurrentGPUSkinCacheVisualizationMode = ViewportInstanceSettings.GPUSkinCacheVisualizationMode;
LevelViewportClient->ExposureSettings = ViewportInstanceSettings.ExposureSettings;
if(ViewportInstanceSettings.ViewportType == LVT_Perspective)
{
LevelViewportClient->SetViewLocation( EditorViewportDefs::DefaultPerspectiveViewLocation );
LevelViewportClient->SetViewRotation( EditorViewportDefs::DefaultPerspectiveViewRotation );
}
LevelViewportClient->SetAllowCinematicControl(ViewportInstanceSettings.bAllowCinematicControl);
LevelViewportClient->SetRealtime(ViewportInstanceSettings.bIsRealtime);
LevelViewportClient->SetShowStats(ViewportInstanceSettings.bShowOnScreenStats);
if (ViewportInstanceSettings.bShowFPS_DEPRECATED)
{
GetMutableDefault<ULevelEditorViewportSettings>()->bSaveEngineStats = true;
ViewportInstanceSettings.EnabledStats.AddUnique(TEXT("FPS"));
}
if (GetDefault<ULevelEditorViewportSettings>()->bSaveEngineStats)
{
GEngine->SetEngineStats(GetWorld(), LevelViewportClient.Get(), ViewportInstanceSettings.EnabledStats, true);
}
LevelViewportClient->VisibilityDelegate.BindSP( this, &SLevelViewport::IsVisible );
LevelViewportClient->ImmersiveDelegate.BindSP( this, &SLevelViewport::IsImmersive );
LevelViewportClient->bDrawBaseInfo = true;
LevelViewportClient->bDrawVertices = true;
LevelViewportClient->ViewFOV = LevelViewportClient->FOVAngle = ViewportInstanceSettings.FOVAngle;
LevelViewportClient->OverrideFarClipPlane( ViewportInstanceSettings.FarViewPlane );
// Set the selection outline flag based on preferences
LevelViewportClient->EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline);
// Always composite editor objects after post processing in the editor
LevelViewportClient->EngineShowFlags.SetCompositeEditorPrimitives(true);
LevelViewportClient->SetViewModes(ViewportInstanceSettings.PerspViewModeIndex, ViewportInstanceSettings.OrthoViewModeIndex );
LevelViewportClient->InitializeViewportInteraction();
bShowEditorToolbar = ViewportInstanceSettings.bShowFullToolbar;
// Always set to true initially
bShowToolbarAndControls = true;
if (FPlatformMisc::IsRemoteSession())
{
// Bind to the change delegate of performance settings and call our handler with a dummy event to set current defaults
UEditorPerformanceSettings* PerformanceSettings = GetMutableDefault< UEditorPerformanceSettings>();
PerformanceSettings->OnSettingChanged().AddSP(this, &SLevelViewport::OnPerformanceSettingsChanged);
FPropertyChangedEvent DummyEvent(nullptr);
OnPerformanceSettingsChanged(PerformanceSettings, DummyEvent);
}
}
/** Updates the real-time overrride applied to the viewport */
void SLevelViewport::OnPerformanceSettingsChanged(UObject* Obj, FPropertyChangedEvent& ChangeEvent)
{
if (FPlatformMisc::IsRemoteSession())
{
const FText RDPRealtimeOverrideName = LOCTEXT("RealtimeOverrideMessage_RDP", "Remote Desktop");
UEditorPerformanceSettings* PerformanceSettings = GetMutableDefault< UEditorPerformanceSettings>();
if (Obj == PerformanceSettings && ensure(LevelViewportClient))
{
// Respond to settings changes by adding or removing the realtime override as appropriate
if (PerformanceSettings->bDisableRealtimeViewportsInRemoteSessions && !LevelViewportClient->HasRealtimeOverride(RDPRealtimeOverrideName))
{
bool bShouldBeRealtime = false;
LevelViewportClient->AddRealtimeOverride(bShouldBeRealtime, RDPRealtimeOverrideName);
}
else if (!PerformanceSettings->bDisableRealtimeViewportsInRemoteSessions && LevelViewportClient->HasRealtimeOverride(RDPRealtimeOverrideName))
{
LevelViewportClient->RemoveRealtimeOverride(RDPRealtimeOverrideName, false);
}
}
}
}
FSceneViewport* SLevelViewport::GetGameSceneViewport() const
{
return ActiveViewport.Get();
}
void SLevelViewport::ToggleViewportToolbarVisibility()
{
if (IsPlayInEditorViewportActive())
{
if (IsImmersive())
{
bShowImmersivePIEToolbar = !bShowImmersivePIEToolbar;
}
else
{
bShowPIEToolbar = !bShowPIEToolbar;
if (!bShowPIEToolbar)
{
bShowImmersivePIEToolbar = false;
}
}
}
else
{
bShowEditorToolbar = !bShowEditorToolbar;
}
}
bool SLevelViewport::IsViewportToolbarVisible() const
{
if (IsPlayInEditorViewportActive())
{
return IsImmersive() ? bShowImmersivePIEToolbar : bShowPIEToolbar;
}
return bShowEditorToolbar;
}
void SLevelViewport::TransitionToPIE(bool bIsSimulating)
{
for (FViewportActorPreview& ActorPreview : ActorPreviews)
{
if (ActorPreview.LevelViewportClient.IsValid() && !ActorPreview.LevelViewportClient->IsSimulateInEditorViewport())
{
ActorPreview.LevelViewportClient->SetIsSimulateInEditorViewport(true);
}
}
}
void SLevelViewport::TransitionFromPIE(bool bIsSimulating)
{
for (FViewportActorPreview& ActorPreview : ActorPreviews)
{
if (ActorPreview.LevelViewportClient.IsValid() && ActorPreview.LevelViewportClient->IsSimulateInEditorViewport())
{
ActorPreview.LevelViewportClient->SetIsSimulateInEditorViewport(false);
}
}
}
EStretch::Type SLevelViewport::OnGetScaleBoxStretch() const
{
FSceneViewport* GameSceneViewport = GetGameSceneViewport();
if (GameSceneViewport && GameSceneViewport->HasFixedSize())
{
return EStretch::ScaleToFit;
}
return EStretch::Fill;
}
FVector2D SLevelViewport::GetSViewportSize() const
{
FSceneViewport* GameSceneViewport = GetGameSceneViewport();
if (GameSceneViewport && GameSceneViewport->HasFixedSize())
{
return GameSceneViewport->GetSize();
}
return SViewport::FArguments::GetDefaultViewportSize();
}
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
bool bHandled = FPlayWorldCommands::GlobalPlayWorldActions->ProcessCommandBindings( InKeyEvent );
if (HasPlayInEditorViewport())
{
bHandled = bHandled || PIECommands->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);
// Otherwise, give the in-viewport context menu a chance to handle the keypress.
if (!Reply.IsEventHandled() && InViewportMenu.IsValid())
{
Reply = InViewportMenu->GetGeneratedToolbarMenu()->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() : nullptr;
if ( CurrentLevel && !FLevelUtils::IsLevelLocked(CurrentLevel) )
{
if ( HandleDragObjects(MyGeometry, DragDropEvent) )
{
// Hide the decorator before dropping the object to avoid having a decorator present for the
// entire duration of an async asset build if required.
// Restore the decorator visibility if the drop fails to preserve previous behavior.
DragDropEvent.GetOperation()->SetDecoratorVisibility(false);
if ( !HandlePlaceDraggedObjects(MyGeometry, DragDropEvent, /*bCreateDropPreview=*/true) )
{
DragDropEvent.GetOperation()->SetDecoratorVisibility(true);
}
}
}
bDragEnterReentranceGuard = false;
}
}
void SLevelViewport::OnDragLeave( const FDragDropEvent& DragDropEvent )
{
if ( LevelViewportClient->HasDropPreviewActors() )
{
LevelViewportClient->DestroyDropPreviewElements();
}
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (Operation.IsValid())
{
Operation->SetDecoratorVisibility(true);
if (Operation->IsOfType<FDecoratedDragDropOp>())
{
TSharedPtr<FDecoratedDragDropOp> DragDropOp = StaticCastSharedPtr<FDecoratedDragDropOp>(Operation);
DragDropOp->ResetToDefaultToolTip();
}
}
}
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<FExportTextDragDropOp>())
{
bValidDrag = true;
}
else if (Operation->IsOfType<FBrushBuilderDragDropOp>())
{
bValidDrag = true;
auto BrushOperation = StaticCastSharedPtr<FBrushBuilderDragDropOp>( Operation );
new(SelectedAssetDatas) FAssetData(BrushOperation->GetBrushBuilder().Get());
}
else if (Operation->IsOfType<FInViewportUIDragOperation>())
{
bValidDrag = true;
}
else
{
GetAssetsFromDrag(DragDropEvent, SelectedAssetDatas);
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 = FVector2f(MyGeometry.AbsoluteToLocal( DragDropEvent.GetScreenSpacePosition() ) * MyGeometry.Scale).IntPoint();
CachedOnDropLocalMousePos -= ViewportOrigin;
}
// 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);
bValidDrag = false;
HintText = DropResult.HintText;
break;
}
else
{
if ( HintText.IsEmpty() )
{
HintText = DropResult.HintText;
}
}
}
if ( Operation->IsOfType<FAssetDragDropOp>() )
{
auto AssetOperation = StaticCastSharedPtr<FAssetDragDropOp>(DragDropEvent.GetOperation());
AssetOperation->SetToolTip(HintText, nullptr);
}
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;
TScriptInterface<IAssetFactoryInterface> AssetFactory = nullptr;
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<FAssetDragDropOp>())
{
bValidDrop = true;
DroppedObjects.Empty();
TSharedPtr<FAssetDragDropOp> DragDropOp = StaticCastSharedPtr<FAssetDragDropOp>( Operation );
AssetFactory = DragDropOp->GetAssetFactory();
bAllAssetWereLoaded = true;
for (const FAssetData& AssetData : DragDropOp->GetAssets())
{
UObject* Asset = AssetData.GetAsset();
if ( Asset != nullptr )
{
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;
GetAssetsFromDrag(DragDropEvent, DroppedAssetDatas);
bAllAssetWereLoaded = true;
for (int32 AssetIdx = 0; AssetIdx < DroppedAssetDatas.Num(); ++AssetIdx)
{
const FAssetData& AssetData = DroppedAssetDatas[AssetIdx];
UObject* Asset = AssetData.GetAsset();
if ( Asset != nullptr )
{
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
// Note that this can run gc, better hold onto our objects!
TArray<TStrongObjectPtr<UObject>> DroppedObjectsLocked;
Algo::Transform(DroppedObjects, DroppedObjectsLocked, [](UObject* Obj){ return TStrongObjectPtr<UObject>(Obj); }); // Transform to accomodate explicit ctor
LevelViewportClient->DestroyDropPreviewElements();
if( !bShowDropContextMenu || !bCreateDropPreview )
{
// Otherwise just attempt to drop the object(s)
FLevelEditorViewportClient::FDropObjectOptions DropOptions;
DropOptions.FactoryToUse = AssetFactory;
DropOptions.bOnlyDropOnTarget = false;
DropOptions.bCreateDropPreview = bCreateDropPreview;
// Only select actor on drop
DropOptions.bSelectOutput = !bCreateDropPreview;
TArray<FTypedElementHandle> Unused;
bDropSuccessful = LevelViewportClient->DropObjectsAtCoordinates(CachedOnDropLocalMousePos.X, CachedOnDropLocalMousePos.Y,
DroppedObjects, Unused, DropOptions);
}
else if ( bAllAssetWereLoaded && DroppedObjects.Num() > 0 )
{
FWidgetPath WidgetPath = DragDropEvent.GetEventPath() != nullptr ? *DragDropEvent.GetEventPath() : FWidgetPath();
FSlateApplication::Get().PushMenu(
SharedThis( this ),
WidgetPath,
BuildViewportDragDropContextMenu(),
DragDropEvent.GetScreenSpacePosition(),
FPopupTransitionEffect( FPopupTransitionEffect::ContextMenu ) );
bDropSuccessful = true;
}
// Give the editor focus (quick Undo/Redo support after a drag drop operation)
if(ParentLevelEditor.IsValid())
{
FGlobalTabmanager::Get()->DrawAttentionToTabManager(ParentLevelEditor.Pin()->GetTabManager().ToSharedRef());
}
if(bDropSuccessful)
{
SetKeyboardFocusToThisViewport();
}
return bDropSuccessful;
}
return false;
}
void SLevelViewport::GetAssetsFromDrag(const FDragDropEvent& DragDropEvent, TArray<FAssetData>& AssetDataArray)
{
AssetDataArray = AssetUtil::ExtractAssetDataFromDrag(DragDropEvent);
// Did we get anything?
if (AssetDataArray.Num() == 0)
{
// Get files from the drag/drop.
const TSharedPtr<FDragDropOperation>& Operation = DragDropEvent.GetOperation();
if (Operation.IsValid())
{
if (Operation->IsOfType<FExternalDragOperation>())
{
TSharedPtr<FExternalDragOperation> DragDropOp = StaticCastSharedPtr<FExternalDragOperation>(Operation);
if (DragDropOp->HasFiles())
{
const TArray<FString>& Files = DragDropOp->GetFiles();
if (Files.Num() > 0)
{
// See if anyone else can get us an asset.
UEditorAssetSubsystem* EditorAssetSubsystem = GEditor->GetEditorSubsystem<UEditorAssetSubsystem>();
if (EditorAssetSubsystem != nullptr)
{
EditorAssetSubsystem->GetOnExtractAssetFromFile().Broadcast(Files, AssetDataArray);
}
}
}
}
}
}
}
FReply SLevelViewport::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
if (DragDropEvent.GetOperation()->IsOfType<FInViewportUIDragOperation>())
{
FVector2D ScreenSpaceDropLocation = DragDropEvent.GetScreenSpacePosition() - DragDropEvent.GetOperationAs<FInViewportUIDragOperation>()->GetDecoratorOffsetFromCursor();
DragDropEvent.GetOperationAs<FInViewportUIDragOperation>()->BroadcastDropEvent(MyGeometry.AbsoluteToLocal(ScreenSpaceDropLocation));
}
else
{
ULevel* CurrentLevel = (GetWorld()) ? GetWorld()->GetCurrentLevel() : nullptr;
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();
}
}
return FReply::Unhandled();
}
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 (PIEOverlayBorder.IsValid())
{
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();
if (!bPIEHasFocus || !UE::UnrealEd::ShowNewViewportToolbars())
{
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
if (bNeedToUpdatePreviews)
{
const bool bPreviewInDesktopViewport = !IVREditorModule::Get().IsVREditorModeActive();
if (GetDefault<ULevelEditorViewportSettings>()->bPreviewSelectedCameras && GCurrentLevelEditingViewportClient == LevelViewportClient.Get())
{
PreviewSelectedCameraActors(bPreviewInDesktopViewport);
}
else
{
// We're no longer the active viewport client, so remove any existing previewed actors
PreviewActors(TArray<AActor*>(), bPreviewInDesktopViewport);
}
bNeedToUpdatePreviews = false;
}
}
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 )
{
using namespace UE::SLevelViewport::Internal;
if( World && ( ( World == GetWorld() ) || ( World->EditorViews[LevelViewportClient->ViewportType].CamUpdated ) ) )
{
if( MapChangeType == EMapChangeType::LoadMap )
{
ResetNewLevelViewFlags();
LoadViewportInfo(World, LevelViewportClient.Get(), GetMutableDefault<ULevelEditorViewportSettings>());
}
else if( (MapChangeType == EMapChangeType::SaveMap) || (MapChangeType == EMapChangeType::TearDownWorld))
{
SaveViewportInfo(World, LevelViewportClient.Get(), GetMutableDefault<ULevelEditorViewportSettings>());
}
else if( MapChangeType == EMapChangeType::NewMap )
{
ResetNewLevelViewFlags();
LevelViewportClient->ResetViewForNewMap();
}
World->EditorViews[LevelViewportClient->ViewportType].CamUpdated = false;
World->ChangeFeatureLevel(GEditor->GetActiveFeatureLevelPreviewType());
RedrawViewport(true);
}
}
void SLevelViewport::OnEditorClose()
{
UE::SLevelViewport::Internal::SaveViewportInfo(GetWorld(), LevelViewportClient.Get(), GetMutableDefault<ULevelEditorViewportSettings>());
}
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 ) );
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 );
BindDropCommands( UICommandListRef );
// Re-Map Perspective command so that
// - Perspective menu entry radial checkbox is not flagged when actors are being piloted (including cameras)
// - Clicking on the Perspective entry interrupts piloting
{
const TWeakPtr<SLevelViewport>& LevelViewportWeak = SharedThis(this).ToWeakPtr();
UICommandListRef.MapAction(
FEditorViewportCommands::Get().Perspective,
FExecuteAction::CreateLambda(
[LevelViewportWeak]()
{
if (const TSharedPtr<::SLevelViewport>& LevelViewportPinned =
LevelViewportWeak.Pin())
{
// If piloting, stop
LevelViewportPinned->OnActorUnlock();
if (const TSharedPtr<FEditorViewportClient>& ViewportClient = LevelViewportPinned->GetViewportClient())
{
// Set to perspective
ViewportClient->SetViewportType(LVT_Perspective);
}
}
}
),
FCanExecuteAction(),
FIsActionChecked::CreateLambda(
[LevelViewportWeak]()
{
if (const TSharedPtr<::SLevelViewport>& LevelViewportPinned =
LevelViewportWeak.Pin())
{
// Don't check action when piloting an actor
if (LevelViewportPinned->IsAnyActorLocked())
{
return false;
}
if (const TSharedPtr<FEditorViewportClient>& ViewportClient = LevelViewportPinned->GetViewportClient())
{
return ViewportClient->IsPerspective();
}
}
return false;
}
)
);
}
if ( ParentLevelEditor.IsValid() )
{
UICommandListRef.Append(ParentLevelEditor.Pin()->GetLevelEditorActions().ToSharedRef());
}
UICommandListRef.SetCanProduceActionForCommand( FUICommandList::FCanProduceActionForCommand::CreateSP(this, &SLevelViewport::CanProduceActionForCommand) );
// Exposes the current level viewport command list to subscribers from other systems
FInputBindingManager::Get().RegisterCommandList(FLevelViewportCommands::Get().GetContextName(), CommandList.ToSharedRef());
PIECommands = MakeShared<FUICommandList>();
BindPIECommands(*PIECommands);
}
void SLevelViewport::BindOptionCommands( FUICommandList& OutCommandList )
{
const FLevelViewportCommands& ViewportActions = FLevelViewportCommands::Get();
OutCommandList.MapAction(
ViewportActions.AdvancedSettings,
FExecuteAction::CreateSP( this, &SLevelViewport::OnAdvancedSettings ) );
OutCommandList.MapAction(
ViewportActions.PlaySettings,
FExecuteAction::CreateSP( this, &SLevelViewport::OnPlaySettings ) );
OutCommandList.MapAction(
ViewportActions.ToggleMaximize,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleMaximizeMode ),
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanToggleMaximizeMode ) );
OutCommandList.MapAction(
ViewportActions.ToggleGameView,
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleGameView ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsInGameView ) );
OutCommandList.MapAction(
ViewportActions.ToggleImmersive,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleImmersive),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsImmersive ) );
OutCommandList.MapAction(
ViewportActions.ToggleSidebarAllTabs,
FExecuteAction::CreateSP(this, &SLevelViewport::OnToggleSidebarTabs)
);
OutCommandList.MapAction(
ViewportActions.ToggleCinematicPreview,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllowCinematicPreview ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::AllowsCinematicPreview )
);
OutCommandList.MapAction(
ViewportActions.ToggleAllowConstrainedAspectRatioInPreview,
FExecuteAction::CreateLambda([]() { GEditor->ToggleAllowConstrainedAspectRatioInPreview(); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([]() { return GEditor->GetAllowConstrainedAspectRatioInPreview(); }),
FIsActionButtonVisible::CreateLambda([]() { return GEditor->IsFeatureLevelPreviewActive() && (GEditor->PreviewPlatform.GetConstrainedAspectRatio() != 0.0f); })
);
IAssetRegistry & AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
TArray<FTopLevelAssetPath> ClassNames;
TSet<FTopLevelAssetPath> DerivedClassNames;
ClassNames.Add(ACameraActor::StaticClass()->GetClassPathName());
AssetRegistry.GetDerivedClassNames(ClassNames, TSet<FTopLevelAssetPath>(), DerivedClassNames);
for (FTopLevelAssetPath ClassPathName : DerivedClassNames)
{
FString Name = ClassPathName.ToString();
// Ignore generated types that cannot be spawned
if (Name.Contains("SKEL_") || Name.Contains("REINST_"))
{
continue;
}
UClass* CameraClass = FindObject<UClass>(ClassPathName);
if (!CameraClass || CameraClass->HasAnyClassFlags(CLASS_Abstract|CLASS_NotPlaceable|CLASS_HideDropDown|CLASS_Hidden))
{
continue;
}
// Look for existing UI Command info so one isn't created for every viewport
FName CommandName;
{
int32 DotIndex = -1;
Name.FindLastChar('.', DotIndex);
CommandName = *Name.Mid(DotIndex + 1);
}
TSharedPtr<FUICommandInfo> * FoundCamera = FLevelViewportCommands::Get().CreateCameras.FindByPredicate([CommandName](TSharedPtr<FUICommandInfo> Camera) { return Camera->GetCommandName() == CommandName; });
if (FoundCamera)
{
OutCommandList.MapAction(
*FoundCamera,
FExecuteAction::CreateSP(this, &SLevelViewport::OnCreateCameraActor, CameraClass)
);
}
else
{
// If command info isn't found, create a new one
TSharedRef<FUICommandInfo> NewCamera = FUICommandInfoDecl(FLevelViewportCommands::Get().AsShared(), CommandName, CameraClass->GetDisplayNameText(), FText::Format(LOCTEXT("SpawnCamerasTooltip", "Spawn Camera here of type {0}"), FText::FromString(Name))).UserInterfaceType(EUserInterfaceActionType::Button).DefaultChord(FInputChord());
OutCommandList.MapAction(
NewCamera,
FExecuteAction::CreateSP(this, &SLevelViewport::OnCreateCameraActor, CameraClass)
);
FLevelViewportCommands::Get().CreateCameras.Add(NewCamera);
}
}
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::NumMappedBookmarks; ++BookmarkIndex )
{
OutCommandList.MapAction(
ViewportActions.JumpToBookmarkCommands[BookmarkIndex],
FExecuteAction::CreateSP( this, &SLevelViewport::OnJumpToBookmark, BookmarkIndex ),
FCanExecuteAction::CreateSP(this, &SLevelViewport::OnHasBookmarkSet, 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.CompactBookmarks,
FExecuteAction::CreateSP( this, &SLevelViewport::OnCompactBookmarks )
);
OutCommandList.MapAction(
ViewportActions.ClearAllBookmarks,
FExecuteAction::CreateSP( this, &SLevelViewport::OnClearAllBookmarks )
);
OutCommandList.MapAction(
ViewportActions.ToggleViewportToolbar,
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleViewportToolbarVisibility ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportToolbarVisible )
);
}
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.SelectPilotedActor,
FExecuteAction::CreateSP( this, &SLevelViewport::OnSelectLockedActor ),
FCanExecuteAction::CreateSP( this, &SLevelViewport::CanExecuteSelectLockedActor )
);
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 )
);
if (TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin())
{
if (TSharedPtr<FEditorViewportTabContent> ParentTabContentPinned = LayoutPinned->GetParentTabContent().Pin())
{
ParentTabContentPinned->BindViewportLayoutCommands(OutCommandList, ConfigKey);
// Override single panel configuration so that it acts like a one-way "maximize" button
OutCommandList.MapAction(
ViewportActions.ViewportConfig_OnePane,
FExecuteAction::CreateSP(this, &SLevelViewport::MakeMaximized, true, true),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SLevelViewport::IsMaximized)
);
}
}
FBufferVisualizationMenuCommands::Get().BindCommands(OutCommandList, Client);
FNaniteVisualizationMenuCommands::Get().BindCommands(OutCommandList, Client);
FLumenVisualizationMenuCommands::Get().BindCommands(OutCommandList, Client);
if (Substrate::IsSubstrateEnabled())
{
FSubstrateVisualizationMenuCommands::Get().BindCommands(OutCommandList, Client);
}
if (IsGroomEnabled())
{
FGroomVisualizationMenuCommands::Get().BindCommands(OutCommandList, Client);
}
FVirtualShadowMapVisualizationMenuCommands::Get().BindCommands(OutCommandList, Client);
if (UseVirtualTexturing(GMaxRHIShaderPlatform))
{
FVirtualTextureVisualizationMenuCommands::Get().BindCommands(OutCommandList, Client);
}
}
void SLevelViewport::BindShowCommands( FUICommandList& OutCommandList )
{
FLevelViewportCommands& LevelViewportCommands = FLevelViewportCommands::Get();
OutCommandList.MapAction(
LevelViewportCommands.UseDefaultShowFlags,
FExecuteAction::CreateSP( this, &SLevelViewport::OnUseDefaultShowFlags, false ) );
SEditorViewport::BindShowCommands( OutCommandList );
// Show Volumes
{
// Map 'Show All' and 'Hide All' commands
OutCommandList.MapAction(
LevelViewportCommands.ShowAllVolumes,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllVolumeActors, true ) );
OutCommandList.MapAction(
LevelViewportCommands.HideAllVolumes,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllVolumeActors, false ) );
LevelViewportCommands.RegisterShowVolumeCommands();
const TArray<FLevelViewportCommands::FShowMenuCommand>& ShowVolumeCommands = LevelViewportCommands.ShowVolumeCommands;
for (int32 VolumeCommandIndex = 0; VolumeCommandIndex < ShowVolumeCommands.Num(); ++VolumeCommandIndex)
{
OutCommandList.MapAction(
ShowVolumeCommands[ VolumeCommandIndex ].ShowMenuItem,
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleShowVolumeClass, VolumeCommandIndex ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsVolumeVisible, VolumeCommandIndex ) );
}
}
// Show Layers
{
auto CanToggleAllLayers = [this]() { return !UWorld::IsPartitionedWorld(GetWorld()); };
// Map 'Show All' and 'Hide All' commands
OutCommandList.MapAction(
LevelViewportCommands.ShowAllLayers,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllLayers, true ),
FCanExecuteAction::CreateLambda(CanToggleAllLayers));
OutCommandList.MapAction(
LevelViewportCommands.HideAllLayers,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllLayers, false ),
FCanExecuteAction::CreateLambda(CanToggleAllLayers));
}
// Show Sprite Categories
{
// Map 'Show All' and 'Hide All' commands
OutCommandList.MapAction(
LevelViewportCommands.ShowAllSprites,
FExecuteAction::CreateSP( this, &SLevelViewport::OnToggleAllSpriteCategories, true ) );
OutCommandList.MapAction(
LevelViewportCommands.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
LevelViewportCommands.RegisterShowSpriteCommands();
const TArray<FLevelViewportCommands::FShowMenuCommand>& ShowSpriteCommands = LevelViewportCommands.ShowSpriteCommands;
for (int32 SpriteCommandIndex = 0; SpriteCommandIndex < ShowSpriteCommands.Num(); ++SpriteCommandIndex)
{
OutCommandList.MapAction(
ShowSpriteCommands[SpriteCommandIndex].ShowMenuItem,
FExecuteAction::CreateSP(this, &SLevelViewport::ToggleSpriteCategory, SpriteCommandIndex),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SLevelViewport::IsSpriteCategoryVisible, SpriteCommandIndex));
}
}
// Show Stat Categories
{
#if STATS
// Map 'Hide All' command
OutCommandList.MapAction(
LevelViewportCommands.HideAllStats,
FExecuteAction::CreateSP(this, &SLevelViewport::OnToggleAllStatCommands, false));
#endif
for (auto StatCatIt = LevelViewportCommands.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));
}
void SLevelViewport::BindPIECommands(FUICommandList& OutCommandList)
{
OutCommandList.MapAction(
FLevelViewportCommands::Get().ToggleViewportToolbar,
FExecuteAction::CreateSP( this, &SLevelViewport::ToggleViewportToolbarVisibility ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SLevelViewport::IsViewportToolbarVisible )
);
OutCommandList.MapAction(FLevelViewportCommands::Get().UseDefaultShowFlags,
FExecuteAction::CreateSP(this, &SLevelViewport::ResetPIEShowFlags)
);
for (const FShowFlagMenuCommands::FShowFlagCommand& FlagCommand : FShowFlagMenuCommands::Get().GetCommands())
{
OutCommandList.MapAction(FlagCommand.ShowMenuItem,
FExecuteAction::CreateSP(this, &SLevelViewport::TogglePIEShowFlag, FlagCommand.FlagIndex),
FCanExecuteAction::CreateStatic(&FEngineShowFlags::IsForceFlagSet, static_cast<uint32>(FlagCommand.FlagIndex)),
FIsActionChecked::CreateSP(this, &SLevelViewport::IsPIEShowFlagEnabled, FlagCommand.FlagIndex)
);
}
const FEditorViewportCommands& Commands = FEditorViewportCommands::Get();
#define MAP_VIEWMODE_ACTION( ViewModeCommand, ViewModeID ) \
OutCommandList.MapAction( \
ViewModeCommand, \
FExecuteAction::CreateSP( this, &SLevelViewport::SetPIEViewMode, ViewModeID ), \
FCanExecuteAction(), \
FIsActionChecked::CreateSP( this, &SLevelViewport::IsPIEViewModeEnabled, ViewModeID ) )
// Map each view mode
MAP_VIEWMODE_ACTION( Commands.WireframeMode, VMI_BrushWireframe );
MAP_VIEWMODE_ACTION( Commands.UnlitMode, VMI_Unlit );
MAP_VIEWMODE_ACTION( Commands.LitMode, VMI_Lit );
MAP_VIEWMODE_ACTION( Commands.LitWireframeMode, VMI_Lit_Wireframe);
if (IsRayTracingAllowed())
{
MAP_VIEWMODE_ACTION(Commands.PathTracingMode, VMI_PathTracing);
MAP_VIEWMODE_ACTION(Commands.RayTracingDebugMode, VMI_RayTracingDebug);
// const FRayTracingDebugVisualizationMenuCommands& RtDebugCommands = FRayTracingDebugVisualizationMenuCommands::Get();
// RtDebugCommands.BindCommands(CommandListRef, Client);
}
MAP_VIEWMODE_ACTION( Commands.DetailLightingMode, VMI_Lit_DetailLighting );
MAP_VIEWMODE_ACTION( Commands.LightingOnlyMode, VMI_LightingOnly );
MAP_VIEWMODE_ACTION( Commands.LightComplexityMode, VMI_LightComplexity );
MAP_VIEWMODE_ACTION( Commands.ShaderComplexityMode, VMI_ShaderComplexity );
MAP_VIEWMODE_ACTION( Commands.QuadOverdrawMode, VMI_QuadOverdraw);
MAP_VIEWMODE_ACTION( Commands.ShaderComplexityWithQuadOverdrawMode, VMI_ShaderComplexityWithQuadOverdraw );
MAP_VIEWMODE_ACTION( Commands.TexStreamAccPrimitiveDistanceMode, VMI_PrimitiveDistanceAccuracy );
MAP_VIEWMODE_ACTION( Commands.TexStreamAccMeshUVDensityMode, VMI_MeshUVDensityAccuracy);
MAP_VIEWMODE_ACTION( Commands.TexStreamAccMaterialTextureScaleMode, VMI_MaterialTextureScaleAccuracy );
MAP_VIEWMODE_ACTION( Commands.RequiredTextureResolutionMode, VMI_RequiredTextureResolution);
MAP_VIEWMODE_ACTION( Commands.StationaryLightOverlapMode, VMI_StationaryLightOverlap );
if (IsStaticLightingAllowed())
{
MAP_VIEWMODE_ACTION(Commands.LightmapDensityMode, VMI_LightmapDensity);
}
MAP_VIEWMODE_ACTION( Commands.ReflectionOverrideMode, VMI_ReflectionOverride );
MAP_VIEWMODE_ACTION( Commands.GroupLODColorationMode, VMI_GroupLODColoration);
MAP_VIEWMODE_ACTION( Commands.LODColorationMode, VMI_LODColoration );
MAP_VIEWMODE_ACTION( Commands.HLODColorationMode, VMI_HLODColoration);
MAP_VIEWMODE_ACTION( Commands.VisualizeBufferMode, VMI_VisualizeBuffer );
MAP_VIEWMODE_ACTION( Commands.VisualizeNaniteMode, VMI_VisualizeNanite );
MAP_VIEWMODE_ACTION( Commands.VisualizeLumenMode, VMI_VisualizeLumen );
MAP_VIEWMODE_ACTION( Commands.VisualizeSubstrateMode, VMI_VisualizeSubstrate);
MAP_VIEWMODE_ACTION( Commands.VisualizeGroomMode, VMI_VisualizeGroom);
MAP_VIEWMODE_ACTION( Commands.VisualizeVirtualShadowMapMode, VMI_VisualizeVirtualShadowMap );
MAP_VIEWMODE_ACTION( Commands.VisualizeVirtualTextureMode, VMI_VisualizeVirtualTexture);
MAP_VIEWMODE_ACTION( Commands.CollisionPawn, VMI_CollisionPawn);
MAP_VIEWMODE_ACTION( Commands.CollisionVisibility, VMI_CollisionVisibility);
MAP_VIEWMODE_ACTION( Commands.VisualizeLWCComplexity, VMI_LWCComplexity);
}
void SLevelViewport::ResetPIEShowFlags()
{
if (UGameViewportClient* GameClient = GetPlayClient())
{
GameClient->EngineShowFlags = FEngineShowFlags(EShowFlagInitMode::ESFIM_Game);
RefreshPIEViewport();
}
}
void SLevelViewport::TogglePIEShowFlag(FEngineShowFlags::EShowFlag Flag)
{
if (UGameViewportClient* GameClient = GetPlayClient())
{
GameClient->GetEngineShowFlags()->SetSingleFlag(Flag, !GameClient->EngineShowFlags.GetSingleFlag(Flag));
RefreshPIEViewport();
}
}
bool SLevelViewport::IsPIEShowFlagEnabled(FEngineShowFlags::EShowFlag Flag) const
{
if (UGameViewportClient* GameClient = GetPlayClient())
{
return GameClient->GetEngineShowFlags()->GetSingleFlag(Flag);
}
return false;
}
void SLevelViewport::SetPIEViewMode(EViewModeIndex ViewMode)
{
if (UGameViewportClient* GameClient = GetPlayClient())
{
GameClient->SetViewMode(ViewMode);
RefreshPIEViewport();
}
}
bool SLevelViewport::IsPIEViewModeEnabled(EViewModeIndex ViewMode) const
{
if (UGameViewportClient* GameClient = GetPlayClient())
{
return GameClient->ViewModeIndex == ViewMode;
}
return false;
}
void SLevelViewport::RefreshPIEViewport()
{
if (UGameViewportClient* GameClient = GetPlayClient())
{
FSlateThrottleManager::Get().DisableThrottle(true);
FTimerHandle TimerHandle;
GameClient->GetWorld()->GetTimerManager().SetTimer(TimerHandle, []
{
FSlateThrottleManager::Get().DisableThrottle(false);
}, 0.1f, false);
}
}
TAutoConsoleVariable<int32> CVarLevelEditorPIEInputOverride(
TEXT("LevelEditorPIEInputOverride"),
1,
TEXT("Enable/disable overriding PIE gampeplay input with level editor PIE shorctuts.")
);
bool SLevelViewport::OnPIEViewportInputOverride(FInputKeyEventArgs& Input)
{
if (CVarLevelEditorPIEInputOverride.GetValueOnGameThread() == 0)
{
return false;
}
if (Input.Event != IE_Pressed)
{
return false;
}
FModifierKeysState Modifiers = FSlateApplication::Get().GetModifierKeys();
FInputChord CheckChord(Input.Key, EModifierKey::FromBools(
Modifiers.IsControlDown(),
Modifiers.IsAltDown(),
Modifiers.IsShiftDown(),
Modifiers.IsCommandDown()
));
// This function will completely bypass any game input.
// The commands that can do this should be limited and carefully considered.
const TSharedPtr<FUICommandInfo>& Command = FLevelViewportCommands::Get().ToggleViewportToolbar;
if (Command->HasActiveChord(CheckChord))
{
if (const FUIAction* Action = PIECommands->GetActionForCommand(Command))
{
return Action->Execute();
}
}
return false;
}
const FSlateBrush* SLevelViewport::OnGetViewportBorderBrush() const
{
const FSlateBrush* BorderBrush = nullptr;
if( FSlateApplication::Get().IsNormalExecution() )
{
// 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 if(!IsMaximized())
{
BorderBrush = NonMaximizedBorder;
}
}
else
{
BorderBrush = DebuggingBorder;
}
return BorderBrush;
}
EVisibility SLevelViewport::OnGetFocusedViewportIndicatorVisibility() const
{
EVisibility BaseVisibility = OnGetViewportContentVisibility();
if (BaseVisibility != EVisibility::Collapsed)
{
// 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())
{
return EVisibility::HitTestInvisible;
}
else
{
return EVisibility::Collapsed;
}
}
return BaseVisibility;
}
FSlateColor SLevelViewport::OnGetViewportBorderColorAndOpacity() const
{
FLinearColor ViewportBorderColorAndOpacity = FLinearColor::White;
if( FSlateApplication::Get().IsNormalExecution() )
{
if( ViewTransitionAnim.IsPlaying() )
{
ViewportBorderColorAndOpacity = FLinearColor( 1.0f, 1.0f, 1.0f, 1.0f - ViewTransitionAnim.GetLerp() );
}
}
return ViewportBorderColorAndOpacity;
}
EVisibility SLevelViewport::OnGetViewportContentVisibility() const
{
// Do not show any of the viewports inner slate content (active viewport borders, etc) when we are playing in editor and in immersive mode
// as they are meaningless in that situation
EVisibility BaseVisibility = SEditorViewport::OnGetViewportContentVisibility();
if (BaseVisibility != EVisibility::Visible)
{
return BaseVisibility;
}
return ( ( IsPlayInEditorViewportActive() && IsImmersive() ) || GEngine->IsStereoscopic3D( ActiveViewport.Get() ) ) ? EVisibility::Collapsed : EVisibility::Visible;
}
EVisibility SLevelViewport::GetToolBarVisibility() const
{
// Do not show the toolbar if this viewport has a play in editor session, or we're in the VR Editor
return ( IsPlayInEditorViewportActive() || GEngine->IsStereoscopic3D( ActiveViewport.Get() ) ) ? EVisibility::Collapsed : OnGetViewportContentVisibility();
}
EVisibility SLevelViewport::GetMaximizeToggleVisibility() const
{
bool bIsMaximizeSupported = false;
bool bShowMaximizeToggle = false;
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
if (LayoutPinned.IsValid())
{
bIsMaximizeSupported = LayoutPinned->IsMaximizeSupported();
bShowMaximizeToggle = !LayoutPinned->IsTransitioning();
}
// Do not show the maximize/minimize toggle when in immersive mode
return (!bIsMaximizeSupported || IsImmersive()) ? EVisibility::Collapsed : (bShowMaximizeToggle ? EVisibility::Visible : EVisibility::Hidden);
}
EVisibility SLevelViewport::GetCloseImmersiveButtonVisibility() const
{
// Do not show the Immersive toggle button when not in immersive mode
return (IsImmersive() && bShowToolbarAndControls) ? EVisibility::Visible : EVisibility::Collapsed;
}
EVisibility SLevelViewport::GetTransformToolbarVisibility() const
{
if (IsActiveLevelViewport())
{
// Only return visible if we are/were the active viewport.
return EVisibility::Visible;
}
return EVisibility::Hidden;
}
bool SLevelViewport::IsMaximized() const
{
if( ParentLayout.IsValid() && !ConfigKey.IsNone())
{
return ParentLayout.Pin()->IsViewportMaximized( ConfigKey );
}
// Assume the viewport is always maximized if we have no layout for some reason
return true;
}
bool SLevelViewport::CanMaximize() const
{
if (TSharedPtr<FLevelViewportLayout> PinnedParentLayout = ParentLayout.Pin())
{
return PinnedParentLayout->IsMaximizeSupported();
}
return false;
}
TSharedRef<FEditorViewportClient> SLevelViewport::MakeEditorViewportClient()
{
return LevelViewportClient.ToSharedRef();
}
TSharedPtr<SWidget> SLevelViewport::MakeViewportToolbar()
{
const TSharedRef<SLevelViewportToolBar> OldViewportToolbar =
SAssignNew(LevelViewportToolBar, SLevelViewportToolBar)
.Viewport(SharedThis(this))
.Visibility_Lambda(
[this]() -> EVisibility
{
return GetToolBarVisibility();
}
)
.IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute());
return
// clang-format off
SNew(SVerticalBox)
.Visibility( EVisibility::SelfHitTestInvisible )
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 1.0f, 0, 0)
.VAlign(VAlign_Top)
[
OldViewportToolbar
]
+SVerticalBox::Slot()
.VAlign(VAlign_Top)
.HAlign(HAlign_Left)
[
SNew(SActorPilotViewportToolbar)
.Viewport( SharedThis( this ) )
.Visibility(this, &SLevelViewport::GetLockedIconVisibility)
];
// clang-format on
}
TSharedPtr<SWidget> SLevelViewport::BuildViewportToolbar()
{
const FName ViewportToolbarName = "LevelEditor.ViewportToolbar";
// Register the viewport toolbar if another viewport hasn't already (it's shared).
{
if (!UToolMenus::Get()->IsMenuRegistered(ViewportToolbarName))
{
UToolMenu* const ViewportToolbarMenu = UToolMenus::Get()->RegisterMenu(
ViewportToolbarName, NAME_None /* parent */, EMultiBoxType::SlimHorizontalToolBar
);
ViewportToolbarMenu->StyleName = "ViewportToolbar";
ViewportToolbarMenu->bSeparateSections = false;
// Add the left-aligned part of the viewport toolbar.
{
FToolMenuSection& LeftSection = ViewportToolbarMenu->AddSection("Left");
LeftSection.AddEntry(UE::UnrealEd::CreateTransformsSubmenu());
UE::LevelEditor::ExtendTransformSubmenu("LevelEditor.ViewportToolbar.Transform");
LeftSection.AddEntry(UE::UnrealEd::CreateSnappingSubmenu());
UE::LevelEditor::ExtendSnappingSubmenu("LevelEditor.ViewportToolbar.Snapping");
}
// Add the right-aligned part of the viewport toolbar.
{
FToolMenuSection& RightSection = ViewportToolbarMenu->AddSection("Right");
RightSection.Alignment = EToolMenuSectionAlign::Last;
// Add the "Camera" submenu.
{
// This matches the name used in SLevelViewportToolBar::GenerateCameraMenu for
// the old level editor viewport toolbar camera menu.
const FName ParentSubmenuName = "LevelEditor.LevelViewportToolBar.Camera";
// Create our parent menu.
if (!UToolMenus::Get()->IsMenuRegistered(ParentSubmenuName))
{
UToolMenus::Get()->RegisterMenu(ParentSubmenuName);
}
// Build the menu name our Camera menu will be using so we can extend it and set
// the old viewport toolbar camera menu as our parent.
const FName SubmenuName = UToolMenus::JoinMenuPaths(ViewportToolbarName, "Camera");
// Register our submenu explicitly so we can set the old viewport toolbar's camera menu as our parent.
UToolMenus::Get()->RegisterMenu(SubmenuName, ParentSubmenuName);
// Extending using both Level Editor specific and UnrealEd generic entries
RightSection.AddEntry(UE::LevelEditor::CreateToolbarCameraSubmenu());
UE::LevelEditor::ExtendCameraSubmenu(SubmenuName);
}
// Add the "View Modes" sub menu.
{
// Stay backward-compatible with the old viewport toolbar.
{
// Create our grandparent menu.
if (!UToolMenus::Get()->IsMenuRegistered("UnrealEd.ViewportToolbar.View"))
{
UToolMenus::Get()->RegisterMenu("UnrealEd.ViewportToolbar.View");
}
// Create our parent menu.
if (!UToolMenus::Get()->IsMenuRegistered("LevelEditor.LevelViewportToolbar.View"))
{
UToolMenus::Get()->RegisterMenu(
"LevelEditor.LevelViewportToolbar.View", "UnrealEd.ViewportToolbar.View"
);
}
// Create our menu.
UToolMenus::Get()->RegisterMenu(
"LevelEditor.ViewportToolbar.ViewModes", "LevelEditor.LevelViewportToolbar.View"
);
}
// Add the level editor specific entries.
UE::LevelEditor::ExtendViewModesSubmenu("LevelEditor.ViewportToolbar.ViewModes");
// Create and add the submenu entry to make the menu we just created and extended appear in the
// viewport toolbar itself.
RightSection.AddEntry(UE::UnrealEd::CreateViewModesSubmenu());
}
// Add the "Show" submenu.
{
// Stay backward-compatible with the old viewport toolbar.
{
if (!UToolMenus::Get()->IsMenuRegistered("LevelEditor.LevelViewportToolbar.Show"))
{
UToolMenus::Get()->RegisterMenu("LevelEditor.LevelViewportToolbar.Show");
}
UToolMenus::Get()->RegisterMenu(
"LevelEditor.ViewportToolbar.Show", "LevelEditor.LevelViewportToolbar.Show"
);
}
RightSection.AddEntry(UE::LevelEditor::CreateShowSubmenu());
}
RightSection.AddEntry(UE::LevelEditor::CreatePerformanceAndScalabilitySubmenu());
RightSection.AddEntry(UE::LevelEditor::CreateSettingsSubmenu());
}
// Sections added for compatibility with old toolbar transform menu extensions
{
// Existing code might add entries via FToolBarBuilder Extenders,
// These extenders are based off of toolbar section & extension names.
// The following sections names are used in STransformViewportToolBar::MakeTransformToolBar(...),
// This allows legacy extensions to be added directly to the toolbar.
ViewportToolbarMenu->AddSection("Transform").Alignment = EToolMenuSectionAlign::Last;
ViewportToolbarMenu->AddSection("LocationGridSnap").Alignment = EToolMenuSectionAlign::Last;
ViewportToolbarMenu->AddSection("RotationGridSnap").Alignment = EToolMenuSectionAlign::Last;
ViewportToolbarMenu->AddSection("Layer2DSnap").Alignment = EToolMenuSectionAlign::Last;
ViewportToolbarMenu->AddSection("ScaleGridSnap").Alignment = EToolMenuSectionAlign::Last;
ViewportToolbarMenu->AddSection("CameraSpeed").Alignment = EToolMenuSectionAlign::Last;
FToolMenuSection& FallbackSection = ViewportToolbarMenu->AddSection("Fallback");
FallbackSection.Alignment = EToolMenuSectionAlign::Last;
FallbackSection.AddDynamicEntry("FallbackExtensionPanels", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& Section)
{
ULegacyLevelViewportToolbarContext* Context = Section.FindContext<ULegacyLevelViewportToolbarContext>();
if (!Context || !Context->LevelViewport.IsValid() || !Context->LevelViewportToolBarWidget.IsValid())
{
return;
}
UViewportToolBarContext* ExtensionContext = NewObject<UViewportToolBarContext>();
ExtensionContext->Viewport = Context->LevelViewport;
ExtensionContext->ViewportToolBar = Context->LevelViewportToolBarWidget;
auto MakeExtensionPanel = [ExtensionContext, &Section](FName ExtensionId)
{
FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitWidget(
ExtensionId,
SNew(SExtensionPanel)
.ExtensionPanelID(ExtensionId)
.ExtensionContext(ExtensionContext),
FText::GetEmpty(),
true, false, true
));
Entry.ToolBarData.ResizeParams.VisibleInOverflow = false;
};
MakeExtensionPanel("LevelViewportToolBar.LeftExtension");
MakeExtensionPanel("LevelViewportToolBar.MiddleExtension");
MakeExtensionPanel("LevelViewportToolBar.RightExtension");
MakeExtensionPanel("LevelViewportToolBar.RightmostExtension");
}));
}
{
// The viewport sizing entries should _always_ be the last section.
// Doing otherwise breaks the customized behavior of the overflow menu.
FToolMenuSection& SizingSection = ViewportToolbarMenu->AddSection("ViewportSizing");
SizingSection.Alignment = EToolMenuSectionAlign::Last;
SizingSection.AddEntry(UE::LevelEditor::CreateViewportSizingSubmenu());
}
}
}
FToolMenuContext ViewportToolbarContext;
{
ViewportToolbarContext.AppendCommandList(GetCommandList());
// Note that these extenders can now leak between submenus of the viewport toolbar.
{
// Stay backward-compatible with legacy view menu extenders.
ViewportToolbarContext.AddExtender(UE::LevelEditor::GetViewModesLegacyExtenders());
FLevelEditorModule& LevelEditorModule =
FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
TSharedRef<FUICommandList> CommandListRef = GetCommandList().ToSharedRef();
// Stay backward-compatible with legacy show menu extenders.
{
TSharedPtr<FExtender> Extenders = LevelEditorModule.AssembleExtenders(
CommandListRef, LevelEditorModule.GetAllLevelViewportShowMenuExtenders()
);
ViewportToolbarContext.AddExtender(Extenders);
}
// Stay backward-compatible with legacy view menu extenders
{
TSharedPtr<FExtender> ViewMenuExtenders = LevelEditorModule.AssembleExtenders(
CommandListRef, LevelEditorModule.GetAllLevelEditorToolbarViewMenuExtenders()
);
ViewportToolbarContext.AddExtender(ViewMenuExtenders);
}
// Stay backward-compatible with legacy options menu extenders
{
TSharedPtr<FExtender> ViewMenuExtenders = LevelEditorModule.AssembleExtenders(
CommandListRef, LevelEditorModule.GetAllLevelViewportOptionsMenuExtenders()
);
if (ViewMenuExtenders.IsValid())
{
ViewportToolbarContext.AddExtender(ViewMenuExtenders);
}
}
// Stay backward-compatible with legacy toolbar menu extenders
{
TSharedPtr<FExtender> OldToolbarExtenders =
LevelEditorModule.GetToolBarExtensibilityManager()->GetAllExtenders();
if (OldToolbarExtenders.IsValid())
{
ViewportToolbarContext.AddExtender(OldToolbarExtenders);
}
}
}
// Add the UnrealEd viewport toolbar context.
{
UUnrealEdViewportToolbarContext* const ContextObject = NewObject<UUnrealEdViewportToolbarContext>();
ContextObject->Viewport = SharedThis(this);
ViewportToolbarContext.AddObject(ContextObject);
}
// Add the level editor viewport toolbar context.
{
ULevelViewportContext* const ContextObject = NewObject<ULevelViewportContext>();
ContextObject->LevelViewport = SharedThis(this);
ViewportToolbarContext.AddObject(ContextObject);
}
// Support legacy SExtensionPanel extensions
if (LevelViewportToolBar)
{
// Provide a non-null toolbar widget so that legacy extensions with bad null handling don't crash
ULegacyLevelViewportToolbarContext* const LegacyContext = NewObject<ULegacyLevelViewportToolbarContext>();
LegacyContext->LevelViewport = SharedThis(this);
LegacyContext->LevelViewportToolBarWidget = LevelViewportToolBar;
ViewportToolbarContext.AddObject(LegacyContext);
}
ExtendToolbarContext(ViewportToolbarContext);
}
// clang-format off
return SNew(SWidgetSwitcher)
.WidgetIndex(this, &SLevelViewport::GetViewportToolbarIndex)
.Visibility_Lambda([this]
{
// TODO: Move this to GetToolBarVisibility() once the old toolbar is deprecated
if (GEngine->IsStereoscopic3D(ActiveViewport.Get()))
{
return EVisibility::Collapsed;
}
return IsViewportToolbarVisible() ? EVisibility::Visible : EVisibility::Collapsed;
})
+ SWidgetSwitcher::Slot()
[
UToolMenus::Get()->GenerateWidget(ViewportToolbarName, ViewportToolbarContext)
]
+ SWidgetSwitcher::Slot()
[
BuildPIEViewportToolbar()
];
// clang-format on
}
TSharedRef<SWidget> SLevelViewport::BuildPIEViewportToolbar()
{
const FName ToolbarName = "LevelEditor.PIEViewportToolbar";
if (!UToolMenus::Get()->IsMenuRegistered(ToolbarName))
{
UToolMenu* ToolbarMenu = UToolMenus::Get()->RegisterMenu(ToolbarName, NAME_None, EMultiBoxType::SlimHorizontalToolBar);
ToolbarMenu->StyleName = "ViewportToolbar";
ToolbarMenu->bSeparateSections = false;
{
FToolMenuSection& LeftSection = ToolbarMenu->AddSection("Left");
LeftSection.AddEntry(FToolMenuEntry::InitDynamicEntry(
"DynamicPIEDetachHint",
FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& Section)
{
ULevelViewportContext* Context = Section.FindContext<ULevelViewportContext>();
if (!Context)
{
return;
}
TSharedPtr<SLevelViewport> Viewport = Context->LevelViewport.Pin();
Section.AddEntry(FToolMenuEntry::InitWidget(
"PIEDetachHint",
SNew(SBox)
.Padding(FMargin(8.f, 0.f, 0.f, 0.f))
.Visibility_Lambda([WeakViewport = Context->LevelViewport]
{
const ULevelEditorPlaySettings* EditorPlayInSettings = GetDefault<ULevelEditorPlaySettings>();
if (!EditorPlayInSettings->ShowMouseControlLabel)
{
return EVisibility::Collapsed;
}
if (TSharedPtr<SLevelViewport> Viewport = WeakViewport.Pin())
{
return Viewport->GetActiveViewport()->HasMouseCapture() ? EVisibility::Visible : EVisibility::Collapsed;
}
return EVisibility::Collapsed;
})
[
Viewport->BuildMouseCaptureWidget()
],
FText::GetEmpty()
));
})
));
}
{
FToolMenuSection& RightSection = ToolbarMenu->AddSection("Right");
RightSection.Alignment = EToolMenuSectionAlign::Last;
RightSection.AddEntry(UE::LevelEditor::CreatePIEViewModesSubmenu());
RightSection.AddEntry(UE::LevelEditor::CreatePIEShowSubmenu());
RightSection.AddEntry(UE::LevelEditor::CreatePIEPerformanceAndScalabilitySubmenu());
RightSection.AddEntry(UE::LevelEditor::CreatePIESettingsSubmenu());
}
}
FToolMenuContext ToolbarContext;
{
ToolbarContext.AppendCommandList(PIECommands);
ToolbarContext.AppendCommandList(GetCommandList());
// Add the UnrealEd viewport toolbar context.
{
UUnrealEdViewportToolbarContext* const ContextObject = NewObject<UUnrealEdViewportToolbarContext>();
ContextObject->Viewport = SharedThis(this);
ToolbarContext.AddObject(ContextObject);
ContextObject->ExcludedShowMenuFlags.Append({
FEngineShowFlags::SF_Cameras,
FEngineShowFlags::SF_CameraFrustums,
FEngineShowFlags::SF_Grid
});
ContextObject->IsViewModeSupported.BindLambda([](EViewModeIndex ViewMode)
{
switch (ViewMode)
{
case VMI_Lit_Wireframe:
return false;
default:
return true;
}
});
ContextObject->DoesViewModeMenuShowSection.BindLambda([](UE::UnrealEd::EHidableViewModeMenuSections InMenuSection)
{
switch (InMenuSection)
{
case UE::UnrealEd::EHidableViewModeMenuSections::GPUSkinCache:
return false;
case UE::UnrealEd::EHidableViewModeMenuSections::RayTracingDebug:
return false;
default:
return true;
}
});
}
// Add the level editor viewport toolbar context.
{
ULevelViewportContext* const ContextObject = NewObject<ULevelViewportContext>();
ContextObject->LevelViewport = SharedThis(this);
ToolbarContext.AddObject(ContextObject);
}
}
return UToolMenus::Get()->GenerateWidget(ToolbarName, ToolbarContext);
}
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();
}
int32 SLevelViewport::GetViewportToolbarIndex() const
{
return IsPlayInEditorViewportActive() ? 1 : 0;
}
void SLevelViewport::OnAdvancedSettings()
{
FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer("Editor", "LevelEditor", "Viewport");
}
void SLevelViewport::OnPlaySettings()
{
FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer("Editor", "LevelEditor", "PlayIn");
}
void SLevelViewport::OnToggleImmersive()
{
const bool bWantImmersive = !IsImmersive();
// We always want to animate in response to user-interactive toggling of maximized state
constexpr bool bAllowAnimation = true;
MakeImmersive(bWantImmersive, bAllowAnimation);
}
void SLevelViewport::OnToggleSidebarTabs()
{
ParentLevelEditor.Pin()->GetTabManager()->ToggleSidebarOpenTabs();
}
bool SLevelViewport::IsImmersive() const
{
if( ParentLayout.IsValid() && !ConfigKey.IsNone())
{
return ParentLayout.Pin()->IsViewportImmersive( ConfigKey );
}
// Assume the viewport is not immersive if we have no layout for some reason
return false;
}
void SLevelViewport::OnCreateCameraActor(UClass* InClass)
{
// Find the perspective viewport we were using
FViewport* pViewPort = GEditor->GetActiveViewport();
FLevelEditorViewportClient* ViewportClient = nullptr;
for( FLevelEditorViewportClient* LevelViewport : GEditor->GetLevelViewportClients())
{
if( LevelViewport->IsPerspective() && LevelViewport->Viewport == pViewPort )
{
ViewportClient = LevelViewport;
break;
}
}
if( ViewportClient == nullptr )
{
// 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 = Cast<ACameraActor>(ViewportClient->GetWorld()->SpawnActor(InClass));
pNewCamera->SetActorLocation( ViewportClient->GetViewLocation(), false );
pNewCamera->SetActorRotation( ViewportClient->GetViewRotation() );
pNewCamera->GetCameraComponent()->SetFieldOfView( ViewportClient->ViewFOV );
// Deselect any currently selected actors, then select newly created camera
UE::SLevelViewport::Internal::SelectActor(pNewCamera);
// Send notification about actors that may have changed
ULevel::LevelDirtiedEvent.Broadcast();
// Redraw viewports to show new camera
GEditor->RedrawAllViewports();
}
bool SLevelViewport::IsActiveLevelViewport() const
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(LevelEditorName);
return LevelEditorModule.GetFirstActiveViewport().Get() == this;
}
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()
{
bool bGameViewEnable = !LevelViewportClient->IsInGameView();
// "Mode Widget" should not automatically be reactivated by selecting an actor after "Game View" is enabled
LevelViewportClient->bAlwaysShowModeWidgetAfterSelectionChanges = bGameViewEnable ? false : true;
LevelViewportClient->SetGameView(bGameViewEnable);
if (!bGameViewEnable)
{
// LevelViewportClient->bShowWidget is set to "false" when entering game mode
// Need to turn it back to "true" when exiting game mode
LevelViewportClient->ShowWidget(true);
}
}
bool SLevelViewport::CanToggleGameView() const
{
return LevelViewportClient->IsPerspective();
}
bool SLevelViewport::IsInGameView() const
{
return LevelViewportClient->IsInGameView();
}
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( nullptr, 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 )
{
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
if (bVisible)
{
// clear all hidden layers
LevelViewportClient->ViewHiddenLayers.Empty();
}
else
{
// hide them all
TArray<FName> AllLayerNames;
Layers->AddAllLayerNamesTo(AllLayerNames);
LevelViewportClient->ViewHiddenLayers = AllLayerNames;
}
// update actor visibility for this view
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
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
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.IsNone())
{
FLevelEditorViewportInstanceSettings ViewportInstanceSettings;
ViewportInstanceSettings.ViewportType = LevelViewportClient->ViewportType;
// Get saved defaults if specified
FString ConfigKeyAsString = ConfigKey.ToString();
const FLevelEditorViewportInstanceSettings* const ViewportInstanceSettingsPtr = GetDefault<ULevelEditorViewportSettings>()->GetViewportInstanceSettings(ConfigKeyAsString);
ViewportInstanceSettings = ViewportInstanceSettingsPtr ? *ViewportInstanceSettingsPtr : LoadLegacyConfigFromIni(ConfigKeyAsString, 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(nullptr, LevelViewportClient.Get());
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
Layers->UpdatePerViewVisibility(LevelViewportClient.Get());
}
LevelViewportClient->Invalidate();
}
void SLevelViewport::SetKeyboardFocusToThisViewport()
{
if( ensure( ViewportWidget.IsValid() ) )
{
// Set keyboard focus directly
FSlateApplication::Get().SetKeyboardFocus( ViewportWidget.ToSharedRef() );
}
}
void SLevelViewport::SaveConfig(const FString& ConfigName) const
{
if(GUnrealEd && GetDefault<ULevelEditorViewportSettings>())
{
// When we startup the editor we always start it up in IsInGameView()=false mode
FEngineShowFlags& EditorShowFlagsToSave = LevelViewportClient->IsInGameView() ? LevelViewportClient->LastEngineShowFlags : LevelViewportClient->EngineShowFlags;
FEngineShowFlags& GameShowFlagsToSave = LevelViewportClient->IsInGameView() ? LevelViewportClient->EngineShowFlags : LevelViewportClient->LastEngineShowFlags;
FLevelEditorViewportInstanceSettings ViewportInstanceSettings;
if (const FLevelEditorViewportInstanceSettings* CurrentViewportInstanceSettingsPtr = GetDefault<ULevelEditorViewportSettings>()->GetViewportInstanceSettings(ConfigName))
{
ViewportInstanceSettings = *CurrentViewportInstanceSettingsPtr;
}
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.NaniteVisualizationMode = LevelViewportClient->CurrentNaniteVisualizationMode;
ViewportInstanceSettings.LumenVisualizationMode = LevelViewportClient->CurrentLumenVisualizationMode;
ViewportInstanceSettings.SubstrateVisualizationMode = LevelViewportClient->CurrentSubstrateVisualizationMode;
ViewportInstanceSettings.GroomVisualizationMode = LevelViewportClient->CurrentGroomVisualizationMode;
ViewportInstanceSettings.VirtualShadowMapVisualizationMode = LevelViewportClient->CurrentVirtualShadowMapVisualizationMode;
ViewportInstanceSettings.VirtualTextureVisualizationMode = LevelViewportClient->CurrentVirtualTextureVisualizationMode;
ViewportInstanceSettings.RayTracingDebugVisualizationMode = LevelViewportClient->CurrentRayTracingDebugVisualizationMode;
ViewportInstanceSettings.GPUSkinCacheVisualizationMode = LevelViewportClient->CurrentGPUSkinCacheVisualizationMode;
ViewportInstanceSettings.ExposureSettings = LevelViewportClient->ExposureSettings;
ViewportInstanceSettings.FOVAngle = LevelViewportClient->FOVAngle;
ViewportInstanceSettings.bAllowCinematicControl = LevelViewportClient->AllowsCinematicControl();
LevelViewportClient->SaveRealtimeStateToConfig(ViewportInstanceSettings.bIsRealtime);
ViewportInstanceSettings.bShowOnScreenStats = LevelViewportClient->ShouldShowStats();
ViewportInstanceSettings.FarViewPlane = LevelViewportClient->GetFarClipPlaneOverride();
ViewportInstanceSettings.bShowFullToolbar = bShowEditorToolbar;
if(GetDefault<ULevelEditorViewportSettings>()->bSaveEngineStats)
{
const TArray<FString>* EnabledStats = nullptr;
// If the selected viewport is currently hosting a PIE session, we need to make sure we copy to stats from the active viewport
// Note: This happens if you close the editor while it's running because SwapStatCommands gets called after the config save when shutting down.
if(IsPlayInEditorViewportActive())
{
EnabledStats = ActiveViewport->GetClient()->GetEnabledStats();
}
else
{
EnabledStats = LevelViewportClient->GetEnabledStats();
}
check(EnabledStats);
ViewportInstanceSettings.EnabledStats = *EnabledStats;
}
GetMutableDefault<ULevelEditorViewportSettings>()->SetViewportInstanceSettings(ConfigName, ViewportInstanceSettings);
}
}
FLevelEditorViewportInstanceSettings SLevelViewport::LoadLegacyConfigFromIni(const FString& InConfigKey, const FLevelEditorViewportInstanceSettings& InDefaultSettings)
{
FLevelEditorViewportInstanceSettings ViewportInstanceSettings = InDefaultSettings;
const FString& IniSection = FLayoutSaveRestore::GetAdditionalLayoutConfigIni();
{
int32 ViewportTypeAsInt = ViewportInstanceSettings.ViewportType;
GConfig->GetInt(*IniSection, *(InConfigKey + TEXT(".Type")), ViewportTypeAsInt, GEditorPerProjectIni);
ViewportInstanceSettings.ViewportType = (ViewportTypeAsInt == -1 || ViewportTypeAsInt == 255) ? LVT_None : static_cast<ELevelViewportType>(ViewportTypeAsInt); // LVT_None used to be -1 or 255
if(ViewportInstanceSettings.ViewportType == LVT_None)
{
ViewportInstanceSettings.ViewportType = LVT_Perspective;
}
}
GConfig->GetString(*IniSection, *(InConfigKey + TEXT(".EditorShowFlags")), ViewportInstanceSettings.EditorShowFlagsString, GEditorPerProjectIni);
GConfig->GetString(*IniSection, *(InConfigKey + TEXT(".GameShowFlags")), ViewportInstanceSettings.GameShowFlagsString, GEditorPerProjectIni);
// A single view mode index has been deprecated in favor of separate perspective and orthographic settings
EViewModeIndex LegacyViewModeIndex = VMI_Unknown;
{
int32 LegacyVMIAsInt = VMI_Unknown;
GConfig->GetInt(*IniSection, *(InConfigKey+TEXT(".ViewModeIndex")), LegacyVMIAsInt, GEditorPerProjectIni);
LegacyViewModeIndex = (LegacyVMIAsInt == -1) ? VMI_Unknown : static_cast<EViewModeIndex>(LegacyVMIAsInt); // VMI_Unknown used to be -1
}
if(!GConfig->GetInt(*IniSection, *(InConfigKey+TEXT(".PerspViewModeIndex")), (int32&)ViewportInstanceSettings.PerspViewModeIndex, GEditorPerProjectIni))
{
if(ViewportInstanceSettings.ViewportType == LVT_Perspective)
{
// This viewport may pre-date the ViewModeIndex setting (VMI_Unknown), if so, try to be backward compatible
ViewportInstanceSettings.PerspViewModeIndex = (LegacyViewModeIndex == VMI_Unknown) ? FindViewMode(LevelViewportClient->EngineShowFlags) : LegacyViewModeIndex;
}
else
{
// Default to Lit for a perspective viewport
ViewportInstanceSettings.PerspViewModeIndex = VMI_Lit;
}
}
if(!GConfig->GetInt(*IniSection, *(InConfigKey+TEXT(".OrthoViewModeIndex")), (int32&)ViewportInstanceSettings.OrthoViewModeIndex, GEditorPerProjectIni))
{
// Default to Brush Wireframe for an orthographic viewport
ViewportInstanceSettings.OrthoViewModeIndex = (ViewportInstanceSettings.ViewportType != LVT_Perspective && LegacyViewModeIndex != VMI_Unknown) ? LegacyViewModeIndex : VMI_BrushWireframe;
}
{
FString BufferVisualizationModeString;
if(GConfig->GetString(*IniSection, *(InConfigKey+TEXT(".BufferVisualizationMode")), BufferVisualizationModeString, GEditorPerProjectIni))
{
ViewportInstanceSettings.BufferVisualizationMode = *BufferVisualizationModeString;
}
}
{
FString ExposureSettingsString;
if(GConfig->GetString(*IniSection, *(InConfigKey + TEXT(".ExposureSettings")), ExposureSettingsString, GEditorPerProjectIni))
{
ViewportInstanceSettings.ExposureSettings.SetFromString(*ExposureSettingsString);
}
}
GConfig->GetBool(*IniSection, *(InConfigKey + TEXT(".bIsRealtime")), ViewportInstanceSettings.bIsRealtime, GEditorPerProjectIni);
GConfig->GetBool(*IniSection, *(InConfigKey + TEXT(".bWantStats")), ViewportInstanceSettings.bShowOnScreenStats, GEditorPerProjectIni);
GConfig->GetBool(*IniSection, *(InConfigKey + TEXT(".bWantFPS")), ViewportInstanceSettings.bShowFPS_DEPRECATED, GEditorPerProjectIni);
GConfig->GetFloat(*IniSection, *(InConfigKey + TEXT(".FOVAngle")), ViewportInstanceSettings.FOVAngle, GEditorPerProjectIni);
GConfig->GetBool(*IniSection, *(InConfigKey + TEXT(".bAllowCinematicControl")), ViewportInstanceSettings.bAllowCinematicControl, GEditorPerProjectIni);
return ViewportInstanceSettings;
}
void SLevelViewport::OnSetBookmark( int32 BookmarkIndex )
{
IBookmarkTypeTools::Get().CreateOrSetBookmark( BookmarkIndex, LevelViewportClient.Get() );
}
void SLevelViewport::OnJumpToBookmark( int32 BookmarkIndex )
{
IBookmarkTypeTools::Get().JumpToBookmark( BookmarkIndex, TSharedPtr<struct FBookmarkBaseJumpToSettings>(), LevelViewportClient.Get() );
}
bool SLevelViewport::OnHasBookmarkSet(int32 BookmarkIndex)
{
return IBookmarkTypeTools::Get().CheckBookmark(BookmarkIndex, LevelViewportClient.Get());
}
void SLevelViewport::OnClearBookmark(int32 BookmarkIndex)
{
IBookmarkTypeTools::Get().ClearBookmark(BookmarkIndex, LevelViewportClient.Get());
}
void SLevelViewport::OnClearAllBookmarks()
{
IBookmarkTypeTools::Get().ClearAllBookmarks(LevelViewportClient.Get());
}
void SLevelViewport::OnCompactBookmarks()
{
IBookmarkTypeTools::Get().CompactBookmarks(LevelViewportClient.Get());
}
void SLevelViewport::OnToggleAllowCinematicPreview()
{
// Reset the FOV of Viewport for cases where we have been previewing the cinematic with a changing FOV
LevelViewportClient->ViewFOV = LevelViewportClient->AllowsCinematicControl() ? LevelViewportClient->ViewFOV : LevelViewportClient->FOVAngle;
LevelViewportClient->SetAllowCinematicControl( !LevelViewportClient->AllowsCinematicControl() );
LevelViewportClient->Invalidate( false );
}
bool SLevelViewport::AllowsCinematicPreview() const
{
return LevelViewportClient->AllowsCinematicControl();
}
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 != nullptr)
{
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);
}
}
}
void SLevelViewport::OnActorLockToggleFromMenu()
{
OnActorUnlock();
}
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::SetAllowsCinematicControl(bool bAllow)
{
LevelViewportClient->SetAllowCinematicControl(bAllow);
}
bool SLevelViewport::GetAllowsCinematicControl() const
{
return LevelViewportClient->AllowsCinematicControl();
}
void SLevelViewport::FindSelectedInLevelScript()
{
GUnrealEd->FindSelectedActorsInLevelScript();
}
bool SLevelViewport::CanFindSelectedInLevelScript() const
{
AActor* Actor = GEditor->GetSelectedActors()->GetTop<AActor>();
return (Actor != nullptr);
}
void SLevelViewport::OnSelectLockedActor()
{
if (AActor* LockedActor = LevelViewportClient->GetActiveActorLock().Get())
{
// Deselect any currently selected actors, then select the locked/piloted actor
UE::SLevelViewport::Internal::SelectActor(LockedActor);
}
}
bool SLevelViewport::CanExecuteSelectLockedActor() const
{
if (const AActor* LockedActor = LevelViewportClient->GetActiveActorLock().Get())
{
return LockedActor->IsSelectable();
}
return false;
}
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);
// Move perspective camera back to pre-piloting transform
LevelViewportClient->SetViewLocation(CachedPerspectiveCameraTransform.GetLocation());
LevelViewportClient->SetViewRotation(CachedPerspectiveCameraTransform.GetRotation());
Invalidate();
// 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( FSceneOutlinerTreeItemRef TreeItem, const STableRow<FSceneOutlinerTreeItemPtr>& InRow ) override
{
if (FActorTreeItem* ActorItem = TreeItem->CastTo<FActorTreeItem>())
{
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(FAppStyle::GetBrush(bLocked ? "PropertyWindow.Locked" : "PropertyWindow.Unlocked"))
.ColorAndOpacity(bLocked ? FLinearColor::White : FLinearColor(1.0f, 1.0f, 1.0f, 0.5f))
];
}
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();
// Force invalidate hit proxy immediately to avoid a crash
// After level change a mouse click can be processed before the deferred invalidate of hitproxies is performed.
LevelViewportClient->Viewport->InvalidateHitProxy();
// Also update preview viewports
for (const FViewportActorPreview& CurActorPreview : ActorPreviews)
{
if (CurActorPreview.LevelViewportClient.IsValid())
{
CurActorPreview.LevelViewportClient->Viewport->Invalidate();
CurActorPreview.LevelViewportClient->Viewport->InvalidateHitProxy();
}
}
}
else
{
// Invalidate only display pixels.
LevelViewportClient->Viewport->InvalidateDisplay();
// Also update preview viewports
for (const FViewportActorPreview& CurActorPreview : ActorPreviews)
{
if (CurActorPreview.LevelViewportClient.IsValid())
{
CurActorPreview.LevelViewportClient->Viewport->InvalidateDisplay();
}
}
}
RefreshPIEViewport();
}
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::MakeMaximized(bool bWantMaximized, bool bAllowAnimation)
{
if (TSharedPtr<FLevelViewportLayout> Layout = ParentLayout.Pin())
{
const FName ViewportName = ConfigKey;
if (!ViewportName.IsNone())
{
const bool bWantImmersive = IsImmersive();
Layout->RequestMaximizeViewport( ViewportName, bWantMaximized, bWantImmersive, bAllowAnimation );
}
}
}
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 );
}
if (bWantImmersive && LevelViewportClient->IsVisualizeCalibrationMaterialEnabled())
{
bShowToolbarAndControls = false;
}
else
{
bShowToolbarAndControls = true;
}
}
}
/**
* 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();
}
UGameViewportClient* SLevelViewport::GetPlayClient() const
{
return PlayClient.Get();
}
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);
}
// In game mode, selecting any actor should make LevelViewportClient->bShowWidget be "true"
LevelViewportClient->ShowWidget(true);
LevelViewportClient->EngineShowFlags.SetSelection(true);
LevelViewportClient->EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline);
}
bNeedToUpdatePreviews = true;
}
void SLevelViewport::OnElementSelectionChanged(const UTypedElementSelectionSet* SelectionSet, bool bForceRefresh)
{
// Request preview update. It's possible that the actor currently selected has forbidden default preview.
// However, we need to show the preview widget when some of its child components selected.
bNeedToUpdatePreviews = true;
}
void SLevelViewport::PreviewSelectedCameraActors(const bool bPreviewInDesktopViewport)
{
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 (!FLevelEditorViewportClient::IsDroppingPreviewActor() && CanGetCameraInformationFromActor(SelectedActor))
{
ActorsToPreview.Add(SelectedActor);
}
}
PreviewActors(ActorsToPreview, bPreviewInDesktopViewport);
}
class SActorPreview : public SCompoundWidget
{
public:
~SActorPreview();
SLATE_BEGIN_ARGS( SActorPreview )
: _ViewportWidth( 240 ),
_ViewportHeight( 180 ),
_IsInteractive( false ) {}
/** 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 )
/** Parent Viewport this preview is part of.*/
SLATE_ARGUMENT(bool, IsInteractive)
/** Optional */
SLATE_DEFAULT_SLOT( FArguments, Content )
SLATE_END_ARGS()
/** Called by Slate to construct this widget */
void Construct( const FArguments& InArgs );
/** @return Returns this actor preview's viewport widget */
const TSharedPtr< SViewport > GetViewportWidget() const
{
return ViewportWidget;
}
/** 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 camera settings of the preview actor.*/
FText OnFilmbackText() 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 for VR mode */
const FSlateBrush* GetVRPinButtonIconBrush() const;
/** 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;
/** Called when the detach button is clicked */
FReply OnToggleDetachButtonClicked();
/** Swap between the attached and detached icons */
const FSlateBrush* GetDetachButtonIconBrush() const;
/** @return the tooltip to display when hovering over the detach button */
FText GetDetachButtonToolTipText() 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);
TSharedPtr<SViewport> PreviewViewport;
auto GetPreviewContent = [&PreviewViewport](const FArguments& InOpArgs)->TSharedRef<SWidget>
{
if (InOpArgs._Content.Widget == SNullWidget::NullWidget)
{
return SAssignNew(PreviewViewport, SViewport)
.RenderDirectlyToWindow(false)
.IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute())
.EnableGammaCorrection(false) // Scene rendering handles gamma correction
.EnableBlending(true);
}
else
{
return InOpArgs._Content.Widget;
}
};
// We usually don't want actor preview viewports to be interactive at all, but some custom actor previews may want to override this
EVisibility BorderVisibility = (InArgs._IsInteractive ? EVisibility::SelfHitTestInvisible : EVisibility::HitTestInvisible);
//We draw certain buttons depending on whether we're in editor or VR mode
EVisibility VRVisibility = IVREditorModule::Get().IsVREditorModeActive() ? EVisibility::Visible : EVisibility::Hidden;
EVisibility EditorVisibility = IVREditorModule::Get().IsVREditorModeActive() ? EVisibility::Hidden : EVisibility::Visible;
this->ChildSlot
[
SNew(SBorder)
.Padding(0.f)
.Visibility(EVisibility::SelfHitTestInvisible)
.BorderImage(FAppStyle::GetBrush("NoBorder"))
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
.Padding(FMargin(0, 0, PaddingBeforeBorder, PaddingBeforeBorder))
[
SNew( SOverlay )
+SOverlay::Slot()
[
SNew( SBorder )
.Visibility(BorderVisibility)
.Padding( 16.0f )
.BorderImage( FAppStyle::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()
[
GetPreviewContent(InArgs)
]
+SOverlay::Slot()
.Padding(PreviewTextPadding)
.HAlign(HAlign_Center)
[
SNew( STextBlock )
.Text( this, &SActorPreview::OnReadText )
.Font( FCoreStyle::GetDefaultFontStyle("Bold", 10) )
.ShadowOffset( FVector2D::UnitVector )
.WrapTextAt( this, &SActorPreview::OnReadTextWidth )
]
+ SOverlay::Slot()
.Padding(PreviewTextPadding)
.HAlign(HAlign_Center)
.VAlign(VAlign_Bottom)
[
SNew(STextBlock)
.Text(this, &SActorPreview::OnFilmbackText)
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 10))
.ShadowOffset(FVector2D::UnitVector)
]
]
]
]
+SOverlay::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Bottom)
.Padding(24.0f)
[
// Create a button to pin/unpin this viewport
SNew(SButton)
.ContentPadding(0.f)
.ForegroundColor(FSlateColor::UseForeground())
.ButtonStyle(FAppStyle::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(EditorVisibility)
// Pass along the block's tool-tip string
.ToolTipText(this, &SActorPreview::GetPinButtonToolTipText)
]
+SOverlay::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Bottom)
.Padding( 0.f )
[
SNew(SBox)
.WidthOverride(45.f)
.HeightOverride(45.f)
[
// Create a button to pin/unpin this viewport
SNew( SButton )
.ContentPadding(0.f)
.ForegroundColor( FSlateColor::UseForeground() )
.ButtonStyle( FAppStyle::Get(), "ToggleButton" )
.IsFocusable(false)
[
SNew( SImage )
.Visibility( EVisibility::Visible )
.Image( this, &SActorPreview::GetVRPinButtonIconBrush )
]
// Bind the button's "on clicked" event to our object's method for this
.OnClicked( this, &SActorPreview::OnTogglePinnedButtonClicked )
.Visibility( VRVisibility )
// Pass along the block's tool-tip string
.ToolTipText( this, &SActorPreview::GetPinButtonToolTipText )
]
]
+ SOverlay::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
.Padding(0.f)
[
SNew(SBox)
.WidthOverride(45.f)
.HeightOverride(45.f)
[
// Create a button to attach/detach this viewport
SNew(SButton)
.ContentPadding(0.f)
.ForegroundColor(FSlateColor::UseForeground())
.ButtonStyle(FAppStyle::Get(), "ToggleButton")
.IsFocusable(false)
[
SNew(SImage)
.Visibility(EVisibility::Visible)
.Image(this, &SActorPreview::GetDetachButtonIconBrush)
]
// Bind the button's "on clicked" event to our object's method for this
.OnClicked(this, &SActorPreview::OnToggleDetachButtonClicked)
.Visibility(VRVisibility)
// Pass along the block's tool-tip string
.ToolTipText(this, &SActorPreview::GetDetachButtonToolTipText)
]
]
]
];
ViewportWidget = PreviewViewport;
// 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::GetVRPinButtonIconBrush() const
{
const FSlateBrush* IconBrush = nullptr;
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
if (ParentViewportPtr.IsValid())
{
if (ParentViewportPtr->IsActorPreviewPinned(PreviewActorPtr))
{
IconBrush = FAppStyle::GetBrush("VRViewportActorPreview.Pinned");
}
else
{
IconBrush = FAppStyle::GetBrush("VRViewportActorPreview.Unpinned");
}
}
return IconBrush;
}
const FSlateBrush* SActorPreview::GetPinButtonIconBrush() const
{
const FSlateBrush* IconBrush = nullptr;
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
if (ParentViewportPtr.IsValid())
{
if ( ParentViewportPtr->IsActorPreviewPinned(PreviewActorPtr) )
{
IconBrush = FAppStyle::GetBrush( "ViewportActorPreview.Pinned" );
}
else
{
IconBrush = FAppStyle::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;
}
FReply SActorPreview::OnToggleDetachButtonClicked()
{
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
if (ParentViewportPtr.IsValid())
{
ParentViewportPtr->ToggleActorPreviewIsPanelDetached(PreviewActorPtr);
}
return FReply::Handled();
}
const FSlateBrush* SActorPreview::GetDetachButtonIconBrush() const
{
const FSlateBrush* IconBrush = nullptr;
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
if (ParentViewportPtr.IsValid())
{
if (ParentViewportPtr->IsActorPreviewDetached(PreviewActorPtr))
{
IconBrush = FAppStyle::GetBrush("VRViewportActorPreview.Attached");
}
else
{
IconBrush = FAppStyle::GetBrush("VRViewportActorPreview.Detached");
}
}
return IconBrush;
}
FText SActorPreview::GetDetachButtonToolTipText() const
{
FText CurrentToolTipText = LOCTEXT("DetachPreviewActorTooltip", "Detach Preview from actor");
TSharedPtr<SLevelViewport> ParentViewportPtr = ParentViewport.Pin();
if (ParentViewportPtr.IsValid())
{
if (ParentViewportPtr->IsActorPreviewDetached(PreviewActorPtr))
{
CurrentToolTipText = LOCTEXT("AttachPreviewActorTooltip", "Attach Preview to actor");
}
}
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 = FAppStyle::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();
}
}
FText SActorPreview::OnFilmbackText() const
{
if (PreviewActorPtr.IsValid())
{
UActorComponent* ViewComponent = FLevelEditorViewportClient::FindViewComponentForActor(PreviewActorPtr.Get());
UCameraComponent* CameraComponent = Cast<UCameraComponent>(ViewComponent);
if (CameraComponent)
{
return CameraComponent->GetFilmbackText();
}
}
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 FIntPoint::IntType MaximumHeight = 428;
// Used to make sure default viewport scale * parent viewport height = roughly same size as original windows
const float PreviewScalingFactor = 0.06308f;
FIntPoint::IntType ParentHeight = MaximumHeight;
if ( ParentViewport.IsValid() )
{
ParentHeight = ParentViewport.Pin()->GetActiveViewport()->GetSizeXY().Y;
}
return FMath::Clamp( GetDefault<ULevelEditorViewportSettings>()->CameraPreviewSize * static_cast<float>(ParentHeight) * PreviewScalingFactor, MinimumHeight, MaximumHeight );
}
float SActorPreview::OnReadTextWidth() const
{
return OnReadWidth().Get() - (PreviewTextPadding*2.0f);
}
void SLevelViewport::PreviewActors( const TArray< AActor* >& InActorsToPreview, const bool bPreviewInDesktopViewport /*= true*/)
{
TArray< AActor* > ActorsToPreview(InActorsToPreview);
TArray< AActor* > NewActorsToPreview;
TArray< AActor* > ActorsToStopPreviewing;
for (TWeakObjectPtr<AActor> Actor : AlwaysPreviewActors)
{
AActor *CurActor = Actor.Get();
if (CurActor != nullptr)
{
ActorsToPreview.AddUnique(CurActor);
}
}
// Look for actors that we no longer want to preview
for( auto ActorPreviewIt = ActorPreviews.CreateConstIterator(); ActorPreviewIt; ++ActorPreviewIt )
{
auto ExistingActor = ActorPreviewIt->Actor.Get();
if( ExistingActor != nullptr )
{
auto bShouldKeepActor = false;
for( auto ActorIt = ActorsToPreview.CreateConstIterator(); ActorIt; ++ActorIt )
{
auto CurActor = *ActorIt;
if( CurActor != nullptr && 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 != nullptr && 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 == nullptr )
{
// decrement index so we don't miss next preview after deleting
RemoveActorPreview( PreviewIndex-- , nullptr, bPreviewInDesktopViewport);
}
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-- , CurActor, bPreviewInDesktopViewport);
break;
}
}
}
}
}
// Create previews for any actors that we need to
if( NewActorsToPreview.Num() > 0 )
{
for (AActor* CurActor : NewActorsToPreview)
{
TSharedPtr<SWidget> CustomPreviewContent;
if (UActorComponent* PreviewComp = FLevelEditorViewportClient::FindViewComponentForActor(CurActor))
{
CustomPreviewContent = PreviewComp->GetCustomEditorPreviewWidget();
}
const bool bNeedsLevelViewport = !CustomPreviewContent.IsValid();
TSharedPtr<FLevelEditorViewportClient> ActorPreviewLevelViewportClient;
if (bNeedsLevelViewport)
{
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);
if (!bPreviewInDesktopViewport)
{
ActorPreviewLevelViewportClient->EngineShowFlags.Tonemapper = false;
}
// 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 cinematics to possess these views
ActorPreviewLevelViewportClient->SetAllowCinematicControl( 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();
// Preview the play world if the current actor is in the play world
if (CurActor->GetWorld()->IsGameWorld())
{
ActorPreviewLevelViewportClient->SetIsSimulateInEditorViewport(true);
}
}
TSharedPtr< SActorPreview > ActorPreviewWidget;
if (CustomPreviewContent.IsValid())
{
SAssignNew(ActorPreviewWidget, SActorPreview)
.PreviewActor(CurActor)
.ParentViewport(SharedThis(this))
.IsInteractive(true)
.Content()
[
CustomPreviewContent.ToSharedRef()
];
}
else
{
SAssignNew(ActorPreviewWidget, SActorPreview)
.PreviewActor(CurActor)
.ParentViewport(SharedThis(this));
}
TSharedPtr<FSceneViewport> ActorPreviewSceneViewport;
if (bNeedsLevelViewport)
{
TSharedPtr<SViewport> ActorPreviewViewportWidget = ActorPreviewWidget->GetViewportWidget();
ActorPreviewSceneViewport = MakeShareable( new FSceneViewport( ActorPreviewLevelViewportClient.Get(), ActorPreviewViewportWidget) );
{
ActorPreviewLevelViewportClient->Viewport = ActorPreviewSceneViewport.Get();
if (ensure(ActorPreviewViewportWidget.IsValid()))
{
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!
IVREditorModule& VREditorModule = IVREditorModule::Get();
if (!bPreviewInDesktopViewport)
{
VREditorModule.UpdateActorPreview( NewActorPreview.PreviewWidget.ToSharedRef(), ActorPreviews.Num()-1, CurActor);
}
else
{
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();
}
}
bool SLevelViewport::IsActorAlwaysPreview(TWeakObjectPtr<AActor> Actor) const
{
return AlwaysPreviewActors.Contains(Actor);
}
void SLevelViewport::SetActorAlwaysPreview(TWeakObjectPtr<AActor> PreviewActor, bool bAlwaysPreview)
{
if (!IsActorAlwaysPreview(PreviewActor) && bAlwaysPreview)
{
AlwaysPreviewActors.Add(PreviewActor);
bNeedToUpdatePreviews = true;
}
else if (IsActorAlwaysPreview(PreviewActor) && !bAlwaysPreview)
{
AlwaysPreviewActors.Remove(PreviewActor);
bNeedToUpdatePreviews = true;
}
}
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();
}
}
}
}
}
void SLevelViewport::ToggleActorPreviewIsPanelDetached(TWeakObjectPtr<AActor> PreviewActor)
{
if (PreviewActor.IsValid())
{
AActor* PreviewActorPtr = PreviewActor.Get();
for (FViewportActorPreview& ActorPreview : ActorPreviews)
{
if (ActorPreview.Actor.IsValid())
{
if (PreviewActorPtr == ActorPreview.Actor.Get())
{
ActorPreview.ToggleIsPanelDetached();
IVREditorModule& VREditorModule = IVREditorModule::Get();
//Disable current actor preview
VREditorModule.UpdateActorPreview(SNullWidget::NullWidget, ActorPreviews.Num() - 1, PreviewActor.Get(), !ActorPreview.bIsPanelDetached);
//Enable new current preview (the old one was detached or attached, the new one is the opposite
VREditorModule.UpdateActorPreview(ActorPreview.PreviewWidget.ToSharedRef(), ActorPreviews.Num() - 1, PreviewActor.Get(), ActorPreview.bIsPanelDetached);
}
}
}
}
}
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;
}
bool SLevelViewport::IsActorPreviewDetached(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.bIsPanelDetached;
}
}
}
}
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(const FViewportActorPreview& CurActorPreview : ActorPreviews)
{
if (CurActorPreview.LevelViewportClient.IsValid())
{
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()
{
const bool bPreviewInDesktopViewport = !IVREditorModule::Get().IsVREditorModeActive();
// 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(bPreviewInDesktopViewport);
}
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*>(), bPreviewInDesktopViewport);
}
}
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::GetCurrentScreenPercentageText() const
{
return FText::FromString(FString::Printf(TEXT("%3d%%"), int32(GetLevelViewportClient().GetPreviewScreenPercentage())));
}
EVisibility SLevelViewport::GetSelectedActorsCurrentLevelTextVisibility() const
{
EVisibility ContentVisibility = OnGetViewportContentVisibility();
if (ContentVisibility == EVisibility::Visible)
{
ContentVisibility = EVisibility::SelfHitTestInvisible;
}
return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient)
&& (GEditor->GetSelectedActorCount() > 0)
&& !IsPlayInEditorViewportActive()
&& GetWorld() && GetWorld()->GetCurrentLevel() && GetWorld()->GetCurrentLevel()->OwningWorld->GetLevels().Num() > 1
&& !GetWorld()->IsPartitionedWorld()
? ContentVisibility : EVisibility::Collapsed;
}
FText SLevelViewport::GetSelectedActorsCurrentLevelText(bool bDrawOnlyLabel) const
{
// Display the currently selected actor's level
FText LabelName;
FText CurrentLevelName;
if (ActiveViewport.IsValid() && (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) && GetWorld())
{
if (ActiveViewport->GetPlayInEditorIsSimulate() || !ActiveViewport->GetClient()->GetWorld()->IsGameWorld())
{
if (bDrawOnlyLabel)
{
LabelName = LOCTEXT("SelectedActorsCurrentLevelLabel", "Selected Actor(s) in");
}
else
{
ULevel* LevelToMakeCurrent = nullptr;
// Look to the selected actors for the level to make current.
// If actors from multiple levels are selected, do nothing.
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
AActor* Actor = static_cast<AActor*>(*It);
checkSlow(Actor->IsA(AActor::StaticClass()));
ULevel* ActorLevel = Actor->GetLevel();
if (!LevelToMakeCurrent)
{
// First assignment.
LevelToMakeCurrent = ActorLevel;
}
else if (LevelToMakeCurrent != ActorLevel)
{
// Actors from multiple levels are selected -- abort.
LevelToMakeCurrent = nullptr;
break;
}
}
FText ActualLevelName = LOCTEXT("MultipleLevelValues", "Multiple Levels");
if (LevelToMakeCurrent)
{
ActualLevelName = FText::FromName(FPackageName::GetShortFName(LevelToMakeCurrent->GetOutermost()->GetFName()));
}
if (LevelToMakeCurrent == 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::GetCurrentScreenPercentageVisibility() const
{
bool Visible = !IsPlayInEditorViewportActive() &&
GetLevelViewportClient().SupportsPreviewResolutionFraction() &&
GetLevelViewportClient().GetPreviewScreenPercentage() > 100;
return Visible ? EVisibility::Visible : 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() && bShowToolbarAndControls) ? OnGetViewportContentVisibility() : EVisibility::Collapsed;
}
void SLevelViewport::OnSetViewportConfiguration(FName ConfigurationName)
{
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
if (LayoutPinned.IsValid())
{
TSharedPtr<FEditorViewportTabContent> ViewportTabPinned = LayoutPinned->GetParentTabContent().Pin();
if (ViewportTabPinned.IsValid())
{
ViewportTabPinned->SetViewportConfiguration(ConfigurationName);
FSlateApplication::Get().DismissAllMenus();
UToolMenus::Get()->CleanupStaleWidgetsNextTick(true);
}
}
}
bool SLevelViewport::IsViewportConfigurationSet(FName ConfigurationName) const
{
TSharedPtr<FLevelViewportLayout> LayoutPinned = ParentLayout.Pin();
if (LayoutPinned.IsValid())
{
TSharedPtr<FEditorViewportTabContent> 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.IsNone())
{
TSharedPtr<ILevelViewportLayoutEntity> Entity = StaticCastSharedPtr<ILevelViewportLayoutEntity>(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.IsNone())
{
// Important - RefreshViewportConfiguration does not save config values. We save its state first, to ensure that .TypeWithinLayout (below) doesn't get overwritten
TSharedPtr<FEditorViewportTabContent> ViewportTabPinned = LayoutPinned->GetParentTabContent().Pin();
if (!ViewportTabPinned)
{
return;
}
ViewportTabPinned->SaveConfig();
const FString& IniSection = FLayoutSaveRestore::GetAdditionalLayoutConfigIni();
GConfig->SetString( *IniSection, *( ConfigKey.ToString() + TEXT(".TypeWithinLayout") ), *InLayoutType.ToString(), GEditorPerProjectIni );
// Force a refresh of the tab content
ViewportTabPinned->RefreshViewportConfiguration();
FSlateApplication::Get().DismissAllMenus();
}
}
void SLevelViewport::ToggleViewportTypeActivationWithinLayout(FName InLayoutType)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (GetViewportTypeWithinLayout() != InLayoutType)
{
SetViewportTypeWithinLayout(InLayoutType);
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
bool SLevelViewport::IsViewportTypeWithinLayoutEqual(FName InLayoutType)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return GetViewportTypeWithinLayout() == InLayoutType;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
void SLevelViewport::StartPlayInEditorSession(UGameViewportClient* InPlayClient, 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();
PlayClient = InPlayClient;
// Set up binding overrides that can handle input before any player input
FOverrideInputKeyHandler& PlayInputKeyOverride = PlayClient->OnOverrideInputKey();
if (PlayInputKeyOverride.IsBound())
{
// Carry forward the previous binding and allow it to take precedence
FOverrideInputKeyHandler OldHandler = PlayInputKeyOverride;
PlayInputKeyOverride.BindLambda([WeakThis = SharedThis(this).ToWeakPtr(), OldHandler](FInputKeyEventArgs& Args)
{
if (OldHandler.IsBound() && OldHandler.Execute(Args))
{
return true;
}
if (TSharedPtr<SLevelViewport> Viewport = WeakThis.Pin())
{
return Viewport->OnPIEViewportInputOverride(Args);
}
return false;
});
}
else
{
PlayInputKeyOverride.BindSP(this, &SLevelViewport::OnPIEViewportInputOverride);
}
// 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( InPlayClient, 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 );
LevelViewportClient->AddRealtimeOverride(false, LOCTEXT("LevelViewport_RealTimeDisableOnPie", "Disable LevelViewport Realtime for PIE"));
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared());
PlayClient->SetViewportOverlayWidget(ParentWindow, PIEViewportOverlayWidget.ToSharedRef());
PlayClient->SetGameLayerManager(GameLayerManager);
// Set the scene viewport on PIE
if (GameLayerManager.IsValid() && !bInSimulateInEditor)
{
GameLayerManager->SetSceneViewport(ActiveViewport.Get());
}
// Our viewport widget should start rendering the new viewport for the play in editor scene
ViewportWidget->SetViewportInterface( ActiveViewport.ToSharedRef() );
// Let the viewport client know what viewport it is associated with
PlayClient->Viewport = ActiveViewport.Get();
// Register the new viewport widget with Slate for viewport specific message routing.
FSlateApplication::Get().RegisterGameViewport(ViewportWidget.ToSharedRef() );
ULevelEditorPlaySettings const* EditorPlayInSettings = GetDefault<ULevelEditorPlaySettings>();
check(EditorPlayInSettings);
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::StartingPlayInEditor;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
if (EditorPlayInSettings->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/GamePreview/StartPlayInEditor_Cue.StartPlayInEditor_Cue"));
}
bPIEHasFocus = ActiveViewport->HasMouseCapture();
if(EditorPlayInSettings->ShowMouseControlLabel && !GEngine->IsStereoscopic3D( ActiveViewport.Get() ) && !UE::UnrealEd::ShowNewViewportToolbars())
{
ELabelAnchorMode AnchorMode = EditorPlayInSettings->MouseControlLabelPosition.GetValue();
ShowMouseCaptureLabel(AnchorMode);
}
GEngine->BroadcastLevelActorListChanged();
// register for preview feature level change
UEditorEngine* Editor = CastChecked<UEditorEngine>(GEngine);
PIEPreviewFeatureLevelChangedHandle = Editor->OnPreviewFeatureLevelChanged().AddLambda([InPlayClient](ERHIFeatureLevel::Type NewFeatureLevel)
{
InPlayClient->GetWorld()->ChangeFeatureLevel(NewFeatureLevel);
});
}
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->ShouldShowMouseCursor())
{
return EVisibility::HitTestInvisible;
}
}
return EVisibility::Collapsed;
}
FLinearColor SLevelViewport::GetMouseCaptureLabelColorAndOpacity() const
{
static const FName DefaultForegroundName("DefaultForeground");
FSlateColor SlateColor = FAppStyle::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() && UICommand->GetFirstValidChord()->IsValidChord())
{
// Just pick the first key bind that is valid for a text suggestion
Chord = UICommand->GetFirstValidChord().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);
{
ViewportOverlay->AddSlot()
.HAlign(HAlign)
.VAlign(VAlign)
[
SAssignNew( PIEOverlayBorder, SBorder )
.BorderImage( FAppStyle::GetBrush("NoBorder") )
.Visibility(this, &SLevelViewport::GetMouseCaptureLabelVisibility)
.ColorAndOpacity( this, &SLevelViewport::GetMouseCaptureLabelColorAndOpacity )
.ForegroundColor( FLinearColor::White )
.Padding(15.0f)
[
SNew( SButton )
.ButtonStyle( FAppStyle::Get(), "EditorViewportToolBar.MenuButton" )
.IsFocusable(false)
.ButtonColorAndOpacity( FSlateColor(FLinearColor::Black) )
.ForegroundColor( FLinearColor::White )
[
BuildMouseCaptureWidget()
]
]
];
}
}
TSharedRef<SWidget> SLevelViewport::BuildMouseCaptureWidget()
{
return 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(FAppStyle::GetBrush("LevelViewport.CursorIcon"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.Padding(2.0f, 2.0f)
[
SNew(STextBlock)
.Text(this, &SLevelViewport::GetMouseCaptureLabelText)
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 9))
.ColorAndOpacity(FLinearColor::White)
];
}
void SLevelViewport::HideMouseCaptureLabel()
{
if (PIEOverlayBorder.IsValid())
{
ViewportOverlay->RemoveSlot(PIEOverlayBorder.ToSharedRef());
PIEOverlayBorder.Reset();
}
}
void SLevelViewport::ResetNewLevelViewFlags()
{
const bool bUseSavedDefaults = true;
OnUseDefaultShowFlags(bUseSavedDefaults);
}
void SLevelViewport::EndPlayInEditorSession()
{
check( HasPlayInEditorViewport() );
FSlateApplication::Get().UnregisterGameViewport();
check( InactiveViewport.IsValid() );
const bool bCheckMissingOverride = false;
LevelViewportClient->RemoveRealtimeOverride(LOCTEXT("LevelViewport_RealTimeDisableOnPie", "Disable LevelViewport Realtime for PIE"), bCheckMissingOverride);
PlayClient.Reset();
if( IsPlayInEditorViewportActive() )
{
{
TSharedPtr<FSceneViewport> GameViewport = ActiveViewport;
ActiveViewport = InactiveViewport;
ActiveViewport->OnPlayWorldViewportSwapped( *GameViewport );
// Play in editor viewport was active, swap back to our level editor viewport
GameViewport->SetViewportClient( nullptr );
// We should be the only thing holding on to viewports
check( GameViewport.IsUnique() );
}
// Ensure our active viewport is for level editing
check( ActiveViewport->GetClient() == LevelViewportClient.Get() );
// If we're going back to VR Editor, refresh the level viewport's render target so the HMD will present frames here
if( GEngine->IsStereoscopic3D( ActiveViewport.Get() ) )
{
ActiveViewport->UpdateViewportRHI( false, ActiveViewport->GetSizeXY().X, ActiveViewport->GetSizeXY().Y, ActiveViewport->GetWindowMode(), PF_Unknown );
}
else
{
// Restore camera settings that may be adversely affected by PIE
LevelViewportClient->RestoreCameraFromPIE();
RedrawViewport(true);
// Remove camera roll from any PIE camera applied in this viewport. A rolled camera is hard to use for editing
LevelViewportClient->RemoveCameraRoll();
}
}
else
{
InactiveViewport->SetViewportClient( nullptr );
}
// Reset our game layer manager's active to that of the active editor viewport
GameLayerManager->SetSceneViewport(ActiveViewport.Get());
// 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();
HideMouseCaptureLabel();
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::ReturningToEditor;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
if (GetDefault<ULevelEditorPlaySettings>()->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/EndPlayInEditor_Cue.EndPlayInEditor_Cue" ) );
}
GEngine->BroadcastLevelActorListChanged();
// Remove preview feature level delegate if set
if (PIEPreviewFeatureLevelChangedHandle.IsValid())
{
CastChecked<UEditorEngine>(GEngine)->OnPreviewFeatureLevelChanged().Remove(PIEPreviewFeatureLevelChangedHandle);
PIEPreviewFeatureLevelChangedHandle.Reset();
}
}
void SLevelViewport::SwapViewportsForSimulateInEditor()
{
// Ensure our active viewport was the play in editor viewport
check( IsPlayInEditorViewportActive() );
// Remove the mouse control label - not relevant for SIE
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 );
if (GameLayerManager.IsValid())
{
GameLayerManager->SetSceneViewport(ActiveViewport.Get());
}
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;
if (GetDefault<ULevelEditorPlaySettings>()->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/GamePreview/PossessPlayer_Cue.PossessPlayer_Cue"));
}
}
void SLevelViewport::SwapViewportsForPlayInEditor()
{
// Ensure our inactive viewport was the play in editor viewport
check( !IsPlayInEditorViewportActive() && HasPlayInEditorViewport() );
// Put the mouse control label up again.
ULevelEditorPlaySettings const* EditorPlayInSettings = GetDefault<ULevelEditorPlaySettings>();
check(EditorPlayInSettings);
if(EditorPlayInSettings->ShowMouseControlLabel && !GEngine->IsStereoscopic3D( ActiveViewport.Get() ) && !UE::UnrealEd::ShowNewViewportToolbars())
{
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 );
if (GameLayerManager.IsValid())
{
GameLayerManager->SetSceneViewport(ActiveViewport.Get());
}
InactiveViewportWidgetEditorContent = ViewportWidget->GetContent();
ViewportWidget->SetViewportInterface( ActiveViewport.ToSharedRef() );
// Register the game viewport with slate which will capture the mouse and lock it to the viewport
FSlateApplication::Get().RegisterGameViewport( ViewportWidget.ToSharedRef() );
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::StartingPlayInEditor;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
if (EditorPlayInSettings->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/EjectFromPlayer_Cue.EjectFromPlayer_Cue" ) );
}
}
void SLevelViewport::OnSimulateSessionStarted()
{
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::StartingSimulate;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
if (GetDefault<ULevelEditorPlaySettings>()->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/StartSimulate_Cue.StartSimulate_Cue" ) );
}
// Make sure the viewport's hit proxies are invalidated. If not done, clicking in the viewport could select an editor world actor
ActiveViewport->InvalidateHitProxy();
}
void SLevelViewport::OnSimulateSessionFinished()
{
// Kick off a quick transition effect (border graphics)
ViewTransitionType = EViewTransition::ReturningToEditor;
ViewTransitionAnim = FCurveSequence( 0.0f, 1.5f, ECurveEaseFunction::CubicOut );
bViewTransitionAnimPending = true;
if (GetDefault<ULevelEditorPlaySettings>()->EnablePIEEnterAndExitSounds)
{
GEditor->PlayEditorSound( TEXT( "/Engine/EditorSounds/GamePreview/EndSimulate_Cue.EndSimulate_Cue" ) );
}
// Make sure the viewport's hit proxies are invalidated. If not done, clicking in the viewport could select a pie world actor
ActiveViewport->InvalidateHitProxy();
}
EVisibility SLevelViewport::GetLockedIconVisibility() const
{
return IsAnyActorLocked() && UE::UnrealEd::ShowOldViewportToolbars() ? 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() : nullptr;
}
FReply SLevelViewport::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent)
{
// SEditorViewport redirects all clicks to focus the viewport widget.
// This means clicking on the toolbar in PIE captures the mouse cursor for the game.
return FReply::Unhandled();
}
void SLevelViewport::ToggleInViewportContextMenu()
{
USelection* ActorSelection = GEditor->GetSelectedActors();
if (ActorSelection->Num())
{
if (!bIsInViewportMenuShowing)
{
// Set up the correct menu location first
if (!bIsInViewportMenuInitialized)
{
FVector2D NewViewportContextMenuLocation = GetDefault<ULevelEditorViewportSettings>()->LastInViewportMenuLocation;
if (!NewViewportContextMenuLocation.IsZero())
{
UpdateInViewportMenuLocation(NewViewportContextMenuLocation);
}
else
{
AActor* SelectedActor = ActorSelection->GetTop<AActor>();
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
GetActiveViewport(),
LevelViewportClient->GetScene(),
LevelViewportClient->EngineShowFlags)
.SetRealtimeUpdate(IsRealtime()));
// SceneView is deleted with the ViewFamily
FSceneView* SceneView = LevelViewportClient->CalcSceneView(&ViewFamily);
const float InvDpiScale = 1.0f / LevelViewportClient->GetDPIScale();
FVector2D ScreenPos;
SceneView->WorldToPixel(SelectedActor->GetTransform().GetLocation(), ScreenPos);
ScreenPos *= InvDpiScale;
const float EdgeFactor = 0.85f;
const float MinX = static_cast<float>(SceneView->UnscaledViewRect.Width()) * InvDpiScale * (1.f - EdgeFactor);
const float MinY = static_cast<float>(SceneView->UnscaledViewRect.Height()) * InvDpiScale * (1.f - EdgeFactor);
const float MaxX = static_cast<float>(SceneView->UnscaledViewRect.Width()) * InvDpiScale * EdgeFactor;
const float MaxY = (static_cast<float>(SceneView->UnscaledViewRect.Height()) * InvDpiScale * EdgeFactor);
const bool bOutside = ScreenPos.X < MinX || ScreenPos.X > MaxX || ScreenPos.Y < MinY || ScreenPos.Y > MaxY;
if (bOutside)
{
ScreenPos.X = (static_cast<float>(SceneView->UnscaledViewRect.Width()) * InvDpiScale) / 2.0f;
ScreenPos.Y = (static_cast<float>(SceneView->UnscaledViewRect.Height()) * InvDpiScale) / 2.0f;
}
UpdateInViewportMenuLocation(ScreenPos);
}
bIsInViewportMenuInitialized = true;
}
bIsInViewportMenuShowing = true;
InViewportMenu = SNew(SInViewportDetails)
.InOwningViewport(SharedThis(this))
.InOwningLevelEditor(ParentLevelEditor.Pin());
InViewportMenuWrapper = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Top)
.HAlign(HAlign_Left)
.Padding(TAttribute<FMargin>(this, &SLevelViewport::GetContextMenuPadding))
[
InViewportMenu.ToSharedRef()
];
// Immediately update it (otherwise it will appear empty)
{
TArray<UObject*> SelectedActors;
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
AActor* Actor = static_cast<AActor*>(*It);
checkSlow(Actor->IsA(AActor::StaticClass()));
if (IsValidChecked(Actor))
{
SelectedActors.Add(Actor);
}
}
const bool bForceRefresh = true;
InViewportMenu->SetObjects(SelectedActors, bForceRefresh);
}
AddOverlayWidget(InViewportMenuWrapper.ToSharedRef());
}
else
{
HideInViewportContextMenu();
}
}
}
void SLevelViewport::HideInViewportContextMenu()
{
if (InViewportMenuWrapper.IsValid())
{
RemoveOverlayWidget(InViewportMenuWrapper.ToSharedRef());
}
bIsInViewportMenuShowing = false;
InViewportMenu.Reset();
}
bool SLevelViewport::CanToggleInViewportContextMenu()
{
return SLevelViewport::bInViewportMenuEnabled;
}
void SLevelViewport::EnableInViewportMenu()
{
SLevelViewport::bInViewportMenuEnabled = !SLevelViewport::bInViewportMenuEnabled;
}
FMargin SLevelViewport::GetContextMenuPadding() const
{
return FMargin(InViewportContextMenuLocation.X, InViewportContextMenuLocation.Y, 0, 0);
}
void SLevelViewport::RemoveActorPreview( int32 PreviewIndex, AActor* Actor, const bool bRemoveFromDesktopViewport /*=true */ )
{
IVREditorModule& VREditorModule = IVREditorModule::Get();
if (!bRemoveFromDesktopViewport)
{
if (ActorPreviews.IsValidIndex(PreviewIndex))
{
VREditorModule.UpdateActorPreview(SNullWidget::NullWidget, PreviewIndex, Actor, ActorPreviews[PreviewIndex].bIsPanelDetached);
}
}
else
{
// Remove widget from viewport overlay
ActorPreviewHorizontalBox->RemoveSlot(ActorPreviews[PreviewIndex].PreviewWidget.ToSharedRef());
}
if (ActorPreviews.IsValidIndex(PreviewIndex))
{
// Clean up our level viewport client
if (ActorPreviews[PreviewIndex].LevelViewportClient.IsValid())
{
ActorPreviews[PreviewIndex].LevelViewportClient->Viewport = nullptr;
}
// 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, int32 ZOrder)
{
ViewportOverlay->AddSlot(ZOrder)
[
OverlaidWidget
];
}
void SLevelViewport::RemoveOverlayWidget(TSharedRef<SWidget> OverlaidWidget)
{
ViewportOverlay->RemoveSlot(OverlaidWidget);
}
bool SLevelViewport::CanProduceActionForCommand(const TSharedRef<const FUICommandInfo>& Command) const
{
return IsActiveLevelViewport();
}
void SLevelViewport::LockActorInternal(AActor* NewActorToLock)
{
if (NewActorToLock != nullptr)
{
LevelViewportClient->SetActorLock(NewActorToLock);
if (LevelViewportClient->IsPerspective() && LevelViewportClient->GetActiveActorLock().IsValid())
{
// Store perspective camera transform before piloting
CachedPerspectiveCameraTransform = LevelViewportClient->GetViewTransform();
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: CAMERA: Support richer camera interactions in SIE; this may shake out naturally if everything uses camera components though
bool bFoundCamInfo = false;
if (UActorComponent* ViewComponent = FLevelEditorViewportClient::FindViewComponentForActor(Actor))
{
bFoundCamInfo = ViewComponent->GetEditorPreviewInfo(/*DeltaTime =*/0.0f, out_CameraInfo);
ensure(bFoundCamInfo);
}
return bFoundCamInfo;
}
bool SLevelViewport::CanGetCameraInformationFromActor(AActor* Actor)
{
FMinimalViewInfo CameraInfo;
return GetCameraInformationFromActor(Actor, /*out*/ CameraInfo);
}
void SLevelViewport::OnFloatingButtonClicked()
{
// if one of the viewports floating buttons has been clicked, update the global viewport ptr
LevelViewportClient->SetLastKeyViewport();
}
EVisibility SLevelViewport::GetToolbarVisibility() const
{
return GetOptionsMenuVisibility();
}
EVisibility SLevelViewport::GetOptionsMenuVisibility() const
{
return bShowToolbarAndControls ? EVisibility::Visible : EVisibility::Collapsed;
}
void SLevelViewport::RemoveAllPreviews(const bool bRemoveFromDesktopViewport /**= true*/)
{
// Clean up any actor preview viewports
for (FViewportActorPreview& ActorPreview : ActorPreviews)
{
ActorPreview.bIsPinned = false;
}
PreviewActors(TArray< AActor* >(), bRemoveFromDesktopViewport);
}
#undef LOCTEXT_NAMESPACE