Merge branch 'TIF/Runtime' into TIF/Jenkins
This commit is contained in:
@@ -125,8 +125,8 @@ ly_add_target(
|
||||
AZ::AssetProcessorBatch.Static
|
||||
)
|
||||
|
||||
if(LY_FIRST_PROJECT_PATH)
|
||||
set_property(TARGET AssetProcessor AssetProcessorBatch APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_FIRST_PROJECT_PATH}\"")
|
||||
if(LY_DEFAULT_PROJECT_PATH)
|
||||
set_property(TARGET AssetProcessor AssetProcessorBatch APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_DEFAULT_PROJECT_PATH}\"")
|
||||
endif()
|
||||
|
||||
# Adds the AssetProcessorBatch target as a C preprocessor define so that it can be used as a Settings Registry
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.02554 10L9 15.0362V18L0 9L9 0V2.98885L4 8H18V10H4.02554Z" fill="#1e70eb"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 230 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 17L15 14H9L12 17Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 190 B |
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f82f22df64b93d4bec91e56b60efa3d5ce2915ce388a2dc627f1ab720678e3d5
|
||||
size 334987
|
||||
oid sha256:4a5881b8d6cfbc4ceefb14ab96844484fe19407ee030824768f9fcce2f729d35
|
||||
size 2949
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8358f4dad9878c662b9819b2b346622af691eb45f8eddc28fff79a50650ae6cf
|
||||
size 2503
|
||||
@@ -7,6 +7,7 @@
|
||||
<file>AddOffset.svg</file>
|
||||
<file>AddOffset_Hover.svg</file>
|
||||
<file>ArrowBack.svg</file>
|
||||
<file>ArrowBack_Hover.svg</file>
|
||||
<file>build.svg</file>
|
||||
<file>FolderOffset.svg</file>
|
||||
<file>FolderOffset_Hover.svg</file>
|
||||
@@ -18,6 +19,7 @@
|
||||
<file>Linux.svg</file>
|
||||
<file>macOS.svg</file>
|
||||
<file>DefaultProjectImage.png</file>
|
||||
<file>DefaultTemplate.png</file>
|
||||
<file>ArrowDownLine.svg</file>
|
||||
<file>ArrowUpLine.svg</file>
|
||||
<file>o3de.svg</file>
|
||||
@@ -26,5 +28,8 @@
|
||||
<file>Backgrounds/FirstTimeBackgroundImage.jpg</file>
|
||||
<file>ArrowDownLine.svg</file>
|
||||
<file>ArrowUpLine.svg</file>
|
||||
<file>CarrotArrowDown.svg</file>
|
||||
<file>Summary.svg</file>
|
||||
<file>WindowClose.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -115,7 +115,7 @@ QTabBar::tab:pressed
|
||||
/************** General (Modal windows) **************/
|
||||
|
||||
#header {
|
||||
background-color:#111111;
|
||||
background-color:#111111;
|
||||
min-height:80px;
|
||||
max-height:80px;
|
||||
}
|
||||
@@ -136,11 +136,9 @@ QTabBar::tab:pressed
|
||||
#header QPushButton:focus {
|
||||
border:none;
|
||||
}
|
||||
#header QPushButton:hover {
|
||||
background:#333333 url(:/ArrowBack.svg) no-repeat center;
|
||||
}
|
||||
#header QPushButton:hover,
|
||||
#header QPushButton:pressed {
|
||||
background:#222222 url(:/ArrowBack.svg) no-repeat center;
|
||||
background:transparent url(:/ArrowBack_Hover.svg) no-repeat center;
|
||||
}
|
||||
|
||||
#headerTitle {
|
||||
@@ -172,8 +170,8 @@ QTabBar::tab:pressed
|
||||
|
||||
#footer > QPushButton {
|
||||
qproperty-flat: true;
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #0095f2, stop: 1.0 #1e70eb);
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #0095f2, stop: 1.0 #1e70eb);
|
||||
border-radius: 3px;
|
||||
min-height: 28px;
|
||||
max-height: 28px;
|
||||
@@ -181,26 +179,26 @@ QTabBar::tab:pressed
|
||||
margin-right:30px;
|
||||
}
|
||||
#footer > QPushButton:hover {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #10A5f2, stop: 1.0 #2e80eb);
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #10A5f2, stop: 1.0 #2e80eb);
|
||||
}
|
||||
#footer > QPushButton:pressed {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #0085e2, stop: 1.0 #0e60db);
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #0085e2, stop: 1.0 #0e60db);
|
||||
}
|
||||
|
||||
#footer > QPushButton[secondary="true"] {
|
||||
margin-right: 10px;
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #888888, stop: 1.0 #555555);
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #888888, stop: 1.0 #555555);
|
||||
}
|
||||
#footer > QPushButton[secondary="true"]:hover {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #999999, stop: 1.0 #666666);
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #999999, stop: 1.0 #666666);
|
||||
}
|
||||
#footer > QPushButton[secondary="true"]:pressed {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #555555, stop: 1.0 #777777);
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #555555, stop: 1.0 #777777);
|
||||
}
|
||||
|
||||
/************** Project Settings **************/
|
||||
@@ -210,9 +208,6 @@ QTabBar::tab:pressed
|
||||
|
||||
#projectTemplate {
|
||||
margin: 55px 0 0 50px;
|
||||
max-width: 780px;
|
||||
min-height:200px;
|
||||
max-height:200px;
|
||||
}
|
||||
#projectTemplateLabel {
|
||||
font-size:16px;
|
||||
@@ -227,11 +222,67 @@ QTabBar::tab:pressed
|
||||
|
||||
#projectTemplateDetails {
|
||||
background-color:#444444;
|
||||
max-width:240px;
|
||||
max-width:20%;
|
||||
min-width:240px;
|
||||
margin-left:30px;
|
||||
}
|
||||
|
||||
#projectTemplateDetails #displayName,
|
||||
#projectTemplateDetails #includedGemsTitle {
|
||||
font-size:18px;
|
||||
}
|
||||
|
||||
#projectTemplateDetails #moreGems {
|
||||
font-size:14px;
|
||||
margin-top:20px;
|
||||
}
|
||||
|
||||
#projectTemplateDetails #includedGemsTitle {
|
||||
margin-top:5px;
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
#projectTemplateDetails #summary {
|
||||
padding-bottom:0px;
|
||||
border-bottom:2px solid #555555;
|
||||
min-height:80px;
|
||||
qproperty-alignment: AlignTop;
|
||||
}
|
||||
|
||||
#projectTemplateDetails #browseCatalog {
|
||||
margin:5px 0px 15px 0px;
|
||||
}
|
||||
|
||||
#projectTemplate QPushButton {
|
||||
qproperty-flat: true;
|
||||
min-width: 96px;
|
||||
max-width: 96px;
|
||||
min-height: 160px;
|
||||
max-height: 160px;
|
||||
}
|
||||
#projectTemplate #templateLabel {
|
||||
qproperty-alignment: AlignCenter;
|
||||
}
|
||||
#projectTemplate QPushButton #templateImage {
|
||||
border:3px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
#projectTemplate QPushButton[Checked="true"] #templateImage {
|
||||
border:3px solid #1e70eb;
|
||||
}
|
||||
#projectTemplate QPushButton[Checked="true"] #templateLabel {
|
||||
font-weight:bold;
|
||||
}
|
||||
#projectTemplate QPushButton:hover {
|
||||
background-color: #444444;
|
||||
}
|
||||
#projectTemplate QPushButton:focus {
|
||||
outline: none;
|
||||
border:none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#projectSettingsTab::tab-bar {
|
||||
left: 60px;
|
||||
}
|
||||
@@ -240,6 +291,30 @@ QTabBar::tab:pressed
|
||||
height:50px;
|
||||
}
|
||||
|
||||
#projectSettingsTab::tab-bar > QPushButton {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #888888, stop: 1.0 #555555);
|
||||
qproperty-flat: true;
|
||||
margin-right:30px;
|
||||
margin-bottom:12px;
|
||||
margin-top:0px;
|
||||
min-width:170px;
|
||||
max-width:170px;
|
||||
min-height:26px;
|
||||
max-height:26px;
|
||||
border-radius: 3px;
|
||||
text-align:center;
|
||||
font-size:13px;
|
||||
}
|
||||
#projectSettingsTab::tab-bar > QPushButton:hover {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #999999, stop: 1.0 #666666);
|
||||
}
|
||||
#projectSettingsTab::tab-bar > QPushButton:pressed {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #555555, stop: 1.0 #777777);
|
||||
}
|
||||
|
||||
#projectSettingsTopFrame {
|
||||
background-color:#1E252F;
|
||||
}
|
||||
@@ -311,6 +386,7 @@ QTabBar::tab:pressed
|
||||
#projectButton > #labelButton {
|
||||
border:1px solid white;
|
||||
}
|
||||
|
||||
#projectButton > #labelButton:hover,
|
||||
#projectButton > #labelButton:pressed {
|
||||
border:1px solid #1e70eb;
|
||||
@@ -350,12 +426,38 @@ QTabBar::tab:pressed
|
||||
max-height:278px;
|
||||
}
|
||||
|
||||
QProgressBar {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
padding: 0px;
|
||||
min-height: 14px;
|
||||
font-size: 2px;
|
||||
}
|
||||
|
||||
QProgressBar::chunk {
|
||||
background-color: #1E70EB;
|
||||
}
|
||||
|
||||
/************** Gem Catalog **************/
|
||||
|
||||
#GemCatalogTitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#GemCatalogCart {
|
||||
background-color: #555555;
|
||||
}
|
||||
|
||||
#GemCatalogCartCountLabel {
|
||||
font-size: 12px;
|
||||
background-color: #4285F4;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#GemCatalogCartOverlaySectionLabel {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/************** Gem Catalog (Inspector) **************/
|
||||
|
||||
#GemCatalogInspector {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5714 6.7619H16.7619C16.7619 4.13333 14.6286 2 12 2C9.37145 2 7.23811 4.13333 7.23811 6.7619H3.42859V22H20.5714V6.7619ZM12 3.90476C13.581 3.90476 14.8572 5.18095 14.8572 6.7619H9.14287C9.14287 5.18095 10.4191 3.90476 12 3.90476ZM13.7997 11.7424L12.0288 9.24095H11.9712L10.2003 11.7424H13.7997ZM5.00002 12.4335L10.9158 19.4063L9.03253 12.4335H5.00002ZM10.14 12.4335L11.9356 19.5147C11.9548 19.5189 11.974 19.521 11.9959 19.521C12.0178 19.521 12.0398 19.5189 12.0617 19.5147L13.86 12.4335H10.14ZM14.9675 12.4335H18.8547H19L13.0842 19.398L14.9675 12.4335ZM8.26457 9.24095H11.075L9.52787 11.4253L8.26457 9.24095ZM8.83789 11.7424L7.5193 9.46191L5.01921 11.7424H5.14804H8.83789ZM18.9808 11.7424H18.8547H15.1512L16.4286 9.41397L18.9808 11.7424ZM15.6558 9.24095H12.9252L14.4647 11.4146L15.6558 9.24095Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 966 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5507 19.5506L19.5213 2.58008L21.289 4.34785L4.31846 21.3184L2.5507 19.5506Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.28706 2.65015L21.2576 19.6207L19.4899 21.3885L2.51929 4.41791L4.28706 2.65015Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 394 B |
@@ -15,6 +15,7 @@
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <NewProjectSettingsScreen.h>
|
||||
#include <ScreenHeaderWidget.h>
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHBoxLayout>
|
||||
@@ -41,24 +42,33 @@ namespace O3DE::ProjectManager
|
||||
|
||||
m_stack = new QStackedWidget(this);
|
||||
m_stack->setObjectName("body");
|
||||
m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
|
||||
m_stack->addWidget(new NewProjectSettingsScreen());
|
||||
m_gemCatalog = new GemCatalogScreen();
|
||||
m_stack->addWidget(m_gemCatalog);
|
||||
m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred,QSizePolicy::Expanding));
|
||||
|
||||
m_newProjectSettingsScreen = new NewProjectSettingsScreen(this);
|
||||
m_stack->addWidget(m_newProjectSettingsScreen);
|
||||
|
||||
m_gemCatalogScreen = new GemCatalogScreen(this);
|
||||
m_stack->addWidget(m_gemCatalogScreen);
|
||||
vLayout->addWidget(m_stack);
|
||||
|
||||
QDialogButtonBox* backNextButtons = new QDialogButtonBox();
|
||||
backNextButtons->setObjectName("footer");
|
||||
vLayout->addWidget(backNextButtons);
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox();
|
||||
buttons->setObjectName("footer");
|
||||
vLayout->addWidget(buttons);
|
||||
|
||||
m_backButton = backNextButtons->addButton(tr("Back"), QDialogButtonBox::RejectRole);
|
||||
m_backButton->setProperty("secondary", true);
|
||||
m_nextButton = backNextButtons->addButton(tr("Next"), QDialogButtonBox::ApplyRole);
|
||||
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
connect(m_newProjectSettingsScreen, &ScreenWidget::ChangeScreenRequest, this, &CreateProjectCtrl::OnChangeScreenRequest);
|
||||
|
||||
connect(m_backButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandleBackButton);
|
||||
connect(m_nextButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandleNextButton);
|
||||
m_secondaryButton = buttons->addButton(tr("Back"), QDialogButtonBox::RejectRole);
|
||||
m_secondaryButton->setProperty("secondary", true);
|
||||
m_secondaryButton->setVisible(false);
|
||||
connect(m_secondaryButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandleSecondaryButton);
|
||||
|
||||
Update();
|
||||
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
|
||||
m_primaryButton = buttons->addButton(tr("Create Project"), QDialogButtonBox::ApplyRole);
|
||||
connect(m_primaryButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandlePrimaryButton);
|
||||
|
||||
setLayout(vLayout);
|
||||
}
|
||||
|
||||
@@ -80,8 +90,10 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
if (m_stack->currentIndex() > 0)
|
||||
{
|
||||
m_stack->setCurrentIndex(m_stack->currentIndex() - 1);
|
||||
Update();
|
||||
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
PreviousScreen();
|
||||
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -89,70 +101,123 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::HandleNextButton()
|
||||
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
void CreateProjectCtrl::HandleSecondaryButton()
|
||||
{
|
||||
ScreenWidget* currentScreen = reinterpret_cast<ScreenWidget*>(m_stack->currentWidget());
|
||||
ProjectManagerScreen screenEnum = currentScreen->GetScreenEnum();
|
||||
|
||||
if (screenEnum == ProjectManagerScreen::NewProjectSettings)
|
||||
if (m_stack->currentIndex() > 0)
|
||||
{
|
||||
auto newProjectScreen = reinterpret_cast<NewProjectSettingsScreen*>(currentScreen);
|
||||
if (newProjectScreen)
|
||||
{
|
||||
if (!newProjectScreen->Validate())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings"));
|
||||
return;
|
||||
}
|
||||
|
||||
m_projectInfo = newProjectScreen->GetProjectInfo();
|
||||
m_projectTemplatePath = newProjectScreen->GetProjectTemplatePath();
|
||||
|
||||
// The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog.
|
||||
m_gemCatalog->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/true);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_stack->currentIndex() != m_stack->count() - 1)
|
||||
{
|
||||
m_stack->setCurrentIndex(m_stack->currentIndex() + 1);
|
||||
Update();
|
||||
// return to Project Settings page
|
||||
PreviousScreen();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto result = PythonBindingsInterface::Get()->CreateProject(m_projectTemplatePath, m_projectInfo);
|
||||
// Configure Gems
|
||||
NextScreen();
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::Update()
|
||||
{
|
||||
if (m_stack->currentWidget() == m_gemCatalogScreen)
|
||||
{
|
||||
m_header->setSubTitle(tr("Configure project with Gems"));
|
||||
m_secondaryButton->setVisible(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_header->setSubTitle(tr("Enter Project Details"));
|
||||
m_secondaryButton->setVisible(true);
|
||||
m_secondaryButton->setText(tr("Configure Gems"));
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::OnChangeScreenRequest(ProjectManagerScreen screen)
|
||||
{
|
||||
if (screen == ProjectManagerScreen::GemCatalog)
|
||||
{
|
||||
HandleSecondaryButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
emit ChangeScreenRequest(screen);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::NextScreen()
|
||||
{
|
||||
if (m_stack->currentIndex() < m_stack->count())
|
||||
{
|
||||
if(CurrentScreenIsValid())
|
||||
{
|
||||
m_stack->setCurrentIndex(m_stack->currentIndex() + 1);
|
||||
|
||||
QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
|
||||
m_gemCatalogScreen->ReinitForProject(projectTemplatePath + "/Template", /*isNewProject=*/true);
|
||||
|
||||
Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::warning(this, tr("Invalid project settings"), tr("Please correct the indicated project settings and try again."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::PreviousScreen()
|
||||
{
|
||||
// we don't require the current screen to be valid when moving back
|
||||
if (m_stack->currentIndex() > 0)
|
||||
{
|
||||
m_stack->setCurrentIndex(m_stack->currentIndex() - 1);
|
||||
Update();
|
||||
}
|
||||
}
|
||||
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
|
||||
void CreateProjectCtrl::HandlePrimaryButton()
|
||||
{
|
||||
CreateProject();
|
||||
}
|
||||
|
||||
bool CreateProjectCtrl::CurrentScreenIsValid()
|
||||
{
|
||||
if (m_stack->currentWidget() == m_newProjectSettingsScreen)
|
||||
{
|
||||
return m_newProjectSettingsScreen->Validate();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::CreateProject()
|
||||
{
|
||||
if (m_newProjectSettingsScreen->Validate())
|
||||
{
|
||||
ProjectInfo projectInfo = m_newProjectSettingsScreen->GetProjectInfo();
|
||||
QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
|
||||
|
||||
auto result = PythonBindingsInterface::Get()->CreateProject(projectTemplatePath, projectInfo);
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
// automatically register the project
|
||||
PythonBindingsInterface::Get()->AddProject(m_projectInfo.m_path);
|
||||
PythonBindingsInterface::Get()->AddProject(projectInfo.m_path);
|
||||
|
||||
// adding gems is not implemented yet because we don't know what targets to add or how to add them
|
||||
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
m_gemCatalogScreen->EnableDisableGemsForProject(projectInfo.m_path);
|
||||
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
|
||||
projectInfo.m_needsBuild = true;
|
||||
emit NotifyBuildProject(projectInfo);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("Project creation failed"), tr("Failed to create project."));
|
||||
}
|
||||
|
||||
// Enable/disable gems for the newly created project.
|
||||
m_gemCatalog->EnableDisableGemsForProject(m_projectInfo.m_path);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::Update()
|
||||
{
|
||||
ScreenWidget* currentScreen = reinterpret_cast<ScreenWidget*>(m_stack->currentWidget());
|
||||
if (currentScreen && currentScreen->GetScreenEnum() == ProjectManagerScreen::GemCatalog)
|
||||
{
|
||||
m_header->setTitle(tr("Create Project"));
|
||||
m_header->setSubTitle(tr("Configure project with Gems"));
|
||||
m_nextButton->setText(tr("Create Project"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_header->setTitle(tr("Create Project"));
|
||||
m_header->setSubTitle(tr("Enter Project Details"));
|
||||
m_nextButton->setText(tr("Next"));
|
||||
QMessageBox::warning(this, tr("Invalid project settings"), tr("Please correct the indicated project settings and try again."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenWidget.h>
|
||||
#include <ProjectInfo.h>
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
#endif
|
||||
|
||||
// due to current limitations, customizing template Gems is disabled
|
||||
#define TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QStackedWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(QPushButton)
|
||||
QT_FORWARD_DECLARE_CLASS(QLabel)
|
||||
@@ -24,6 +26,8 @@ QT_FORWARD_DECLARE_CLASS(QLabel)
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(ScreenHeader)
|
||||
QT_FORWARD_DECLARE_CLASS(NewProjectSettingsScreen)
|
||||
QT_FORWARD_DECLARE_CLASS(GemCatalogScreen)
|
||||
|
||||
class CreateProjectCtrl
|
||||
: public ScreenWidget
|
||||
@@ -36,21 +40,37 @@ namespace O3DE::ProjectManager
|
||||
|
||||
protected slots:
|
||||
void HandleBackButton();
|
||||
void HandleNextButton();
|
||||
void HandlePrimaryButton();
|
||||
|
||||
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
void OnChangeScreenRequest(ProjectManagerScreen screen);
|
||||
void HandleSecondaryButton();
|
||||
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
|
||||
private:
|
||||
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
void Update();
|
||||
void NextScreen();
|
||||
void PreviousScreen();
|
||||
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
|
||||
QStackedWidget* m_stack;
|
||||
ScreenHeader* m_header;
|
||||
bool CurrentScreenIsValid();
|
||||
void CreateProject();
|
||||
|
||||
QPushButton* m_backButton;
|
||||
QPushButton* m_nextButton;
|
||||
QStackedWidget* m_stack = nullptr;
|
||||
ScreenHeader* m_header = nullptr;
|
||||
|
||||
QPushButton* m_primaryButton = nullptr;
|
||||
|
||||
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
QPushButton* m_secondaryButton = nullptr;
|
||||
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
|
||||
QString m_projectTemplatePath;
|
||||
ProjectInfo m_projectInfo;
|
||||
|
||||
GemCatalogScreen* m_gemCatalog = nullptr;
|
||||
|
||||
NewProjectSettingsScreen* m_newProjectSettingsScreen = nullptr;
|
||||
GemCatalogScreen* m_gemCatalogScreen = nullptr;
|
||||
};
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -10,24 +10,229 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzQtComponents/Components/SearchLineEdit.h>
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <TagWidget.h>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemSortFilterProxyModel* filterProxyModel, QWidget* parent)
|
||||
CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_gemModel(gemModel)
|
||||
{
|
||||
setObjectName("GemCatalogCart");
|
||||
|
||||
m_layout = new QVBoxLayout();
|
||||
m_layout->setSpacing(0);
|
||||
m_layout->setMargin(0);
|
||||
m_layout->setAlignment(Qt::AlignTop);
|
||||
setLayout(m_layout);
|
||||
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
|
||||
QPushButton* closeButton = new QPushButton();
|
||||
closeButton->setFlat(true);
|
||||
closeButton->setFocusPolicy(Qt::NoFocus);
|
||||
closeButton->setIcon(QIcon(":/WindowClose.svg"));
|
||||
connect(closeButton, &QPushButton::clicked, this, [=]
|
||||
{
|
||||
deleteLater();
|
||||
});
|
||||
hLayout->addSpacerItem(new QSpacerItem(10, 0, QSizePolicy::Expanding));
|
||||
hLayout->addWidget(closeButton);
|
||||
m_layout->addLayout(hLayout);
|
||||
|
||||
// enabled
|
||||
{
|
||||
m_enabledWidget = new QWidget();
|
||||
m_enabledWidget->setFixedWidth(s_width);
|
||||
m_layout->addWidget(m_enabledWidget);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
m_enabledWidget->setLayout(layout);
|
||||
|
||||
m_enabledLabel = new QLabel();
|
||||
m_enabledLabel->setObjectName("GemCatalogCartOverlaySectionLabel");
|
||||
layout->addWidget(m_enabledLabel);
|
||||
m_enabledTagContainer = new TagContainerWidget();
|
||||
layout->addWidget(m_enabledTagContainer);
|
||||
}
|
||||
|
||||
// disabled
|
||||
{
|
||||
m_disabledWidget = new QWidget();
|
||||
m_disabledWidget->setFixedWidth(s_width);
|
||||
m_layout->addWidget(m_disabledWidget);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
m_disabledWidget->setLayout(layout);
|
||||
|
||||
m_disabledLabel = new QLabel();
|
||||
m_disabledLabel->setObjectName("GemCatalogCartOverlaySectionLabel");
|
||||
layout->addWidget(m_disabledLabel);
|
||||
m_disabledTagContainer = new TagContainerWidget();
|
||||
layout->addWidget(m_disabledTagContainer);
|
||||
}
|
||||
|
||||
setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
|
||||
|
||||
Update();
|
||||
connect(gemModel, &GemModel::dataChanged, this, [=]
|
||||
{
|
||||
Update();
|
||||
});
|
||||
}
|
||||
|
||||
void CartOverlayWidget::Update()
|
||||
{
|
||||
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded();
|
||||
if (toBeAdded.isEmpty())
|
||||
{
|
||||
m_enabledWidget->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_enabledTagContainer->Update(ConvertFromModelIndices(toBeAdded));
|
||||
m_enabledLabel->setText(QString("%1 %2").arg(QString::number(toBeAdded.size()), tr("Gems to be enabled")));
|
||||
m_enabledWidget->show();
|
||||
}
|
||||
|
||||
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved();
|
||||
if (toBeRemoved.isEmpty())
|
||||
{
|
||||
m_disabledWidget->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_disabledTagContainer->Update(ConvertFromModelIndices(toBeRemoved));
|
||||
m_disabledLabel->setText(QString("%1 %2").arg(QString::number(toBeRemoved.size()), tr("Gems to be disabled")));
|
||||
m_disabledWidget->show();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList CartOverlayWidget::ConvertFromModelIndices(const QVector<QModelIndex>& gems) const
|
||||
{
|
||||
QStringList gemNames;
|
||||
gemNames.reserve(gems.size());
|
||||
for (const QModelIndex& modelIndex : gems)
|
||||
{
|
||||
gemNames.push_back(GemModel::GetName(modelIndex));
|
||||
}
|
||||
return gemNames;
|
||||
}
|
||||
|
||||
CartButton::CartButton(GemModel* gemModel, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_gemModel(gemModel)
|
||||
{
|
||||
m_layout = new QHBoxLayout();
|
||||
m_layout->setMargin(0);
|
||||
setLayout(m_layout);
|
||||
|
||||
QPushButton* iconButton = new QPushButton();
|
||||
iconButton->setFlat(true);
|
||||
iconButton->setFocusPolicy(Qt::NoFocus);
|
||||
iconButton->setIcon(QIcon(":/Summary.svg"));
|
||||
iconButton->setFixedSize(s_iconSize, s_iconSize);
|
||||
connect(iconButton, &QPushButton::clicked, this, &CartButton::ShowOverlay);
|
||||
m_layout->addWidget(iconButton);
|
||||
|
||||
m_countLabel = new QLabel();
|
||||
m_countLabel->setObjectName("GemCatalogCartCountLabel");
|
||||
m_countLabel->setFixedHeight(s_iconSize - 1); // Compensate for the empty icon space by using a slightly smaller label height.
|
||||
m_layout->addWidget(m_countLabel);
|
||||
|
||||
m_dropDownButton = new QPushButton();
|
||||
m_dropDownButton->setFlat(true);
|
||||
m_dropDownButton->setFocusPolicy(Qt::NoFocus);
|
||||
m_dropDownButton->setIcon(QIcon(":/CarrotArrowDown.svg"));
|
||||
m_dropDownButton->setFixedSize(s_arrowDownIconSize, s_arrowDownIconSize);
|
||||
connect(m_dropDownButton, &QPushButton::clicked, this, &CartButton::ShowOverlay);
|
||||
m_layout->addWidget(m_dropDownButton);
|
||||
|
||||
// Adjust the label text whenever the model gets updated.
|
||||
connect(gemModel, &GemModel::dataChanged, [=]
|
||||
{
|
||||
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded();
|
||||
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved();
|
||||
|
||||
const int count = toBeAdded.size() + toBeRemoved.size();
|
||||
m_countLabel->setText(QString::number(count));
|
||||
|
||||
m_dropDownButton->setVisible(!toBeAdded.isEmpty() || !toBeRemoved.isEmpty());
|
||||
|
||||
// Automatically close the overlay window in case there are no gems to be enabled or disabled anymore.
|
||||
if (m_cartOverlay && toBeAdded.isEmpty() && toBeRemoved.isEmpty())
|
||||
{
|
||||
m_cartOverlay->deleteLater();
|
||||
m_cartOverlay = nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CartButton::mousePressEvent([[maybe_unused]] QMouseEvent* event)
|
||||
{
|
||||
ShowOverlay();
|
||||
}
|
||||
|
||||
void CartButton::ShowOverlay()
|
||||
{
|
||||
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded();
|
||||
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved();
|
||||
if (toBeAdded.isEmpty() && toBeRemoved.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_cartOverlay)
|
||||
{
|
||||
// Directly delete the former overlay before creating the new one.
|
||||
// Don't use deleteLater() here. This might overwrite the new overlay pointer
|
||||
// depending on the event queue.
|
||||
delete m_cartOverlay;
|
||||
}
|
||||
|
||||
m_cartOverlay = new CartOverlayWidget(m_gemModel, this);
|
||||
connect(m_cartOverlay, &QWidget::destroyed, this, [=]
|
||||
{
|
||||
// Reset the overlay pointer on destruction to prevent dangling pointers.
|
||||
m_cartOverlay = nullptr;
|
||||
});
|
||||
m_cartOverlay->show();
|
||||
|
||||
const QPoint parentPos = m_dropDownButton->mapToParent(m_dropDownButton->pos());
|
||||
const QPoint globalPos = m_dropDownButton->mapToGlobal(m_dropDownButton->pos());
|
||||
const QPoint offset(-4, 10);
|
||||
m_cartOverlay->setGeometry(globalPos.x() - parentPos.x() - m_cartOverlay->width() + width() + offset.x(),
|
||||
globalPos.y() + offset.y(),
|
||||
m_cartOverlay->width(),
|
||||
m_cartOverlay->height());
|
||||
}
|
||||
|
||||
CartButton::~CartButton()
|
||||
{
|
||||
// Make sure the overlay window is automatically closed in case the gem catalog is destroyed.
|
||||
if (m_cartOverlay)
|
||||
{
|
||||
m_cartOverlay->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, QWidget* parent)
|
||||
: QFrame(parent)
|
||||
{
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
hLayout->setAlignment(Qt::AlignLeft);
|
||||
hLayout->setMargin(0);
|
||||
hLayout->setContentsMargins(10, 7, 10, 7);
|
||||
setLayout(hLayout);
|
||||
|
||||
setObjectName("GemCatalogHeaderWidget");
|
||||
|
||||
hLayout->addSpacing(7);
|
||||
setFixedHeight(s_height);
|
||||
|
||||
QLabel* titleLabel = new QLabel(tr("Gem Catalog"));
|
||||
titleLabel->setObjectName("GemCatalogTitle");
|
||||
@@ -35,17 +240,23 @@ namespace O3DE::ProjectManager
|
||||
|
||||
hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
|
||||
|
||||
AzQtComponents::SearchLineEdit* filterLineEdit = new AzQtComponents::SearchLineEdit();
|
||||
filterLineEdit->setStyleSheet("background-color: #DDDDDD;");
|
||||
connect(filterLineEdit, &QLineEdit::textChanged, this, [=](const QString& text)
|
||||
m_filterLineEdit = new AzQtComponents::SearchLineEdit();
|
||||
m_filterLineEdit->setStyleSheet("background-color: #DDDDDD;");
|
||||
connect(m_filterLineEdit, &QLineEdit::textChanged, this, [=](const QString& text)
|
||||
{
|
||||
filterProxyModel->SetSearchString(text);
|
||||
});
|
||||
hLayout->addWidget(filterLineEdit);
|
||||
hLayout->addWidget(m_filterLineEdit);
|
||||
|
||||
hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
|
||||
hLayout->addSpacerItem(new QSpacerItem(140, 0, QSizePolicy::Fixed));
|
||||
|
||||
setFixedHeight(60);
|
||||
hLayout->addSpacerItem(new QSpacerItem(75, 0, QSizePolicy::Fixed));
|
||||
|
||||
CartButton* cartButton = new CartButton(gemModel);
|
||||
hLayout->addWidget(cartButton);
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::ReinitForProject()
|
||||
{
|
||||
m_filterLineEdit->setText({});
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -13,19 +13,81 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <AzQtComponents/Components/SearchLineEdit.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <TagWidget.h>
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QDialog>
|
||||
#include <QMoveEvent>
|
||||
#include <QVBoxLayout>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class CartOverlayWidget
|
||||
: public QWidget
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
CartOverlayWidget(GemModel* gemModel, QWidget* parent = nullptr);
|
||||
void Update();
|
||||
|
||||
private:
|
||||
QStringList ConvertFromModelIndices(const QVector<QModelIndex>& gems) const;
|
||||
|
||||
QVBoxLayout* m_layout = nullptr;
|
||||
GemModel* m_gemModel = nullptr;
|
||||
|
||||
QWidget* m_enabledWidget = nullptr;
|
||||
QLabel* m_enabledLabel = nullptr;
|
||||
TagContainerWidget* m_enabledTagContainer = nullptr;
|
||||
|
||||
QWidget* m_disabledWidget = nullptr;
|
||||
QLabel* m_disabledLabel = nullptr;
|
||||
TagContainerWidget* m_disabledTagContainer = nullptr;
|
||||
|
||||
inline constexpr static int s_width = 240;
|
||||
};
|
||||
|
||||
class CartButton
|
||||
: public QWidget
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
CartButton(GemModel* gemModel, QWidget* parent = nullptr);
|
||||
~CartButton();
|
||||
void ShowOverlay();
|
||||
|
||||
private:
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
|
||||
GemModel* m_gemModel = nullptr;
|
||||
QHBoxLayout* m_layout = nullptr;
|
||||
QLabel* m_countLabel = nullptr;
|
||||
QPushButton* m_dropDownButton = nullptr;
|
||||
CartOverlayWidget* m_cartOverlay = nullptr;
|
||||
|
||||
inline constexpr static int s_iconSize = 24;
|
||||
inline constexpr static int s_arrowDownIconSize = 8;
|
||||
};
|
||||
|
||||
class GemCatalogHeaderWidget
|
||||
: public QFrame
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
explicit GemCatalogHeaderWidget(GemSortFilterProxyModel* filterProxyModel, QWidget* parent = nullptr);
|
||||
explicit GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, QWidget* parent = nullptr);
|
||||
~GemCatalogHeaderWidget() = default;
|
||||
|
||||
void ReinitForProject();
|
||||
|
||||
private:
|
||||
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
|
||||
inline constexpr static int s_height = 60;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <GemCatalog/GemListHeaderWidget.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <QVBoxLayout>
|
||||
@@ -35,8 +34,8 @@ namespace O3DE::ProjectManager
|
||||
vLayout->setSpacing(0);
|
||||
setLayout(vLayout);
|
||||
|
||||
GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(m_proxModel);
|
||||
vLayout->addWidget(headerWidget);
|
||||
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel);
|
||||
vLayout->addWidget(m_headerWidget);
|
||||
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
hLayout->setMargin(0);
|
||||
@@ -77,10 +76,11 @@ namespace O3DE::ProjectManager
|
||||
m_filterWidget->deleteLater();
|
||||
}
|
||||
|
||||
m_proxModel->ResetFilters();
|
||||
m_filterWidget = new GemFilterWidget(m_proxModel);
|
||||
m_filterWidgetLayout->addWidget(m_filterWidget);
|
||||
|
||||
m_proxModel->InvalidateFilter();
|
||||
m_headerWidget->ReinitForProject();
|
||||
|
||||
// Select the first entry after everything got correctly sized
|
||||
QTimer::singleShot(200, [=]{
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenWidget.h>
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <GemCatalog/GemFilterWidget.h>
|
||||
#include <GemCatalog/GemListView.h>
|
||||
#include <GemCatalog/GemInspector.h>
|
||||
@@ -40,6 +41,7 @@ namespace O3DE::ProjectManager
|
||||
GemListView* m_gemListView = nullptr;
|
||||
GemInspector* m_gemInspector = nullptr;
|
||||
GemModel* m_gemModel = nullptr;
|
||||
GemCatalogHeaderWidget* m_headerWidget = nullptr;
|
||||
GemSortFilterProxyModel* m_proxModel = nullptr;
|
||||
QVBoxLayout* m_filterWidgetLayout = nullptr;
|
||||
GemFilterWidget* m_filterWidget = nullptr;
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QFont standardFont(options.font);
|
||||
standardFont.setPixelSize(s_fontSize);
|
||||
QFontMetrics standardFontMetrics(standardFont);
|
||||
|
||||
painter->save();
|
||||
painter->setClipping(true);
|
||||
@@ -78,8 +79,10 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
|
||||
// Gem name
|
||||
const QString gemName = GemModel::GetName(modelIndex);
|
||||
QString gemName = GemModel::GetName(modelIndex);
|
||||
QFont gemNameFont(options.font);
|
||||
const int firstColumnMaxTextWidth = s_summaryStartX - 30;
|
||||
gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth);
|
||||
gemNameFont.setPixelSize(s_gemNameFontSize);
|
||||
gemNameFont.setBold(true);
|
||||
QRect gemNameRect = GetTextRect(gemNameFont, gemName, s_gemNameFontSize);
|
||||
@@ -90,7 +93,8 @@ namespace O3DE::ProjectManager
|
||||
painter->drawText(gemNameRect, Qt::TextSingleLine, gemName);
|
||||
|
||||
// Gem creator
|
||||
const QString gemCreator = GemModel::GetCreator(modelIndex);
|
||||
QString gemCreator = GemModel::GetCreator(modelIndex);
|
||||
gemCreator = standardFontMetrics.elidedText(gemCreator, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth);
|
||||
QRect gemCreatorRect = GetTextRect(standardFont, gemCreator, s_fontSize);
|
||||
gemCreatorRect.moveTo(contentRect.left(), contentRect.top() + gemNameRect.height());
|
||||
|
||||
|
||||
@@ -130,4 +130,15 @@ namespace O3DE::ProjectManager
|
||||
invalidate();
|
||||
emit OnInvalidated();
|
||||
}
|
||||
|
||||
void GemSortFilterProxyModel::ResetFilters()
|
||||
{
|
||||
m_searchString.clear();
|
||||
m_gemOriginFilter = {};
|
||||
m_platformFilter = {};
|
||||
m_typeFilter = {};
|
||||
m_featureFilter = {};
|
||||
|
||||
InvalidateFilter();
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace O3DE::ProjectManager
|
||||
void SetFeatures(const QSet<QString>& features) { m_featureFilter = features; InvalidateFilter(); }
|
||||
|
||||
void InvalidateFilter();
|
||||
void ResetFilters();
|
||||
|
||||
signals:
|
||||
void OnInvalidated();
|
||||
|
||||
@@ -14,8 +14,12 @@
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <FormLineEditWidget.h>
|
||||
#include <FormBrowseEditWidget.h>
|
||||
#include <TemplateButtonWidget.h>
|
||||
#include <PathValidator.h>
|
||||
#include <EngineInfo.h>
|
||||
#include <CreateProjectCtrl.h>
|
||||
#include <TagWidget.h>
|
||||
#include <AzQtComponents/Components/FlowLayout.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -28,10 +32,12 @@
|
||||
#include <QSpacerItem>
|
||||
#include <QStandardPaths>
|
||||
#include <QFrame>
|
||||
#include <QScrollArea>
|
||||
#include <QAbstractButton>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
constexpr const char* k_pathProperty = "Path";
|
||||
constexpr const char* k_templateIndexProperty = "TemplateIndex";
|
||||
|
||||
NewProjectSettingsScreen::NewProjectSettingsScreen(QWidget* parent)
|
||||
: ProjectSettingsScreen(parent)
|
||||
@@ -59,30 +65,69 @@ namespace O3DE::ProjectManager
|
||||
projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel");
|
||||
containerLayout->addWidget(projectTemplateDetailsLabel);
|
||||
|
||||
QHBoxLayout* templateLayout = new QHBoxLayout(this);
|
||||
containerLayout->addItem(templateLayout);
|
||||
|
||||
// we might have enough templates that we need to scroll
|
||||
QScrollArea* templatesScrollArea = new QScrollArea(this);
|
||||
QWidget* scrollWidget = new QWidget();
|
||||
|
||||
FlowLayout* flowLayout = new FlowLayout(0, s_spacerSize, s_spacerSize);
|
||||
scrollWidget->setLayout(flowLayout);
|
||||
|
||||
templatesScrollArea->setWidget(scrollWidget);
|
||||
templatesScrollArea->setWidgetResizable(true);
|
||||
|
||||
m_projectTemplateButtonGroup = new QButtonGroup(this);
|
||||
m_projectTemplateButtonGroup->setObjectName("templateButtonGroup");
|
||||
|
||||
// QButtonGroup has overloaded buttonClicked methods so we need the QOverload
|
||||
connect(
|
||||
m_projectTemplateButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
|
||||
[=](QAbstractButton* button)
|
||||
{
|
||||
if (button && button->property(k_templateIndexProperty).isValid())
|
||||
{
|
||||
int projectIndex = button->property(k_templateIndexProperty).toInt();
|
||||
UpdateTemplateDetails(m_templates.at(projectIndex));
|
||||
}
|
||||
});
|
||||
|
||||
auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates();
|
||||
if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty())
|
||||
{
|
||||
for (const ProjectTemplateInfo& projectTemplate : templatesResult.GetValue())
|
||||
{
|
||||
QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this);
|
||||
radioButton->setProperty(k_pathProperty, projectTemplate.m_path);
|
||||
m_projectTemplateButtonGroup->addButton(radioButton);
|
||||
m_templates = templatesResult.GetValue();
|
||||
|
||||
containerLayout->addWidget(radioButton);
|
||||
// sort alphabetically by display name because they could be in any order
|
||||
std::sort(m_templates.begin(), m_templates.end(), [](const ProjectTemplateInfo& arg1, const ProjectTemplateInfo& arg2)
|
||||
{
|
||||
return arg1.m_displayName.toLower() < arg2.m_displayName.toLower();
|
||||
});
|
||||
|
||||
for (int index = 0; index < m_templates.size(); ++index)
|
||||
{
|
||||
ProjectTemplateInfo projectTemplate = m_templates.at(index);
|
||||
QString projectPreviewPath = projectTemplate.m_path + "/Template/preview.png";
|
||||
QFileInfo doesPreviewExist(projectPreviewPath);
|
||||
if (!doesPreviewExist.exists() || !doesPreviewExist.isFile())
|
||||
{
|
||||
projectPreviewPath = ":/DefaultTemplate.png";
|
||||
}
|
||||
TemplateButton* templateButton = new TemplateButton(projectPreviewPath, projectTemplate.m_displayName, this);
|
||||
templateButton->setCheckable(true);
|
||||
templateButton->setProperty(k_templateIndexProperty, index);
|
||||
|
||||
m_projectTemplateButtonGroup->addButton(templateButton);
|
||||
|
||||
flowLayout->addWidget(templateButton);
|
||||
}
|
||||
|
||||
m_projectTemplateButtonGroup->buttons().first()->setChecked(true);
|
||||
}
|
||||
containerLayout->addWidget(templatesScrollArea);
|
||||
}
|
||||
projectTemplateWidget->setLayout(containerLayout);
|
||||
m_verticalLayout->addWidget(projectTemplateWidget);
|
||||
|
||||
QWidget* projectTemplateDetails = new QWidget(this);
|
||||
QFrame* projectTemplateDetails = CreateTemplateDetails(s_templateDetailsContentMargin);
|
||||
projectTemplateDetails->setObjectName("projectTemplateDetails");
|
||||
m_horizontalLayout->addWidget(projectTemplateDetails);
|
||||
}
|
||||
@@ -109,11 +154,71 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void NewProjectSettingsScreen::NotifyCurrentScreen()
|
||||
{
|
||||
if (!m_templates.isEmpty())
|
||||
{
|
||||
UpdateTemplateDetails(m_templates.first());
|
||||
}
|
||||
|
||||
Validate();
|
||||
}
|
||||
|
||||
QString NewProjectSettingsScreen::GetProjectTemplatePath()
|
||||
{
|
||||
return m_projectTemplateButtonGroup->checkedButton()->property(k_pathProperty).toString();
|
||||
const int templateIndex = m_projectTemplateButtonGroup->checkedButton()->property(k_templateIndexProperty).toInt();
|
||||
return m_templates.at(templateIndex).m_path;
|
||||
}
|
||||
|
||||
QFrame* NewProjectSettingsScreen::CreateTemplateDetails(int margin)
|
||||
{
|
||||
QFrame* projectTemplateDetails = new QFrame(this);
|
||||
projectTemplateDetails->setObjectName("projectTemplateDetails");
|
||||
QVBoxLayout* templateDetailsLayout = new QVBoxLayout();
|
||||
templateDetailsLayout->setContentsMargins(margin, margin, margin, margin);
|
||||
templateDetailsLayout->setAlignment(Qt::AlignTop);
|
||||
{
|
||||
m_templateDisplayName = new QLabel(this);
|
||||
m_templateDisplayName->setObjectName("displayName");
|
||||
templateDetailsLayout->addWidget(m_templateDisplayName);
|
||||
|
||||
m_templateSummary = new QLabel(this);
|
||||
m_templateSummary->setObjectName("summary");
|
||||
m_templateSummary->setWordWrap(true);
|
||||
templateDetailsLayout->addWidget(m_templateSummary);
|
||||
|
||||
QLabel* includedGemsTitle = new QLabel(tr("Included Gems"), this);
|
||||
includedGemsTitle->setObjectName("includedGemsTitle");
|
||||
templateDetailsLayout->addWidget(includedGemsTitle);
|
||||
|
||||
m_templateIncludedGems = new TagContainerWidget(this);
|
||||
m_templateIncludedGems->setObjectName("includedGems");
|
||||
templateDetailsLayout->addWidget(m_templateIncludedGems);
|
||||
|
||||
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
QLabel* moreGemsLabel = new QLabel(tr("Looking for more Gems?"), this);
|
||||
moreGemsLabel->setObjectName("moreGems");
|
||||
templateDetailsLayout->addWidget(moreGemsLabel);
|
||||
|
||||
QLabel* browseCatalogLabel = new QLabel(tr("Browse the Gems Catalog to further customize your project."), this);
|
||||
browseCatalogLabel->setObjectName("browseCatalog");
|
||||
browseCatalogLabel->setWordWrap(true);
|
||||
templateDetailsLayout->addWidget(browseCatalogLabel);
|
||||
|
||||
QPushButton* configureGemsButton = new QPushButton(tr("Configure with more Gems"), this);
|
||||
connect(configureGemsButton, &QPushButton::clicked, this, [=]()
|
||||
{
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
|
||||
});
|
||||
templateDetailsLayout->addWidget(configureGemsButton);
|
||||
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
}
|
||||
projectTemplateDetails->setLayout(templateDetailsLayout);
|
||||
return projectTemplateDetails;
|
||||
}
|
||||
|
||||
void NewProjectSettingsScreen::UpdateTemplateDetails(const ProjectTemplateInfo& templateInfo)
|
||||
{
|
||||
m_templateDisplayName->setText(templateInfo.m_displayName);
|
||||
m_templateSummary->setText(templateInfo.m_summary);
|
||||
m_templateIncludedGems->Update(templateInfo.m_includedGems);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -13,12 +13,17 @@
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ProjectSettingsScreen.h>
|
||||
#include <ProjectTemplateInfo.h>
|
||||
#include <QVector>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QButtonGroup)
|
||||
QT_FORWARD_DECLARE_CLASS(QLabel)
|
||||
QT_FORWARD_DECLARE_CLASS(QFrame)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(TagContainerWidget)
|
||||
class NewProjectSettingsScreen
|
||||
: public ProjectSettingsScreen
|
||||
{
|
||||
@@ -33,8 +38,17 @@ namespace O3DE::ProjectManager
|
||||
|
||||
private:
|
||||
QString GetDefaultProjectPath();
|
||||
QFrame* CreateTemplateDetails(int margin);
|
||||
void UpdateTemplateDetails(const ProjectTemplateInfo& templateInfo);
|
||||
|
||||
QButtonGroup* m_projectTemplateButtonGroup;
|
||||
QLabel* m_templateDisplayName;
|
||||
QLabel* m_templateSummary;
|
||||
TagContainerWidget* m_templateIncludedGems;
|
||||
QVector<ProjectTemplateInfo> m_templates;
|
||||
|
||||
inline constexpr static int s_spacerSize = 20;
|
||||
inline constexpr static int s_templateDetailsContentMargin = 20;
|
||||
};
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ProjectBuilder.h>
|
||||
#include <ProjectButtonWidget.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
|
||||
#include <QProcess>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QMessageBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
#include <QDir>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
|
||||
//#define MOCK_BUILD_PROJECT true
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
// 10 Minutes
|
||||
constexpr int MaxBuildTimeMSecs = 600000;
|
||||
static const QString BuildPathPostfix = "windows_vs2019";
|
||||
static const QString ErrorLogPathPostfix = "CMakeFiles/CMakeProjectBuildError.log";
|
||||
|
||||
ProjectBuilderWorker::ProjectBuilderWorker(const ProjectInfo& projectInfo)
|
||||
: QObject()
|
||||
, m_projectInfo(projectInfo)
|
||||
{
|
||||
}
|
||||
|
||||
void ProjectBuilderWorker::BuildProject()
|
||||
{
|
||||
#ifdef MOCK_BUILD_PROJECT
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
QThread::sleep(1);
|
||||
UpdateProgress(i * 10);
|
||||
}
|
||||
Done(m_projectPath);
|
||||
#else
|
||||
EngineInfo engineInfo;
|
||||
|
||||
AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
|
||||
if (engineInfoResult.IsSuccess())
|
||||
{
|
||||
engineInfo = engineInfoResult.GetValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
emit Done(tr("Failed to get engine info."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Show some kind of progress with very approximate estimates
|
||||
UpdateProgress(1);
|
||||
|
||||
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment());
|
||||
// Append cmake path to PATH incase it is missing
|
||||
QDir cmakePath(engineInfo.m_path);
|
||||
cmakePath.cd("cmake/runtime/bin");
|
||||
QString pathValue = currentEnvironment.value("PATH");
|
||||
pathValue += ";" + cmakePath.path();
|
||||
currentEnvironment.insert("PATH", pathValue);
|
||||
|
||||
QProcess configProjectProcess;
|
||||
configProjectProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||
configProjectProcess.setWorkingDirectory(m_projectInfo.m_path);
|
||||
configProjectProcess.setProcessEnvironment(currentEnvironment);
|
||||
|
||||
configProjectProcess.start(
|
||||
"cmake",
|
||||
QStringList
|
||||
{
|
||||
"-B",
|
||||
QDir(m_projectInfo.m_path).filePath(BuildPathPostfix),
|
||||
"-S",
|
||||
m_projectInfo.m_path,
|
||||
"-G",
|
||||
"Visual Studio 16",
|
||||
"-DLY_3RDPARTY_PATH=" + engineInfo.m_thirdPartyPath
|
||||
});
|
||||
|
||||
if (!configProjectProcess.waitForStarted())
|
||||
{
|
||||
emit Done(tr("Configuring project failed to start."));
|
||||
return;
|
||||
}
|
||||
if (!configProjectProcess.waitForFinished(MaxBuildTimeMSecs))
|
||||
{
|
||||
WriteErrorLog(configProjectProcess.readAllStandardOutput());
|
||||
emit Done(tr("Configuring project timed out. See log for details"));
|
||||
return;
|
||||
}
|
||||
|
||||
QString configProjectOutput(configProjectProcess.readAllStandardOutput());
|
||||
if (configProjectProcess.exitCode() != 0 || !configProjectOutput.contains("Generating done"))
|
||||
{
|
||||
WriteErrorLog(configProjectOutput);
|
||||
emit Done(tr("Configuring project failed. See log for details."));
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateProgress(20);
|
||||
|
||||
QProcess buildProjectProcess;
|
||||
buildProjectProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||
buildProjectProcess.setWorkingDirectory(m_projectInfo.m_path);
|
||||
buildProjectProcess.setProcessEnvironment(currentEnvironment);
|
||||
|
||||
buildProjectProcess.start(
|
||||
"cmake",
|
||||
QStringList
|
||||
{
|
||||
"--build",
|
||||
QDir(m_projectInfo.m_path).filePath(BuildPathPostfix),
|
||||
"--target",
|
||||
m_projectInfo.m_projectName + ".GameLauncher",
|
||||
"Editor",
|
||||
"--config",
|
||||
"profile"
|
||||
});
|
||||
|
||||
if (!buildProjectProcess.waitForStarted())
|
||||
{
|
||||
emit Done(tr("Building project failed to start."));
|
||||
return;
|
||||
}
|
||||
if (!buildProjectProcess.waitForFinished(MaxBuildTimeMSecs))
|
||||
{
|
||||
WriteErrorLog(configProjectProcess.readAllStandardOutput());
|
||||
emit Done(tr("Building project timed out. See log for details"));
|
||||
return;
|
||||
}
|
||||
|
||||
QString buildProjectOutput(buildProjectProcess.readAllStandardOutput());
|
||||
if (configProjectProcess.exitCode() != 0)
|
||||
{
|
||||
WriteErrorLog(buildProjectOutput);
|
||||
emit Done(tr("Building project failed. See log for details."));
|
||||
}
|
||||
else
|
||||
{
|
||||
emit Done("");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QString ProjectBuilderWorker::LogFilePath() const
|
||||
{
|
||||
QDir logFilePath(m_projectInfo.m_path);
|
||||
logFilePath.cd(BuildPathPostfix);
|
||||
return logFilePath.filePath(ErrorLogPathPostfix);
|
||||
}
|
||||
|
||||
void ProjectBuilderWorker::WriteErrorLog(const QString& log)
|
||||
{
|
||||
QFile logFile(LogFilePath());
|
||||
// Overwrite file with truncate
|
||||
if (logFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
|
||||
{
|
||||
QTextStream output(&logFile);
|
||||
output << log;
|
||||
logFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
ProjectBuilderController::ProjectBuilderController(const ProjectInfo& projectInfo, ProjectButton* projectButton, QWidget* parent)
|
||||
: QObject()
|
||||
, m_projectInfo(projectInfo)
|
||||
, m_projectButton(projectButton)
|
||||
, m_parent(parent)
|
||||
{
|
||||
m_worker = new ProjectBuilderWorker(m_projectInfo);
|
||||
m_worker->moveToThread(&m_workerThread);
|
||||
|
||||
connect(&m_workerThread, &QThread::finished, m_worker, &ProjectBuilderWorker::deleteLater);
|
||||
connect(&m_workerThread, &QThread::started, m_worker, &ProjectBuilderWorker::BuildProject);
|
||||
connect(m_worker, &ProjectBuilderWorker::Done, this, &ProjectBuilderController::HandleResults);
|
||||
connect(m_worker, &ProjectBuilderWorker::UpdateProgress, this, &ProjectBuilderController::UpdateUIProgress);
|
||||
}
|
||||
|
||||
ProjectBuilderController::~ProjectBuilderController()
|
||||
{
|
||||
m_workerThread.quit();
|
||||
m_workerThread.wait();
|
||||
}
|
||||
|
||||
void ProjectBuilderController::Start()
|
||||
{
|
||||
m_workerThread.start();
|
||||
UpdateUIProgress(0);
|
||||
}
|
||||
|
||||
void ProjectBuilderController::SetProjectButton(ProjectButton* projectButton)
|
||||
{
|
||||
m_projectButton = projectButton;
|
||||
}
|
||||
|
||||
QString ProjectBuilderController::GetProjectPath() const
|
||||
{
|
||||
return m_projectInfo.m_path;
|
||||
}
|
||||
|
||||
void ProjectBuilderController::UpdateUIProgress(int progress)
|
||||
{
|
||||
if (m_projectButton)
|
||||
{
|
||||
m_projectButton->SetButtonOverlayText(QString("%1 (%2%)\n\n").arg(tr("Building Project..."), QString::number(progress)));
|
||||
m_projectButton->SetProgressBarValue(progress);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectBuilderController::HandleResults(const QString& result)
|
||||
{
|
||||
if (!result.isEmpty())
|
||||
{
|
||||
if (result.contains(tr("log")))
|
||||
{
|
||||
QMessageBox::StandardButton openLog = QMessageBox::critical(
|
||||
m_parent,
|
||||
tr("Project Failed to Build!"),
|
||||
result + tr("\n\nWould you like to view log?"),
|
||||
QMessageBox::No | QMessageBox::Yes);
|
||||
|
||||
if (openLog == QMessageBox::Yes)
|
||||
{
|
||||
// Open application assigned to this file type
|
||||
QDesktopServices::openUrl(QUrl("file:///" + m_worker->LogFilePath()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(m_parent, tr("Project Failed to Build!"), result);
|
||||
}
|
||||
}
|
||||
|
||||
emit Done();
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ProjectInfo.h>
|
||||
|
||||
#include <QThread>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(ProjectButton)
|
||||
|
||||
class ProjectBuilderWorker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ProjectBuilderWorker(const ProjectInfo& projectInfo);
|
||||
~ProjectBuilderWorker() = default;
|
||||
|
||||
QString LogFilePath() const;
|
||||
|
||||
public slots:
|
||||
void BuildProject();
|
||||
|
||||
signals:
|
||||
void UpdateProgress(int progress);
|
||||
void Done(QString result);
|
||||
|
||||
private:
|
||||
void WriteErrorLog(const QString& log);
|
||||
|
||||
ProjectInfo m_projectInfo;
|
||||
};
|
||||
|
||||
class ProjectBuilderController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ProjectBuilderController(const ProjectInfo& projectInfo, ProjectButton* projectButton, QWidget* parent = nullptr);
|
||||
~ProjectBuilderController();
|
||||
|
||||
void SetProjectButton(ProjectButton* projectButton);
|
||||
QString GetProjectPath() const;
|
||||
|
||||
public slots:
|
||||
void Start();
|
||||
void UpdateUIProgress(int progress);
|
||||
void HandleResults(const QString& result);
|
||||
|
||||
signals:
|
||||
void Done();
|
||||
|
||||
private:
|
||||
ProjectInfo m_projectInfo;
|
||||
ProjectBuilderWorker* m_worker;
|
||||
QThread m_workerThread;
|
||||
ProjectButton* m_projectButton;
|
||||
QWidget* m_parent;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
#include <ProjectButtonWidget.h>
|
||||
|
||||
#include <AzQtComponents/Utilities/DesktopUtilities.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <QPixmap>
|
||||
#include <QMenu>
|
||||
#include <QSpacerItem>
|
||||
#include <QProgressBar>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -31,11 +32,26 @@ namespace O3DE::ProjectManager
|
||||
: QLabel(parent)
|
||||
{
|
||||
setObjectName("labelButton");
|
||||
|
||||
QVBoxLayout* vLayout = new QVBoxLayout(this);
|
||||
vLayout->setContentsMargins(0, 0, 0, 0);
|
||||
vLayout->setSpacing(5);
|
||||
|
||||
setLayout(vLayout);
|
||||
m_overlayLabel = new QLabel("", this);
|
||||
m_overlayLabel->setObjectName("labelButtonOverlay");
|
||||
m_overlayLabel->setWordWrap(true);
|
||||
m_overlayLabel->setAlignment(Qt::AlignCenter);
|
||||
m_overlayLabel->setVisible(false);
|
||||
vLayout->addWidget(m_overlayLabel);
|
||||
|
||||
m_buildButton = new QPushButton(tr("Build Project"), this);
|
||||
m_buildButton->setVisible(false);
|
||||
|
||||
m_progressBar = new QProgressBar(this);
|
||||
m_progressBar->setObjectName("labelButtonProgressBar");
|
||||
m_progressBar->setVisible(false);
|
||||
vLayout->addWidget(m_progressBar);
|
||||
}
|
||||
|
||||
void LabelButton::mousePressEvent([[maybe_unused]] QMouseEvent* event)
|
||||
@@ -57,7 +73,22 @@ namespace O3DE::ProjectManager
|
||||
m_overlayLabel->setText(text);
|
||||
}
|
||||
|
||||
ProjectButton::ProjectButton(const ProjectInfo& projectInfo, QWidget* parent)
|
||||
QLabel* LabelButton::GetOverlayLabel()
|
||||
{
|
||||
return m_overlayLabel;
|
||||
}
|
||||
|
||||
QProgressBar* LabelButton::GetProgressBar()
|
||||
{
|
||||
return m_progressBar;
|
||||
}
|
||||
|
||||
QPushButton* LabelButton::GetBuildButton()
|
||||
{
|
||||
return m_buildButton;
|
||||
}
|
||||
|
||||
ProjectButton::ProjectButton(const ProjectInfo& projectInfo, QWidget* parent, bool processing)
|
||||
: QFrame(parent)
|
||||
, m_projectInfo(projectInfo)
|
||||
{
|
||||
@@ -66,10 +97,18 @@ namespace O3DE::ProjectManager
|
||||
m_projectInfo.m_imagePath = ":/DefaultProjectImage.png";
|
||||
}
|
||||
|
||||
Setup();
|
||||
BaseSetup();
|
||||
if (processing)
|
||||
{
|
||||
ProcessingSetup();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadySetup();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectButton::Setup()
|
||||
void ProjectButton::BaseSetup()
|
||||
{
|
||||
setObjectName("projectButton");
|
||||
|
||||
@@ -81,49 +120,81 @@ namespace O3DE::ProjectManager
|
||||
m_projectImageLabel = new LabelButton(this);
|
||||
m_projectImageLabel->setFixedSize(s_projectImageWidth, s_projectImageHeight);
|
||||
m_projectImageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
connect(m_projectImageLabel, &LabelButton::triggered, [this]() { emit OpenProject(m_projectInfo.m_path); });
|
||||
vLayout->addWidget(m_projectImageLabel);
|
||||
|
||||
m_projectImageLabel->setPixmap(
|
||||
QPixmap(m_projectInfo.m_imagePath).scaled(m_projectImageLabel->size(), Qt::KeepAspectRatioByExpanding));
|
||||
|
||||
QMenu* newProjectMenu = new QMenu(this);
|
||||
m_editProjectAction = newProjectMenu->addAction(tr("Edit Project Settings..."));
|
||||
newProjectMenu->addSeparator();
|
||||
m_copyProjectAction = newProjectMenu->addAction(tr("Duplicate"));
|
||||
newProjectMenu->addSeparator();
|
||||
m_removeProjectAction = newProjectMenu->addAction(tr("Remove from O3DE"));
|
||||
m_deleteProjectAction = newProjectMenu->addAction(tr("Delete this Project"));
|
||||
|
||||
QFrame* footer = new QFrame(this);
|
||||
m_projectFooter = new QFrame(this);
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
hLayout->setContentsMargins(0, 0, 0, 0);
|
||||
footer->setLayout(hLayout);
|
||||
m_projectFooter->setLayout(hLayout);
|
||||
{
|
||||
QLabel* projectNameLabel = new QLabel(m_projectInfo.m_displayName, this);
|
||||
hLayout->addWidget(projectNameLabel);
|
||||
|
||||
QPushButton* projectMenuButton = new QPushButton(this);
|
||||
projectMenuButton->setObjectName("projectMenuButton");
|
||||
projectMenuButton->setMenu(newProjectMenu);
|
||||
hLayout->addWidget(projectMenuButton);
|
||||
}
|
||||
|
||||
vLayout->addWidget(footer);
|
||||
|
||||
connect(m_projectImageLabel, &LabelButton::triggered, [this]() { emit OpenProject(m_projectInfo.m_path); });
|
||||
connect(m_editProjectAction, &QAction::triggered, [this]() { emit EditProject(m_projectInfo.m_path); });
|
||||
connect(m_copyProjectAction, &QAction::triggered, [this]() { emit CopyProject(m_projectInfo.m_path); });
|
||||
connect(m_removeProjectAction, &QAction::triggered, [this]() { emit RemoveProject(m_projectInfo.m_path); });
|
||||
connect(m_deleteProjectAction, &QAction::triggered, [this]() { emit DeleteProject(m_projectInfo.m_path); });
|
||||
vLayout->addWidget(m_projectFooter);
|
||||
}
|
||||
|
||||
void ProjectButton::SetButtonEnabled(bool enabled)
|
||||
void ProjectButton::ProcessingSetup()
|
||||
{
|
||||
m_projectImageLabel->GetOverlayLabel()->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
|
||||
m_projectImageLabel->SetEnabled(false);
|
||||
m_projectImageLabel->SetOverlayText(tr("Processing...\n\n"));
|
||||
|
||||
QProgressBar* progressBar = m_projectImageLabel->GetProgressBar();
|
||||
progressBar->setVisible(true);
|
||||
progressBar->setValue(0);
|
||||
}
|
||||
|
||||
void ProjectButton::ReadySetup()
|
||||
{
|
||||
connect(m_projectImageLabel, &LabelButton::triggered, [this]() { emit OpenProject(m_projectInfo.m_path); });
|
||||
connect(m_projectImageLabel->GetBuildButton(), &QPushButton::clicked, [this](){ emit BuildProject(m_projectInfo); });
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->addAction(tr("Edit Project Settings..."), this, [this]() { emit EditProject(m_projectInfo.m_path); });
|
||||
menu->addAction(tr("Build"), this, [this]() { emit BuildProject(m_projectInfo); });
|
||||
menu->addSeparator();
|
||||
menu->addAction(tr("Open Project folder..."), this, [this]()
|
||||
{
|
||||
AzQtComponents::ShowFileOnDesktop(m_projectInfo.m_path);
|
||||
});
|
||||
menu->addSeparator();
|
||||
menu->addAction(tr("Duplicate"), this, [this]() { emit CopyProject(m_projectInfo.m_path); });
|
||||
menu->addSeparator();
|
||||
menu->addAction(tr("Remove from O3DE"), this, [this]() { emit RemoveProject(m_projectInfo.m_path); });
|
||||
menu->addAction(tr("Delete this Project"), this, [this]() { emit DeleteProject(m_projectInfo.m_path); });
|
||||
|
||||
QPushButton* projectMenuButton = new QPushButton(this);
|
||||
projectMenuButton->setObjectName("projectMenuButton");
|
||||
projectMenuButton->setMenu(menu);
|
||||
m_projectFooter->layout()->addWidget(projectMenuButton);
|
||||
}
|
||||
|
||||
void ProjectButton::SetLaunchButtonEnabled(bool enabled)
|
||||
{
|
||||
m_projectImageLabel->SetEnabled(enabled);
|
||||
}
|
||||
|
||||
void ProjectButton::ShowBuildButton(bool show)
|
||||
{
|
||||
QSpacerItem* buttonSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
m_projectImageLabel->layout()->addItem(buttonSpacer);
|
||||
m_projectImageLabel->layout()->addWidget(m_projectImageLabel->GetBuildButton());
|
||||
m_projectImageLabel->GetBuildButton()->setVisible(show);
|
||||
}
|
||||
|
||||
void ProjectButton::SetButtonOverlayText(const QString& text)
|
||||
{
|
||||
m_projectImageLabel->SetOverlayText(text);
|
||||
}
|
||||
|
||||
void ProjectButton::SetProgressBarValue(int progress)
|
||||
{
|
||||
m_projectImageLabel->GetProgressBar()->setValue(progress);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
QT_FORWARD_DECLARE_CLASS(QPixmap)
|
||||
QT_FORWARD_DECLARE_CLASS(QPushButton)
|
||||
QT_FORWARD_DECLARE_CLASS(QAction)
|
||||
QT_FORWARD_DECLARE_CLASS(QProgressBar)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -36,6 +37,10 @@ namespace O3DE::ProjectManager
|
||||
void SetEnabled(bool enabled);
|
||||
void SetOverlayText(const QString& text);
|
||||
|
||||
QLabel* GetOverlayLabel();
|
||||
QProgressBar* GetProgressBar();
|
||||
QPushButton* GetBuildButton();
|
||||
|
||||
signals:
|
||||
void triggered();
|
||||
|
||||
@@ -44,6 +49,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
private:
|
||||
QLabel* m_overlayLabel;
|
||||
QProgressBar* m_progressBar;
|
||||
QPushButton* m_buildButton;
|
||||
bool m_enabled = true;
|
||||
};
|
||||
|
||||
@@ -53,11 +60,13 @@ namespace O3DE::ProjectManager
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
explicit ProjectButton(const ProjectInfo& m_projectInfo, QWidget* parent = nullptr);
|
||||
explicit ProjectButton(const ProjectInfo& m_projectInfo, QWidget* parent = nullptr, bool processing = false);
|
||||
~ProjectButton() = default;
|
||||
|
||||
void SetButtonEnabled(bool enabled);
|
||||
void SetLaunchButtonEnabled(bool enabled);
|
||||
void ShowBuildButton(bool show);
|
||||
void SetButtonOverlayText(const QString& text);
|
||||
void SetProgressBarValue(int progress);
|
||||
|
||||
signals:
|
||||
void OpenProject(const QString& projectName);
|
||||
@@ -65,15 +74,15 @@ namespace O3DE::ProjectManager
|
||||
void CopyProject(const QString& projectName);
|
||||
void RemoveProject(const QString& projectName);
|
||||
void DeleteProject(const QString& projectName);
|
||||
void BuildProject(const ProjectInfo& projectInfo);
|
||||
|
||||
private:
|
||||
void Setup();
|
||||
void BaseSetup();
|
||||
void ProcessingSetup();
|
||||
void ReadySetup();
|
||||
|
||||
ProjectInfo m_projectInfo;
|
||||
LabelButton* m_projectImageLabel;
|
||||
QAction* m_editProjectAction;
|
||||
QAction* m_copyProjectAction;
|
||||
QAction* m_removeProjectAction;
|
||||
QAction* m_deleteProjectAction;
|
||||
QFrame* m_projectFooter;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -15,13 +15,16 @@
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
ProjectInfo::ProjectInfo(const QString& path, const QString& projectName, const QString& displayName,
|
||||
const QString& imagePath, const QString& backgroundImagePath, bool isNew)
|
||||
const QString& origin, const QString& summary, const QString& imagePath, const QString& backgroundImagePath,
|
||||
bool needsBuild)
|
||||
: m_path(path)
|
||||
, m_projectName(projectName)
|
||||
, m_displayName(displayName)
|
||||
, m_origin(origin)
|
||||
, m_summary(summary)
|
||||
, m_imagePath(imagePath)
|
||||
, m_backgroundImagePath(backgroundImagePath)
|
||||
, m_isNew(isNew)
|
||||
, m_needsBuild(needsBuild)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <AzCore/Math/Uuid.h>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
@@ -23,8 +24,17 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
public:
|
||||
ProjectInfo() = default;
|
||||
ProjectInfo(const QString& path, const QString& projectName, const QString& displayName,
|
||||
const QString& imagePath, const QString& backgroundImagePath, bool isNew);
|
||||
|
||||
ProjectInfo(
|
||||
const QString& path,
|
||||
const QString& projectName,
|
||||
const QString& displayName,
|
||||
const QString& origin,
|
||||
const QString& summary,
|
||||
const QString& imagePath,
|
||||
const QString& backgroundImagePath,
|
||||
bool needsBuild);
|
||||
|
||||
bool operator==(const ProjectInfo& rhs);
|
||||
bool operator!=(const ProjectInfo& rhs);
|
||||
|
||||
@@ -36,12 +46,16 @@ namespace O3DE::ProjectManager
|
||||
// From project.json
|
||||
QString m_projectName;
|
||||
QString m_displayName;
|
||||
QString m_origin;
|
||||
QString m_summary;
|
||||
QStringList m_userTags;
|
||||
|
||||
// Used on projects home screen
|
||||
QString m_imagePath;
|
||||
QString m_backgroundImagePath;
|
||||
|
||||
// Used in project creation
|
||||
bool m_isNew = false; //! Is this a new project or existing
|
||||
|
||||
bool m_needsBuild = false; //! Does this project need to be built
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace O3DE::ProjectManager
|
||||
QString m_name;
|
||||
QString m_path;
|
||||
QString m_summary;
|
||||
QStringList m_includedGems;
|
||||
QStringList m_canonicalTags;
|
||||
QStringList m_userTags;
|
||||
};
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -192,6 +194,49 @@ namespace O3DE::ProjectManager
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsVS2019Installed_internal()
|
||||
{
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||
QString programFilesPath = environment.value("ProgramFiles(x86)");
|
||||
QString vsWherePath = programFilesPath + "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
|
||||
|
||||
QFileInfo vsWhereFile(vsWherePath);
|
||||
if (vsWhereFile.exists() && vsWhereFile.isFile())
|
||||
{
|
||||
QProcess vsWhereProcess;
|
||||
vsWhereProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
vsWhereProcess.start(
|
||||
vsWherePath,
|
||||
QStringList{ "-version", "16.0", "-latest", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"-property", "isComplete" });
|
||||
|
||||
if (!vsWhereProcess.waitForStarted())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (vsWhereProcess.waitForReadyRead())
|
||||
{
|
||||
}
|
||||
|
||||
QString vsWhereOutput(vsWhereProcess.readAllStandardOutput());
|
||||
if (vsWhereOutput.startsWith("1"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsVS2019Installed()
|
||||
{
|
||||
static bool vs2019Installed = IsVS2019Installed_internal();
|
||||
|
||||
return vs2019Installed;
|
||||
}
|
||||
|
||||
ProjectManagerScreen GetProjectManagerScreen(const QString& screen)
|
||||
{
|
||||
auto iter = s_ProjectManagerStringNames.find(screen);
|
||||
@@ -202,6 +247,5 @@ namespace O3DE::ProjectManager
|
||||
|
||||
return ProjectManagerScreen::Invalid;
|
||||
}
|
||||
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -25,6 +25,9 @@ namespace O3DE::ProjectManager
|
||||
bool CopyProject(const QString& origPath, const QString& newPath);
|
||||
bool DeleteProjectFiles(const QString& path, bool force = false);
|
||||
bool MoveProject(const QString& origPath, const QString& newPath, QWidget* parent = nullptr);
|
||||
|
||||
bool IsVS2019Installed();
|
||||
|
||||
ProjectManagerScreen GetProjectManagerScreen(const QString& screen);
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <ProjectButtonWidget.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <ProjectBuilder.h>
|
||||
#include <ScreensCtrl.h>
|
||||
|
||||
#include <AzQtComponents/Components/FlowLayout.h>
|
||||
#include <AzCore/Platform.h>
|
||||
@@ -42,6 +44,8 @@
|
||||
#include <QSettings>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#include <QQueue>
|
||||
#include <QDir>
|
||||
|
||||
//#define DISPLAY_PROJECT_DEV_DATA true
|
||||
|
||||
@@ -66,6 +70,14 @@ namespace O3DE::ProjectManager
|
||||
m_stack->addWidget(m_projectsContent);
|
||||
|
||||
vLayout->addWidget(m_stack);
|
||||
|
||||
connect(reinterpret_cast<ScreensCtrl*>(parent), &ScreensCtrl::NotifyBuildProject, this, &ProjectsScreen::SuggestBuildProject);
|
||||
}
|
||||
|
||||
ProjectsScreen::~ProjectsScreen()
|
||||
|
||||
{
|
||||
delete m_currentBuilder;
|
||||
}
|
||||
|
||||
QFrame* ProjectsScreen::CreateFirstTimeContent()
|
||||
@@ -110,7 +122,7 @@ namespace O3DE::ProjectManager
|
||||
return frame;
|
||||
}
|
||||
|
||||
QFrame* ProjectsScreen::CreateProjectsContent()
|
||||
QFrame* ProjectsScreen::CreateProjectsContent(QString buildProjectPath, ProjectButton** projectButton)
|
||||
{
|
||||
QFrame* frame = new QFrame(this);
|
||||
frame->setObjectName("projectsContent");
|
||||
@@ -158,30 +170,43 @@ namespace O3DE::ProjectManager
|
||||
projectsScrollArea->setWidgetResizable(true);
|
||||
|
||||
#ifndef DISPLAY_PROJECT_DEV_DATA
|
||||
// Iterate once to insert building project first
|
||||
if (!buildProjectPath.isEmpty())
|
||||
{
|
||||
buildProjectPath = QDir::fromNativeSeparators(buildProjectPath);
|
||||
for (auto project : projectsResult.GetValue())
|
||||
{
|
||||
if (QDir::fromNativeSeparators(project.m_path) == buildProjectPath)
|
||||
{
|
||||
ProjectButton* buildingProjectButton = CreateProjectButton(project, flowLayout, true);
|
||||
|
||||
if (projectButton)
|
||||
{
|
||||
*projectButton = buildingProjectButton;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto project : projectsResult.GetValue())
|
||||
#else
|
||||
ProjectInfo project = projectsResult.GetValue().at(0);
|
||||
for (int i = 0; i < 15; i++)
|
||||
#endif
|
||||
{
|
||||
ProjectButton* projectButton;
|
||||
|
||||
QString projectPreviewPath = project.m_path + m_projectPreviewImagePath;
|
||||
QFileInfo doesPreviewExist(projectPreviewPath);
|
||||
if (doesPreviewExist.exists() && doesPreviewExist.isFile())
|
||||
// Add all other projects skipping building project
|
||||
// Safe if no building project because it is just an empty string
|
||||
if (project.m_path != buildProjectPath)
|
||||
{
|
||||
project.m_imagePath = projectPreviewPath;
|
||||
ProjectButton* projectButtonWidget = CreateProjectButton(project, flowLayout);
|
||||
|
||||
if (RequiresBuildProjectIterator(project.m_path) != m_requiresBuild.end())
|
||||
{
|
||||
projectButtonWidget->ShowBuildButton(true);
|
||||
}
|
||||
}
|
||||
|
||||
projectButton = new ProjectButton(project, this);
|
||||
|
||||
flowLayout->addWidget(projectButton);
|
||||
|
||||
connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject);
|
||||
connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject);
|
||||
connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject);
|
||||
connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject);
|
||||
connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject);
|
||||
}
|
||||
|
||||
layout->addWidget(projectsScrollArea);
|
||||
@@ -191,6 +216,60 @@ namespace O3DE::ProjectManager
|
||||
return frame;
|
||||
}
|
||||
|
||||
ProjectButton* ProjectsScreen::CreateProjectButton(ProjectInfo& project, QLayout* flowLayout, bool processing)
|
||||
{
|
||||
ProjectButton* projectButton;
|
||||
|
||||
QString projectPreviewPath = project.m_path + m_projectPreviewImagePath;
|
||||
QFileInfo doesPreviewExist(projectPreviewPath);
|
||||
if (doesPreviewExist.exists() && doesPreviewExist.isFile())
|
||||
{
|
||||
project.m_imagePath = projectPreviewPath;
|
||||
}
|
||||
|
||||
projectButton = new ProjectButton(project, this, processing);
|
||||
|
||||
flowLayout->addWidget(projectButton);
|
||||
|
||||
if (!processing)
|
||||
{
|
||||
connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject);
|
||||
connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject);
|
||||
connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject);
|
||||
connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject);
|
||||
connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject);
|
||||
}
|
||||
connect(projectButton, &ProjectButton::BuildProject, this, &ProjectsScreen::QueueBuildProject);
|
||||
|
||||
return projectButton;
|
||||
}
|
||||
|
||||
void ProjectsScreen::ResetProjectsContent()
|
||||
{
|
||||
// refresh the projects content by re-creating it for now
|
||||
if (m_projectsContent)
|
||||
{
|
||||
m_stack->removeWidget(m_projectsContent);
|
||||
m_projectsContent->deleteLater();
|
||||
}
|
||||
|
||||
// Make sure to update builder with latest Project Button
|
||||
if (m_currentBuilder)
|
||||
{
|
||||
ProjectButton* projectButtonPtr;
|
||||
|
||||
m_projectsContent = CreateProjectsContent(m_currentBuilder->GetProjectPath(), &projectButtonPtr);
|
||||
m_currentBuilder->SetProjectButton(projectButtonPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_projectsContent = CreateProjectsContent();
|
||||
}
|
||||
|
||||
m_stack->addWidget(m_projectsContent);
|
||||
m_stack->setCurrentWidget(m_projectsContent);
|
||||
}
|
||||
|
||||
ProjectManagerScreen ProjectsScreen::GetScreenEnum()
|
||||
{
|
||||
return ProjectManagerScreen::Projects;
|
||||
@@ -237,7 +316,7 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
if (ProjectUtils::AddProjectDialog(this))
|
||||
{
|
||||
emit ResetScreenRequest(ProjectManagerScreen::Projects);
|
||||
ResetProjectsContent();
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
}
|
||||
}
|
||||
@@ -245,38 +324,47 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
if (!projectPath.isEmpty())
|
||||
{
|
||||
AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
|
||||
AZStd::string executableFilename = "Editor";
|
||||
AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
|
||||
auto cmdPath = AZ::IO::FixedMaxPathString::format("%s -regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(), projectPath.toStdString().c_str());
|
||||
if (!WarnIfInBuildQueue(projectPath))
|
||||
{
|
||||
AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
|
||||
AZStd::string executableFilename = "Editor";
|
||||
AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
|
||||
auto cmdPath = AZ::IO::FixedMaxPathString::format(
|
||||
"%s -regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(),
|
||||
projectPath.toStdString().c_str());
|
||||
|
||||
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
processLaunchInfo.m_commandlineParameters = cmdPath;
|
||||
bool launchSucceeded = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
|
||||
if (!launchSucceeded)
|
||||
{
|
||||
AZ_Error("ProjectManager", false, "Failed to launch editor");
|
||||
QMessageBox::critical( this, tr("Error"), tr("Failed to launch the Editor, please verify the project settings are valid."));
|
||||
}
|
||||
else
|
||||
{
|
||||
// prevent the user from accidentally pressing the button while the editor is launching
|
||||
// and let them know what's happening
|
||||
ProjectButton* button = qobject_cast<ProjectButton*>(sender());
|
||||
if (button)
|
||||
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
processLaunchInfo.m_commandlineParameters = cmdPath;
|
||||
bool launchSucceeded = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
|
||||
if (!launchSucceeded)
|
||||
{
|
||||
button->SetButtonEnabled(false);
|
||||
button->SetButtonOverlayText(tr("Opening Editor..."));
|
||||
AZ_Error("ProjectManager", false, "Failed to launch editor");
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"), tr("Failed to launch the Editor, please verify the project settings are valid."));
|
||||
}
|
||||
else
|
||||
{
|
||||
// prevent the user from accidentally pressing the button while the editor is launching
|
||||
// and let them know what's happening
|
||||
ProjectButton* button = qobject_cast<ProjectButton*>(sender());
|
||||
if (button)
|
||||
{
|
||||
button->SetLaunchButtonEnabled(false);
|
||||
button->SetButtonOverlayText(tr("Opening Editor..."));
|
||||
}
|
||||
|
||||
// enable the button after 3 seconds
|
||||
constexpr int waitTimeInMs = 3000;
|
||||
QTimer::singleShot(waitTimeInMs, this, [this, button] {
|
||||
if (button)
|
||||
// enable the button after 3 seconds
|
||||
constexpr int waitTimeInMs = 3000;
|
||||
QTimer::singleShot(
|
||||
waitTimeInMs, this,
|
||||
[this, button]
|
||||
{
|
||||
button->SetButtonEnabled(true);
|
||||
}
|
||||
});
|
||||
if (button)
|
||||
{
|
||||
button->SetLaunchButtonEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -288,38 +376,90 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
void ProjectsScreen::HandleEditProject(const QString& projectPath)
|
||||
{
|
||||
emit NotifyCurrentProject(projectPath);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
|
||||
if (!WarnIfInBuildQueue(projectPath))
|
||||
{
|
||||
emit NotifyCurrentProject(projectPath);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
|
||||
}
|
||||
}
|
||||
void ProjectsScreen::HandleCopyProject(const QString& projectPath)
|
||||
{
|
||||
// Open file dialog and choose location for copied project then register copy with O3DE
|
||||
if (ProjectUtils::CopyProjectDialog(projectPath, this))
|
||||
if (!WarnIfInBuildQueue(projectPath))
|
||||
{
|
||||
emit ResetScreenRequest(ProjectManagerScreen::Projects);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
// Open file dialog and choose location for copied project then register copy with O3DE
|
||||
if (ProjectUtils::CopyProjectDialog(projectPath, this))
|
||||
{
|
||||
ResetProjectsContent();
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
}
|
||||
}
|
||||
}
|
||||
void ProjectsScreen::HandleRemoveProject(const QString& projectPath)
|
||||
{
|
||||
// Unregister Project from O3DE and reload projects
|
||||
if (ProjectUtils::UnregisterProject(projectPath))
|
||||
if (!WarnIfInBuildQueue(projectPath))
|
||||
{
|
||||
emit ResetScreenRequest(ProjectManagerScreen::Projects);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
// Unregister Project from O3DE and reload projects
|
||||
if (ProjectUtils::UnregisterProject(projectPath))
|
||||
{
|
||||
ResetProjectsContent();
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
}
|
||||
}
|
||||
}
|
||||
void ProjectsScreen::HandleDeleteProject(const QString& projectPath)
|
||||
{
|
||||
QMessageBox::StandardButton warningResult = QMessageBox::warning(
|
||||
this, tr("Delete Project"), tr("Are you sure?\nProject will be removed from O3DE and directory will be deleted!"),
|
||||
QMessageBox::No | QMessageBox::Yes);
|
||||
|
||||
if (warningResult == QMessageBox::Yes)
|
||||
if (!WarnIfInBuildQueue(projectPath))
|
||||
{
|
||||
// Remove project from O3DE and delete from disk
|
||||
HandleRemoveProject(projectPath);
|
||||
ProjectUtils::DeleteProjectFiles(projectPath);
|
||||
QMessageBox::StandardButton warningResult = QMessageBox::warning(this,
|
||||
tr("Delete Project"),
|
||||
tr("Are you sure?\nProject will be unregistered from O3DE and project directory will be deleted from your disk."),
|
||||
QMessageBox::No | QMessageBox::Yes);
|
||||
|
||||
if (warningResult == QMessageBox::Yes)
|
||||
{
|
||||
// Remove project from O3DE and delete from disk
|
||||
HandleRemoveProject(projectPath);
|
||||
ProjectUtils::DeleteProjectFiles(projectPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectsScreen::SuggestBuildProject(const ProjectInfo& projectInfo)
|
||||
{
|
||||
if (projectInfo.m_needsBuild)
|
||||
{
|
||||
if (RequiresBuildProjectIterator(projectInfo.m_path) == m_requiresBuild.end())
|
||||
{
|
||||
m_requiresBuild.append(projectInfo);
|
||||
}
|
||||
ResetProjectsContent();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::information(this,
|
||||
tr("Project Should be rebuilt."),
|
||||
projectInfo.m_projectName + tr(" project likely needs to be rebuilt."));
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectsScreen::QueueBuildProject(const ProjectInfo& projectInfo)
|
||||
{
|
||||
auto requiredIter = RequiresBuildProjectIterator(projectInfo.m_path);
|
||||
if (requiredIter != m_requiresBuild.end())
|
||||
{
|
||||
m_requiresBuild.erase(requiredIter);
|
||||
}
|
||||
|
||||
if (!BuildQueueContainsProject(projectInfo.m_path))
|
||||
{
|
||||
if (m_buildQueue.empty() && !m_currentBuilder)
|
||||
{
|
||||
StartProjectBuild(projectInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_buildQueue.append(projectInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,17 +471,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
else
|
||||
{
|
||||
// refresh the projects content by re-creating it for now
|
||||
if (m_projectsContent)
|
||||
{
|
||||
m_stack->removeWidget(m_projectsContent);
|
||||
m_projectsContent->deleteLater();
|
||||
}
|
||||
|
||||
m_projectsContent = CreateProjectsContent();
|
||||
|
||||
m_stack->addWidget(m_projectsContent);
|
||||
m_stack->setCurrentWidget(m_projectsContent);
|
||||
ResetProjectsContent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,4 +493,89 @@ namespace O3DE::ProjectManager
|
||||
return displayFirstTimeContent;
|
||||
}
|
||||
|
||||
void ProjectsScreen::StartProjectBuild(const ProjectInfo& projectInfo)
|
||||
{
|
||||
if (ProjectUtils::IsVS2019Installed())
|
||||
{
|
||||
QMessageBox::StandardButton buildProject = QMessageBox::information(
|
||||
this,
|
||||
tr("Building \"%1\"").arg(projectInfo.m_projectName),
|
||||
tr("Ready to build \"%1\"?").arg(projectInfo.m_projectName),
|
||||
QMessageBox::No | QMessageBox::Yes);
|
||||
|
||||
if (buildProject == QMessageBox::Yes)
|
||||
{
|
||||
m_currentBuilder = new ProjectBuilderController(projectInfo, nullptr, this);
|
||||
ResetProjectsContent();
|
||||
connect(m_currentBuilder, &ProjectBuilderController::Done, this, &ProjectsScreen::ProjectBuildDone);
|
||||
|
||||
m_currentBuilder->Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
ProjectBuildDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectsScreen::ProjectBuildDone()
|
||||
{
|
||||
delete m_currentBuilder;
|
||||
m_currentBuilder = nullptr;
|
||||
|
||||
if (!m_buildQueue.empty())
|
||||
{
|
||||
StartProjectBuild(m_buildQueue.front());
|
||||
m_buildQueue.pop_front();
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetProjectsContent();
|
||||
}
|
||||
}
|
||||
|
||||
QList<ProjectInfo>::iterator ProjectsScreen::RequiresBuildProjectIterator(const QString& projectPath)
|
||||
{
|
||||
QString nativeProjPath(QDir::toNativeSeparators(projectPath));
|
||||
auto projectIter = m_requiresBuild.begin();
|
||||
for (; projectIter != m_requiresBuild.end(); ++projectIter)
|
||||
{
|
||||
if (QDir::toNativeSeparators(projectIter->m_path) == nativeProjPath)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return projectIter;
|
||||
}
|
||||
|
||||
bool ProjectsScreen::BuildQueueContainsProject(const QString& projectPath)
|
||||
{
|
||||
QString nativeProjPath(QDir::toNativeSeparators(projectPath));
|
||||
for (const ProjectInfo& project : m_buildQueue)
|
||||
{
|
||||
if (QDir::toNativeSeparators(project.m_path) == nativeProjPath)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProjectsScreen::WarnIfInBuildQueue(const QString& projectPath)
|
||||
{
|
||||
if (BuildQueueContainsProject(projectPath))
|
||||
{
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
tr("Action Temporarily Disabled!"),
|
||||
tr("Action not allowed on projects in build queue."));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -13,21 +13,28 @@
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenWidget.h>
|
||||
#include <ProjectInfo.h>
|
||||
|
||||
#include <QQueue>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QPaintEvent)
|
||||
QT_FORWARD_DECLARE_CLASS(QFrame)
|
||||
QT_FORWARD_DECLARE_CLASS(QStackedWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(QLayout)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(ProjectBuilderController);
|
||||
QT_FORWARD_DECLARE_CLASS(ProjectButton);
|
||||
|
||||
class ProjectsScreen
|
||||
: public ScreenWidget
|
||||
{
|
||||
|
||||
public:
|
||||
explicit ProjectsScreen(QWidget* parent = nullptr);
|
||||
~ProjectsScreen() = default;
|
||||
~ProjectsScreen();
|
||||
|
||||
ProjectManagerScreen GetScreenEnum() override;
|
||||
QString GetTabText() override;
|
||||
@@ -35,6 +42,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
protected:
|
||||
void NotifyCurrentScreen() override;
|
||||
void ProjectBuildDone();
|
||||
|
||||
protected slots:
|
||||
void HandleNewProjectButton();
|
||||
@@ -45,19 +53,32 @@ namespace O3DE::ProjectManager
|
||||
void HandleRemoveProject(const QString& projectPath);
|
||||
void HandleDeleteProject(const QString& projectPath);
|
||||
|
||||
void SuggestBuildProject(const ProjectInfo& projectInfo);
|
||||
void QueueBuildProject(const ProjectInfo& projectInfo);
|
||||
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
QFrame* CreateFirstTimeContent();
|
||||
QFrame* CreateProjectsContent();
|
||||
QFrame* CreateProjectsContent(QString buildProjectPath = "", ProjectButton** projectButton = nullptr);
|
||||
ProjectButton* CreateProjectButton(ProjectInfo& project, QLayout* flowLayout, bool processing = false);
|
||||
void ResetProjectsContent();
|
||||
bool ShouldDisplayFirstTimeContent();
|
||||
|
||||
QAction* m_createNewProjectAction;
|
||||
QAction* m_addExistingProjectAction;
|
||||
void StartProjectBuild(const ProjectInfo& projectInfo);
|
||||
QList<ProjectInfo>::iterator RequiresBuildProjectIterator(const QString& projectPath);
|
||||
bool BuildQueueContainsProject(const QString& projectPath);
|
||||
bool WarnIfInBuildQueue(const QString& projectPath);
|
||||
|
||||
QAction* m_createNewProjectAction = nullptr;
|
||||
QAction* m_addExistingProjectAction = nullptr;
|
||||
QPixmap m_background;
|
||||
QFrame* m_firstTimeContent;
|
||||
QFrame* m_projectsContent;
|
||||
QStackedWidget* m_stack;
|
||||
QFrame* m_firstTimeContent = nullptr;
|
||||
QFrame* m_projectsContent = nullptr;
|
||||
QStackedWidget* m_stack = nullptr;
|
||||
QList<ProjectInfo> m_requiresBuild;
|
||||
QQueue<ProjectInfo> m_buildQueue;
|
||||
ProjectBuilderController* m_currentBuilder = nullptr;
|
||||
|
||||
const QString m_projectPreviewImagePath = "/preview.png";
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <pybind11/functional.h>
|
||||
#include <pybind11/embed.h>
|
||||
#include <pybind11/eval.h>
|
||||
#include <pybind11/stl.h>
|
||||
#pragma pop_macro("slots")
|
||||
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
@@ -289,12 +290,14 @@ namespace O3DE::ProjectManager
|
||||
m_engineTemplate = pybind11::module::import("o3de.engine_template");
|
||||
m_enableGemProject = pybind11::module::import("o3de.enable_gem");
|
||||
m_disableGemProject = pybind11::module::import("o3de.disable_gem");
|
||||
m_editProjectProperties = pybind11::module::import("o3de.project_properties");
|
||||
|
||||
// make sure the engine is registered
|
||||
RegisterThisEngine();
|
||||
|
||||
return result == 0 && !PyErr_Occurred();
|
||||
} catch ([[maybe_unused]] const std::exception& e)
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
AZ_Warning("ProjectManagerWindow", false, "Py_Initialize() failed with %s", e.what());
|
||||
return false;
|
||||
@@ -320,25 +323,25 @@ namespace O3DE::ProjectManager
|
||||
bool registrationResult = true; // already registered is considered successful
|
||||
bool pythonResult = ExecuteWithLock(
|
||||
[&]
|
||||
{
|
||||
// check current engine path against all other registered engines
|
||||
// to see if we are already registered
|
||||
auto allEngines = m_manifest.attr("get_engines")();
|
||||
if (pybind11::isinstance<pybind11::list>(allEngines))
|
||||
{
|
||||
// check current engine path against all other registered engines
|
||||
// to see if we are already registered
|
||||
auto allEngines = m_manifest.attr("get_engines")();
|
||||
if (pybind11::isinstance<pybind11::list>(allEngines))
|
||||
for (auto engine : allEngines)
|
||||
{
|
||||
for (auto engine : allEngines)
|
||||
AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"]));
|
||||
if (enginePath.Compare(m_enginePath) == 0)
|
||||
{
|
||||
AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"]));
|
||||
if (enginePath.Compare(m_enginePath) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto result = m_register.attr("register")(m_enginePath.c_str());
|
||||
registrationResult = (result.cast<int>() == 0);
|
||||
});
|
||||
auto result = m_register.attr("register")(m_enginePath.c_str());
|
||||
registrationResult = (result.cast<int>() == 0);
|
||||
});
|
||||
|
||||
bool finalResult = (registrationResult && pythonResult);
|
||||
AZ_Assert(finalResult, "Registration of this engine failed!");
|
||||
@@ -378,12 +381,14 @@ namespace O3DE::ProjectManager
|
||||
auto o3deData = m_manifest.attr("load_o3de_manifest")();
|
||||
if (pybind11::isinstance<pybind11::dict>(o3deData))
|
||||
{
|
||||
engineInfo.m_path = Py_To_String(enginePath);
|
||||
engineInfo.m_defaultGemsFolder = Py_To_String(o3deData["default_gems_folder"]);
|
||||
engineInfo.m_defaultProjectsFolder = Py_To_String(o3deData["default_projects_folder"]);
|
||||
engineInfo.m_path = Py_To_String(enginePath);
|
||||
engineInfo.m_defaultGemsFolder = Py_To_String(o3deData["default_gems_folder"]);
|
||||
engineInfo.m_defaultProjectsFolder = Py_To_String(o3deData["default_projects_folder"]);
|
||||
engineInfo.m_defaultRestrictedFolder = Py_To_String(o3deData["default_restricted_folder"]);
|
||||
engineInfo.m_defaultTemplatesFolder = Py_To_String(o3deData["default_templates_folder"]);
|
||||
engineInfo.m_thirdPartyPath = Py_To_String_Optional(o3deData,"third_party_path","");
|
||||
engineInfo.m_defaultTemplatesFolder = Py_To_String(o3deData["default_templates_folder"]);
|
||||
|
||||
pybind11::str defaultThirdPartyFolder = m_manifest.attr("get_o3de_third_party_folder")();
|
||||
engineInfo.m_thirdPartyPath = Py_To_String_Optional(o3deData,"default_third_party_folder", Py_To_String(defaultThirdPartyFolder));
|
||||
}
|
||||
|
||||
auto engineData = m_manifest.attr("get_engine_json_data")(pybind11::none(), enginePath);
|
||||
@@ -391,8 +396,8 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
try
|
||||
{
|
||||
engineInfo.m_version = Py_To_String_Optional(engineData,"O3DEVersion","0.0.0.0");
|
||||
engineInfo.m_name = Py_To_String_Optional(engineData,"engine_name","O3DE");
|
||||
engineInfo.m_version = Py_To_String_Optional(engineData, "O3DEVersion", "0.0.0.0");
|
||||
engineInfo.m_name = Py_To_String_Optional(engineData, "engine_name", "O3DE");
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
@@ -416,44 +421,32 @@ namespace O3DE::ProjectManager
|
||||
bool PythonBindings::SetEngineInfo(const EngineInfo& engineInfo)
|
||||
{
|
||||
bool result = ExecuteWithLock([&] {
|
||||
pybind11::str enginePath = engineInfo.m_path.toStdString();
|
||||
pybind11::str defaultProjectsFolder = engineInfo.m_defaultProjectsFolder.toStdString();
|
||||
pybind11::str defaultGemsFolder = engineInfo.m_defaultGemsFolder.toStdString();
|
||||
pybind11::str enginePath = engineInfo.m_path.toStdString();
|
||||
pybind11::str defaultProjectsFolder = engineInfo.m_defaultProjectsFolder.toStdString();
|
||||
pybind11::str defaultGemsFolder = engineInfo.m_defaultGemsFolder.toStdString();
|
||||
pybind11::str defaultTemplatesFolder = engineInfo.m_defaultTemplatesFolder.toStdString();
|
||||
pybind11::str defaultThirdPartyFolder = engineInfo.m_thirdPartyPath.toStdString();
|
||||
|
||||
auto registrationResult = m_register.attr("register")(
|
||||
enginePath, // engine_path
|
||||
pybind11::none(), // project_path
|
||||
enginePath, // engine_path
|
||||
pybind11::none(), // project_path
|
||||
pybind11::none(), // gem_path
|
||||
pybind11::none(), // external_subdir_path
|
||||
pybind11::none(), // template_path
|
||||
pybind11::none(), // restricted_path
|
||||
pybind11::none(), // repo_uri
|
||||
pybind11::none(), // external_subdir_path
|
||||
pybind11::none(), // template_path
|
||||
pybind11::none(), // restricted_path
|
||||
pybind11::none(), // repo_uri
|
||||
pybind11::none(), // default_engines_folder
|
||||
defaultProjectsFolder,
|
||||
defaultGemsFolder,
|
||||
defaultTemplatesFolder
|
||||
defaultTemplatesFolder,
|
||||
pybind11::none(), // default_restricted_folder
|
||||
defaultThirdPartyFolder
|
||||
);
|
||||
|
||||
if (registrationResult.cast<int>() != 0)
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto manifest = m_manifest.attr("load_o3de_manifest")();
|
||||
if (pybind11::isinstance<pybind11::dict>(manifest))
|
||||
{
|
||||
try
|
||||
{
|
||||
manifest["third_party_path"] = engineInfo.m_thirdPartyPath.toStdString();
|
||||
m_manifest.attr("save_o3de_manifest")(manifest);
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
AZ_Warning("PythonBindings", false, "Failed to set third party path.");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return result;
|
||||
@@ -477,12 +470,12 @@ namespace O3DE::ProjectManager
|
||||
QVector<GemInfo> gems;
|
||||
|
||||
auto result = ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
for (auto path : m_manifest.attr("get_engine_gems")())
|
||||
{
|
||||
for (auto path : m_manifest.attr("get_engine_gems")())
|
||||
{
|
||||
gems.push_back(GemInfoFromPath(path));
|
||||
}
|
||||
});
|
||||
gems.push_back(GemInfoFromPath(path));
|
||||
}
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>(result.GetError().c_str());
|
||||
@@ -497,13 +490,13 @@ namespace O3DE::ProjectManager
|
||||
QVector<GemInfo> gems;
|
||||
|
||||
auto result = ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
|
||||
{
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
|
||||
{
|
||||
gems.push_back(GemInfoFromPath(path));
|
||||
}
|
||||
});
|
||||
gems.push_back(GemInfoFromPath(path));
|
||||
}
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>(result.GetError().c_str());
|
||||
@@ -518,12 +511,12 @@ namespace O3DE::ProjectManager
|
||||
// Retrieve the path to the cmake file that lists the enabled gems.
|
||||
pybind11::str enabledGemsFilename;
|
||||
auto result = ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
const pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")(
|
||||
pybind11::none(), // project_name
|
||||
pyProjectPath); // project_path
|
||||
});
|
||||
{
|
||||
const pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")(
|
||||
pybind11::none(), // project_name
|
||||
pyProjectPath); // project_path
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>(result.GetError().c_str());
|
||||
@@ -532,13 +525,13 @@ namespace O3DE::ProjectManager
|
||||
// Retrieve the actual list of names from the cmake file.
|
||||
QVector<AZStd::string> gemNames;
|
||||
result = ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename);
|
||||
for (auto gemName : pyGemNames)
|
||||
{
|
||||
const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename);
|
||||
for (auto gemName : pyGemNames)
|
||||
{
|
||||
gemNames.push_back(Py_To_String(gemName));
|
||||
}
|
||||
});
|
||||
gemNames.push_back(Py_To_String(gemName));
|
||||
}
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>(result.GetError().c_str());
|
||||
@@ -552,13 +545,13 @@ namespace O3DE::ProjectManager
|
||||
bool registrationResult = false;
|
||||
bool result = ExecuteWithLock(
|
||||
[&]
|
||||
{
|
||||
pybind11::str projectPath = path.toStdString();
|
||||
auto pythonRegistrationResult = m_register.attr("register")(pybind11::none(), projectPath);
|
||||
{
|
||||
pybind11::str projectPath = path.toStdString();
|
||||
auto pythonRegistrationResult = m_register.attr("register")(pybind11::none(), projectPath);
|
||||
|
||||
// Returns an exit code so boolify it then invert result
|
||||
registrationResult = !pythonRegistrationResult.cast<bool>();
|
||||
});
|
||||
// Returns an exit code so boolify it then invert result
|
||||
registrationResult = !pythonRegistrationResult.cast<bool>();
|
||||
});
|
||||
|
||||
return result && registrationResult;
|
||||
}
|
||||
@@ -568,30 +561,31 @@ namespace O3DE::ProjectManager
|
||||
bool registrationResult = false;
|
||||
bool result = ExecuteWithLock(
|
||||
[&]
|
||||
{
|
||||
pybind11::str projectPath = path.toStdString();
|
||||
auto pythonRegistrationResult = m_register.attr("register")(
|
||||
pybind11::none(), // engine_path
|
||||
projectPath, // project_path
|
||||
pybind11::none(), // gem_path
|
||||
pybind11::none(), // external_subdir_path
|
||||
pybind11::none(), // template_path
|
||||
pybind11::none(), // restricted_path
|
||||
pybind11::none(), // repo_uri
|
||||
pybind11::none(), // default_engines_folder
|
||||
pybind11::none(), // default_projects_folder
|
||||
pybind11::none(), // default_gems_folder
|
||||
pybind11::none(), // default_templates_folder
|
||||
pybind11::none(), // default_restricted_folder
|
||||
pybind11::none(), // external_subdir_engine_path
|
||||
pybind11::none(), // external_subdir_project_path
|
||||
true, // remove
|
||||
false // force
|
||||
{
|
||||
pybind11::str projectPath = path.toStdString();
|
||||
auto pythonRegistrationResult = m_register.attr("register")(
|
||||
pybind11::none(), // engine_path
|
||||
projectPath, // project_path
|
||||
pybind11::none(), // gem_path
|
||||
pybind11::none(), // external_subdir_path
|
||||
pybind11::none(), // template_path
|
||||
pybind11::none(), // restricted_path
|
||||
pybind11::none(), // repo_uri
|
||||
pybind11::none(), // default_engines_folder
|
||||
pybind11::none(), // default_projects_folder
|
||||
pybind11::none(), // default_gems_folder
|
||||
pybind11::none(), // default_templates_folder
|
||||
pybind11::none(), // default_restricted_folder
|
||||
pybind11::none(), // default_third_party_folder
|
||||
pybind11::none(), // external_subdir_engine_path
|
||||
pybind11::none(), // external_subdir_project_path
|
||||
true, // remove
|
||||
false // force
|
||||
);
|
||||
|
||||
// Returns an exit code so boolify it then invert result
|
||||
registrationResult = !pythonRegistrationResult.cast<bool>();
|
||||
});
|
||||
|
||||
// Returns an exit code so boolify it then invert result
|
||||
registrationResult = !pythonRegistrationResult.cast<bool>();
|
||||
});
|
||||
|
||||
return result && registrationResult;
|
||||
}
|
||||
@@ -649,12 +643,12 @@ namespace O3DE::ProjectManager
|
||||
try
|
||||
{
|
||||
// required
|
||||
gemInfo.m_name = Py_To_String(data["gem_name"]);
|
||||
gemInfo.m_name = Py_To_String(data["gem_name"]);
|
||||
|
||||
// optional
|
||||
gemInfo.m_displayName = Py_To_String_Optional(data, "DisplayName", gemInfo.m_name);
|
||||
gemInfo.m_summary = Py_To_String_Optional(data, "Summary", "");
|
||||
gemInfo.m_version = Py_To_String_Optional(data, "Version", "");
|
||||
gemInfo.m_summary = Py_To_String_Optional(data, "Summary", "");
|
||||
gemInfo.m_version = Py_To_String_Optional(data, "Version", "");
|
||||
|
||||
if (data.contains("Tags"))
|
||||
{
|
||||
@@ -677,7 +671,7 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
ProjectInfo projectInfo;
|
||||
projectInfo.m_path = Py_To_String(path);
|
||||
projectInfo.m_isNew = false;
|
||||
projectInfo.m_needsBuild = false;
|
||||
|
||||
auto projectData = m_manifest.attr("get_project_json_data")(pybind11::none(), path);
|
||||
if (pybind11::isinstance<pybind11::dict>(projectData))
|
||||
@@ -685,7 +679,16 @@ namespace O3DE::ProjectManager
|
||||
try
|
||||
{
|
||||
projectInfo.m_projectName = Py_To_String(projectData["project_name"]);
|
||||
projectInfo.m_displayName = Py_To_String_Optional(projectData,"display_name", projectInfo.m_projectName);
|
||||
projectInfo.m_displayName = Py_To_String_Optional(projectData, "display_name", projectInfo.m_projectName);
|
||||
projectInfo.m_origin = Py_To_String_Optional(projectData, "origin", projectInfo.m_origin);
|
||||
projectInfo.m_summary = Py_To_String_Optional(projectData, "summary", projectInfo.m_summary);
|
||||
if (projectData.contains("user_tags"))
|
||||
{
|
||||
for (auto tag : projectData["user_tags"])
|
||||
{
|
||||
projectInfo.m_userTags.append(Py_To_String(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
@@ -727,44 +730,62 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath)
|
||||
{
|
||||
return ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
pybind11::str pyGemPath = gemPath.toStdString();
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
{
|
||||
pybind11::str pyGemPath = gemPath.toStdString();
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
|
||||
m_enableGemProject.attr("enable_gem_in_project")(
|
||||
pybind11::none(), // gem name not needed as path is provided
|
||||
pyGemPath,
|
||||
pybind11::none(), // project name not needed as path is provided
|
||||
pyProjectPath
|
||||
m_enableGemProject.attr("enable_gem_in_project")(
|
||||
pybind11::none(), // gem name not needed as path is provided
|
||||
pyGemPath,
|
||||
pybind11::none(), // project name not needed as path is provided
|
||||
pyProjectPath
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath)
|
||||
{
|
||||
return ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
pybind11::str pyGemPath = gemPath.toStdString();
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
{
|
||||
pybind11::str pyGemPath = gemPath.toStdString();
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
|
||||
m_disableGemProject.attr("disable_gem_in_project")(
|
||||
pybind11::none(), // gem name not needed as path is provided
|
||||
pyGemPath,
|
||||
pybind11::none(), // project name not needed as path is provided
|
||||
pyProjectPath
|
||||
m_disableGemProject.attr("disable_gem_in_project")(
|
||||
pybind11::none(), // gem name not needed as path is provided
|
||||
pyGemPath,
|
||||
pybind11::none(), // project name not needed as path is provided
|
||||
pyProjectPath
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo)
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::UpdateProject(const ProjectInfo& projectInfo)
|
||||
{
|
||||
return false;
|
||||
return ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
std::list<std::string> newTags;
|
||||
for (const auto& i : projectInfo.m_userTags)
|
||||
{
|
||||
newTags.push_back(i.toStdString());
|
||||
}
|
||||
|
||||
m_editProjectProperties.attr("edit_project_props")(
|
||||
pybind11::str(projectInfo.m_path.toStdString()), // proj_path
|
||||
pybind11::none(), // proj_name not used
|
||||
pybind11::str(projectInfo.m_origin.toStdString()), // new_origin
|
||||
pybind11::str(projectInfo.m_displayName.toStdString()), // new_display
|
||||
pybind11::str(projectInfo.m_summary.toStdString()), // new_summary
|
||||
pybind11::str(projectInfo.m_imagePath.toStdString()), // new_icon
|
||||
pybind11::none(), // add_tags not used
|
||||
pybind11::none(), // remove_tags not used
|
||||
pybind11::list(pybind11::cast(newTags))); // replace_tags
|
||||
});
|
||||
}
|
||||
|
||||
ProjectTemplateInfo PythonBindings::ProjectTemplateInfoFromPath(pybind11::handle path)
|
||||
{
|
||||
ProjectTemplateInfo templateInfo;
|
||||
templateInfo.m_path = Py_To_String(path);
|
||||
templateInfo.m_path = Py_To_String(pybind11::str(path));
|
||||
|
||||
auto data = m_manifest.attr("get_template_json_data")(pybind11::none(), path);
|
||||
if (pybind11::isinstance<pybind11::dict>(data))
|
||||
@@ -773,8 +794,8 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
// required
|
||||
templateInfo.m_displayName = Py_To_String(data["display_name"]);
|
||||
templateInfo.m_name = Py_To_String(data["template_name"]);
|
||||
templateInfo.m_summary = Py_To_String(data["summary"]);
|
||||
templateInfo.m_name = Py_To_String(data["template_name"]);
|
||||
templateInfo.m_summary = Py_To_String(data["summary"]);
|
||||
|
||||
// optional
|
||||
if (data.contains("canonical_tags"))
|
||||
@@ -791,6 +812,13 @@ namespace O3DE::ProjectManager
|
||||
templateInfo.m_canonicalTags.push_back(Py_To_String(tag));
|
||||
}
|
||||
}
|
||||
if (data.contains("included_gems"))
|
||||
{
|
||||
for (auto gem : data["included_gems"])
|
||||
{
|
||||
templateInfo.m_includedGems.push_back(Py_To_String(gem));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
@@ -806,7 +834,7 @@ namespace O3DE::ProjectManager
|
||||
QVector<ProjectTemplateInfo> templates;
|
||||
|
||||
bool result = ExecuteWithLock([&] {
|
||||
for (auto path : m_manifest.attr("get_project_templates")())
|
||||
for (auto path : m_manifest.attr("get_templates_for_project_creation")())
|
||||
{
|
||||
templates.push_back(ProjectTemplateInfoFromPath(path));
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<QVector<ProjectInfo>> GetProjects() override;
|
||||
bool AddProject(const QString& path) override;
|
||||
bool RemoveProject(const QString& path) override;
|
||||
bool UpdateProject(const ProjectInfo& projectInfo) override;
|
||||
AZ::Outcome<void, AZStd::string> UpdateProject(const ProjectInfo& projectInfo) override;
|
||||
AZ::Outcome<void, AZStd::string> AddGemToProject(const QString& gemPath, const QString& projectPath) override;
|
||||
AZ::Outcome<void, AZStd::string> RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override;
|
||||
|
||||
@@ -78,5 +78,6 @@ namespace O3DE::ProjectManager
|
||||
pybind11::handle m_manifest;
|
||||
pybind11::handle m_enableGemProject;
|
||||
pybind11::handle m_disableGemProject;
|
||||
pybind11::handle m_editProjectProperties;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace O3DE::ProjectManager
|
||||
* @param projectInfo the info to use to update the project
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
virtual bool UpdateProject(const ProjectInfo& projectInfo) = 0;
|
||||
virtual AZ::Outcome<void, AZStd::string> UpdateProject(const ProjectInfo& projectInfo) = 0;
|
||||
|
||||
/**
|
||||
* Add a gem to a project
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenDefs.h>
|
||||
#include <ProjectInfo.h>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QStyleOption>
|
||||
@@ -61,6 +62,7 @@ namespace O3DE::ProjectManager
|
||||
void GotoPreviousScreenRequest();
|
||||
void ResetScreenRequest(ProjectManagerScreen screen);
|
||||
void NotifyCurrentProject(const QString& projectPath);
|
||||
void NotifyBuildProject(const ProjectInfo& projectInfo);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -177,6 +177,7 @@ namespace O3DE::ProjectManager
|
||||
connect(newScreen, &ScreenWidget::GotoPreviousScreenRequest, this, &ScreensCtrl::GotoPreviousScreen);
|
||||
connect(newScreen, &ScreenWidget::ResetScreenRequest, this, &ScreensCtrl::ResetScreen);
|
||||
connect(newScreen, &ScreenWidget::NotifyCurrentProject, this, &ScreensCtrl::NotifyCurrentProject);
|
||||
connect(newScreen, &ScreenWidget::NotifyBuildProject, this, &ScreensCtrl::NotifyBuildProject);
|
||||
}
|
||||
|
||||
void ScreensCtrl::ResetAllScreens()
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenDefs.h>
|
||||
#include <ProjectInfo.h>
|
||||
|
||||
#include <QStackedWidget>
|
||||
#include <QStack>
|
||||
@@ -39,6 +40,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
signals:
|
||||
void NotifyCurrentProject(const QString& projectPath);
|
||||
void NotifyBuildProject(const ProjectInfo& projectInfo);
|
||||
|
||||
public slots:
|
||||
bool ChangeToScreen(ProjectManagerScreen screen);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <TemplateButtonWidget.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QAbstractButton>
|
||||
#include <QStyle>
|
||||
#include <QVariant>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
|
||||
TemplateButton::TemplateButton(const QString& imagePath, const QString& labelText, QWidget* parent)
|
||||
: QPushButton(parent)
|
||||
{
|
||||
setAutoExclusive(true);
|
||||
|
||||
setObjectName("templateButton");
|
||||
|
||||
QVBoxLayout* vLayout = new QVBoxLayout();
|
||||
vLayout->setSpacing(0);
|
||||
vLayout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(vLayout);
|
||||
|
||||
QLabel* image = new QLabel(this);
|
||||
image->setObjectName("templateImage");
|
||||
image->setPixmap(
|
||||
QPixmap(imagePath).scaled(QSize(s_templateImageWidth,s_templateImageHeight) , Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
vLayout->addWidget(image);
|
||||
|
||||
QLabel* label = new QLabel(labelText, this);
|
||||
label->setObjectName("templateLabel");
|
||||
vLayout->addWidget(label);
|
||||
|
||||
connect(this, &QAbstractButton::toggled, this, &TemplateButton::onToggled);
|
||||
}
|
||||
|
||||
void TemplateButton::onToggled()
|
||||
{
|
||||
setProperty("Checked", isChecked());
|
||||
|
||||
// we must unpolish/polish every child after changing a property
|
||||
// or else they won't use the correct stylesheet selector
|
||||
for (auto child : findChildren<QWidget*>())
|
||||
{
|
||||
child->style()->unpolish(child);
|
||||
child->style()->polish(child);
|
||||
}
|
||||
|
||||
style()->unpolish(this);
|
||||
style()->polish(this);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QPushButton>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class TemplateButton
|
||||
: public QPushButton
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
explicit TemplateButton(const QString& imagePath, const QString& labelText, QWidget* parent = nullptr);
|
||||
~TemplateButton() = default;
|
||||
|
||||
protected slots:
|
||||
void onToggled();
|
||||
|
||||
private:
|
||||
inline constexpr static int s_templateImageWidth = 92;
|
||||
inline constexpr static int s_templateImageHeight = 122;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -58,7 +58,7 @@ namespace O3DE::ProjectManager
|
||||
tabWidget->tabBar()->setObjectName("projectSettingsTabBar");
|
||||
tabWidget->addTab(m_updateSettingsScreen, tr("General"));
|
||||
|
||||
QPushButton* gemsButton = new QPushButton(tr("Add More Gems"), this);
|
||||
QPushButton* gemsButton = new QPushButton(tr("Configure Gems"), this);
|
||||
topBarHLayout->addWidget(gemsButton);
|
||||
tabWidget->setCornerWidget(gemsButton);
|
||||
|
||||
@@ -119,7 +119,9 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void UpdateProjectCtrl::HandleNextButton()
|
||||
{
|
||||
if (m_stack->currentIndex() == ScreenOrder::Settings)
|
||||
bool shouldRebuild = false;
|
||||
|
||||
if (m_stack->currentIndex() == ScreenOrder::Settings && m_updateSettingsScreen)
|
||||
{
|
||||
if (m_updateSettingsScreen)
|
||||
{
|
||||
@@ -134,10 +136,10 @@ namespace O3DE::ProjectManager
|
||||
// Update project if settings changed
|
||||
if (m_projectInfo != newProjectSettings)
|
||||
{
|
||||
bool result = PythonBindingsInterface::Get()->UpdateProject(newProjectSettings);
|
||||
if (!result)
|
||||
auto result = PythonBindingsInterface::Get()->UpdateProject(newProjectSettings);
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project."));
|
||||
QMessageBox::critical(this, tr("Project update failed"), tr(result.GetError().c_str()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -155,11 +157,17 @@ namespace O3DE::ProjectManager
|
||||
m_projectInfo = newProjectSettings;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen)
|
||||
else if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen)
|
||||
{
|
||||
// Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project.
|
||||
m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path);
|
||||
|
||||
shouldRebuild = true;
|
||||
}
|
||||
|
||||
if (shouldRebuild)
|
||||
{
|
||||
emit NotifyBuildProject(m_projectInfo);
|
||||
}
|
||||
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
@@ -181,7 +189,7 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
if (m_stack->currentIndex() == ScreenOrder::Gems)
|
||||
{
|
||||
m_header->setSubTitle(QString(tr("Add More Gems to \"%1\"")).arg(m_projectInfo.m_projectName));
|
||||
m_header->setSubTitle(QString(tr("Configure Gems for \"%1\"")).arg(m_projectInfo.m_projectName));
|
||||
m_nextButton->setText(tr("Confirm"));
|
||||
}
|
||||
else
|
||||
|
||||
@@ -38,6 +38,8 @@ set(FILES
|
||||
Source/ProjectInfo.cpp
|
||||
Source/ProjectUtils.h
|
||||
Source/ProjectUtils.cpp
|
||||
Source/ProjectBuilder.h
|
||||
Source/ProjectBuilder.cpp
|
||||
Source/UpdateProjectSettingsScreen.h
|
||||
Source/UpdateProjectSettingsScreen.cpp
|
||||
Source/NewProjectSettingsScreen.h
|
||||
@@ -60,6 +62,8 @@ set(FILES
|
||||
Source/LinkWidget.cpp
|
||||
Source/TagWidget.h
|
||||
Source/TagWidget.cpp
|
||||
Source/TemplateButtonWidget.h
|
||||
Source/TemplateButtonWidget.cpp
|
||||
Source/GemCatalog/GemCatalogHeaderWidget.h
|
||||
Source/GemCatalog/GemCatalogHeaderWidget.cpp
|
||||
Source/GemCatalog/GemCatalogScreen.h
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
#include <AzToolsFramework/Thumbnails/ThumbnailerNullComponent.h>
|
||||
#include <SliceConverterEditorEntityContextComponent.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -34,6 +35,9 @@ namespace AZ
|
||||
Application::Application(int argc, char** argv)
|
||||
: AzToolsFramework::ToolsApplication(&argc, &argv)
|
||||
{
|
||||
// We need a specialized variant of EditorEntityContextCompnent for the SliceConverter, so we register the descriptor here.
|
||||
RegisterComponentDescriptor(AzToolsFramework::SliceConverterEditorEntityContextComponent::CreateDescriptor());
|
||||
|
||||
AZ::IO::FixedMaxPath projectPath = AZ::Utils::GetProjectPath();
|
||||
if (projectPath.empty())
|
||||
{
|
||||
@@ -110,10 +114,21 @@ namespace AZ
|
||||
|
||||
AZ::ComponentTypeList Application::GetRequiredSystemComponents() const
|
||||
{
|
||||
// Use all of the default system components, but also add in the ThumbnailerNullComponent so that components requiring
|
||||
// a ThumbnailService can still be started up.
|
||||
// By default, we use all of the standard system components.
|
||||
AZ::ComponentTypeList components = AzToolsFramework::ToolsApplication::GetRequiredSystemComponents();
|
||||
|
||||
// Also add in the ThumbnailerNullComponent so that components requiring a ThumbnailService can still be started up.
|
||||
components.emplace_back(azrtti_typeid<AzToolsFramework::Thumbnailer::ThumbnailerNullComponent>());
|
||||
|
||||
// The Slice Converter requires a specialized variant of the EditorEntityContextComponent that exposes the ability
|
||||
// to disable the behavior of activating entities on creation. During conversion, the creation flow will be triggered,
|
||||
// but entity activation requires a significant amount of subsystem initialization that's unneeded for conversion.
|
||||
// So, to get around this, we swap out EditorEntityContextComponent with SliceConverterEditorEntityContextComponent.
|
||||
components.erase(
|
||||
AZStd::remove(
|
||||
components.begin(), components.end(), azrtti_typeid<AzToolsFramework::EditorEntityContextComponent>()),
|
||||
components.end());
|
||||
components.emplace_back(azrtti_typeid<AzToolsFramework::SliceConverterEditorEntityContextComponent>());
|
||||
return components;
|
||||
}
|
||||
} // namespace SerializeContextTools
|
||||
|
||||
@@ -30,13 +30,16 @@
|
||||
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
|
||||
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
||||
#include <Application.h>
|
||||
#include <SliceConverter.h>
|
||||
#include <SliceConverterEditorEntityContextComponent.h>
|
||||
#include <Utilities.h>
|
||||
|
||||
|
||||
// SliceConverter reads in a slice file (saved in an ObjectStream format), instantiates it, creates a prefab out of the data,
|
||||
// and saves the prefab in a JSON format. This can be used for one-time migrations of slices or slice-based levels to prefabs.
|
||||
//
|
||||
@@ -99,12 +102,26 @@ namespace AZ
|
||||
bool result = true;
|
||||
rapidjson::StringBuffer scratchBuffer;
|
||||
|
||||
// For slice conversion, disable the EditorEntityContextComponent logic that activates entities on creation.
|
||||
// This prevents a lot of error messages and crashes during conversion due to lack of full environment and subsystem setup.
|
||||
AzToolsFramework::SliceConverterEditorEntityContextComponent::DisableOnContextEntityLogic();
|
||||
|
||||
// Loop through the list of requested files and convert them.
|
||||
AZStd::vector<AZStd::string> fileList = Utilities::ReadFileListFromCommandLine(application, "files");
|
||||
for (AZStd::string& filePath : fileList)
|
||||
{
|
||||
bool convertResult = ConvertSliceFile(convertSettings.m_serializeContext, filePath, isDryRun);
|
||||
result = result && convertResult;
|
||||
|
||||
// Clear out all registered prefab templates between each top-level file that gets processed.
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
for (auto templateId : m_createdTemplateIds)
|
||||
{
|
||||
// We don't just want to call RemoveAllTemplates() because the root template should remain between file conversions.
|
||||
prefabSystemComponentInterface->RemoveTemplate(templateId);
|
||||
}
|
||||
m_aliasIdMapper.clear();
|
||||
m_createdTemplateIds.clear();
|
||||
}
|
||||
|
||||
DisconnectFromAssetProcessor();
|
||||
@@ -114,6 +131,13 @@ namespace AZ
|
||||
bool SliceConverter::ConvertSliceFile(
|
||||
AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun)
|
||||
{
|
||||
/* To convert a slice file, we read the input file in via ObjectStream, then use the "class ready" callback to convert
|
||||
* the data in memory to a Prefab.
|
||||
* If the input file is a level file (.ly), we actually need to load the level slice file ("levelentities.editor_xml") from
|
||||
* within the level file, which effectively is a zip file of the level slice file and a bunch of legacy level files that won't
|
||||
* be converted, since the systems that would use them no longer exist.
|
||||
*/
|
||||
|
||||
bool result = true;
|
||||
bool packOpened = false;
|
||||
|
||||
@@ -144,7 +168,7 @@ namespace AZ
|
||||
AZ_STRING_ARG(fileExtension.Native()));
|
||||
}
|
||||
|
||||
auto callback = [&outputPath, isDryRun](void* classPtr, const Uuid& classId, SerializeContext* context)
|
||||
auto callback = [this, &outputPath, isDryRun](void* classPtr, const Uuid& classId, SerializeContext* context)
|
||||
{
|
||||
if (classId != azrtti_typeid<AZ::Entity>())
|
||||
{
|
||||
@@ -178,6 +202,13 @@ namespace AZ
|
||||
bool SliceConverter::ConvertSliceToPrefab(
|
||||
AZ::SerializeContext* serializeContext, AZ::IO::PathView outputPath, bool isDryRun, AZ::Entity* rootEntity)
|
||||
{
|
||||
/* Given a root slice entity, we convert it to a prefab by doing the following:
|
||||
* - Locate the SliceComponent
|
||||
* - Take all the entities directly located on the slice, and put them into a prefab
|
||||
* - Fix up any top-level entities to have the prefab container entity as their parent
|
||||
* - If there are any nested slice instances, convert the nested slices to prefabs, then convert the instances.
|
||||
*/
|
||||
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
|
||||
// Find the slice from the root entity.
|
||||
@@ -192,9 +223,14 @@ namespace AZ
|
||||
SliceComponent::EntityList sliceEntities = sliceComponent->GetNewEntities();
|
||||
AZ_Printf("Convert-Slice", " Slice contains %zu entities.\n", sliceEntities.size());
|
||||
|
||||
// Create the Prefab with the entities from the slice
|
||||
// Create the Prefab with the entities from the slice.
|
||||
// The entities are added in a separate step so that we can give them deterministic entity aliases that match their entity Ids
|
||||
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> sourceInstance(
|
||||
prefabSystemComponentInterface->CreatePrefab(sliceEntities, {}, outputPath));
|
||||
prefabSystemComponentInterface->CreatePrefab({}, {}, outputPath));
|
||||
for (auto& entity : sliceEntities)
|
||||
{
|
||||
sourceInstance->AddEntity(*entity, AZStd::string::format("Entity_%s", entity->GetId().ToString().c_str()));
|
||||
}
|
||||
|
||||
// Dispatch events here, because prefab creation might trigger asset loads in rare circumstances.
|
||||
AZ::Data::AssetManager::Instance().DispatchEvents();
|
||||
@@ -204,12 +240,28 @@ namespace AZ
|
||||
AzToolsFramework::Prefab::EntityOptionalReference container = sourceInstance->GetContainerEntity();
|
||||
FixPrefabEntities(container->get(), sliceEntities);
|
||||
|
||||
// Keep track of the template Id we created, we're going to remove it at the end of slice file conversion to make sure
|
||||
// the data doesn't stick around between file conversions.
|
||||
auto templateId = sourceInstance->GetTemplateId();
|
||||
if (templateId == AzToolsFramework::Prefab::InvalidTemplateId)
|
||||
{
|
||||
AZ_Printf("Convert-Slice", " Path error. Path could be invalid, or the prefab may not be loaded in this level.\n");
|
||||
return false;
|
||||
}
|
||||
m_createdTemplateIds.emplace(templateId);
|
||||
|
||||
// Save off a mapping of the original slice entity IDs to the new prefab template entity aliases.
|
||||
// When converting nested slices, this mapping will be needed to fix up the parent entity hierarchy correctly.
|
||||
auto entityAliases = sourceInstance->GetEntityAliases();
|
||||
for (auto& alias : entityAliases)
|
||||
{
|
||||
auto id = sourceInstance->GetEntityId(alias);
|
||||
auto result = m_aliasIdMapper.emplace(TemplateEntityIdPair(templateId, id), alias);
|
||||
if (!result.second)
|
||||
{
|
||||
AZ_Printf("Convert-Slice", " Duplicate entity alias -> entity id entries found, conversion may not be successful.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Update the prefab template with the fixed-up data in our prefab instance.
|
||||
AzToolsFramework::Prefab::PrefabDom prefabDom;
|
||||
@@ -254,21 +306,26 @@ namespace AZ
|
||||
// via an EditorRequests EBus in CreatePrefab, but the subsystem that listens for it isn't present in this tool.)
|
||||
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
|
||||
&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, containerEntity);
|
||||
containerEntity.AddComponent(aznew AzToolsFramework::Prefab::EditorPrefabComponent());
|
||||
if (containerEntity.FindComponent<AzToolsFramework::Prefab::EditorPrefabComponent>() == nullptr)
|
||||
{
|
||||
containerEntity.AddComponent(aznew AzToolsFramework::Prefab::EditorPrefabComponent());
|
||||
}
|
||||
|
||||
// Make all the components on the container entity have deterministic component IDs, so that multiple runs of the tool
|
||||
// on the same slice will produce the same prefab output. We're going to cheat a bit and just use the component type hash
|
||||
// as the component ID. This would break if we had multiple components of the same type, but that currently doesn't
|
||||
// happen for the container entity.
|
||||
auto containerComponents = containerEntity.GetComponents();
|
||||
for (auto& component : containerComponents)
|
||||
{
|
||||
component->SetId(component->GetUnderlyingComponentType().GetHash());
|
||||
}
|
||||
|
||||
// Reparent any root-level slice entities to the container entity.
|
||||
for (auto entity : sliceEntities)
|
||||
{
|
||||
AzToolsFramework::Components::TransformComponent* transformComponent =
|
||||
entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
if (transformComponent)
|
||||
{
|
||||
if (!transformComponent->GetParentId().IsValid())
|
||||
{
|
||||
transformComponent->SetParent(containerEntity.GetId());
|
||||
transformComponent->UpdateCachedWorldTransform();
|
||||
}
|
||||
}
|
||||
constexpr bool onlySetIfInvalid = true;
|
||||
SetParentEntity(*entity, containerEntity.GetId(), onlySetIfInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,9 +333,13 @@ namespace AZ
|
||||
SliceComponent* sliceComponent, AzToolsFramework::Prefab::Instance* sourceInstance,
|
||||
AZ::SerializeContext* serializeContext, bool isDryRun)
|
||||
{
|
||||
/* Given a root slice, find all the nested slices and convert them. */
|
||||
|
||||
// Get the list of nested slices that this slice uses.
|
||||
const SliceComponent::SliceList& sliceList = sliceComponent->GetSlices();
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
|
||||
// For each nested slice, convert it.
|
||||
for (auto& slice : sliceList)
|
||||
{
|
||||
// Get the nested slice asset
|
||||
@@ -312,7 +373,7 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the prefab template for the newly-created nested prefab.
|
||||
// Find the prefab template we created for the newly-created nested prefab.
|
||||
// To get the template, we need to take our absolute slice path and turn it into a project-relative prefab path.
|
||||
AZ::IO::Path nestedPrefabPath = assetPath;
|
||||
nestedPrefabPath.ReplaceExtension("prefab");
|
||||
@@ -346,11 +407,25 @@ namespace AZ
|
||||
}
|
||||
|
||||
bool SliceConverter::ConvertSliceInstance(
|
||||
[[maybe_unused]] AZ::SliceComponent::SliceInstance& instance,
|
||||
[[maybe_unused]] AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
|
||||
AZ::SliceComponent::SliceInstance& instance,
|
||||
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
|
||||
AzToolsFramework::Prefab::TemplateReference nestedTemplate,
|
||||
AzToolsFramework::Prefab::Instance* topLevelInstance)
|
||||
{
|
||||
/* To convert a slice instance, it's important to understand the similarities and differences between slices and prefabs.
|
||||
* Both slices and prefabs have the concept of instances of a nested slice/prefab, where each instance can have its own
|
||||
* set of changed data (transforms, component values, etc).
|
||||
* For slices, the changed data comes from applying a DataPatch to an instantiated set of entities from the nested slice.
|
||||
* From prefabs, the changed data comes from Json patches that are applied to the instantiated set of entities from the
|
||||
* nested prefab. The prefab instance entities also have different IDs than the slice instance entities, so we'll need
|
||||
* to remap some of them along the way.
|
||||
* To get from one to the other, we'll need to do the following:
|
||||
* - Instantiate the nested slice and nested prefab
|
||||
* - Patch the nested slice instance and fix up the entity ID references
|
||||
* - Replace the nested prefab instance entities with the fixed-up slice ones
|
||||
* - Add the nested instance (and the link patch) to the top-level prefab
|
||||
*/
|
||||
|
||||
auto instanceToTemplateInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceToTemplateInterface>::Get();
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
|
||||
@@ -371,22 +446,83 @@ namespace AZ
|
||||
AzToolsFramework::Prefab::PrefabDom unmodifiedNestedInstanceDom;
|
||||
instanceToTemplateInterface->GenerateDomForInstance(unmodifiedNestedInstanceDom, *(nestedInstance.get()));
|
||||
|
||||
// Currently, DataPatch conversions for nested slices aren't implemented, so all nested slice overrides will
|
||||
// be lost.
|
||||
AZ_Warning(
|
||||
"Convert-Slice", false, " Nested slice instances will lose all of their override data during conversion.",
|
||||
nestedTemplate->get().GetFilePath().c_str());
|
||||
// Instantiate a new instance of the nested slice
|
||||
SliceComponent* dependentSlice = sliceAsset.Get()->GetComponent();
|
||||
[[maybe_unused]] AZ::SliceComponent::InstantiateResult instantiationResult = dependentSlice->Instantiate();
|
||||
AZ_Assert(instantiationResult == AZ::SliceComponent::InstantiateResult::Success, "Failed to instantiate instance");
|
||||
|
||||
// Set the container entity of the nested prefab to have the top-level prefab as the parent.
|
||||
// Once DataPatch conversions are supported, this will need to change to nest the prefab under the appropriate entity
|
||||
// within the level.
|
||||
// Apply the data patch for this instance of the nested slice. This will provide us with a version of the slice's entities
|
||||
// with all data overrides applied to them.
|
||||
DataPatch::FlagsMap sourceDataFlags = dependentSlice->GetDataFlagsForInstances().GetDataFlagsForPatching();
|
||||
DataPatch::FlagsMap targetDataFlags = instance.GetDataFlags().GetDataFlagsForPatching(&instance.GetEntityIdToBaseMap());
|
||||
AZ::ObjectStream::FilterDescriptor filterDesc(AZ::Data::AssetFilterNoAssetLoading);
|
||||
|
||||
AZ::SliceComponent::InstantiatedContainer sourceObjects(false);
|
||||
dependentSlice->GetEntities(sourceObjects.m_entities);
|
||||
dependentSlice->GetAllMetadataEntities(sourceObjects.m_metadataEntities);
|
||||
|
||||
const DataPatch& dataPatch = instance.GetDataPatch();
|
||||
auto instantiated =
|
||||
dataPatch.Apply(&sourceObjects, dependentSlice->GetSerializeContext(), filterDesc, sourceDataFlags, targetDataFlags);
|
||||
|
||||
// Run through all the instantiated entities and fix up their parent hierarchy:
|
||||
// - Invalid parents need to get set to the container.
|
||||
// - Valid parents into the top-level instance mean that the nested slice instance is also child-nested under an entity.
|
||||
// Prefabs handle this type of nesting differently - we need to set the parent to the container, and the container's
|
||||
// parent to that other instance.
|
||||
auto containerEntity = nestedInstance->GetContainerEntity();
|
||||
AzToolsFramework::Components::TransformComponent* transformComponent =
|
||||
containerEntity->get().FindComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
if (transformComponent)
|
||||
auto containerEntityId = containerEntity->get().GetId();
|
||||
for (auto entity : instantiated->m_entities)
|
||||
{
|
||||
transformComponent->SetParent(topLevelInstance->GetContainerEntityId());
|
||||
transformComponent->UpdateCachedWorldTransform();
|
||||
AzToolsFramework::Components::TransformComponent* transformComponent =
|
||||
entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
if (transformComponent)
|
||||
{
|
||||
bool onlySetIfInvalid = true;
|
||||
auto parentId = transformComponent->GetParentId();
|
||||
if (parentId.IsValid())
|
||||
{
|
||||
auto parentAlias = m_aliasIdMapper.find(TemplateEntityIdPair(topLevelInstance->GetTemplateId(), parentId));
|
||||
if (parentAlias != m_aliasIdMapper.end())
|
||||
{
|
||||
// Set the container's parent to this entity's parent, and set this entity's parent to the container
|
||||
// (i.e. go from A->B to A->container->B)
|
||||
auto newParentId = topLevelInstance->GetEntityId(parentAlias->second);
|
||||
SetParentEntity(containerEntity->get(), newParentId, false);
|
||||
onlySetIfInvalid = false;
|
||||
}
|
||||
}
|
||||
|
||||
SetParentEntity(*entity, containerEntityId, onlySetIfInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace all the entities in the instance with the new patched ones.
|
||||
// (This is easier than trying to figure out what the patched data changes are - we can let the JSON patch handle it for us)
|
||||
nestedInstance->RemoveNestedEntities(
|
||||
[](const AZStd::unique_ptr<AZ::Entity>&)
|
||||
{
|
||||
return true;
|
||||
});
|
||||
for (auto& entity : instantiated->m_entities)
|
||||
{
|
||||
auto entityAlias = m_aliasIdMapper.find(TemplateEntityIdPair(nestedInstance->GetTemplateId(), entity->GetId()));
|
||||
if (entityAlias != m_aliasIdMapper.end())
|
||||
{
|
||||
nestedInstance->AddEntity(*entity, entityAlias->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Failed to find entity alias.");
|
||||
nestedInstance->AddEntity(*entity);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the container entity of the nested prefab to have the top-level prefab as the parent if it hasn't already gotten
|
||||
// another entity as its parent.
|
||||
{
|
||||
constexpr bool onlySetIfInvalid = true;
|
||||
SetParentEntity(containerEntity->get(), topLevelInstance->GetContainerEntityId(), onlySetIfInvalid);
|
||||
}
|
||||
|
||||
// Add the nested instance itself to the top-level prefab. To do this, we need to add it to our top-level instance,
|
||||
@@ -395,7 +531,22 @@ namespace AZ
|
||||
AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomBefore;
|
||||
instanceToTemplateInterface->GenerateDomForInstance(topLevelInstanceDomBefore, *topLevelInstance);
|
||||
|
||||
AzToolsFramework::Prefab::Instance& addedInstance = topLevelInstance->AddInstance(AZStd::move(nestedInstance));
|
||||
// When creating the new instance, we would like to have deterministic instance aliases. Prefabs that depend on this one
|
||||
// will have patches that reference the alias, so if we reconvert this slice a second time, we would like it to produce
|
||||
// the same results. To get a deterministic and unique alias, we rely on the slice instance. The slice instance contains
|
||||
// a map of slice entity IDs to unique instance entity IDs. We'll just consistently use the first entry in the map as the
|
||||
// unique instance ID.
|
||||
AZStd::string instanceAlias;
|
||||
auto entityIdMap = instance.GetEntityIdMap();
|
||||
if (!entityIdMap.empty())
|
||||
{
|
||||
instanceAlias = AZStd::string::format("Instance_%s", entityIdMap.begin()->second.ToString().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
instanceAlias = AZStd::string::format("Instance_%s", AZ::Entity::MakeId().ToString().c_str());
|
||||
}
|
||||
AzToolsFramework::Prefab::Instance& addedInstance = topLevelInstance->AddInstance(AZStd::move(nestedInstance), instanceAlias);
|
||||
|
||||
AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomAfter;
|
||||
instanceToTemplateInterface->GenerateDomForInstance(topLevelInstanceDomAfter, *topLevelInstance);
|
||||
@@ -418,9 +569,26 @@ namespace AZ
|
||||
AzToolsFramework::Prefab::InvalidLinkId);
|
||||
prefabSystemComponentInterface->PropagateTemplateChanges(topLevelInstance->GetTemplateId());
|
||||
|
||||
AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get()->UpdateTemplateInstancesInQueue();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SliceConverter::SetParentEntity(const AZ::Entity& entity, const AZ::EntityId& parentId, bool onlySetIfInvalid)
|
||||
{
|
||||
AzToolsFramework::Components::TransformComponent* transformComponent =
|
||||
entity.FindComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
if (transformComponent)
|
||||
{
|
||||
// Only set the parent if we didn't set the onlySetIfInvalid flag, or if we did and the parent is currently invalid
|
||||
if (!onlySetIfInvalid || !transformComponent->GetParentId().IsValid())
|
||||
{
|
||||
transformComponent->SetParent(parentId);
|
||||
transformComponent->UpdateCachedWorldTransform();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SliceConverter::PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId)
|
||||
{
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
|
||||
@@ -39,24 +39,35 @@ namespace AZ
|
||||
class SliceConverter : public Converter
|
||||
{
|
||||
public:
|
||||
static bool ConvertSliceFiles(Application& application);
|
||||
bool ConvertSliceFiles(Application& application);
|
||||
|
||||
private:
|
||||
static bool ConnectToAssetProcessor();
|
||||
static void DisconnectFromAssetProcessor();
|
||||
using TemplateEntityIdPair = AZStd::pair<AzToolsFramework::Prefab::TemplateId, AZ::EntityId>;
|
||||
|
||||
static bool ConvertSliceFile(AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun);
|
||||
static bool ConvertSliceToPrefab(
|
||||
bool ConnectToAssetProcessor();
|
||||
void DisconnectFromAssetProcessor();
|
||||
|
||||
bool ConvertSliceFile(AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun);
|
||||
bool ConvertSliceToPrefab(
|
||||
AZ::SerializeContext* serializeContext, AZ::IO::PathView outputPath, bool isDryRun, AZ::Entity* rootEntity);
|
||||
static void FixPrefabEntities(AZ::Entity& containerEntity, SliceComponent::EntityList& sliceEntities);
|
||||
static bool ConvertNestedSlices(
|
||||
void FixPrefabEntities(AZ::Entity& containerEntity, SliceComponent::EntityList& sliceEntities);
|
||||
bool ConvertNestedSlices(
|
||||
SliceComponent* sliceComponent, AzToolsFramework::Prefab::Instance* sourceInstance,
|
||||
AZ::SerializeContext* serializeContext, bool isDryRun);
|
||||
static bool ConvertSliceInstance(
|
||||
bool ConvertSliceInstance(
|
||||
AZ::SliceComponent::SliceInstance& instance, AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
|
||||
AzToolsFramework::Prefab::TemplateReference nestedTemplate, AzToolsFramework::Prefab::Instance* topLevelInstance);
|
||||
static void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId);
|
||||
static bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId);
|
||||
void SetParentEntity(const AZ::Entity& entity, const AZ::EntityId& parentId, bool onlySetIfInvalid);
|
||||
void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId);
|
||||
bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId);
|
||||
|
||||
// Track all of the entity IDs created and the prefab entity aliases that map to them. This mapping is used
|
||||
// with nested slice conversion to remap parent entity IDs to the correct prefab entity IDs.
|
||||
AZStd::unordered_map<TemplateEntityIdPair, AzToolsFramework::Prefab::EntityAlias> m_aliasIdMapper;
|
||||
|
||||
// Track all of the created prefab template IDs on a slice conversion so that they can get removed at the end of the
|
||||
// conversion for that file.
|
||||
AZStd::unordered_set<AzToolsFramework::Prefab::TemplateId> m_createdTemplateIds;
|
||||
};
|
||||
} // namespace SerializeContextTools
|
||||
} // namespace AZ
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
// This class is an inelegant workaround for use by the Slice Converter to selectively disable entity add/remove logic
|
||||
// during slice conversion in the EditorEntityContextComponent. Specifically, the standard versions of these methods will
|
||||
// attempt to activate the entities as they're added. This is both unnecessary and undesirable during slice conversion, since
|
||||
// entity activation requires a lot of subsystems to be active and valid.
|
||||
// Instead, by selectively disabling this logic, the entities can remain in an initialized state, which is sufficient for conversion,
|
||||
// without requiring those extra subsystems.
|
||||
|
||||
// This problem also could have been solved by adding APIs to the EditorEntityContextComponent or the EntityContext, but there aren't
|
||||
// any other known valid use cases for disabling this logic, so the extra APIs would simply encourage "bad behavior" by using them
|
||||
// when they likely aren't necessary or desired.
|
||||
|
||||
class SliceConverterEditorEntityContextComponent
|
||||
: public EditorEntityContextComponent
|
||||
{
|
||||
public:
|
||||
|
||||
AZ_COMPONENT(SliceConverterEditorEntityContextComponent, "{1CB0C38F-8E85-4422-91C6-E1F3B9B4B853}");
|
||||
|
||||
SliceConverterEditorEntityContextComponent() : EditorEntityContextComponent() {}
|
||||
|
||||
// Simple API to selectively disable this logic *only* when performing slice to prefab conversion.
|
||||
static void DisableOnContextEntityLogic()
|
||||
{
|
||||
m_enableOnContextEntityLogic = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void OnContextEntitiesAdded([[maybe_unused]] const EntityList& entities) override
|
||||
{
|
||||
if (m_enableOnContextEntityLogic)
|
||||
{
|
||||
EditorEntityContextComponent::OnContextEntitiesAdded(entities);
|
||||
}
|
||||
}
|
||||
|
||||
void OnContextEntityRemoved([[maybe_unused]] const AZ::EntityId& id) override
|
||||
{
|
||||
if (m_enableOnContextEntityLogic)
|
||||
{
|
||||
EditorEntityContextComponent::OnContextEntityRemoved(id);
|
||||
}
|
||||
}
|
||||
|
||||
// By default, act just like the EditorEntityContextComponent
|
||||
static inline bool m_enableOnContextEntityLogic = true;
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
@@ -125,7 +125,8 @@ int main(int argc, char** argv)
|
||||
}
|
||||
else if (AZ::StringFunc::Equal("convert-slice", action.c_str()))
|
||||
{
|
||||
result = SliceConverter::ConvertSliceFiles(application);
|
||||
SliceConverter sliceConverter;
|
||||
result = sliceConverter.ConvertSliceFiles(application);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ set(FILES
|
||||
Dumper.h
|
||||
Dumper.cpp
|
||||
main.cpp
|
||||
SliceConverterEditorEntityContextComponent.h
|
||||
SliceConverter.h
|
||||
SliceConverter.cpp
|
||||
Utilities.h
|
||||
|
||||
@@ -70,6 +70,52 @@ namespace LUAEditor
|
||||
, m_bIsModified(false)
|
||||
, m_bIsBeingSaved(false)
|
||||
, m_PresetLineAtOpen(1){}
|
||||
|
||||
// Copy constructor does not copy over open file handle
|
||||
DocumentInfo(const DocumentInfo& other)
|
||||
: m_assetId(other.m_assetId)
|
||||
, m_scriptAsset(other.m_scriptAsset)
|
||||
, m_assetName(other.m_assetName)
|
||||
, m_displayName(other.m_displayName)
|
||||
, m_lastKnownModTime(other.m_lastKnownModTime)
|
||||
, m_sourceControlInfo(other.m_sourceControlInfo)
|
||||
, m_bSourceControl_Ready(other.m_bSourceControl_Ready)
|
||||
, m_bSourceControl_BusyGettingStats(other.m_bSourceControl_BusyGettingStats)
|
||||
, m_bSourceControl_BusyRequestingEdit(other.m_bSourceControl_BusyRequestingEdit)
|
||||
, m_bSourceControl_CanWrite(other.m_bSourceControl_CanWrite)
|
||||
, m_bSourceControl_CanCheckOut(other.m_bSourceControl_CanCheckOut)
|
||||
, m_bDataIsLoaded(other.m_bDataIsLoaded)
|
||||
, m_bDataIsWritten(other.m_bDataIsWritten)
|
||||
, m_bCloseAfterSave(other.m_bCloseAfterSave)
|
||||
, m_bUntitledDocument(other.m_bUntitledDocument)
|
||||
, m_bIsModified(other.m_bIsModified)
|
||||
, m_bIsBeingSaved(other.m_bIsBeingSaved)
|
||||
, m_PresetLineAtOpen(other.m_PresetLineAtOpen)
|
||||
{}
|
||||
|
||||
DocumentInfo& operator=(const DocumentInfo& other)
|
||||
{
|
||||
m_assetId = other.m_assetId;
|
||||
m_scriptAsset = other.m_scriptAsset;
|
||||
m_assetName = other.m_assetName;
|
||||
m_displayName = other.m_displayName;
|
||||
m_lastKnownModTime = other.m_lastKnownModTime;
|
||||
m_sourceControlInfo = other.m_sourceControlInfo;
|
||||
m_bSourceControl_Ready = other.m_bSourceControl_Ready;
|
||||
m_bSourceControl_BusyGettingStats = other.m_bSourceControl_BusyGettingStats;
|
||||
m_bSourceControl_BusyRequestingEdit = other.m_bSourceControl_BusyRequestingEdit;
|
||||
m_bSourceControl_CanWrite = other.m_bSourceControl_CanWrite;
|
||||
m_bSourceControl_CanCheckOut = other.m_bSourceControl_CanCheckOut;
|
||||
m_bDataIsLoaded = other.m_bDataIsLoaded;
|
||||
m_bDataIsWritten = other.m_bDataIsWritten;
|
||||
m_bCloseAfterSave = other.m_bCloseAfterSave;
|
||||
m_bUntitledDocument = other.m_bUntitledDocument;
|
||||
m_bIsModified = other.m_bIsModified;
|
||||
m_bIsBeingSaved = other.m_bIsBeingSaved;
|
||||
m_PresetLineAtOpen = other.m_PresetLineAtOpen;
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class ContextInterface
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "StandaloneTools_precompiled.h"
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
StandaloneTools_precompiled.cpp
|
||||
StandaloneTools_precompiled.h
|
||||
targetver.h
|
||||
Source/StandaloneToolsApplication.cpp
|
||||
|
||||
Reference in New Issue
Block a user