Added HardwareTargeting module and tidied up project browser dialogs

Also removed tabs from the project browser dialogs when they're not necessary. Fixed up some incorrect icon sizes to work with the new layout.

[CL 2290664 by Andrew Rodham in Main branch]
This commit is contained in:
Andrew Rodham
2014-09-09 12:16:36 -04:00
committed by UnrealBot
parent e6eebfc15f
commit 85b61bda9e
24 changed files with 1338 additions and 605 deletions

View File

@@ -31,7 +31,8 @@ public class GameProjectGeneration : ModuleRules
"SourceControl",
"TargetPlatform",
"UnrealEd",
"DesktopPlatform"
"DesktopPlatform",
"HardwareTargeting"
}
);

View File

@@ -318,7 +318,7 @@ bool GameProjectUtils::IsValidProjectFileForCreation(const FString& ProjectFile,
{
FFormatNamedArguments Args;
Args.Add( TEXT("MaxProjectPathLength"), MaxProjectPathLength );
OutFailReason = FText::Format( LOCTEXT( "ProjectPathTooLong", "A projects path must not be longer than {MaxProjectPathLength} characters." ), Args );
OutFailReason = FText::Format( LOCTEXT( "ProjectPathTooLong", "A project's path must not be longer than {MaxProjectPathLength} characters." ), Args );
return false;
}
@@ -352,17 +352,13 @@ bool GameProjectUtils::IsValidProjectFileForCreation(const FString& ProjectFile,
if ( ProjectFileExists(ProjectFile) )
{
FFormatNamedArguments Args;
Args.Add( TEXT("ProjectFile"), FText::FromString( ProjectFile ) );
OutFailReason = FText::Format( LOCTEXT( "ProjectFileAlreadyExists", "{ProjectFile} already exists." ), Args );
OutFailReason = LOCTEXT( "ProjectFileAlreadyExists", "This project file already exists." );
return false;
}
if ( FPaths::ConvertRelativePathToFull(FPaths::GetPath(ProjectFile)).StartsWith( FPaths::ConvertRelativePathToFull(FPaths::EngineDir())) )
{
FFormatNamedArguments Args;
Args.Add( TEXT("ProjectFile"), FText::FromString( ProjectFile ) );
OutFailReason = FText::Format( LOCTEXT( "ProjectFileCannotBeUnderEngineFolder", "{ProjectFile} cannot be saved under the Engine folder. Create the project in a different directory." ), Args );
OutFailReason = LOCTEXT( "ProjectFileCannotBeUnderEngineFolder", "Project cannot be saved under the Engine folder. Please choose a different directory." );
return false;
}
@@ -370,8 +366,7 @@ bool GameProjectUtils::IsValidProjectFileForCreation(const FString& ProjectFile,
{
FFormatNamedArguments Args;
Args.Add( TEXT("ProjectFileExtension"), FText::FromString( FProjectDescriptor::GetExtension() ) );
Args.Add( TEXT("ProjectFilePath"), FText::FromString( FPaths::GetPath(ProjectFile) ) );
OutFailReason = FText::Format( LOCTEXT( "AProjectFileAlreadyExistsAtLoction", "Another .{ProjectFileExtension} file already exists in {ProjectFilePath}" ), Args );
OutFailReason = FText::Format( LOCTEXT( "AProjectFileAlreadyExistsAtLoction", "Another .{ProjectFileExtension} file already exists in the specified folder" ), Args );
return false;
}
@@ -404,7 +399,7 @@ bool GameProjectUtils::OpenProject(const FString& ProjectFile, FText& OutFailRea
{
FFormatNamedArguments Args;
Args.Add( TEXT("MaxProjectPathLength"), MaxProjectPathLength );
OutFailReason = FText::Format( LOCTEXT( "ProjectPathTooLong", "A projects path must not be longer than {MaxProjectPathLength} characters." ), Args );
OutFailReason = FText::Format( LOCTEXT( "ProjectPathTooLong", "A project's path must not be longer than {MaxProjectPathLength} characters." ), Args );
return false;
}
@@ -556,9 +551,9 @@ bool GameProjectUtils::CopyStarterContent(const FString& DestProjectFolder, FTex
}
bool GameProjectUtils::CreateProject(const FString& NewProjectFile, const FString& TemplateFile, bool bShouldGenerateCode, bool bCopyStarterContent, FText& OutFailReason)
bool GameProjectUtils::CreateProject(const FProjectInformation& InProjectInfo, FText& OutFailReason)
{
if ( !IsValidProjectFileForCreation(NewProjectFile, OutFailReason) )
if ( !IsValidProjectFileForCreation(InProjectInfo.ProjectFilename, OutFailReason) )
{
return false;
}
@@ -568,22 +563,22 @@ bool GameProjectUtils::CreateProject(const FString& NewProjectFile, const FStrin
bool bProjectCreationSuccessful = false;
FString TemplateName;
if ( TemplateFile.IsEmpty() )
if ( InProjectInfo.TemplateFile.IsEmpty() )
{
bProjectCreationSuccessful = GenerateProjectFromScratch(NewProjectFile, bShouldGenerateCode, bCopyStarterContent, OutFailReason);
TemplateName = bShouldGenerateCode ? TEXT("Basic Code") : TEXT("Blank");
bProjectCreationSuccessful = GenerateProjectFromScratch(InProjectInfo, OutFailReason);
TemplateName = InProjectInfo.bShouldGenerateCode ? TEXT("Basic Code") : TEXT("Blank");
}
else
{
bProjectCreationSuccessful = CreateProjectFromTemplate(NewProjectFile, TemplateFile, bShouldGenerateCode, bCopyStarterContent, OutFailReason);
TemplateName = FPaths::GetBaseFilename(TemplateFile);
bProjectCreationSuccessful = CreateProjectFromTemplate(InProjectInfo, OutFailReason);
TemplateName = FPaths::GetBaseFilename(InProjectInfo.TemplateFile);
}
if( FEngineAnalytics::IsAvailable() )
{
TArray<FAnalyticsEventAttribute> EventAttributes;
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("Template"), TemplateName));
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("ProjectType"), bShouldGenerateCode ? TEXT("C++ Code") : TEXT("Content Only")));
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("ProjectType"), InProjectInfo.bShouldGenerateCode ? TEXT("C++ Code") : TEXT("Content Only")));
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("Outcome"), bProjectCreationSuccessful ? TEXT("Successful") : TEXT("Failed")));
FEngineAnalytics::GetProvider().RecordEvent( TEXT( "Editor.NewProject.ProjectCreated" ), EventAttributes );
@@ -816,14 +811,14 @@ UTemplateProjectDefs* GameProjectUtils::LoadTemplateDefs(const FString& ProjectD
return TemplateDefs;
}
bool GameProjectUtils::GenerateProjectFromScratch(const FString& NewProjectFile, bool bShouldGenerateCode, bool bCopyStarterContent, FText& OutFailReason)
bool GameProjectUtils::GenerateProjectFromScratch(const FProjectInformation& InProjectInfo, FText& OutFailReason)
{
const FString NewProjectFolder = FPaths::GetPath(NewProjectFile);
const FString NewProjectName = FPaths::GetBaseFilename(NewProjectFile);
const FString NewProjectFolder = FPaths::GetPath(InProjectInfo.ProjectFilename);
const FString NewProjectName = FPaths::GetBaseFilename(InProjectInfo.ProjectFilename);
TArray<FString> CreatedFiles;
// Generate config files
if (!GenerateConfigFiles(NewProjectFolder, NewProjectName, bShouldGenerateCode, bCopyStarterContent, CreatedFiles, OutFailReason))
if (!GenerateConfigFiles(InProjectInfo, CreatedFiles, OutFailReason))
{
DeleteCreatedFiles(NewProjectFolder, CreatedFiles);
return false;
@@ -841,7 +836,7 @@ bool GameProjectUtils::GenerateProjectFromScratch(const FString& NewProjectFile,
}
TArray<FString> StartupModuleNames;
if ( bShouldGenerateCode )
if ( InProjectInfo.bShouldGenerateCode )
{
// Generate basic source code files
if ( !GenerateBasicSourceCode(NewProjectFolder / TEXT("Source"), NewProjectName, StartupModuleNames, CreatedFiles, OutFailReason) )
@@ -861,9 +856,9 @@ bool GameProjectUtils::GenerateProjectFromScratch(const FString& NewProjectFile,
// Generate the project file
{
FText LocalFailReason;
if (IProjectManager::Get().GenerateNewProjectFile(NewProjectFile, StartupModuleNames, FDesktopPlatformModule::Get()->GetCurrentEngineIdentifier(), LocalFailReason))
if (IProjectManager::Get().GenerateNewProjectFile(InProjectInfo.ProjectFilename, StartupModuleNames, FDesktopPlatformModule::Get()->GetCurrentEngineIdentifier(), LocalFailReason))
{
CreatedFiles.Add(NewProjectFile);
CreatedFiles.Add(InProjectInfo.ProjectFilename);
}
else
{
@@ -873,23 +868,23 @@ bool GameProjectUtils::GenerateProjectFromScratch(const FString& NewProjectFile,
}
}
if ( bShouldGenerateCode )
if ( InProjectInfo.bShouldGenerateCode )
{
// Generate project files
if ( !GenerateCodeProjectFiles(NewProjectFile, OutFailReason) )
if ( !GenerateCodeProjectFiles(InProjectInfo.ProjectFilename, OutFailReason) )
{
DeleteGeneratedProjectFiles(NewProjectFile);
DeleteGeneratedProjectFiles(InProjectInfo.ProjectFilename);
DeleteCreatedFiles(NewProjectFolder, CreatedFiles);
return false;
}
}
if (bCopyStarterContent)
if (InProjectInfo.bCopyStarterContent)
{
// Copy the starter content
if ( !CopyStarterContent(NewProjectFolder, OutFailReason) )
{
DeleteGeneratedProjectFiles(NewProjectFile);
DeleteGeneratedProjectFiles(InProjectInfo.ProjectFilename);
DeleteCreatedFiles(NewProjectFolder, CreatedFiles);
return false;
}
@@ -899,17 +894,17 @@ bool GameProjectUtils::GenerateProjectFromScratch(const FString& NewProjectFile,
return true;
}
bool GameProjectUtils::CreateProjectFromTemplate(const FString& NewProjectFile, const FString& TemplateFile, bool bShouldGenerateCode, bool bCopyStarterContent, FText& OutFailReason)
bool GameProjectUtils::CreateProjectFromTemplate(const FProjectInformation& InProjectInfo, FText& OutFailReason)
{
const FString ProjectName = FPaths::GetBaseFilename(NewProjectFile);
const FString TemplateName = FPaths::GetBaseFilename(TemplateFile);
const FString SrcFolder = FPaths::GetPath(TemplateFile);
const FString DestFolder = FPaths::GetPath(NewProjectFile);
const FString ProjectName = FPaths::GetBaseFilename(InProjectInfo.ProjectFilename);
const FString TemplateName = FPaths::GetBaseFilename(InProjectInfo.TemplateFile);
const FString SrcFolder = FPaths::GetPath(InProjectInfo.TemplateFile);
const FString DestFolder = FPaths::GetPath(InProjectInfo.ProjectFilename);
if ( !FPlatformFileManager::Get().GetPlatformFile().FileExists(*TemplateFile) )
if ( !FPlatformFileManager::Get().GetPlatformFile().FileExists(*InProjectInfo.TemplateFile) )
{
FFormatNamedArguments Args;
Args.Add( TEXT("TemplateFile"), FText::FromString( TemplateFile ) );
Args.Add( TEXT("TemplateFile"), FText::FromString( InProjectInfo.TemplateFile ) );
OutFailReason = FText::Format( LOCTEXT("InvalidTemplate_MissingProject", "Template project \"{TemplateFile}\" does not exist."), Args );
return false;
}
@@ -918,7 +913,7 @@ bool GameProjectUtils::CreateProjectFromTemplate(const FString& NewProjectFile,
if ( TemplateDefs == NULL )
{
FFormatNamedArguments Args;
Args.Add( TEXT("TemplateFile"), FText::FromString( FPaths::GetBaseFilename(TemplateFile) ) );
Args.Add( TEXT("TemplateFile"), FText::FromString( FPaths::GetBaseFilename(InProjectInfo.TemplateFile) ) );
Args.Add( TEXT("TemplateDefinesFile"), FText::FromString( GetTemplateDefsFilename() ) );
OutFailReason = FText::Format( LOCTEXT("InvalidTemplate_MissingDefs", "Template project \"{TemplateFile}\" does not have definitions file: '{TemplateDefinesFile}'."), Args );
return false;
@@ -1080,9 +1075,28 @@ bool GameProjectUtils::CreateProjectFromTemplate(const FString& NewProjectFile,
}
}
const FString ProjectConfigPath = DestFolder / TEXT("Config");
// Write out the hardware class target settings chosen for this project
{
const FString DefaultEditorIniFilename = ProjectConfigPath / TEXT("DefaultEditor.ini");
FString FileContents;
// Load the existing file - if it doesn't exist we create it
FFileHelper::LoadFileToString(FileContents, *DefaultEditorIniFilename);
FileContents += LINE_TERMINATOR;
FileContents += GetHardwareConfigString(InProjectInfo);
if ( !WriteOutputFile(DefaultEditorIniFilename, FileContents, OutFailReason) )
{
return false;
}
}
// Fixup specific ini values
TArray<FTemplateConfigValue> ConfigValuesToSet;
TemplateDefs->AddConfigValues(ConfigValuesToSet, TemplateName, ProjectName, bShouldGenerateCode);
TemplateDefs->AddConfigValues(ConfigValuesToSet, TemplateName, ProjectName, InProjectInfo.bShouldGenerateCode);
new (ConfigValuesToSet) FTemplateConfigValue(TEXT("DefaultGame.ini"), TEXT("/Script/EngineSettings.GeneralProjectSettings"), TEXT("ProjectID"), FGuid::NewGuid().ToString(), /*InShouldReplaceExistingValue=*/true);
// Add all classname fixups
@@ -1096,7 +1110,7 @@ bool GameProjectUtils::CreateProjectFromTemplate(const FString& NewProjectFile,
for ( auto ConfigIt = ConfigValuesToSet.CreateConstIterator(); ConfigIt; ++ConfigIt )
{
const FTemplateConfigValue& ConfigValue = *ConfigIt;
const FString IniFilename = DestFolder / TEXT("Config") / ConfigValue.ConfigFile;
const FString IniFilename = ProjectConfigPath / ConfigValue.ConfigFile;
bool bSuccessfullyProcessed = false;
TArray<FString> FileLines;
@@ -1204,7 +1218,7 @@ bool GameProjectUtils::CreateProjectFromTemplate(const FString& NewProjectFile,
{
// Load the source project
FProjectDescriptor Project;
if(!Project.Load(TemplateFile, OutFailReason))
if(!Project.Load(InProjectInfo.TemplateFile, OutFailReason))
{
DeleteCreatedFiles(DestFolder, CreatedFiles);
return false;
@@ -1215,8 +1229,8 @@ bool GameProjectUtils::CreateProjectFromTemplate(const FString& NewProjectFile,
Project.EpicSampleNameHash = 0;
// Fix up module names
const FString BaseSourceName = FPaths::GetBaseFilename(TemplateFile);
const FString BaseNewName = FPaths::GetBaseFilename(NewProjectFile);
const FString BaseSourceName = FPaths::GetBaseFilename(InProjectInfo.TemplateFile);
const FString BaseNewName = FPaths::GetBaseFilename(InProjectInfo.ProjectFilename);
for ( auto ModuleIt = Project.Modules.CreateIterator(); ModuleIt; ++ModuleIt )
{
FModuleDescriptor& ModuleInfo = *ModuleIt;
@@ -1224,17 +1238,17 @@ bool GameProjectUtils::CreateProjectFromTemplate(const FString& NewProjectFile,
}
// Save it to disk
if(!Project.Save(NewProjectFile, OutFailReason))
if(!Project.Save(InProjectInfo.ProjectFilename, OutFailReason))
{
DeleteCreatedFiles(DestFolder, CreatedFiles);
return false;
}
// Add it to the list of created files
CreatedFiles.Add(NewProjectFile);
CreatedFiles.Add(InProjectInfo.ProjectFilename);
}
if ( bShouldGenerateCode )
if ( InProjectInfo.bShouldGenerateCode )
{
// resource folder
const FString GameModuleSourcePath = DestFolder / TEXT("Source") / ProjectName;
@@ -1245,28 +1259,28 @@ bool GameProjectUtils::CreateProjectFromTemplate(const FString& NewProjectFile,
}
// Generate project files
if ( !GenerateCodeProjectFiles(NewProjectFile, OutFailReason) )
if ( !GenerateCodeProjectFiles(InProjectInfo.ProjectFilename, OutFailReason) )
{
DeleteGeneratedProjectFiles(NewProjectFile);
DeleteGeneratedProjectFiles(InProjectInfo.ProjectFilename);
DeleteCreatedFiles(DestFolder, CreatedFiles);
return false;
}
}
if (bCopyStarterContent)
if (InProjectInfo.bCopyStarterContent)
{
// Copy the starter content
if ( !CopyStarterContent(DestFolder, OutFailReason) )
{
DeleteGeneratedProjectFiles(NewProjectFile);
DeleteGeneratedProjectFiles(InProjectInfo.ProjectFilename);
DeleteCreatedFiles(DestFolder, CreatedFiles);
return false;
}
}
if (!TemplateDefs->PostGenerateProject(DestFolder, SrcFolder, NewProjectFile, TemplateFile, bShouldGenerateCode, OutFailReason))
if (!TemplateDefs->PostGenerateProject(DestFolder, SrcFolder, InProjectInfo.ProjectFilename, InProjectInfo.TemplateFile, InProjectInfo.bShouldGenerateCode, OutFailReason))
{
DeleteGeneratedProjectFiles(NewProjectFile);
DeleteGeneratedProjectFiles(InProjectInfo.ProjectFilename);
DeleteCreatedFiles(DestFolder, CreatedFiles);
return false;
}
@@ -1395,9 +1409,24 @@ void GameProjectUtils::DeleteGeneratedBuildFiles(const FString& NewProjectFolder
}
}
bool GameProjectUtils::GenerateConfigFiles(const FString& NewProjectPath, const FString& NewProjectName, bool bShouldGenerateCode, bool bCopyStarterContent, TArray<FString>& OutCreatedFiles, FText& OutFailReason)
FString GameProjectUtils::GetHardwareConfigString(const FProjectInformation& InProjectInfo)
{
FString ProjectConfigPath = NewProjectPath / TEXT("Config");
FString HardwareTargeting;
HardwareTargeting += TEXT("[/Script/HardwareTargeting.HardwareTargetingSettings]") LINE_TERMINATOR;
HardwareTargeting += FString::Printf(TEXT("TargetedHardwareClass=%d") LINE_TERMINATOR, int32(InProjectInfo.TargetedHardware));
HardwareTargeting += FString::Printf(TEXT("DefaultGraphicsPerformance=%d") LINE_TERMINATOR, int32(InProjectInfo.DefaultGraphicsPerformance));
HardwareTargeting += LINE_TERMINATOR;
return HardwareTargeting;
}
bool GameProjectUtils::GenerateConfigFiles(const FProjectInformation& InProjectInfo, TArray<FString>& OutCreatedFiles, FText& OutFailReason)
{
const FString NewProjectFolder = FPaths::GetPath(InProjectInfo.ProjectFilename);
const FString NewProjectName = FPaths::GetBaseFilename(InProjectInfo.ProjectFilename);
FString ProjectConfigPath = NewProjectFolder / TEXT("Config");
// DefaultEngine.ini
{
@@ -1407,8 +1436,8 @@ bool GameProjectUtils::GenerateConfigFiles(const FString& NewProjectPath, const
FileContents += TEXT("[URL]") LINE_TERMINATOR;
FileContents += FString::Printf(TEXT("GameName=%s") LINE_TERMINATOR, *NewProjectName);
FileContents += LINE_TERMINATOR;
if (bCopyStarterContent)
if (InProjectInfo.bCopyStarterContent)
{
// for generated/blank projects with starter content, set startup map to be the starter content map
// otherwise, we leave it to be what the template wants.
@@ -1430,7 +1459,7 @@ bool GameProjectUtils::GenerateConfigFiles(const FString& NewProjectPath, const
FileContents += TEXT("[/Script/EngineSettings.GameMapsSettings]") LINE_TERMINATOR;
FileContents += FString::Printf(TEXT("EditorStartupMap=%s") LINE_TERMINATOR, *MapPackagePath);
FileContents += FString::Printf(TEXT("GameDefaultMap=%s") LINE_TERMINATOR, *MapPackagePath);
if (bShouldGenerateCode)
if (InProjectInfo.bShouldGenerateCode)
{
FileContents += FString::Printf(TEXT("GlobalDefaultGameMode=\"/Script/%s.%sGameMode\"") LINE_TERMINATOR, *NewProjectName, *NewProjectName);
}
@@ -1447,24 +1476,6 @@ bool GameProjectUtils::GenerateConfigFiles(const FString& NewProjectPath, const
}
}
// DefaultGame.ini
{
const FString DefaultGameIniFilename = ProjectConfigPath / TEXT("DefaultGame.ini");
FString FileContents;
FileContents += TEXT("[/Script/EngineSettings.GeneralProjectSettings]") LINE_TERMINATOR;
FileContents += FString::Printf( TEXT("ProjectID=%s") LINE_TERMINATOR, *FGuid::NewGuid().ToString() );
FileContents += LINE_TERMINATOR;
if ( WriteOutputFile(DefaultGameIniFilename, FileContents, OutFailReason) )
{
OutCreatedFiles.Add(DefaultGameIniFilename);
}
else
{
return false;
}
}
// DefaultEditor.ini
{
const FString DefaultEditorIniFilename = ProjectConfigPath / TEXT("DefaultEditor.ini");
@@ -1474,6 +1485,8 @@ bool GameProjectUtils::GenerateConfigFiles(const FString& NewProjectPath, const
FileContents += TEXT("bReplaceBlueprintWithClass=true") LINE_TERMINATOR;
FileContents += TEXT("bDontLoadBlueprintOutsideEditor=true") LINE_TERMINATOR;
FileContents += TEXT("bBlueprintIsNotBlueprintType=true") LINE_TERMINATOR;
FileContents += LINE_TERMINATOR;
FileContents += GetHardwareConfigString(InProjectInfo);
if (WriteOutputFile(DefaultEditorIniFilename, FileContents, OutFailReason))
{

View File

@@ -2,6 +2,29 @@
#pragma once
#include "HardwareTargetingModule.h"
struct FProjectInformation
{
FProjectInformation(FString InProjectFilename, bool bInGenerateCode, bool bInCopyStarterContent, FString InTemplateFile = FString())
: ProjectFilename(MoveTemp(InProjectFilename))
, TemplateFile(MoveTemp(InTemplateFile))
, bShouldGenerateCode(bInGenerateCode)
, bCopyStarterContent(bInCopyStarterContent)
, TargetedHardware(EHardwareClass::Desktop)
, DefaultGraphicsPerformance(EGraphicsPreset::Maximum)
{
}
FString ProjectFilename;
FString TemplateFile;
bool bShouldGenerateCode;
bool bCopyStarterContent;
EHardwareClass::Type TargetedHardware;
EGraphicsPreset::Type DefaultGraphicsPerformance;
};
class GameProjectUtils
{
public:
@@ -133,7 +156,7 @@ public:
static bool OpenCodeIDE(const FString& ProjectFile, FText& OutFailReason);
/** Creates the specified project file and all required folders. If TemplateFile is non-empty, it will be used as the template for creation. On failure, OutFailReason will be populated. */
static bool CreateProject(const FString& NewProjectFile, const FString& TemplateFile, bool bShouldGenerateCode, bool bCopyStarterContent, FText& OutFailReason);
static bool CreateProject(const FProjectInformation& InProjectInfo, FText& OutFailReason);
/** Prompts the user to update his project file, if necessary. */
static void CheckForOutOfDateGameProjectFile();
@@ -238,11 +261,14 @@ public:
static void ClearSupportedTargetPlatforms();
private:
static FString GetHardwareConfigString(const FProjectInformation& InProjectInfo);
/** Generates a new project without using a template project */
static bool GenerateProjectFromScratch(const FString& NewProjectFile, bool bShouldGenerateCode, bool bCopyStarterContent, FText& OutFailReason);
static bool GenerateProjectFromScratch(const FProjectInformation& InProjectInfo, FText& OutFailReason);
/** Generates a new project using a template project */
static bool CreateProjectFromTemplate(const FString& NewProjectFile, const FString& TemplateFile, bool bShouldGenerateCode, bool bCopyStarterContent, FText& OutFailReason);
static bool CreateProjectFromTemplate(const FProjectInformation& InProjectInfo, FText& OutFailReason);
/** Copies starter content into the specified project folder. */
static bool CopyStarterContent(const FString& DestProjectFolder, FText& OutFailReason);
@@ -278,7 +304,7 @@ private:
static void DeleteGeneratedBuildFiles(const FString& NewProjectFolder);
/** Creates ini files for a new project. On failure, OutFailReason will be populated. */
static bool GenerateConfigFiles(const FString& NewProjectPath, const FString& NewProjectName, bool bShouldGenerateCode, bool bCopyStarterContent, TArray<FString>& OutCreatedFiles, FText& OutFailReason);
static bool GenerateConfigFiles(const FProjectInformation& InProjectInfo, TArray<FString>& OutCreatedFiles, FText& OutFailReason);
/** Creates the basic source code for a new project. On failure, OutFailReason will be populated. */
static bool GenerateBasicSourceCode(const FString& NewProjectSourcePath, const FString& NewProjectName, TArray<FString>& OutGeneratedStartupModuleNames, TArray<FString>& OutCreatedFiles, FText& OutFailReason);

View File

@@ -20,173 +20,156 @@ void SGameProjectDialog::Construct( const FArguments& InArgs )
{
bool bAtLeastOneVisibleRecentProject = false;
ProjectsTabVisibility = InArgs._AllowProjectOpening ? EVisibility::Visible : EVisibility::Collapsed;
NewProjectTabVisibility = InArgs._AllowProjectCreate ? EVisibility::Visible : EVisibility::Collapsed;
if (InArgs._AllowProjectCreate)
{
NewProjectWizard = SNew(SNewProjectWizard);
}
ChildSlot
[
// Drop shadow border
SNew(SBorder)
.Padding(10.0f )
.BorderImage(FEditorStyle::GetBrush("ContentBrowser.ThumbnailShadow"))
if (InArgs._AllowProjectOpening)
{
ProjectBrowser = SNew(SProjectBrowser);
}
TSharedPtr<SWidget> Content;
if (InArgs._AllowProjectCreate && InArgs._AllowProjectOpening)
{
// Create the Open Project tab button
TSharedRef<SButton> ProjectsTabButton = SNew(SButton)
.ForegroundColor(FCoreStyle::Get().GetSlateColor("Foreground"))
.ButtonStyle(FEditorStyle::Get(), TEXT("NoBorder"))
.OnClicked(this, &SGameProjectDialog::HandleProjectsTabButtonClicked)
.ContentPadding(FMargin(40, 5))
.Text(LOCTEXT("ProjectsTabTitle", "Projects"))
.TextStyle(FEditorStyle::Get(), TEXT("ProjectBrowser.Tab.Text"));
// Create the New Project tab button
TSharedRef<SButton> NewProjectTabButton = SNew(SButton)
.ForegroundColor(FCoreStyle::Get().GetSlateColor("Foreground"))
.ButtonStyle(FEditorStyle::Get(), "NoBorder")
.OnClicked(this, &SGameProjectDialog::HandleNewProjectTabButtonClicked)
.ContentPadding(FMargin(20, 5))
.TextStyle(FEditorStyle::Get(), "ProjectBrowser.Tab.Text")
.Text(LOCTEXT("NewProjectTabTitle", "New Project"))
.ToolTip(IDocumentation::Get()->CreateToolTip(LOCTEXT("NewProjectTabTitle", "New Project"), nullptr, "Shared/LevelEditor", "NewProjectTab"));
// Allow creation and opening, so we need tabs here
ChildSlot
[
SNew(SBorder)
.ColorAndOpacity(this, &SGameProjectDialog::HandleCustomContentColorAndOpacity)
.BorderImage(FEditorStyle::GetBrush("Docking.Tab.ContentAreaBrush"))
.Padding(0)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush( "Docking.Tab.ContentAreaBrush"))
.Padding(10.0f)
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(6.f, 0, 0, 0))
[
SNew(SHorizontalBox)
// Open Project Tab
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SVerticalBox)
SNew( SBorder )
.BorderImage(this, &SGameProjectDialog::OnGetTabBorderImage, ProjectsTab)
.Padding(0)
[
SNew(SOverlay)
+ SVerticalBox::Slot()
.AutoHeight()
+ SOverlay::Slot()
.VAlign(VAlign_Top)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew( SBorder )
.Visibility(ProjectsTabVisibility)
.BorderImage(this, &SGameProjectDialog::HandleProjectsTabHeaderBorderImage)
.Padding(0)
[
SNew(SOverlay)
+ SOverlay::Slot()
.VAlign(VAlign_Top)
[
SNew(SBox)
.HeightOverride(2.0f)
[
SNew(SImage)
.Image(this, &SGameProjectDialog::HandleProjectsTabHeaderImage)
.Visibility(EVisibility::HitTestInvisible)
]
]
+ SOverlay::Slot()
[
SAssignNew(ProjectsTabButton, SButton)
.ForegroundColor(FCoreStyle::Get().GetSlateColor("Foreground"))
.ButtonStyle(FEditorStyle::Get(), TEXT("NoBorder"))
.OnClicked(this, &SGameProjectDialog::HandleProjectsTabButtonClicked)
.ContentPadding(FMargin(40, 5))
.Text(LOCTEXT("ProjectsTabTitle", "Projects"))
.TextStyle(FEditorStyle::Get(), TEXT("ProjectBrowser.Tab.Text"))
]
]
]
+ SHorizontalBox::Slot()
.Padding(InArgs._AllowProjectOpening ? 10 : 0, 0)
.AutoWidth()
[
SNew(SBorder)
.Visibility(NewProjectTabVisibility)
.BorderImage(this, &SGameProjectDialog::HandleNewProjectTabHeaderBorderImage)
.Padding(0)
[
SNew(SOverlay)
+ SOverlay::Slot()
.VAlign(VAlign_Top)
[
SNew(SBox)
.HeightOverride(2.0f)
[
SNew(SImage)
.Image(this, &SGameProjectDialog::HandleNewProjectTabHeaderImage)
.Visibility(EVisibility::HitTestInvisible)
]
]
+ SOverlay::Slot()
[
SAssignNew(NewProjectTabButton, SButton)
.ForegroundColor(FCoreStyle::Get().GetSlateColor("Foreground"))
.ButtonStyle(FEditorStyle::Get(), "NoBorder")
.OnClicked(this, &SGameProjectDialog::HandleNewProjectTabButtonClicked)
.ContentPadding(FMargin(20, 5))
.TextStyle(FEditorStyle::Get(), "ProjectBrowser.Tab.Text")
.Text(LOCTEXT("NewProjectTabTitle", "New Project"))
.ToolTip(IDocumentation::Get()->CreateToolTip(LOCTEXT("NewProjectTabTitle", "New Project"), nullptr, "Shared/LevelEditor", "NewProjectTab"))
]
]
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.HAlign(HAlign_Right)
.Padding(0,5)
[
SNew(SBorder)
.BorderImage(FEditorStyle::Get().GetBrush( "ToolPanel.GroupBorder" ))
.Padding(3)
[
SAssignNew(MarketplaceButton, SButton)
.Visibility( FDesktopPlatformModule::Get()->CanOpenLauncher(true) ? EVisibility::Collapsed : EVisibility::Visible )
.ForegroundColor(this, &SGameProjectDialog::HandleMarketplaceTabButtonForegroundColor)
.ButtonStyle(FEditorStyle::Get(), "ToggleButton")
.OnClicked(this, &SGameProjectDialog::HandleMarketplaceTabButtonClicked)
.ContentPadding(FMargin(20, 0))
.ToolTipText(LOCTEXT("MarketplaceToolTip", "Check out the Marketplace to find new projects!"))
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(2.0f)
.AutoWidth()
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("LevelEditor.OpenMarketplace"))
]
+ SHorizontalBox::Slot()
.Padding( 2.0f )
[
SNew(STextBlock)
.TextStyle(FEditorStyle::Get(), "ProjectBrowser.Tab.Text")
.Text(LOCTEXT("Marketplace", "Marketplace"))
]
]
]
]
SNew(SBox)
.HeightOverride(2.0f)
[
SNew(SImage)
.Image(this, &SGameProjectDialog::OnGetTabHeaderImage, ProjectsTab, ProjectsTabButton)
.Visibility(EVisibility::HitTestInvisible)
]
]
+ SVerticalBox::Slot()
+ SOverlay::Slot()
[
// custom content area
SNew(SBorder)
.ColorAndOpacity(this, &SGameProjectDialog::HandleCustomContentColorAndOpacity)
.BorderImage(FEditorStyle::GetBrush("ProjectBrowser.Background"))
.Padding(10.0f)
[
SAssignNew(ContentAreaSwitcher, SWidgetSwitcher)
.WidgetIndex(InArgs._AllowProjectOpening ? 0 : 1)
+ SWidgetSwitcher::Slot()
[
// project browser
SAssignNew(ProjectBrowser, SProjectBrowser)
.AllowProjectCreate(InArgs._AllowProjectCreate)
.OnNewProjectScreenRequested(this, &SGameProjectDialog::ShowNewProjectTab)
]
+ SWidgetSwitcher::Slot()
[
// new project wizard
SAssignNew(NewProjectWizard, SNewProjectWizard)
]
]
ProjectsTabButton
]
]
]
]
];
ActiveTab = InArgs._AllowProjectOpening ? SGameProjectDialog::ProjectsTab : SGameProjectDialog::NewProjectTab;
ActiveTab = !ProjectBrowser->HasProjects() && InArgs._AllowProjectCreate ? SGameProjectDialog::NewProjectTab : ActiveTab;
+ SHorizontalBox::Slot()
.Padding(FMargin(6.f, 0, 0, 0))
.AutoWidth()
[
SNew(SBorder)
.BorderImage(this, &SGameProjectDialog::OnGetTabBorderImage, NewProjectTab)
.Padding(0)
[
SNew(SOverlay)
+ SOverlay::Slot()
.VAlign(VAlign_Top)
[
SNew(SBox)
.HeightOverride(2.0f)
[
SNew(SImage)
.Image(this, &SGameProjectDialog::OnGetTabHeaderImage, NewProjectTab, NewProjectTabButton)
.Visibility(EVisibility::HitTestInvisible)
]
]
+ SOverlay::Slot()
[
NewProjectTabButton
]
]
]
]
+ SVerticalBox::Slot()
[
SAssignNew(ContentAreaSwitcher, SWidgetSwitcher)
.WidgetIndex(0)
+ SWidgetSwitcher::Slot()
[
ProjectBrowser.ToSharedRef()
]
+ SWidgetSwitcher::Slot()
[
NewProjectWizard.ToSharedRef()
]
]
]
];
}
else
{
TSharedPtr<SWidget> BorderContent;
if (NewProjectWizard.IsValid())
{
BorderContent = NewProjectWizard;
}
else
{
BorderContent = ProjectBrowser;
}
ChildSlot
[
BorderContent.ToSharedRef()
];
}
ActiveTab = InArgs._AllowProjectOpening ? ProjectsTab : NewProjectTab;
if (ProjectBrowser.IsValid())
{
ActiveTab = !ProjectBrowser->HasProjects() && InArgs._AllowProjectCreate ? NewProjectTab : ActiveTab;
}
if (ActiveTab == ProjectsTab)
{
@@ -278,60 +261,6 @@ FLinearColor SGameProjectDialog::HandleCustomContentColorAndOpacity( ) const
}
FReply SGameProjectDialog::HandleMarketplaceTabButtonClicked( )
{
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
if (DesktopPlatform != nullptr)
{
TArray<FAnalyticsEventAttribute> EventAttributes;
if (DesktopPlatform->OpenLauncher(false, TEXT("-OpenMarket")))
{
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("OpenSucceeded"), TEXT("TRUE")));
}
else
{
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("OpenSucceeded"), TEXT("FALSE")));
if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("InstallMarketplacePrompt", "The Marketplace requires the Unreal Engine Launcher, which does not seem to be installed on your computer. Would you like to install it now?")))
{
if (!DesktopPlatform->OpenLauncher(true, TEXT("-OpenMarket")))
{
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("InstallSucceeded"), TEXT("FALSE")));
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Sorry, there was a problem installing the Launcher.\nPlease try to install it manually!")));
}
else
{
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("InstallSucceeded"), TEXT("TRUE")));
}
}
}
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("Source"), TEXT("ProjectBrowser")));
if( FEngineAnalytics::IsAvailable() )
{
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.OpenMarketplace"), EventAttributes);
}
}
return FReply::Handled();
}
FSlateColor SGameProjectDialog::HandleMarketplaceTabButtonForegroundColor( ) const
{
//if (MarketplaceButton->IsHovered())
//{
// return FLinearColor::Black;
//}
//else
//{
return FSlateColor::UseForeground();
//}
}
FReply SGameProjectDialog::HandleNewProjectTabButtonClicked( )
{
ShowNewProjectTab();
@@ -340,25 +269,14 @@ FReply SGameProjectDialog::HandleNewProjectTabButtonClicked( )
}
const FSlateBrush* SGameProjectDialog::HandleNewProjectTabHeaderBorderImage( ) const
const FSlateBrush* SGameProjectDialog::OnGetTabHeaderImage( ETab InTab, TSharedRef<SButton> TabButton ) const
{
if (ActiveTab == NewProjectTab)
{
return FEditorStyle::GetBrush("ProjectBrowser.Tab.ActiveBackground");
}
return FEditorStyle::GetBrush("ProjectBrowser.Tab.Background");
}
const FSlateBrush* SGameProjectDialog::HandleNewProjectTabHeaderImage( ) const
{
if (NewProjectTabButton->IsPressed())
if (TabButton->IsPressed())
{
return FEditorStyle::GetBrush("ProjectBrowser.Tab.PressedHighlight");
}
if ((ActiveTab == NewProjectTab) || NewProjectTabButton->IsHovered())
if ((ActiveTab == InTab) || TabButton->IsHovered())
{
return FEditorStyle::GetBrush("ProjectBrowser.Tab.ActiveHighlight");
}
@@ -374,10 +292,9 @@ FReply SGameProjectDialog::HandleProjectsTabButtonClicked( )
return FReply::Handled();
}
const FSlateBrush* SGameProjectDialog::HandleProjectsTabHeaderBorderImage( ) const
const FSlateBrush* SGameProjectDialog::OnGetTabBorderImage( ETab InTab ) const
{
if (ActiveTab == ProjectsTab)
if (ActiveTab == InTab)
{
return FEditorStyle::GetBrush("ProjectBrowser.Tab.ActiveBackground");
}
@@ -386,20 +303,4 @@ const FSlateBrush* SGameProjectDialog::HandleProjectsTabHeaderBorderImage( ) con
}
const FSlateBrush* SGameProjectDialog::HandleProjectsTabHeaderImage( ) const
{
if (ProjectsTabButton->IsPressed())
{
return FEditorStyle::GetBrush("ProjectBrowser.Tab.PressedHighlight");
}
if ((ActiveTab == ProjectsTab) || ProjectsTabButton->IsHovered())
{
return FEditorStyle::GetBrush("ProjectBrowser.Tab.ActiveHighlight");
}
return FEditorStyle::GetBrush("ProjectBrowser.Tab.Highlight");
}
#undef LOCTEXT_NAMESPACE

View File

@@ -66,29 +66,17 @@ private:
// Callback for getting the color of the custom content area.
FLinearColor HandleCustomContentColorAndOpacity() const;
// Callback for clicking the 'Marketplace' button.
FReply HandleMarketplaceTabButtonClicked( );
// Callback for getting the foreground color of the 'Marketplace' button.
FSlateColor HandleMarketplaceTabButtonForegroundColor( ) const;
// Callback for clicking the 'New Project' button.
FReply HandleNewProjectTabButtonClicked( );
// Callback for getting the border image of the 'New Project' button.
const FSlateBrush* HandleNewProjectTabHeaderBorderImage( ) const;
// Callback for getting the button image of the 'New Project' button.
const FSlateBrush* HandleNewProjectTabHeaderImage( ) const;
// Callback for clicking the 'Projects' button.
FReply HandleProjectsTabButtonClicked( );
// Callback for getting the border image of the 'Projects' button.
const FSlateBrush* HandleProjectsTabHeaderBorderImage( ) const;
// Callback for getting the border image of the specified tab.
const FSlateBrush* OnGetTabBorderImage( ETab InTab ) const;
// Callback for getting the button image of the 'Projects' button.
const FSlateBrush* HandleProjectsTabHeaderImage( ) const;
// Callback for getting the header stripe image for the specified tab.
const FSlateBrush* OnGetTabHeaderImage( ETab InTab, TSharedRef<SButton> TabButton ) const;
private:
@@ -102,12 +90,6 @@ private:
TSharedPtr<SWidgetSwitcher> ContentAreaSwitcher;
TSharedPtr<SProjectBrowser> ProjectBrowser;
TSharedPtr<SNewProjectWizard> NewProjectWizard;
TSharedPtr<SButton> ProjectsTabButton;
TSharedPtr<SButton> NewProjectTabButton;
TSharedPtr<SButton> MarketplaceButton;
ETab ActiveTab;
EVisibility ProjectsTabVisibility;
EVisibility NewProjectTabVisibility;
};

View File

@@ -2,7 +2,6 @@
#pragma once
struct FTemplateItem;
struct FTemplateCategory;
@@ -74,6 +73,9 @@ private:
const FSlateBrush* GetSelectedTemplatePreviewImage() const;
const FSlateBrush* GetSelectedTemplateTypeImage() const;
/** Get the visibility for the selected template preview image */
EVisibility GetSelectedTemplatePreviewVisibility() const;
/** Gets the assembled project filename with path */
FString GetProjectFilenameWithPath() const;
@@ -111,13 +113,22 @@ private:
FText GetNameAndLocationErrorLabelText() const;
/** Handler for when the "copy starter content" checkbox changes state */
void OnCopyStarterContentChanged(ESlateCheckBoxState::Type InNewState);
FReply OnCopyStarterContentClicked();
/** Returns true if the "copy starter content" checkbox should be checked. */
ESlateCheckBoxState::Type IsCopyStarterContentChecked() const;
private:
/** Gets the visibility of the copy starter content option */
EVisibility GetCopyStarterContentCheckboxVisibility() const;
EHardwareClass::Type SelectedHardwareClassTarget;
void SetHardwareClassTarget(EHardwareClass::Type InHardwareClass);
EHardwareClass::Type GetHardwareClassTarget() const { return SelectedHardwareClassTarget; }
EGraphicsPreset::Type SelectedGraphicsPreset;
void SetGraphicsPreset(EGraphicsPreset::Type InGraphicsPreset);
EGraphicsPreset::Type GetGraphicsPreset() const { return SelectedGraphicsPreset; }
private:
FText GetStartContentText() const;
const FSlateBrush* GetStartContentIcon() const;
private:

View File

@@ -97,11 +97,9 @@ SProjectBrowser::SProjectBrowser()
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SProjectBrowser::Construct( const FArguments& InArgs )
{
NewProjectScreenRequestedDelegate = InArgs._OnNewProjectScreenRequested;
bPreventSelectionChangeEvent = false;
ThumbnailBorderPadding = 5;
ThumbnailSize = 128.0f;
bAllowProjectCreate = InArgs._AllowProjectCreate;
// Prepare the categories box
CategoriesBox = SNew(SVerticalBox);
@@ -129,66 +127,121 @@ void SProjectBrowser::Construct( const FArguments& InArgs )
ChildSlot
[
SNew(SVerticalBox)
// Page description
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(10.0f, 0.0f)
SNew(SBorder)
.BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") )
.Padding(8.f)
[
SNew(STextBlock)
.Text(LOCTEXT("SelectProjectDescription", "Select an existing project from the list below."))
]
SNew(SVerticalBox)
// Categories
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") )
.Padding(4)
// Categories
+ SVerticalBox::Slot()
.Padding(8.f)
.FillHeight(1.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.VAlign(VAlign_Top)
.Padding(FMargin(5.f, 10.f))
.HAlign(HAlign_Left)
.Padding(FMargin(0, 0, 0, 5.f))
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(0,0,5,0))
[
SNew(SOverlay)
+SOverlay::Slot()
SNew(SButton)
.Visibility( FDesktopPlatformModule::Get()->CanOpenLauncher(true) ? EVisibility::Collapsed : EVisibility::Visible )
.ButtonStyle(FEditorStyle::Get(), "ToggleButton")
.OnClicked(this, &SProjectBrowser::HandleMarketplaceTabButtonClicked)
.ForegroundColor(FSlateColor::UseForeground())
.ToolTipText(LOCTEXT("MarketplaceToolTip", "Check out the Marketplace to find new projects!"))
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SSearchBox)
.HintText(LOCTEXT("FilterHint", "Filter Projects..."))
.OnTextChanged(this, &SProjectBrowser::OnFilterTextChanged)
]
SNew(SHorizontalBox)
+SOverlay::Slot()
[
SNew(SBorder)
.Visibility(this, &SProjectBrowser::GetFilterActiveOverlayVisibility)
.BorderImage(FEditorStyle::Get().GetBrush("SearchBox.ActiveBorder"))
+ SHorizontalBox::Slot()
.Padding(2.0f)
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("LevelEditor.OpenMarketplace.Small"))
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(2.0f)
[
SNew(STextBlock)
.TextStyle(FEditorStyle::Get(), "ProjectBrowser.Toolbar.Text")
.Text(LOCTEXT("Marketplace", "Marketplace"))
]
]
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SButton)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
.ButtonStyle(FEditorStyle::Get(), "ToggleButton")
.OnClicked(this, &SProjectBrowser::FindProjects)
.ForegroundColor(FSlateColor::UseForeground())
.ToolTipText(LOCTEXT("RefreshProjectList", "Refresh the project list"))
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SImage).Image(FEditorStyle::Get().GetBrush("Icons.Refresh"))
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(2.0f)
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("Icons.Refresh"))
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(2.0f)
[
SNew(STextBlock)
.TextStyle(FEditorStyle::Get(), "ProjectBrowser.Toolbar.Text")
.Text(LOCTEXT("RefreshProjectsText", "Refresh"))
]
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.VAlign(VAlign_Top)
.Padding(FMargin(0, 5.f))
[
SNew(SOverlay)
+SOverlay::Slot()
[
SNew(SSearchBox)
.HintText(LOCTEXT("FilterHint", "Filter Projects..."))
.OnTextChanged(this, &SProjectBrowser::OnFilterTextChanged)
]
+SOverlay::Slot()
[
SNew(SBorder)
.Visibility(this, &SProjectBrowser::GetFilterActiveOverlayVisibility)
.BorderImage(FEditorStyle::Get().GetBrush("SearchBox.ActiveBorder"))
]
]
+ SVerticalBox::Slot()
.Padding(FMargin(0, 5.f))
[
SNew(SScrollBox)
@@ -198,48 +251,48 @@ void SProjectBrowser::Construct( const FArguments& InArgs )
]
]
]
]
+ SVerticalBox::Slot()
.Padding( 0, 40, 0, 0 ) // Lots of vertical padding before the dialog buttons at the bottom
.AutoHeight()
[
SNew( SHorizontalBox )
// Auto-load project
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(4, 0)
+ SVerticalBox::Slot()
.Padding( 0, 40, 0, 0 ) // Lots of vertical padding before the dialog buttons at the bottom
.AutoHeight()
[
SNew(SCheckBox)
.IsChecked(GEditor->GetGameAgnosticSettings().bLoadTheMostRecentlyLoadedProjectAtStartup ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked)
.OnCheckStateChanged(this, &SProjectBrowser::OnAutoloadLastProjectChanged)
.Content()
SNew( SHorizontalBox )
// Auto-load project
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
[
SNew(STextBlock).Text(LOCTEXT("AutoloadOnStartupCheckbox", "Always load last project on startup"))
SNew(SCheckBox)
.IsChecked(GEditor->GetGameAgnosticSettings().bLoadTheMostRecentlyLoadedProjectAtStartup ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked)
.OnCheckStateChanged(this, &SProjectBrowser::OnAutoloadLastProjectChanged)
.Content()
[
SNew(STextBlock).Text(LOCTEXT("AutoloadOnStartupCheckbox", "Always load last project on startup"))
]
]
]
// Browse Button
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(4, 0)
[
SNew(SButton)
.Text(LOCTEXT("BrowseProjectButton", "Browse..."))
.OnClicked(this, &SProjectBrowser::OnBrowseToProjectClicked)
.ContentPadding( FCoreStyle::Get().GetMargin("StandardDialog.ContentPadding") )
]
// Browse Button
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(8.f, 0)
[
SNew(SButton)
.Text(LOCTEXT("BrowseProjectButton", "Browse..."))
.OnClicked(this, &SProjectBrowser::OnBrowseToProjectClicked)
.ContentPadding( FCoreStyle::Get().GetMargin("StandardDialog.ContentPadding") )
]
// Open Button
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.Text(LOCTEXT("OpenProjectButton", "Open"))
.OnClicked(this, &SProjectBrowser::HandleOpenProjectButtonClicked)
.IsEnabled(this, &SProjectBrowser::HandleOpenProjectButtonIsEnabled)
.ContentPadding( FCoreStyle::Get().GetMargin("StandardDialog.ContentPadding") )
// Open Button
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.Text(LOCTEXT("OpenProjectButton", "Open"))
.OnClicked(this, &SProjectBrowser::HandleOpenProjectButtonClicked)
.IsEnabled(this, &SProjectBrowser::HandleOpenProjectButtonIsEnabled)
.ContentPadding( FCoreStyle::Get().GetMargin("StandardDialog.ContentPadding") )
]
]
]
];
@@ -272,7 +325,6 @@ void SProjectBrowser::ConstructCategory( const TSharedRef<SVerticalBox>& InCateg
{
// Title
InCategoriesBox->AddSlot()
.Padding(FMargin(5.f, 0.f))
.AutoHeight()
[
SNew(STextBlock)
@@ -284,7 +336,7 @@ void SProjectBrowser::ConstructCategory( const TSharedRef<SVerticalBox>& InCateg
// Separator
InCategoriesBox->AddSlot()
.AutoHeight()
.Padding(5.0f, 2.0f, 5.0f, 8.0f)
.Padding(0, 2.0f, 0, 8.0f)
[
SNew(SSeparator)
.Visibility(this, &SProjectBrowser::GetProjectCategoryVisibility, Category)
@@ -293,7 +345,7 @@ void SProjectBrowser::ConstructCategory( const TSharedRef<SVerticalBox>& InCateg
// Project tile view
InCategoriesBox->AddSlot()
.AutoHeight()
.Padding(5.0f, 0.0f, 5.0f, 40.0f)
.Padding(0.f, 0.0f, 0.f, 40.0f)
[
SAssignNew(Category->ProjectTileView, STileView<TSharedPtr<FProjectItem> >)
.Visibility(this, &SProjectBrowser::GetProjectCategoryVisibility, Category)
@@ -1160,6 +1212,47 @@ void SProjectBrowser::HandleProjectViewSelectionChanged(TSharedPtr<FProjectItem>
}
}
FReply SProjectBrowser::HandleMarketplaceTabButtonClicked()
{
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
if (DesktopPlatform != nullptr)
{
TArray<FAnalyticsEventAttribute> EventAttributes;
if (DesktopPlatform->OpenLauncher(false, TEXT("-OpenMarket")))
{
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("OpenSucceeded"), TEXT("TRUE")));
}
else
{
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("OpenSucceeded"), TEXT("FALSE")));
if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("InstallMarketplacePrompt", "The Marketplace requires the Unreal Engine Launcher, which does not seem to be installed on your computer. Would you like to install it now?")))
{
if (!DesktopPlatform->OpenLauncher(true, TEXT("-OpenMarket")))
{
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("InstallSucceeded"), TEXT("FALSE")));
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Sorry, there was a problem installing the Launcher.\nPlease try to install it manually!")));
}
else
{
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("InstallSucceeded"), TEXT("TRUE")));
}
}
}
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("Source"), TEXT("ProjectBrowser")));
if( FEngineAnalytics::IsAvailable() )
{
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.OpenMarketplace"), EventAttributes);
}
}
return FReply::Handled();
}
void SProjectBrowser::OnFilterTextChanged(const FText& InText)
{
ProjectItemFilter.SetRawFilterText(InText);

View File

@@ -17,10 +17,6 @@ public:
SLATE_BEGIN_ARGS(SProjectBrowser) { }
SLATE_ARGUMENT(bool, AllowProjectCreate)
SLATE_EVENT(FNewProjectScreenRequested, OnNewProjectScreenRequested)
SLATE_END_ARGS()
public:
@@ -109,6 +105,9 @@ private:
/** Handler for when the selection changes in the project view */
void HandleProjectViewSelectionChanged( TSharedPtr<FProjectItem> ProjectItem, ESelectInfo::Type SelectInfo, FText CategoryName );
/** Callback for clicking the 'Marketplace' button. */
FReply HandleMarketplaceTabButtonClicked( );
/** Called when the text in the filter box is changed */
void OnFilterTextChanged(const FText& InText);
@@ -149,9 +148,6 @@ private:
TSharedPtr<SVerticalBox> CategoriesBox;
bool bHasProjectFiles;
/** True if this UI will enable the creation of new projects, false otherwise */
bool bAllowProjectCreate;
private:
// Holds a delegate that is executed when the new project screen is being requested.

View File

@@ -116,7 +116,7 @@ bool FBuildPromotionNewProjectCreateTest::RunTest(const FString& Parameters)
}
FText FailReason;
if (GameProjectUtils::CreateProject(DesiredProjectFilename, TEXT(""), false, true, FailReason))
if (GameProjectUtils::CreateProject(FProjectInformation(DesiredProjectFilename, false, true), FailReason))
{
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Generated a new project: %s"), *DesiredProjectFilename);
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Test successful!"));