Merge branch 'main' into Prism/gem-db

This commit is contained in:
amzn-mgwynn
2021-06-04 23:19:47 -04:00
committed by GitHub
258 changed files with 10721 additions and 1068 deletions
@@ -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
@@ -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");
@@ -87,8 +126,37 @@ namespace O3DE::ProjectManager
m_projectImageLabel->setPixmap(
QPixmap(m_projectInfo.m_imagePath).scaled(m_projectImageLabel->size(), Qt::KeepAspectRatioByExpanding));
m_projectFooter = new QFrame(this);
QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setContentsMargins(0, 0, 0, 0);
m_projectFooter->setLayout(hLayout);
{
QLabel* projectNameLabel = new QLabel(m_projectInfo.m_displayName, this);
hLayout->addWidget(projectNameLabel);
}
vLayout->addWidget(m_projectFooter);
}
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]()
{
@@ -100,30 +168,33 @@ namespace O3DE::ProjectManager
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); });
QFrame* footer = new QFrame(this);
QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setContentsMargins(0, 0, 0, 0);
footer->setLayout(hLayout);
{
QLabel* projectNameLabel = new QLabel(m_projectInfo.m_displayName, this);
hLayout->addWidget(projectNameLabel);
QPushButton* projectMenuButton = new QPushButton(this);
projectMenuButton->setObjectName("projectMenuButton");
projectMenuButton->setMenu(menu);
hLayout->addWidget(projectMenuButton);
}
vLayout->addWidget(footer);
QPushButton* projectMenuButton = new QPushButton(this);
projectMenuButton->setObjectName("projectMenuButton");
projectMenuButton->setMenu(menu);
m_projectFooter->layout()->addWidget(projectMenuButton);
}
void ProjectButton::SetButtonEnabled(bool enabled)
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,11 +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;
QFrame* m_projectFooter;
};
} // namespace O3DE::ProjectManager
@@ -24,8 +24,10 @@ namespace O3DE::ProjectManager
{
public:
ProjectInfo() = default;
ProjectInfo(const QString& path, const QString& projectName, const QString& displayName, const QString& origin,
const QString& summary, const QString& imagePath, const QString& backgroundImagePath, bool needsBuild);
const QString& summary, const QString& imagePath, const QString& backgroundImagePath, bool needsBuild
bool operator==(const ProjectInfo& rhs);
bool operator!=(const ProjectInfo& rhs);
@@ -46,9 +48,10 @@ namespace O3DE::ProjectManager
QString m_backgroundImagePath;
// Used in project creation
bool m_needsBuild = false; //! Is this a new project or existing
// Used to flag tags for removal
QStringList m_userTagsForRemoval;
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";
@@ -669,7 +669,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))
@@ -779,7 +779,7 @@ namespace O3DE::ProjectManager
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))
@@ -806,6 +806,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)
{
@@ -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
@@ -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)
{
@@ -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);