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

879 lines
34 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MainFrameModule.h"
#include "Features/IModularFeatures.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SBorder.h"
#include "SlateOptMacros.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SButton.h"
#include "GameProjectGenerationModule.h"
#include "MessageLogModule.h"
#include "MRUFavoritesList.h"
#include "Styling/AppStyle.h"
#include "Editor/EditorPerProjectUserSettings.h"
#include "Sound/SoundBase.h"
#include "ISourceCodeAccessor.h"
#include "ISourceCodeAccessModule.h"
#include "Menus/MainMenu.h"
#include "Frame/RootWindowLocation.h"
#include "Kismet2/CompilerResultsLog.h"
#include "IHotReload.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Framework/Commands/GenericCommands.h"
#include "AnalyticsEventAttribute.h"
#include "Interfaces/IAnalyticsProvider.h"
#include "EngineAnalytics.h"
#include "Editor/EditorPerformanceSettings.h"
#include "HAL/PlatformApplicationMisc.h"
#include "HAL/PlatformFileManager.h"
#include "ToolMenuContext.h"
#include "Toolkits/FConsoleCommandExecutor.h"
#include "Misc/MessageDialog.h"
#include "ProfilingDebugging/StallDetector.h"
#include "Interfaces/IEditorMainFrameProvider.h"
DEFINE_LOG_CATEGORY(LogMainFrame);
#define LOCTEXT_NAMESPACE "FMainFrameModule"
static FAutoConsoleCommand ResizeMainFrameCommand(
TEXT("Editor.ResizeMainFrame"),
TEXT(""),
FConsoleCommandWithArgsDelegate::CreateStatic(&FMainFrameModule::HandleResizeMainFrameCommand)
);
const FText StaticGetApplicationTitle( const bool bIncludeGameName )
{
static const FText ApplicationTitle = NSLOCTEXT("UnrealEditor", "ApplicationTitle", "Unreal Editor");
if (bIncludeGameName && FApp::HasProjectName())
{
FFormatNamedArguments Args;
Args.Add(TEXT("GameName"), FText::FromString( FString( FApp::GetProjectName())));
Args.Add(TEXT("AppTitle"), ApplicationTitle);
const EBuildConfiguration BuildConfig = FApp::GetBuildConfiguration();
if (BuildConfig != EBuildConfiguration::Shipping && BuildConfig != EBuildConfiguration::Development && BuildConfig != EBuildConfiguration::Unknown)
{
Args.Add( TEXT("Config"), EBuildConfigurations::ToText(BuildConfig));
return FText::Format( NSLOCTEXT("UnrealEditor", "AppTitleGameNameWithConfig", "{GameName} [{Config}] - {AppTitle}"), Args );
}
return FText::Format( NSLOCTEXT("UnrealEditor", "AppTitleGameName", "{GameName} - {AppTitle}"), Args );
}
return ApplicationTitle;
}
/* FProjectDialogProvider
*****************************************************************************/
void FProjectDialogProvider::Register()
{
IModularFeatures::Get().RegisterModularFeature(IEditorMainFrameProvider::GetModularFeatureName(), this);
}
void FProjectDialogProvider::UnRegister()
{
IModularFeatures::Get().UnregisterModularFeature(IEditorMainFrameProvider::GetModularFeatureName(), this);
}
bool FProjectDialogProvider::IsRequestingMainFrameControl() const
{
return !FApp::HasProjectName();
}
FMainFrameWindowOverrides FProjectDialogProvider::GetDesiredWindowConfiguration() const
{
FMainFrameWindowOverrides ProjectDialogWindowConfig;
// Force tabs restored from layout that have no window (the LevelEditor tab) to use a docking area with
// embedded title area content. We need to override the behavior here because we're creating the actual
// window ourselves instead of letting the tab management system create it for us.
ProjectDialogWindowConfig.bEmbedTitleAreaContent = false;
// Do not maximize the window initially. Keep a small dialog feel.
ProjectDialogWindowConfig.bInitiallyMaximized = false;
ProjectDialogWindowConfig.WindowSize = FMainFrameModule::GetProjectBrowserWindowSize();
ProjectDialogWindowConfig.bIsUserSizable = true;
ProjectDialogWindowConfig.bSupportsMaximize = true;
ProjectDialogWindowConfig.bSupportsMinimize = true;
ProjectDialogWindowConfig.CenterRules = EAutoCenter::PreferredWorkArea;
// When opening the project dialog, show "Project Browser" in the window title
ProjectDialogWindowConfig.WindowTitle = LOCTEXT("ProjectBrowserDialogTitle", "Unreal Project Browser");
return ProjectDialogWindowConfig;
}
TSharedRef<SWidget> FProjectDialogProvider::CreateMainFrameContentWidget() const
{
return FGameProjectGenerationModule::Get().CreateGameProjectDialog(/*bAllowProjectOpening=*/true, /*bAllowProjectCreate=*/true);
}
/* IMainFrameModule implementation
*****************************************************************************/
void FMainFrameModule::CreateDefaultMainFrame(const bool bStartImmersive, const bool bStartPIE)
{
CreateDefaultMainFrameAuxiliary(bStartImmersive, bStartPIE, /*bIsBeingRecreated*/false);
}
void FMainFrameModule::RecreateDefaultMainFrame(const bool bStartImmersive, const bool bStartPIE)
{
check(!bRecreatingDefaultMainFrame);
TGuardValue<bool> GuardRecreatingDefaultMainFrame(bRecreatingDefaultMainFrame, true);
// Clean previous default main frame
if (IsWindowInitialized())
{
// Clean FGlobalTabmanager
FGlobalTabmanager::Get()->CloseAllAreas();
// Clean FSlateApplication
FSlateApplication::Get().CloseAllWindowsImmediately();
}
// (Re-)create default main frame
CreateDefaultMainFrameAuxiliary(bStartImmersive, bStartPIE, /*bIsBeingRecreated*/true);
}
void FMainFrameModule::CreateDefaultMainFrameAuxiliary(const bool bStartImmersive, const bool bStartPIE, const bool bIsBeingRecreated)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FMainFrameModule::CreateDefaultMainFrame);
if (!IsWindowInitialized())
{
FRootWindowLocation DefaultWindowLocation;
FMainFrameWindowOverrides WindowConfig;
bool bShowStartupDialogInPlaceOfMainEditor = false;
TSharedPtr<SWidget> MainFrameContent;
FGameProjectGenerationModule::Get();
TArray<IEditorMainFrameProvider*> MainFrameProviders = IModularFeatures::Get().GetModularFeatureImplementations<IEditorMainFrameProvider>(IEditorMainFrameProvider::GetModularFeatureName());
for (IEditorMainFrameProvider* Provider: MainFrameProviders)
{
if (Provider && Provider->IsRequestingMainFrameControl())
{
WindowConfig = Provider->GetDesiredWindowConfiguration();
MainFrameContent = Provider->CreateMainFrameContentWidget();
bShowStartupDialogInPlaceOfMainEditor = true;
// We can only have one main frame provider, which means we just use the first active one
break;
}
}
if (WindowConfig.ScreenPosition.IsSet())
{
DefaultWindowLocation.ScreenPosition = WindowConfig.ScreenPosition.GetValue();
}
if (WindowConfig.WindowSize.IsSet())
{
DefaultWindowLocation.WindowSize = WindowConfig.WindowSize.GetValue();
}
if (WindowConfig.bInitiallyMaximized.IsSet())
{
DefaultWindowLocation.InitiallyMaximized = WindowConfig.bInitiallyMaximized.GetValue();
}
if (!bShowStartupDialogInPlaceOfMainEditor)
{
if( bStartImmersive )
{
// Start maximized if we are in immersive mode
DefaultWindowLocation.InitiallyMaximized = true;
}
const bool bIncludeGameName = true;
WindowConfig.WindowTitle = GetApplicationTitle( bIncludeGameName );
}
TSharedRef<SWindow> RootWindow = SNew(SWindow)
.AutoCenter(WindowConfig.CenterRules)
.Title( WindowConfig.WindowTitle )
.CloseButtonToolTipText(LOCTEXT("MainFrameModuleCloseButtonToolTip", "Close Unreal Editor"))
.IsInitiallyMaximized( DefaultWindowLocation.InitiallyMaximized )
.ScreenPosition( DefaultWindowLocation.ScreenPosition )
.ClientSize( DefaultWindowLocation.WindowSize )
.CreateTitleBar( !WindowConfig.bEmbedTitleAreaContent )
.SizingRule( WindowConfig.bIsUserSizable ? ESizingRule::UserSized : ESizingRule::FixedSize )
.SupportsMaximize( WindowConfig.bSupportsMaximize )
.SupportsMinimize( WindowConfig.bSupportsMinimize );
const bool bShowRootWindowImmediately = false;
FSlateApplication::Get().AddWindow( RootWindow, bShowRootWindowImmediately );
FGlobalTabmanager::Get()->SetRootWindow(RootWindow);
FGlobalTabmanager::Get()->SetAllowWindowMenuBar(true);
FSlateNotificationManager::Get().SetRootWindow(RootWindow);
bool bLevelEditorIsMainTab = false;
if (!bShowStartupDialogInPlaceOfMainEditor)
{
// Get desktop metrics
FDisplayMetrics DisplayMetrics;
FSlateApplication::Get().GetDisplayMetrics( DisplayMetrics );
const float DPIScale = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint((float)DisplayMetrics.PrimaryDisplayWorkAreaRect.Left, (float)DisplayMetrics.PrimaryDisplayWorkAreaRect.Top);
// Setup a position and size for the main frame window that's centered in the desktop work area
const float CenterScale = 0.65f;
const FVector2D DisplaySize(
DisplayMetrics.PrimaryDisplayWorkAreaRect.Right - DisplayMetrics.PrimaryDisplayWorkAreaRect.Left,
DisplayMetrics.PrimaryDisplayWorkAreaRect.Bottom - DisplayMetrics.PrimaryDisplayWorkAreaRect.Top );
const FVector2D WindowSize = (CenterScale * DisplaySize) / DPIScale;
// IMPORTANT: If you want to change the default value of "LevelEditor_Layout_v1.1" or "UnrealEd_Layout_v1.4" (even if you only change their version numbers), these are the steps to follow:
// 1. Check out Engine\Config\Layouts\DefaultLayout.ini in Perforce.
// 2. Change the code below as you wish and compile the code.
// 3. (Optional:) Save your current layout so you can load it later.
// 4. Close the editor.
// 5. Manually remove Engine\Saved\Config\Windows\EditorLayout.ini
// 6. Open the Editor, which will auto-regenerate a default EditorLayout.ini that uses your new code below.
// 7. "Window" --> "Save Layout" --> "Save Layout As..."
// - Name: Default Editor Layout
// - Description: Default layout that the Unreal Editor automatically generates
// 8. Either click on the toast generated by Unreal that would open the saving path or manually open Engine\Saved\Config\Layouts\ in your explorer
// 9. Move and rename the new file (Engine\Saved\Config\Layouts\Default_Editor_Layout.ini) into Engine\Config\Layouts\DefaultLayout.ini. You might also have to modify:
// 9.1. QAGame/Config/DefaultEditorLayout.ini
// 9.2. Engine/Config/BaseEditorLayout.ini
// 9.3. Etc
// 10. Push the new "DefaultLayout.ini" together with your new code.
// 11. Also update these instructions if you change the version number (e.g., from "UnrealEd_Layout_v1.4" to "UnrealEd_Layout_v1.5").
const FName LayoutName = TEXT("UnrealEd_Layout_v1.5");
const TSharedRef<FTabManager::FLayout> DefaultLayout =
// We persist the positioning of the level editor and the content browser.
// The asset editors currently do not get saved.
FTabManager::NewLayout(LayoutName)
->AddArea
(
// level editor window
FTabManager::NewPrimaryArea()
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(2.0f)
->AddTab("LevelEditor", ETabState::OpenedTab)
->AddTab("DockedToolkit", ETabState::ClosedTab)
)
)
->AddArea
(
// toolkits window
FTabManager::NewArea(WindowSize)
->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(1.0f)
->AddTab("StandaloneToolkit", ETabState::ClosedTab)
)
)
->AddArea
(
// settings window
FTabManager::NewArea(WindowSize)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(1.0f)
->AddTab("EditorSettings", ETabState::ClosedTab)
->AddTab("ProjectSettings", ETabState::ClosedTab)
->AddTab("PluginsEditor", ETabState::ClosedTab)
)
);
const bool bPrimaryAreaMustHaveOpenedTabsToBeValid = true;
const EOutputCanBeNullptr OutputCanBeNullptr = EOutputCanBeNullptr::IfNoOpenTabValid;
TArray<FString> RemovedOlderLayoutVersions;
const TSharedRef<FTabManager::FLayout> LoadedLayout = FLayoutSaveRestore::LoadFromConfig(GEditorLayoutIni,
DefaultLayout, OutputCanBeNullptr, RemovedOlderLayoutVersions);
// If older fields of the layout name (i.e., lower versions than "UnrealEd_Layout_v1.4") were found
if (RemovedOlderLayoutVersions.Num() > 0)
{
// FMessageDialog - Notify the user that the layout version was updated and the current layout uses a deprecated one
const FText WarningText = FText::Format(LOCTEXT("MainFrameModuleVersionErrorBody", "The expected Unreal Editor layout version is \"{0}\", while only version \"{1}\" was found. I.e., the current layout was created with a previous version of Unreal that is deprecated and no longer compatible.\n\nUnreal will continue with the default layout for its current version, the deprecated one has been removed.\n\nYou can create and save your custom layouts with \"Window\"->\"Save Layout\"->\"Save Layout As...\"."),
FText::FromString(LayoutName.ToString()), FText::FromString(RemovedOlderLayoutVersions[0]));
UE_LOG(LogMainFrame, Warning, TEXT("%s"), *WarningText.ToString());
// If user is trying to load a specific layout with "Load", also warn them with a message dialog
if (bIsBeingRecreated)
{
FMessageDialog::Open(EAppMsgType::Ok, WarningText, LOCTEXT("MainFrameModuleVersionErrorTitle", "Unreal Editor Layout Version Mismatch"));
}
}
FToolMenuContext EmptyContext;
MakeMainMenu(FGlobalTabmanager::Get(), "MainFrame.NomadMainMenu", EmptyContext);
MainFrameContent = FGlobalTabmanager::Get()->RestoreFrom(LoadedLayout, RootWindow, WindowConfig.bEmbedTitleAreaContent, OutputCanBeNullptr);
// MainFrameContent will only be nullptr if its main area contains invalid tabs (probably some layout bug). If so, reset layout to avoid potential crashes
if (!MainFrameContent.IsValid())
{
// This code will recurse to load the default layout. If that also fail we will get into an infinite loop
check(!bIsReCreatingDefaultLayoutAsFallback)
TGuardValue<bool> GuardRecreatingDefaultLayoutFallback(bIsReCreatingDefaultLayoutAsFallback, true);
TGuardValue<bool> GuardRecreatingDefaultMainFrame(bRecreatingDefaultMainFrame, true);
// Clean FSlateApplication & FGlobalTabmanager
FGlobalTabmanager::Get()->CloseAllAreas();
FSlateApplication::Get().CloseAllWindowsImmediately();
// Remove and reload file
GConfig->UnloadFile(GEditorLayoutIni); // We must re-read it to avoid the Editor to use a previously cached name and description
const FString FaultyEditorLayoutPath = GEditorLayoutIni + TEXT("_faulty.ini");
FPlatformFileManager::Get().GetPlatformFile().MoveFile(*FaultyEditorLayoutPath, *GEditorLayoutIni);
GConfig->LoadFile(GEditorLayoutIni);
// Warn user/developer
const FString WarningMessage = FString::Format(TEXT("UnrealEd layout could not be loaded from the config file {0}, reseting this config file to the default one."), { *GEditorLayoutIni });
UE_LOG(LogMainFrame, Warning, TEXT("%s"), *WarningMessage);
ensureMsgf(false, TEXT("%s Some additional testing of that layout file should be done. Saved as %s."), *WarningMessage, *FaultyEditorLayoutPath);
// Reload default main frame
CreateDefaultMainFrame(bStartImmersive, bStartPIE);
return;
}
bLevelEditorIsMainTab = true;
}
check(MainFrameContent.IsValid());
RootWindow->SetContent(MainFrameContent.ToSharedRef());
TSharedPtr<SDockTab> MainTab;
if ( bLevelEditorIsMainTab )
{
MainTab = FGlobalTabmanager::Get()->TryInvokeTab( FTabId("LevelEditor") );
// make sure we only allow the message log to be shown when we have a level editor main tab
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>(TEXT("MessageLog"));
MessageLogModule.EnableMessageLogDisplay(!FApp::IsUnattended());
}
// Initialize the main frame window
MainFrameHandler->OnMainFrameGenerated( MainTab, RootWindow );
if (bDelayedShowMainFrame)
{
// Setup delegate to show main frame
DelayedShowMainFrameDelegate.BindLambda([this, RootWindow, bStartImmersive, bStartPIE]()
{
// Show the window!
MainFrameHandler->ShowMainFrameWindow(RootWindow, bStartImmersive, bStartPIE);
});
}
else
{
// Show the window!
MainFrameHandler->ShowMainFrameWindow(RootWindow, bStartImmersive, bStartPIE);
}
MRUFavoritesList = new FMainMRUFavoritesList;
MRUFavoritesList->ReadFromINI();
MainFrameCreationFinishedEvent.Broadcast(RootWindow, bShowStartupDialogInPlaceOfMainEditor);
}
}
bool FMainFrameModule::IsRecreatingDefaultMainFrame() const
{
return bRecreatingDefaultMainFrame;
}
TSharedRef<SWidget> FMainFrameModule::MakeMainMenu(const TSharedPtr<FTabManager>& TabManager, const FName MenuName, FToolMenuContext& ToolMenuContext) const
{
// Can't make the MainMenu without the global editor commands having been registered
FGlobalEditorCommonCommands::Register();
return FMainMenu::MakeMainMenu(TabManager, MenuName, ToolMenuContext);
}
TSharedRef<SWidget> FMainFrameModule::MakeDeveloperTools( const TArray<FMainFrameDeveloperTool>& AdditionalTools ) const
{
struct Local
{
static FText GetFrameRateAsString()
{
// Clamp to avoid huge averages at startup or after hitches
const float AverageFPS = 1.0f / FSlateApplication::Get().GetAverageDeltaTime();
const float ClampedFPS = ( AverageFPS < 0.0f || AverageFPS > 4000.0f ) ? 0.0f : AverageFPS;
static const FNumberFormattingOptions FormatOptions = FNumberFormattingOptions()
.SetMinimumFractionalDigits(1)
.SetMaximumFractionalDigits(1);
return FText::AsNumber( ClampedFPS, &FormatOptions );
}
static FText GetFrameTimeAsString()
{
// Clamp to avoid huge averages at startup or after hitches
const float AverageMS = FSlateApplication::Get().GetAverageDeltaTime() * 1000.0f;
const float ClampedMS = ( AverageMS < 0.0f || AverageMS > 4000.0f ) ? 0.0f : AverageMS;
static const FNumberFormattingOptions FormatOptions = FNumberFormattingOptions()
.SetMinimumFractionalDigits(1)
.SetMaximumFractionalDigits(1);
static const FText FrameTimeFmt = FText::FromString(TEXT("{0} ms"));
return FText::Format( FrameTimeFmt, FText::AsNumber( ClampedMS, &FormatOptions ) );
}
static FText GetMemoryAsString()
{
// Only refresh process memory allocated after every so often, to reduce fixed frame time overhead
static SIZE_T StaticLastTotalAllocated = 0;
static int32 QueriesUntilUpdate = 1;
if( --QueriesUntilUpdate <= 0 )
{
// Query OS for process memory used
FPlatformMemoryStats MemoryStats = FPlatformMemory::GetStats();
StaticLastTotalAllocated = MemoryStats.UsedPhysical;
// Wait 60 queries until we refresh memory again
QueriesUntilUpdate = 60;
}
static const FNumberFormattingOptions FormatOptions = FNumberFormattingOptions()
.SetMinimumFractionalDigits(2)
.SetMaximumFractionalDigits(2);
static const FText MemorySizeFmt = FText::FromString(TEXT("{0} mb"));
return FText::Format( MemorySizeFmt, FText::AsNumber( (float)StaticLastTotalAllocated / ( 1024.0f * 1024.0f ), &FormatOptions ) );
}
static FText GetUObjectCountAsString()
{
return FText::AsNumber(GUObjectArray.GetObjectArrayNumMinusAvailable());
}
#if STALL_DETECTOR
static FText GetStallCountAsString()
{
return FText::AsNumber(UE::FStallDetectorStats::TotalTriggeredCount.Get());
}
#endif // STALL_DETECTOR
static void OpenVideo( FString SourceFilePath )
{
FPlatformProcess::ExploreFolder( *( FPaths::GetPath(SourceFilePath) ) );
}
/** @return Returns true if frame rate and memory should be displayed in the UI */
static EVisibility ShouldShowFrameRateAndMemory()
{
return GetDefault<UEditorPerformanceSettings>()->bShowFrameRateAndMemory ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed;
}
static void AddSlot(TSharedRef<SHorizontalBox>& HorizontalBox, const FSlateFontInfo& LabelFont, const FSlateFontInfo& ValueFont, const FMainFrameDeveloperTool& DeveloperTool)
{
HorizontalBox->AddSlot()
.AutoWidth()
.Padding(4.0f, 0.0f, 4.0f, 0.0f)
[
SNew(SHorizontalBox)
.Visibility(DeveloperTool.Visibility)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Bottom)
[
SNew(STextBlock)
.Text(DeveloperTool.Label)
.Font(LabelFont)
.ColorAndOpacity(FLinearColor(0.3f, 0.3f, 0.3f))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Bottom)
[
SNew(STextBlock)
.Text(DeveloperTool.Value)
.Font(ValueFont)
.ColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f))
]
];
}
};
const FSlateFontInfo& SmallFixedFont = FAppStyle::GetFontStyle(TEXT("MainFrame.DebugTools.SmallFont") );
const FSlateFontInfo& NormalFixedFont = FAppStyle::GetFontStyle(TEXT("MainFrame.DebugTools.NormalFont") );
const FSlateFontInfo& LabelFont = FAppStyle::GetFontStyle(TEXT("MainFrame.DebugTools.LabelFont") );
TSharedRef<SHorizontalBox> DeveloperToolWidget =
SNew( SHorizontalBox )
.Visibility(GIsDemoMode ? EVisibility::Collapsed : EVisibility::HitTestInvisible);
for (const FMainFrameDeveloperTool& DeveloperTool : AdditionalTools)
{
Local::AddSlot(DeveloperToolWidget, LabelFont, NormalFixedFont, DeveloperTool);
}
{
FMainFrameDeveloperTool FpsDeveloperTool;
FpsDeveloperTool.Visibility = TAttribute<EVisibility>::Create(&Local::ShouldShowFrameRateAndMemory);
FpsDeveloperTool.Label = LOCTEXT("FrameRateLabel", "FPS: ");
FpsDeveloperTool.Value = TAttribute<FText>::Create(&Local::GetFrameRateAsString);
Local::AddSlot(DeveloperToolWidget, LabelFont, NormalFixedFont, FpsDeveloperTool);
}
{
FMainFrameDeveloperTool FrameDeveloperTool;
FrameDeveloperTool.Visibility = TAttribute<EVisibility>::Create(&Local::ShouldShowFrameRateAndMemory);
FrameDeveloperTool.Label = LOCTEXT("FrameRate/FrameTime", "/ ");
FrameDeveloperTool.Value = TAttribute<FText>::Create(&Local::GetFrameTimeAsString);
Local::AddSlot(DeveloperToolWidget, LabelFont, NormalFixedFont, FrameDeveloperTool);
}
{
FMainFrameDeveloperTool MemDeveloperTool;
MemDeveloperTool.Visibility = TAttribute<EVisibility>::Create(&Local::ShouldShowFrameRateAndMemory);
MemDeveloperTool.Label = LOCTEXT("MemoryLabel", "Mem: ");
MemDeveloperTool.Value = TAttribute<FText>::Create(&Local::GetMemoryAsString);
Local::AddSlot(DeveloperToolWidget, LabelFont, NormalFixedFont, MemDeveloperTool);
}
{
FMainFrameDeveloperTool ObjDeveloperTool;
ObjDeveloperTool.Visibility = TAttribute<EVisibility>::Create(&Local::ShouldShowFrameRateAndMemory);
ObjDeveloperTool.Label = LOCTEXT("UObjectCountLabel", "Objs: ");
ObjDeveloperTool.Value = TAttribute<FText>::Create(&Local::GetUObjectCountAsString);
Local::AddSlot(DeveloperToolWidget, LabelFont, NormalFixedFont, ObjDeveloperTool);
}
{
#if STALL_DETECTOR
FMainFrameDeveloperTool StallsDeveloperTool;
StallsDeveloperTool.Visibility = TAttribute<EVisibility>::Create(&Local::ShouldShowFrameRateAndMemory);
StallsDeveloperTool.Label = LOCTEXT("StallsLabel", "Stalls: ");
StallsDeveloperTool.Value = TAttribute<FText>::Create(&Local::GetStallCountAsString);
Local::AddSlot(DeveloperToolWidget, LabelFont, NormalFixedFont, StallsDeveloperTool);
#endif // STALL_DETECTOR
}
// Invisible border, so that we can animate our box panel size
return SNew( SBorder )
.Visibility( EVisibility::SelfHitTestInvisible )
.Padding( FMargin(0.0f, 0.0f, 0.0f, 1.0f) )
.VAlign(VAlign_Bottom)
.BorderImage( FAppStyle::GetBrush("NoBorder") )
[
SNew( SHorizontalBox )
.Visibility( EVisibility::SelfHitTestInvisible )
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding( 0.0f )
[
DeveloperToolWidget
]
];
}
void FMainFrameModule::SetLevelNameForWindowTitle( const FString& InLevelFileName )
{
LoadedLevelName = (InLevelFileName.Len() > 0)
? FPaths::GetBaseFilename(InLevelFileName)
: NSLOCTEXT("UnrealEd", "Untitled", "Untitled" ).ToString();
}
void FMainFrameModule::SetApplicationTitleOverride(const FText& NewOverriddenApplicationTitle)
{
OverriddenWindowTitle = NewOverriddenApplicationTitle;
}
/* IModuleInterface implementation
*****************************************************************************/
void FMainFrameModule::StartupModule( )
{
bDelayedShowMainFrame = false;
bRecreatingDefaultMainFrame = false;
MRUFavoritesList = NULL;
ProjectDialogProvider.Register();
ensureMsgf(!IsRunningGame(), TEXT("The MainFrame module should only be loaded when running the editor. Code that extends the editor, adds menu items, etc... should not run when running in -game mode or in a non-WITH_EDITOR build"));
MainFrameHandler = MakeShareable(new FMainFrameHandler);
FGenericCommands::Register();
FMainFrameCommands::Register();
// Exposes the main frame command list to subscribers from other systems
FInputBindingManager::Get().RegisterCommandList(FMainFrameCommands::Get().GetContextName(), FMainFrameCommands::ActionList);
SetLevelNameForWindowTitle(TEXT(""));
// Register to find out about when hot reload completes, so we can show a notification
IHotReloadModule& HotReloadModule = IHotReloadModule::Get();
HotReloadModule.OnModuleCompilerStarted().AddRaw( this, &FMainFrameModule::HandleLevelEditorModuleCompileStarted );
HotReloadModule.OnModuleCompilerFinished().AddRaw( this, &FMainFrameModule::HandleLevelEditorModuleCompileFinished );
FCoreUObjectDelegates::ReloadCompleteDelegate.AddRaw( this, &FMainFrameModule::HandleReloadFinished );
#if WITH_EDITOR
ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked<ISourceCodeAccessModule>("SourceCodeAccess");
SourceCodeAccessModule.OnLaunchingCodeAccessor().AddRaw( this, &FMainFrameModule::HandleCodeAccessorLaunching );
SourceCodeAccessModule.OnDoneLaunchingCodeAccessor().AddRaw( this, &FMainFrameModule::HandleCodeAccessorLaunched );
SourceCodeAccessModule.OnOpenFileFailed().AddRaw( this, &FMainFrameModule::HandleCodeAccessorOpenFileFailed );
#endif
ModuleCompileStartTime = 0.0f;
// migrate old layout settings
FLayoutSaveRestore::MigrateConfig(GEditorPerProjectIni, GEditorLayoutIni);
}
void FMainFrameModule::ShutdownModule( )
{
ClearDelayedShowMainFrameDelegate();
// Destroy the main frame window
TSharedPtr< SWindow > ParentWindow( GetParentWindow() );
if( ParentWindow.IsValid() )
{
ParentWindow->DestroyWindowImmediately();
}
MainFrameHandler.Reset();
FMainFrameCommands::Unregister();
if( IHotReloadModule::IsAvailable() )
{
IHotReloadModule& HotReloadModule = IHotReloadModule::Get();
FCoreUObjectDelegates::ReloadCompleteDelegate.RemoveAll( this );
HotReloadModule.OnModuleCompilerStarted().RemoveAll( this );
HotReloadModule.OnModuleCompilerFinished().RemoveAll( this );
}
#if WITH_EDITOR
if(FModuleManager::Get().IsModuleLoaded("SourceCodeAccess"))
{
ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::GetModuleChecked<ISourceCodeAccessModule>("SourceCodeAccess");
SourceCodeAccessModule.OnLaunchingCodeAccessor().RemoveAll( this );
SourceCodeAccessModule.OnDoneLaunchingCodeAccessor().RemoveAll( this );
SourceCodeAccessModule.OnOpenFileFailed().RemoveAll( this );
}
#endif
ProjectDialogProvider.UnRegister();
}
void FMainFrameModule::HandleResizeMainFrameCommand(const TArray<FString>& Args)
{
if (Args.Num() == 2)
{
FVector2D Size;
Size.X = FPlatformString::Atof(*Args[0]);
Size.Y = FPlatformString::Atof(*Args[1]);
if (Size.X > 0 && Size.Y > 0)
{
FGlobalTabmanager::Get()->GetRootWindow()->ReshapeWindow(FGlobalTabmanager::Get()->GetRootWindow()->GetPositionInScreen(), Size);
}
}
}
/* FMainFrameModule event handlers
*****************************************************************************/
void FMainFrameModule::HandleLevelEditorModuleCompileStarted( bool bIsAsyncCompile )
{
ModuleCompileStartTime = FPlatformTime::Seconds();
if (CompileNotificationPtr.IsValid())
{
CompileNotificationPtr.Pin()->ExpireAndFadeout();
}
if ( GEditor )
{
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileStart_Cue.CompileStart_Cue"));
}
FNotificationInfo Info( NSLOCTEXT("MainFrame", "RecompileInProgress", "Compiling C++ Code") );
Info.Image = FAppStyle::GetBrush(TEXT("LevelEditor.RecompileGameCode"));
Info.ExpireDuration = 5.0f;
Info.bFireAndForget = false;
// We can only show the cancel button on async builds
if (bIsAsyncCompile)
{
Info.ButtonDetails.Add(FNotificationButtonInfo(LOCTEXT("CancelC++Compilation", "Cancel"), FText(), FSimpleDelegate::CreateRaw(this, &FMainFrameModule::OnCancelCodeCompilationClicked)));
}
CompileNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info);
if (CompileNotificationPtr.IsValid())
{
CompileNotificationPtr.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
}
}
void FMainFrameModule::OnCancelCodeCompilationClicked()
{
IHotReloadModule::Get().RequestStopCompilation();
}
void FMainFrameModule::HandleLevelEditorModuleCompileFinished(const FString& LogDump, ECompilationResult::Type CompilationResult, bool bShowLog)
{
// Track stats
{
const float ModuleCompileDuration = (float)(FPlatformTime::Seconds() - ModuleCompileStartTime);
UE_LOG(LogMainFrame, Log, TEXT("MainFrame: Module compiling took %.3f seconds"), ModuleCompileDuration);
if( FEngineAnalytics::IsAvailable() )
{
TArray< FAnalyticsEventAttribute > CompileAttribs;
CompileAttribs.Add(FAnalyticsEventAttribute(TEXT("Duration"), FString::Printf(TEXT("%.3f"), ModuleCompileDuration)));
CompileAttribs.Add(FAnalyticsEventAttribute(TEXT("Result"), ECompilationResult::ToString(CompilationResult)));
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Modules.Recompile"), CompileAttribs);
}
}
TSharedPtr<SNotificationItem> NotificationItem = CompileNotificationPtr.Pin();
if (NotificationItem.IsValid())
{
if (!ECompilationResult::Failed(CompilationResult))
{
if ( GEditor )
{
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileSuccess_Cue.CompileSuccess_Cue"));
}
NotificationItem->SetText(NSLOCTEXT("MainFrame", "RecompileComplete", "Compile Complete!"));
NotificationItem->SetExpireDuration( 5.0f );
NotificationItem->SetCompletionState(SNotificationItem::CS_Success);
}
else
{
struct Local
{
static void ShowCompileLog()
{
FMessageLogModule& MessageLogModule = FModuleManager::GetModuleChecked<FMessageLogModule>("MessageLog");
MessageLogModule.OpenMessageLog(FCompilerResultsLog::GetLogName());
}
};
if ( GEditor )
{
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"));
}
if (CompilationResult == ECompilationResult::FailedDueToHeaderChange)
{
NotificationItem->SetText(NSLOCTEXT("MainFrame", "RecompileFailedDueToHeaderChange", "Compile failed due to the header changes. Close the editor and recompile project in IDE to apply changes."));
}
else if (CompilationResult == ECompilationResult::Canceled)
{
NotificationItem->SetText(NSLOCTEXT("MainFrame", "RecompileCanceled", "Compile Canceled!"));
}
else
{
NotificationItem->SetText(NSLOCTEXT("MainFrame", "RecompileFailed", "Compile Failed!"));
}
NotificationItem->SetCompletionState(SNotificationItem::CS_Fail);
NotificationItem->SetHyperlink(FSimpleDelegate::CreateStatic(&Local::ShowCompileLog));
NotificationItem->SetExpireDuration(30.0f);
}
NotificationItem->ExpireAndFadeout();
CompileNotificationPtr.Reset();
}
}
void FMainFrameModule::HandleReloadFinished( EReloadCompleteReason Reason )
{
// Only play the notification for hot reloads that were triggered automatically. If the user triggered the hot reload, they'll
// have a different visual cue for that, such as the "Compiling Complete!" notification
if( Reason == EReloadCompleteReason::HotReloadAutomatic )
{
FNotificationInfo Info( LOCTEXT("HotReloadFinished", "Hot Reload Complete!") );
Info.Image = FAppStyle::GetBrush(TEXT("LevelEditor.RecompileGameCode"));
Info.FadeInDuration = 0.1f;
Info.FadeOutDuration = 0.5f;
Info.ExpireDuration = 1.5f;
Info.bUseThrobber = false;
Info.bUseSuccessFailIcons = true;
Info.bUseLargeFont = true;
Info.bFireAndForget = false;
Info.bAllowThrottleWhenFrameRateIsLow = false;
auto NotificationItem = FSlateNotificationManager::Get().AddNotification( Info );
NotificationItem->SetCompletionState(SNotificationItem::CS_Success);
NotificationItem->ExpireAndFadeout();
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileSuccess_Cue.CompileSuccess_Cue"));
}
}
void FMainFrameModule::HandleCodeAccessorLaunched( const bool WasSuccessful )
{
TSharedPtr<SNotificationItem> NotificationItem = CodeAccessorNotificationPtr.Pin();
if (NotificationItem.IsValid())
{
ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked<ISourceCodeAccessModule>("SourceCodeAccess");
const FText AccessorNameText = SourceCodeAccessModule.GetAccessor().GetNameText();
if (WasSuccessful)
{
NotificationItem->SetText( FText::Format(LOCTEXT("CodeAccessorLoadComplete", "{0} loaded!"), AccessorNameText) );
NotificationItem->SetCompletionState(SNotificationItem::CS_Success);
}
else
{
NotificationItem->SetText( FText::Format(LOCTEXT("CodeAccessorLoadFailed", "{0} failed to launch!"), AccessorNameText) );
NotificationItem->SetCompletionState(SNotificationItem::CS_Fail);
}
NotificationItem->ExpireAndFadeout();
CodeAccessorNotificationPtr.Reset();
}
}
void FMainFrameModule::HandleCodeAccessorLaunching()
{
if (CodeAccessorNotificationPtr.IsValid())
{
CodeAccessorNotificationPtr.Pin()->ExpireAndFadeout();
}
ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked<ISourceCodeAccessModule>("SourceCodeAccess");
const FText AccessorNameText = SourceCodeAccessModule.GetAccessor().GetNameText();
FNotificationInfo Info( FText::Format(LOCTEXT("CodeAccessorLoadInProgress", "Loading {0}"), AccessorNameText) );
Info.bFireAndForget = false;
CodeAccessorNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info);
CodeAccessorNotificationPtr.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
}
void FMainFrameModule::HandleCodeAccessorOpenFileFailed(const FString& Filename)
{
auto* Info = new FNotificationInfo(FText::Format(LOCTEXT("FileNotFound", "Could not find code file, {0}"), FText::FromString(Filename)));
Info->ExpireDuration = 3.0f;
FSlateNotificationManager::Get().QueueNotification(Info);
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FMainFrameModule, MainFrame);