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

1215 lines
40 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SEditorViewport.h"
#include "EditorInteractiveGizmoManager.h"
#include "Misc/Paths.h"
#include "Framework/Commands/UICommandList.h"
#include "Misc/App.h"
#include "Widgets/Layout/SBorder.h"
#include "Settings/LevelEditorViewportSettings.h"
#include "ThumbnailRendering/ThumbnailManager.h"
#include "EngineGlobals.h"
#include "Engine/TextureStreamingTypes.h"
#include "EditorModeManager.h"
#include "Slate/SceneViewport.h"
#include "EditorViewportCommands.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Settings/EditorProjectSettings.h"
#include "Kismet2/DebuggerCommands.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SSpinBox.h"
#include "Widgets/Input/SCheckBox.h"
#include "MaterialShaderQualitySettings.h"
#include "RayTracingDebugVisualizationMenuCommands.h"
#include "GPUSkinCacheVisualizationMenuCommands.h"
#include "GPUSkinCache.h"
#include "Widgets/Colors/SComplexGradient.h"
#include "Widgets/SBoxPanel.h"
#include "Modules/ModuleManager.h"
#include "IPreviewProfileController.h"
#include "ISettingsModule.h"
#include "ShowFlagMenuCommands.h"
#include "ViewportToolbar/UnrealEdViewportToolbar.h"
#if WITH_DUMPGPU
#include "RenderGraph.h"
#endif
#include "LevelEditorViewport.h"
#include "SEditorViewportGridPanel.h"
#define LOCTEXT_NAMESPACE "EditorViewport"
SEditorViewport::SEditorViewport()
: LastTickTime(0)
, bInvalidated(false)
{
}
SEditorViewport::~SEditorViewport()
{
// Close viewport
if( Client.IsValid() )
{
Client->Viewport = NULL;
}
// Release our reference to the viewport client
Client.Reset();
check( SceneViewport.IsUnique() );
}
void SEditorViewport::Construct( const FArguments& InArgs )
{
CreatePreviewProfileController();
// Create Viewport Widget
// clang-format off
SAssignNew(ViewportWidget, SViewport)
.ShowEffectWhenDisabled(false)
.EnableGammaCorrection(false) // Scene rendering handles this
.AddMetaData(InArgs.MetaData.Num() > 0 ? InArgs.MetaData[0] : MakeShareable(new FTagMetaData(TEXT("LevelEditorViewport"))))
.ViewportSize(InArgs._ViewportSize)
[
SAssignNew(ViewportOverlay, SOverlay)
];
// clang-format on
Client = MakeEditorViewportClient();
if (!Client->VisibilityDelegate.IsBound())
{
Client->VisibilityDelegate.BindSP(this, &SEditorViewport::IsVisible);
}
SceneViewport = MakeShareable( new FSceneViewport( Client.Get(), ViewportWidget ) );
Client->Viewport = SceneViewport.Get();
ViewportWidget->SetViewportInterface(SceneViewport.ToSharedRef());
if ( Client->IsRealtime() )
{
ActiveTimerHandle = RegisterActiveTimer( 0.f, FWidgetActiveTimerDelegate::CreateSP( this, &SEditorViewport::EnsureTick ) );
}
CommandList = MakeShareable( new FUICommandList );
// Ensure the commands are registered
FEditorViewportCommands::Register();
BindCommands();
// clang-format off
ViewportOverlay->AddSlot()
[
SNew(SBorder)
.BorderImage(this, &SEditorViewport::OnGetViewportBorderBrush)
.BorderBackgroundColor(this, &SEditorViewport::OnGetViewportBorderColorAndOpacity)
.Visibility(this, &SEditorViewport::GetActiveBorderVisibility)
.Padding(0.0f)
.ShowEffectWhenDisabled(false)
];
// clang-format on
// clang-format off
ViewportOverlay->AddSlot()
.VAlign(VAlign_Top)
[
SNew(SBox)
.Visibility(this, &SEditorViewport::OnGetFocusedViewportIndicatorVisibility)
.MaxDesiredHeight(1.0f)
.MinDesiredHeight(1.0f)
[
CreateViewportIndicatorWidget(
TAttribute<EVisibility>::CreateSP(this, &SEditorViewport::OnGetFocusedViewportIndicatorVisibility)
).ToSharedRef()
]
];
// clang-format on
TSharedPtr<SVerticalBox> VerticalBox;
// clang-format off
ChildSlot
[
SAssignNew(VerticalBox, SVerticalBox)
];
// clang-format on
// Set up viewport toolbars.
{
const TSharedPtr<SWidget> OldViewportToolbar = MakeViewportToolbar();
TSharedPtr<SWidget> NewViewportToolbar = BuildViewportToolbar();
// Allow programatically-migrated viewports (e.g. SCommonEditorViewportToolbarBase) to register themselves on construction as supporting either position
// This indirection is done to minimize API changes (e.g. using a specific toolbar interface). The goal is that clients do nothing and get upgrades.
if (!NewViewportToolbar && OldViewportToolbar)
{
// Overrides of SCommonEditorViewportToolbarBase can construct the full base toolbar and then overwrite the child slot with their own content.
// This check ensures that the automatic upgrade is only allowed if we end up with a widget containing the child that we expected to have
// when the upgrade was requested.
if (TSharedPtr<SWidget> ExpectedChild = AutoUpgradeWidgetChild.Pin())
{
if (OldViewportToolbar->GetChildren()->Num() == 1 && OldViewportToolbar->GetChildren()->GetChildAt(0) == ExpectedChild)
{
bLegacyToolbarIsAutomaticallyUpgradable = true;
NewViewportToolbar = OldViewportToolbar;
}
}
}
if (OldViewportToolbar)
{
const bool bHasNewViewportToolbar = NewViewportToolbar.IsValid();
// clang-format off
ViewportOverlay->AddSlot()
.VAlign(VAlign_Top)
[
SNew(SBox)
.Visibility_Lambda(
[bHasNewViewportToolbar]() -> EVisibility
{
// Always show the old viewport toolbar if there is no new one.
if (!bHasNewViewportToolbar)
{
return EVisibility::Visible;
}
return UE::UnrealEd::ShowOldViewportToolbars() ? EVisibility::Visible: EVisibility::Collapsed;
}
)
[
OldViewportToolbar.ToSharedRef()
]
];
// clang-format on
}
// If new toolbar is available, let's add it on top of viewport
if (NewViewportToolbar)
{
const bool bHasOldViewportToolbar = OldViewportToolbar.IsValid();
// clang-format off
VerticalBox->AddSlot()
.AutoHeight()
[
SNew(SBox)
.Visibility_Lambda(
[bHasOldViewportToolbar, bHasLegacyUpgradedToolbar = bLegacyToolbarIsAutomaticallyUpgradable]() -> EVisibility
{
// Always show the new viewport toolbar if there is no old one.
if (!bHasOldViewportToolbar)
{
return EVisibility::Visible;
}
// In the case of the old toolbar being upgraded, don't show it twice
return (UE::UnrealEd::ShowNewViewportToolbars() && (!bHasLegacyUpgradedToolbar || !UE::UnrealEd::ShowOldViewportToolbars())) ? EVisibility::Visible: EVisibility::Collapsed;
}
)
[
NewViewportToolbar.ToSharedRef()
]
];
// clang-format on
}
}
// clang-format off
bool bIsMainViewport = false;
float MainViewportX = 0;
float MainViewportY = 0;
TSharedPtr<FEditorViewportClient> EditorViewportClient = GetViewportClient();
if (EditorViewportClient.IsValid())
{
if (EditorViewportClient->IsLevelEditorClient())
{
const FLevelEditorViewportClient* LevelClient = static_cast<FLevelEditorViewportClient*>(EditorViewportClient.Get());
if (LevelClient->IsPerspective())
{
bIsMainViewport = true;
}
if (LevelClient->Viewport != nullptr)
{
MainViewportX = LevelClient->Viewport->GetSizeXY().X;
MainViewportY = LevelClient->Viewport->GetSizeXY().Y;
}
}
}
if (bIsMainViewport)
{
// We wrap the main editor viewport in the middle of a 3x3 grid in order to restrict aspect ratio for preview platforms that request it.
VerticalBox->AddSlot()
[
SNew(SGlobalPlayWorldActions)
[
SNew(SEditorViewportGridPanel)
.ViewportWidget(ViewportWidget)
]
];
}
else
{
VerticalBox->AddSlot()
[
SNew(SGlobalPlayWorldActions)
[
ViewportWidget.ToSharedRef()
]
];
}
// clang-format on
PopulateViewportOverlays(ViewportOverlay.ToSharedRef());
// Any code retrieving DPI scale before this point might have done that too soon, when the parent Window of the
// Viewport Widget is not valid yet. This causes the Viewport Client to cache a default DPI scale instead of the
// correct and expected value.
// An example of this happening would be FEditorViewportClient::GetPreviewScreenPercentage() which is called when
// creating Screen Percentage menus in Asset Editors (e.g. Level, Static Mesh, etc.)
// The following RequestUpdateDPIScale() call marks the DPI value for refresh.
// This ensures CachedDPIScale value can be properly retrieved at the right time, once the Widget is fully setup and its parent window can be accessed.
GetViewportClient()->RequestUpdateDPIScale();
}
FReply SEditorViewport::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
{
FReply Reply = FReply::Unhandled();
if( CommandList->ProcessCommandBindings( InKeyEvent ) )
{
Reply = FReply::Handled();
Client->Invalidate();
}
return Reply;
}
bool SEditorViewport::SupportsKeyboardFocus() const
{
return true;
}
FReply SEditorViewport::OnFocusReceived( const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent )
{
// forward focus to the viewport
return FReply::Handled().SetUserFocus(ViewportWidget.ToSharedRef(), InFocusEvent.GetCause());
}
void SEditorViewport::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
LastTickTime = FPlatformTime::Seconds();
}
void SEditorViewport::BindCommands()
{
FUICommandList& CommandListRef = *CommandList;
const FEditorViewportCommands& Commands = FEditorViewportCommands::Get();
TSharedRef<FEditorViewportClient> ClientRef = Client.ToSharedRef();
CommandListRef.MapAction(
Commands.ToggleRealTime,
FExecuteAction::CreateSP( this, &SEditorViewport::OnToggleRealtime ),
FCanExecuteAction::CreateSP(this, &SEditorViewport::CanToggleRealtime),
FIsActionChecked::CreateSP(this, &SEditorViewport::IsRealtime));
CommandListRef.MapAction(
Commands.ToggleStats,
FExecuteAction::CreateSP( this, &SEditorViewport::OnToggleStats ),
FCanExecuteAction(),
FIsActionChecked::CreateSP(ClientRef, &FEditorViewportClient::ShouldShowStats));
CommandListRef.MapAction(
Commands.ToggleFPS,
FExecuteAction::CreateSP(this, &SEditorViewport::ToggleStatCommand, FString("FPS")),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SEditorViewport::IsStatCommandVisible, FString("FPS")));
CommandListRef.MapAction(
Commands.IncrementPositionGridSize,
FExecuteAction::CreateSP( this, &SEditorViewport::OnIncrementPositionGridSize )
);
CommandListRef.MapAction(
Commands.DecrementPositionGridSize,
FExecuteAction::CreateSP( this, &SEditorViewport::OnDecrementPositionGridSize )
);
CommandListRef.MapAction(
Commands.IncrementRotationGridSize,
FExecuteAction::CreateSP( this, &SEditorViewport::OnIncrementRotationGridSize )
);
CommandListRef.MapAction(
Commands.DecrementRotationGridSize,
FExecuteAction::CreateSP( this, &SEditorViewport::OnDecrementRotationGridSize )
);
CommandListRef.MapAction(
Commands.Perspective,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetViewportType, LVT_Perspective ),
FCanExecuteAction(),
FIsActionChecked::CreateSP(ClientRef, &FEditorViewportClient::IsActiveViewportType, LVT_Perspective));
CommandListRef.MapAction(
Commands.Front,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetViewportType, LVT_OrthoFront ),
FCanExecuteAction(),
FIsActionChecked::CreateSP(ClientRef, &FEditorViewportClient::IsActiveViewportType, LVT_OrthoFront));
CommandListRef.MapAction(
Commands.Left,
FExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::SetViewportType, LVT_OrthoLeft),
FCanExecuteAction(),
FIsActionChecked::CreateSP(ClientRef, &FEditorViewportClient::IsActiveViewportType, LVT_OrthoLeft));
CommandListRef.MapAction(
Commands.Top,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetViewportType, LVT_OrthoTop ),
FCanExecuteAction(),
FIsActionChecked::CreateSP(ClientRef, &FEditorViewportClient::IsActiveViewportType, LVT_OrthoTop));
CommandListRef.MapAction(
Commands.Back,
FExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::SetViewportType, LVT_OrthoBack),
FCanExecuteAction(),
FIsActionChecked::CreateSP(ClientRef, &FEditorViewportClient::IsActiveViewportType, LVT_OrthoBack));
CommandListRef.MapAction(
Commands.Right,
FExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::SetViewportType, LVT_OrthoRight),
FCanExecuteAction(),
FIsActionChecked::CreateSP(ClientRef, &FEditorViewportClient::IsActiveViewportType, LVT_OrthoRight));
CommandListRef.MapAction(
Commands.Bottom,
FExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::SetViewportType, LVT_OrthoBottom),
FCanExecuteAction(),
FIsActionChecked::CreateSP(ClientRef, &FEditorViewportClient::IsActiveViewportType, LVT_OrthoBottom));
CommandListRef.MapAction(
Commands.Next,
FExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::RotateViewportType),
FCanExecuteAction(),
FIsActionChecked::CreateSP(ClientRef, &FEditorViewportClient::IsActiveViewportTypeInRotation));
CommandListRef.MapAction(
Commands.ScreenCapture,
FExecuteAction::CreateSP( this, &SEditorViewport::OnScreenCapture ),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SEditorViewport::DoesAllowScreenCapture)
);
CommandListRef.MapAction(
Commands.ScreenCaptureForProjectThumbnail,
FExecuteAction::CreateSP( this, &SEditorViewport::OnScreenCaptureForProjectThumbnail ),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SEditorViewport::DoesAllowScreenCapture)
);
CommandListRef.MapAction(
Commands.SelectMode,
FExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::SetWidgetMode, UE::Widget::WM_None),
FCanExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::CanSetWidgetMode, UE::Widget::WM_None),
FIsActionChecked::CreateSP(this, &SEditorViewport::IsWidgetModeActive, UE::Widget::WM_None)
);
CommandListRef.MapAction(
Commands.TranslateMode,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetWidgetMode, UE::Widget::WM_Translate ),
FCanExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::CanSetWidgetMode, UE::Widget::WM_Translate ),
FIsActionChecked::CreateSP( this, &SEditorViewport::IsWidgetModeActive, UE::Widget::WM_Translate )
);
CommandListRef.MapAction(
Commands.RotateMode,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetWidgetMode, UE::Widget::WM_Rotate ),
FCanExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::CanSetWidgetMode, UE::Widget::WM_Rotate ),
FIsActionChecked::CreateSP( this, &SEditorViewport::IsWidgetModeActive, UE::Widget::WM_Rotate )
);
CommandListRef.MapAction(
Commands.ScaleMode,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetWidgetMode, UE::Widget::WM_Scale ),
FCanExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::CanSetWidgetMode, UE::Widget::WM_Scale ),
FIsActionChecked::CreateSP( this, &SEditorViewport::IsWidgetModeActive, UE::Widget::WM_Scale )
);
CommandListRef.MapAction(
Commands.TranslateRotateMode,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetWidgetMode, UE::Widget::WM_TranslateRotateZ ),
FCanExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::CanSetWidgetMode, UE::Widget::WM_TranslateRotateZ ),
FIsActionChecked::CreateSP( this, &SEditorViewport::IsWidgetModeActive, UE::Widget::WM_TranslateRotateZ ),
FIsActionButtonVisible::CreateSP( this, &SEditorViewport::IsTranslateRotateModeVisible )
);
CommandListRef.MapAction(
Commands.TranslateRotate2DMode,
FExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::SetWidgetMode, UE::Widget::WM_2D),
FCanExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::CanSetWidgetMode, UE::Widget::WM_2D),
FIsActionChecked::CreateSP(this, &SEditorViewport::IsWidgetModeActive, UE::Widget::WM_2D),
FIsActionButtonVisible::CreateSP(this, &SEditorViewport::Is2DModeVisible)
);
CommandListRef.MapAction(
Commands.ShrinkTransformWidget,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::AdjustTransformWidgetSize, -1 )
);
CommandListRef.MapAction(
Commands.ExpandTransformWidget,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::AdjustTransformWidgetSize, 1 )
);
CommandListRef.MapAction(
Commands.RelativeCoordinateSystem_World,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetWidgetCoordSystemSpace, COORD_World ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SEditorViewport::IsCoordSystemActive, COORD_World )
);
CommandListRef.MapAction(
Commands.RelativeCoordinateSystem_Local,
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetWidgetCoordSystemSpace, COORD_Local ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SEditorViewport::IsCoordSystemActive, COORD_Local )
);
CommandListRef.MapAction(
Commands.RelativeCoordinateSystem_Parent,
FExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::SetWidgetCoordSystemSpace, COORD_Parent),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SEditorViewport::IsCoordSystemActive, COORD_Parent)
);
CommandListRef.MapAction(
Commands.RelativeCoordinateSystem_Explicit,
FExecuteAction::CreateSP(ClientRef, &FEditorViewportClient::SetWidgetCoordSystemSpace, COORD_Explicit),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SEditorViewport::IsCoordSystemActive, COORD_Explicit)
);
CommandListRef.MapAction(
Commands.CycleTransformGizmos,
FExecuteAction::CreateSP( this, &SEditorViewport::OnCycleWidgetMode ),
FCanExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::CanCycleWidgetMode )
);
CommandListRef.MapAction(
Commands.CycleTransformGizmoCoordSystem,
FExecuteAction::CreateSP( this, &SEditorViewport::OnCycleCoordinateSystem )
);
CommandListRef.MapAction(
Commands.FocusViewportToSelection,
FExecuteAction::CreateSP( this, &SEditorViewport::OnFocusViewportToSelection )
//FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::ExecuteExecCommand, FString( TEXT("CAMERA ALIGN ACTIVEVIEWPORTONLY") ) )
);
CommandListRef.MapAction(
Commands.SurfaceSnapping,
FExecuteAction::CreateStatic( &SEditorViewport::OnToggleSurfaceSnap ),
FCanExecuteAction(),
FIsActionChecked::CreateStatic( &SEditorViewport::OnIsSurfaceSnapEnabled )
);
CommandListRef.MapAction(
Commands.RotateToSurfaceNormal,
FExecuteAction::CreateStatic( &SEditorViewport::OnToggleRotateToSurfaceNormal ),
FCanExecuteAction(),
FIsActionChecked::CreateStatic( &SEditorViewport::IsRotateToSurfaceNormalEnabled )
);
CommandListRef.MapAction(
(Client.IsValid() && Client->IsLevelEditorClient()) ? Commands.ToggleInGameExposure : Commands.ToggleAutoExposure,
FExecuteAction::CreateSP( this, &SEditorViewport::ChangeExposureSetting),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SEditorViewport::IsExposureSettingSelected ) );
CommandListRef.MapAction(
Commands.ToggleInViewportContextMenu,
FExecuteAction::CreateSP(this, &SEditorViewport::ToggleInViewportContextMenu),
FCanExecuteAction::CreateSP(this, &SEditorViewport::CanToggleInViewportContextMenu)
);
CommandListRef.MapAction(
Commands.ToggleOverrideViewportScreenPercentage,
FExecuteAction::CreateSP(this, &SEditorViewport::TogglePreviewingScreenPercentage),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SEditorViewport::IsPreviewingScreenPercentage));
CommandListRef.MapAction(
Commands.OpenEditorPerformanceProjectSettings,
FExecuteAction::CreateSP(this, &SEditorViewport::OnOpenViewportPerformanceProjectSettings));
CommandListRef.MapAction(
Commands.OpenEditorPerformanceEditorPreferences,
FExecuteAction::CreateSP(this, &SEditorViewport::OnOpenViewportPerformanceEditorPreferences));
CommandListRef.MapAction(
Commands.ToggleDistanceBasedCameraSpeed,
FExecuteAction::CreateStatic(&SEditorViewport::OnToggleDistanceBasedCameraSpeed),
FCanExecuteAction(),
FIsActionChecked::CreateStatic(&SEditorViewport::IsDistanceBasedCameraSpeedEnabled));
// Simple macro for binding many view mode UI commands
#define MAP_VIEWMODEPARAM_ACTION( ViewModeCommand, ViewModeParam ) \
CommandListRef.MapAction( \
ViewModeCommand, \
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetViewModeParam, ViewModeParam ), \
FCanExecuteAction(), \
FIsActionChecked::CreateSP( ClientRef, &FEditorViewportClient::IsViewModeParam, ViewModeParam ) )
#define MAP_VIEWMODE_ACTION( ViewModeCommand, ViewModeID ) \
CommandListRef.MapAction( \
ViewModeCommand, \
FExecuteAction::CreateSP( ClientRef, &FEditorViewportClient::SetViewMode, ViewModeID ), \
FCanExecuteAction(), \
FIsActionChecked::CreateSP( ClientRef, &FEditorViewportClient::IsViewModeEnabled, 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);
if (GEnableGPUSkinCache)
{
MAP_VIEWMODE_ACTION(Commands.VisualizeGPUSkinCacheMode, VMI_VisualizeGPUSkinCache);
FGPUSkinCacheVisualizationMenuCommands::Get().BindCommands(CommandListRef, Client);
}
MAP_VIEWMODEPARAM_ACTION( Commands.TexStreamAccMeshUVDensityAll, -1 );
for (int32 TexCoordIndex = 0; TexCoordIndex < TEXSTREAM_MAX_NUM_UVCHANNELS; ++TexCoordIndex)
{
MAP_VIEWMODEPARAM_ACTION( Commands.TexStreamAccMeshUVDensitySingle[TexCoordIndex], TexCoordIndex );
}
MAP_VIEWMODEPARAM_ACTION( Commands.TexStreamAccMaterialTextureScaleAll, -1 );
for (int32 TextureIndex = 0; TextureIndex < TEXSTREAM_MAX_NUM_TEXTURES_PER_MATERIAL; ++TextureIndex)
{
MAP_VIEWMODEPARAM_ACTION( Commands.TexStreamAccMaterialTextureScaleSingle[TextureIndex], TextureIndex );
MAP_VIEWMODEPARAM_ACTION( Commands.RequiredTextureResolutionSingle[TextureIndex], TextureIndex );
}
BindShowCommands( CommandListRef );
}
void SEditorViewport::BindShowCommands( FUICommandList& OutCommandList )
{
FShowFlagMenuCommands::Get().BindCommands(OutCommandList, Client);
}
EVisibility SEditorViewport::OnGetViewportContentVisibility() const
{
FEditorModeTools* EditorModeTools = Client->GetModeTools();
const bool bIsViewportUIHidden = EditorModeTools && EditorModeTools->IsViewportUIHidden();
return bIsViewportUIHidden ? EVisibility::Collapsed : EVisibility::SelfHitTestInvisible;
}
void SEditorViewport::OnToggleRealtime()
{
if (Client->IsRealtime())
{
Client->SetRealtime( false );
if ( ActiveTimerHandle.IsValid() )
{
UnRegisterActiveTimer( ActiveTimerHandle.Pin().ToSharedRef() );
}
}
else
{
Client->SetRealtime( true );
ActiveTimerHandle = RegisterActiveTimer( 0.f, FWidgetActiveTimerDelegate::CreateSP( this, &SEditorViewport::EnsureTick ) );
}
}
bool SEditorViewport::CanToggleRealtime() const
{
return !Client->IsRealtimeOverrideSet();
}
void SEditorViewport::SetRenderDirectlyToWindow( const bool bInRenderDirectlyToWindow )
{
ViewportWidget->SetRenderDirectlyToWindow( bInRenderDirectlyToWindow );
}
void SEditorViewport::EnableStereoRendering( const bool bInEnableStereoRendering )
{
ViewportWidget->EnableStereoRendering( bInEnableStereoRendering );
}
void SEditorViewport::OnToggleStats()
{
bool bIsEnabled = Client->ShouldShowStats();
Client->SetShowStats( !bIsEnabled );
if( !bIsEnabled )
{
// We cannot show stats unless realtime rendering is enabled
if ( !Client->IsRealtime() )
{
Client->SetRealtime( true );
ActiveTimerHandle = RegisterActiveTimer( 0.f, FWidgetActiveTimerDelegate::CreateSP( this, &SEditorViewport::EnsureTick ) );
}
// let the user know how they can enable stats via the console
FNotificationInfo Info(LOCTEXT("StatsEnableHint", "Stats display can be toggled via the STAT [type] console command"));
Info.ExpireDuration = 3.0f;
/* Temporarily remove the link until the page is updated
Info.HyperlinkText = LOCTEXT("StatsEnableHyperlink", "Learn more");
Info.Hyperlink = FSimpleDelegate::CreateStatic([](){ IDocumentation::Get()->Open(TEXT("Engine/Basics/ConsoleCommands#statisticscommands")); });
*/
FSlateNotificationManager::Get().AddNotification(Info);
}
}
void SEditorViewport::ToggleStatCommand(FString CommandName)
{
GEngine->ExecEngineStat(GetWorld(), Client.Get(), *CommandName);
// Invalidate the client to render once in case the click was on the checkbox itself (which doesn't dismiss the menu)
Client->Invalidate();
}
bool SEditorViewport::IsStatCommandVisible(FString CommandName) const
{
// Only if realtime and stats are also enabled should we show the stat as visible
return Client->IsRealtime() && Client->ShouldShowStats() && Client->IsStatEnabled(CommandName);
}
void SEditorViewport::ToggleShowFlag(uint32 EngineShowFlagIndex)
{
bool bOldState = Client->EngineShowFlags.GetSingleFlag(EngineShowFlagIndex);
Client->EngineShowFlags.SetSingleFlag(EngineShowFlagIndex, !bOldState);
// If changing collision flag, need to do special handling for hidden objects
if (EngineShowFlagIndex == FEngineShowFlags::EShowFlag::SF_Collision)
{
Client->UpdateHiddenCollisionDrawing();
}
// Invalidate clients which aren't real-time so we see the changes
Client->Invalidate();
}
bool SEditorViewport::IsShowFlagEnabled(uint32 EngineShowFlagIndex) const
{
return Client->EngineShowFlags.GetSingleFlag(EngineShowFlagIndex);
}
void SEditorViewport::ChangeExposureSetting()
{
Client->ExposureSettings.bFixed = !Client->ExposureSettings.bFixed;
Client->Invalidate();
}
bool SEditorViewport::IsExposureSettingSelected() const
{
return !Client->ExposureSettings.bFixed;
}
void SEditorViewport::Invalidate()
{
bInvalidated = true;
if (!ActiveTimerHandle.IsValid())
{
ActiveTimerHandle = RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SEditorViewport::EnsureTick));
}
}
bool SEditorViewport::IsRealtime() const
{
return Client->IsRealtime();
}
bool SEditorViewport::IsVisible() const
{
const float VisibilityTimeThreshold = .25f;
// 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.
// Also, always render the viewport if DumpGPU is active, regardless of tick time threshold -- otherwise these don't show up due to lag
// caused by the GPU dump being triggered.
return
LastTickTime == 0.0 || // Never been ticked
FPlatformTime::Seconds() - LastTickTime <= VisibilityTimeThreshold // Ticked recently
#if WITH_DUMPGPU
|| FRDGBuilder::IsDumpingFrame() // GPU dump in progress
#endif
;
}
void SEditorViewport::OnScreenCapture()
{
Client->TakeScreenshot(Client->Viewport, true);
}
void SEditorViewport::OnScreenCaptureForProjectThumbnail()
{
if ( FApp::HasProjectName() )
{
const FString BaseFilename = FString(FApp::GetProjectName()) + TEXT(".png");
const FString ScreenshotFilename = FPaths::Combine(*FPaths::ProjectDir(), *BaseFilename);
UThumbnailManager::CaptureProjectThumbnail(Client->Viewport, ScreenshotFilename, true);
}
}
EVisibility SEditorViewport::GetTransformToolbarVisibility() const
{
return (Client->GetWidgetMode() != UE::Widget::WM_None) ? EVisibility::Visible : EVisibility::Hidden;
}
TSharedRef<SWidget> SEditorViewport::BuildFixedEV100Menu() const
{
const float EV100Min = -10.f;
const float EV100Max = 20.f;
return
SNew( SBox )
.HAlign( HAlign_Right )
[
SNew( SBox )
.Padding( FMargin(0.0f, 0.0f, 0.0f, 0.0f) )
.WidthOverride( 100.0f )
[
SNew ( SBorder )
.BorderImage(FAppStyle::Get().GetBrush("Menu.WidgetBorder"))
.Padding(FMargin(1.0f))
[
SNew(SSpinBox<float>)
.Style(&FAppStyle::Get(), "Menu.SpinBox")
.Font( FAppStyle::GetFontStyle( TEXT( "MenuItem.Font" ) ) )
.MinValue(EV100Min)
.MaxValue(EV100Max)
.Value( this, &SEditorViewport::OnGetFixedEV100Value )
.OnValueChanged( const_cast<SEditorViewport*>(this), &SEditorViewport::OnFixedEV100ValueChanged )
.ToolTipText(LOCTEXT( "EV100ToolTip", "Sets the exposure value of the camera using the specified EV100. Exposure = 1 / (1.2 * 2^EV100)"))
.IsEnabled( this, &SEditorViewport::IsFixedEV100Enabled )
]
]
];
};
TSharedRef<SWidget> SEditorViewport::BuildWireframeMenu() const
{
return
SNew( SBox )
.HAlign( HAlign_Right )
[
SNew( SBox )
.Padding( FMargin(0.0f, 0.0f, 0.0f, 0.0f) )
.WidthOverride( 100.0f )
[
SNew ( SBorder )
.BorderImage(FAppStyle::Get().GetBrush("Menu.WidgetBorder"))
.Padding(FMargin(1.0f))
[
SNew(SSpinBox<float>)
.Style(&FAppStyle::Get(), "Menu.SpinBox")
.Font( FAppStyle::GetFontStyle( TEXT( "MenuItem.Font" ) ) )
.MinValue(0.f)
.MaxValue(1.f)
.SupportDynamicSliderMaxValue(false)
.Value( this, &SEditorViewport::OnGetWireframeOpacity )
.OnValueChanged( const_cast<SEditorViewport*>(this), &SEditorViewport::OnWireframeOpacityChanged )
.ToolTipText(LOCTEXT("WireframeOpacity_ToolTip", "Adjust opacity of wireframes in view."))
]
]
];
};
void SEditorViewport::UpdateInViewportMenuLocation(const FVector2D InLocation)
{
InViewportContextMenuLocation = InLocation;
ULevelEditorViewportSettings* LevelEditorViewportSettings = GetMutableDefault<ULevelEditorViewportSettings>();
LevelEditorViewportSettings->LastInViewportMenuLocation = InLocation;
LevelEditorViewportSettings->SaveConfig();
}
float SEditorViewport::OnGetFixedEV100Value() const
{
if( Client.IsValid() )
{
return Client->ExposureSettings.FixedEV100;
}
return 0;
}
bool SEditorViewport::IsFixedEV100Enabled() const
{
if( Client.IsValid() )
{
return Client->ExposureSettings.bFixed;
}
return false;
}
void SEditorViewport::OnFixedEV100ValueChanged(float NewValue)
{
if( Client.IsValid() )
{
Client->ExposureSettings.bFixed = true;
Client->ExposureSettings.FixedEV100 = NewValue;
Client->Invalidate();
}
}
void SEditorViewport::OnWireframeOpacityChanged(float Opacity)
{
if( Client.IsValid() )
{
Client->WireframeOpacity = Opacity;
Client->Invalidate();
}
}
float SEditorViewport::OnGetWireframeOpacity() const
{
if(Client.IsValid())
{
return Client->WireframeOpacity;
}
return 0.8f;
}
bool SEditorViewport::IsWidgetModeActive( UE::Widget::EWidgetMode Mode ) const
{
return Client->GetWidgetMode() == Mode;
}
bool SEditorViewport::IsTranslateRotateModeVisible() const
{
return GetDefault<ULevelEditorViewportSettings>()->bAllowTranslateRotateZWidget;
}
bool SEditorViewport::Is2DModeVisible() const
{
return GetDefault<ULevelEditor2DSettings>()->bEnable2DWidget;
}
bool SEditorViewport::IsCoordSystemActive(ECoordSystem CoordSystem) const
{
return Client->GetWidgetCoordSystemSpace() == CoordSystem;
}
void SEditorViewport::OnCycleWidgetMode()
{
UE::Widget::EWidgetMode WidgetMode = Client->GetWidgetMode();
// Can't cycle the widget mode if we don't currently have a widget
if (WidgetMode == UE::Widget::WM_None)
{
return;
}
int32 WidgetModeAsInt = WidgetMode;
do
{
++WidgetModeAsInt;
if ((WidgetModeAsInt == UE::Widget::WM_TranslateRotateZ) && (!GetDefault<ULevelEditorViewportSettings>()->bAllowTranslateRotateZWidget))
{
++WidgetModeAsInt;
}
if ((WidgetModeAsInt == UE::Widget::WM_2D) && (!GetDefault<ULevelEditor2DSettings>()->bEnable2DWidget))
{
++WidgetModeAsInt;
}
if( WidgetModeAsInt == UE::Widget::WM_Max )
{
WidgetModeAsInt -= UE::Widget::WM_Max;
}
}
while( !Client->CanSetWidgetMode( (UE::Widget::EWidgetMode)WidgetModeAsInt ) && WidgetModeAsInt != WidgetMode );
Client->SetWidgetMode( (UE::Widget::EWidgetMode)WidgetModeAsInt );
}
void SEditorViewport::OnCycleCoordinateSystem()
{
int32 CoordSystemAsInt = Client->GetWidgetCoordSystemSpace();
++CoordSystemAsInt;
// parent mode is only supported with new trs gizmos for now
int CoordMax = COORD_Parent;
if (UEditorInteractiveGizmoManager::UsesNewTRSGizmos())
{
CoordMax = UEditorInteractiveGizmoManager::IsExplicitModeEnabled() ? COORD_Max : COORD_Explicit;
}
if( CoordSystemAsInt >= CoordMax )
{
CoordSystemAsInt = COORD_World;
}
Client->SetWidgetCoordSystemSpace( (ECoordSystem)CoordSystemAsInt );
}
UWorld* SEditorViewport::GetWorld() const
{
return Client->GetWorld();
}
void SEditorViewport::OnToggleSurfaceSnap()
{
auto* Settings = GetMutableDefault<ULevelEditorViewportSettings>();
Settings->SnapToSurface.bEnabled = !Settings->SnapToSurface.bEnabled;
}
bool SEditorViewport::OnIsSurfaceSnapEnabled()
{
return GetDefault<ULevelEditorViewportSettings>()->SnapToSurface.bEnabled;
}
void SEditorViewport::OnToggleRotateToSurfaceNormal()
{
auto& Settings = GetMutableDefault<ULevelEditorViewportSettings>()->SnapToSurface;
Settings.bSnapRotation = !Settings.bSnapRotation;
// If user is editing snapping settings, we assume they also want snapping turned on
if (!Settings.bEnabled)
{
Settings.bEnabled = true;
}
}
bool SEditorViewport::IsRotateToSurfaceNormalEnabled()
{
const auto& Settings = GetDefault<ULevelEditorViewportSettings>()->SnapToSurface;
return Settings.bSnapRotation;
}
void SEditorViewport::OnToggleDistanceBasedCameraSpeed()
{
if (ULevelEditorViewportSettings* ViewportSettings = GetMutableDefault<ULevelEditorViewportSettings>())
{
ViewportSettings->bUseDistanceScaledCameraSpeed = !ViewportSettings->bUseDistanceScaledCameraSpeed;
}
}
bool SEditorViewport::IsDistanceBasedCameraSpeedEnabled()
{
if (const ULevelEditorViewportSettings* const ViewportSettings = GetDefault<ULevelEditorViewportSettings>())
{
return ViewportSettings->bUseDistanceScaledCameraSpeed;
}
return false;
}
TSharedPtr<SWidget> SEditorViewport::CreateViewportIndicatorWidget(const TAttribute<EVisibility>& InVisibility)
{
// This makes a gradient that displays whether a viewport is active
static FLinearColor ActiveBorderColor =
FAppStyle::Get().GetSlateColor("EditorViewport.ActiveBorderColor").GetSpecifiedColor();
static FLinearColor ActiveBorderColorTransparent(ActiveBorderColor.R, ActiveBorderColor.G, ActiveBorderColor.B, 0.0f);
static TArray<FLinearColor> GradientStops{ ActiveBorderColorTransparent, ActiveBorderColor, ActiveBorderColorTransparent };
return SNew(SBox)
.Visibility(InVisibility)
.MaxDesiredHeight(1.0f)
.MinDesiredHeight(1.0f)
[
SNew(SComplexGradient)
.GradientColors(GradientStops)
.Orientation(EOrientation::Orient_Vertical)
];
}
bool SEditorViewport::IsPreviewingScreenPercentage() const
{
return Client->IsPreviewingScreenPercentage();
}
void SEditorViewport::TogglePreviewingScreenPercentage()
{
Client->SetPreviewingScreenPercentage(!IsPreviewingScreenPercentage());
}
void SEditorViewport::OnOpenViewportPerformanceProjectSettings()
{
FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer("Project", "Editor", "EditorPerformanceProjectSettings");
}
void SEditorViewport::OnOpenViewportPerformanceEditorPreferences()
{
FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer("Editor", "General", "EditorPerformanceSettings");
}
TSharedPtr<IPreviewProfileController> SEditorViewport::GetPreviewProfileController()
{
if (!PreviewProfileController)
{
PreviewProfileController = CreatePreviewProfileController();
}
return PreviewProfileController;
}
void SEditorViewport::MarkLegacyToolbarChildAsAutomaticallyUpgradable(const TSharedRef<SWidget>& ExpectedChild)
{
AutoUpgradeWidgetChild = ExpectedChild;
}
EActiveTimerReturnType SEditorViewport::EnsureTick( double InCurrentTime, float InDeltaTime )
{
// Keep the timer going if we're realtime or were invalidated this frame
const bool bShouldContinue = Client->IsRealtime() || bInvalidated;
bInvalidated = false;
return bShouldContinue ? EActiveTimerReturnType::Continue : EActiveTimerReturnType::Stop;
}
EVisibility SEditorViewport::GetActiveBorderVisibility() const
{
EVisibility BaseVisibility = OnGetViewportContentVisibility();
if (BaseVisibility != EVisibility::Collapsed)
{
// The active border should never be hit testable as it overlays viewport UI but is for display purposes only
return EVisibility::HitTestInvisible;
}
return BaseVisibility;
}
///////////////////////////////////////////////////////////////////////////////
// begin feature level control functions block
///////////////////////////////////////////////////////////////////////////////
EShaderPlatform SEditorViewport::GetShaderPlatformHelper(const ERHIFeatureLevel::Type FeatureLevel) const
{
UMaterialShaderQualitySettings* MaterialShaderQualitySettings = UMaterialShaderQualitySettings::Get();
const FName& PreviewPlatform = MaterialShaderQualitySettings->GetPreviewPlatform();
EShaderPlatform ShaderPlatform = PreviewPlatform != NAME_None ? ShaderFormatToLegacyShaderPlatform(PreviewPlatform) : SP_NumPlatforms;
if (ShaderPlatform == SP_NumPlatforms)
{
ShaderPlatform = GetFeatureLevelShaderPlatform(FeatureLevel);
}
return ShaderPlatform;
}
TSharedRef<SWidget> SEditorViewport::BuildFeatureLevelWidget() const
{
TSharedRef<SWidget> BoxWidget = SNew(SHorizontalBox)
.Visibility(this, &SEditorViewport::GetCurrentFeatureLevelPreviewTextVisibility)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 1.0f, 2.0f, 1.0f)
[
SNew(STextBlock)
.Text(this, &SEditorViewport::GetCurrentFeatureLevelPreviewText, true)
.ShadowOffset(FVector2D(1, 1))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f, 1.0f, 2.0f, 1.0f)
[
SNew(STextBlock)
.Text(this, &SEditorViewport::GetCurrentFeatureLevelPreviewText, false)
.ShadowOffset(FVector2D(1, 1))
];
return BoxWidget;
}
EVisibility SEditorViewport::GetCurrentFeatureLevelPreviewTextVisibility() const
{
if (Client->GetWorld() && !GLevelEditorModeTools().IsViewportUIHidden())
{
return (GEditor && GEditor->IsFeatureLevelPreviewActive()) ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed;
}
else
{
return EVisibility::Collapsed;
}
}
FText SEditorViewport::GetCurrentFeatureLevelPreviewText(bool bDrawOnlyLabel) const
{
FText LabelName;
if (bDrawOnlyLabel)
{
LabelName = LOCTEXT("PreviewPlatformLabel", "Preview Platform:");
}
else
{
UWorld* World = Client->GetWorld();
if (World != nullptr)
{
ERHIFeatureLevel::Type TargetFeatureLevel = World->GetFeatureLevel();
const FText& PlatformText = GEditor->PreviewPlatform.GetFriendlyName();
LabelName = FText::Format(LOCTEXT("WorldFeatureLevel", "{0}"), PlatformText);
}
}
return LabelName;
}
///////////////////////////////////////////////////////////////////////////////
// end feature level control functions block
///////////////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE