Merge branch 'development' into o3de_sdk/installer_configs

Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>

# Conflicts:
#	Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp
#	cmake/Platform/Common/Install_common.cmake
This commit is contained in:
Esteban Papp
2021-10-25 12:47:36 -07:00
1023 changed files with 33913 additions and 13107 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ ly_add_target(
3rdParty::pybind11
AZ::AzCore
AZ::AzFramework
AZ::AzQtComponents
AZ::AzToolsFramework
)
ly_add_target(
@@ -40,5 +40,6 @@
<file>Delete.svg</file>
<file>Download.svg</file>
<file>in_progress.gif</file>
<file>gem.svg</file>
</qresource>
</RCC>
@@ -61,6 +61,24 @@ QTabBar::tab:focus {
color: #4082eb;
}
#ToastNotification {
background-color: black;
border-radius: 20px;
border:1px solid #dddddd;
qproperty-minimumSize: 100px 50px;
}
#ToastNotification #icon_frame {
border-radius: 4px;
qproperty-minimumSize: 44px 20px;
}
#ToastNotification #iconLabel {
qproperty-minimumSize: 30px 20px;
qproperty-maximumSize: 30px 20px;
margin-left: 6px;
}
/************** General (Forms) **************/
#formLineEditWidget,
@@ -218,6 +236,10 @@ QTabBar::tab:focus {
color: #666666;
}
#verticalSeparatingLine {
color: #888888;
}
/************** Project Settings **************/
#projectSettings {
margin-top:42px;
@@ -481,6 +503,34 @@ QProgressBar::chunk {
font-weight: 600;
}
#gemCatalogMenuButton {
qproperty-flat: true;
max-width:36px;
min-width:36px;
max-height:24px;
min-height:24px;
}
#GemCatalogCartOverlayGemDownloadHeader {
margin:0;
padding: 0px;
background-color: #333333;
}
#GemCatalogCartOverlayGemDownloadBG {
margin:0;
padding: 0px;
background-color: #444444;
}
#gemCatalogMenuButton {
qproperty-flat: true;
max-width:36px;
min-width:36px;
max-height:24px;
min-height:24px;
}
#GemCatalogHeaderLabel {
font-size: 12px;
color: #FFFFFF;
@@ -0,0 +1,3 @@
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.3771 6.26808L18.0075 0.389195C17.9407 0.271396 17.844 0.173353 17.7271 0.105004C17.6101 0.0366557 17.4772 0.000430184 17.3418 0H4.15017C4.01474 0.000430184 3.88184 0.0366557 3.76492 0.105004C3.64801 0.173353 3.55125 0.271396 3.48444 0.389195L0.10459 6.26808C0.0213476 6.41083 -0.0136462 6.57661 0.00480427 6.74082C0.0232547 6.90502 0.0941655 7.05891 0.20701 7.17962L10.1724 17.7596C10.2442 17.8355 10.3308 17.896 10.4267 17.9373C10.5227 17.9787 10.6261 18 10.7306 18C10.8351 18 10.9385 17.9787 11.0345 17.9373C11.1305 17.896 11.217 17.8355 11.2888 17.7596L21.2542 7.17962C21.3703 7.06129 21.4451 6.90857 21.4672 6.74428C21.4894 6.57999 21.4578 6.41294 21.3771 6.26808ZM12.5998 7.17962L10.7562 13.7345L8.88195 7.17962H12.5998ZM8.42107 5.64332L7.25348 1.54654H14.218L13.0504 5.64332H8.42107ZM9.44526 14.687L2.31685 7.17962H7.24324L9.44526 14.687ZM14.2385 7.17962H19.1546L11.9853 14.7382L14.2385 7.17962ZM19.2878 5.64332H14.6789L15.8465 1.54654H16.9014L19.2878 5.64332ZM4.64178 1.54654H5.66598L6.83356 5.64332H2.23492L4.64178 1.54654Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -49,6 +49,8 @@ namespace O3DE::ProjectManager
m_stack->addWidget(m_gemCatalogScreen);
vLayout->addWidget(m_stack);
connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, &CreateProjectCtrl::OnChangeScreenRequest);
// When there are multiple project templates present, we re-gather the gems when changing the selected the project template.
connect(m_newProjectSettingsScreen, &NewProjectSettingsScreen::OnTemplateSelectionChanged, this, [=](int oldIndex, [[maybe_unused]] int newIndex)
{
@@ -133,7 +135,7 @@ namespace O3DE::ProjectManager
}
else
{
emit GotoPreviousScreenRequest();
emit GoToPreviousScreenRequest();
}
}
@@ -0,0 +1,98 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <DownloadController.h>
#include <DownloadWorker.h>
#include <PythonBindings.h>
#include <AzCore/std/algorithm.h>
#include <QMessageBox>
namespace O3DE::ProjectManager
{
DownloadController::DownloadController(QWidget* parent)
: QObject()
, m_lastProgress(0)
, m_parent(parent)
{
m_worker = new DownloadWorker();
m_worker->moveToThread(&m_workerThread);
connect(&m_workerThread, &QThread::started, m_worker, &DownloadWorker::StartDownload);
connect(m_worker, &DownloadWorker::Done, this, &DownloadController::HandleResults);
connect(m_worker, &DownloadWorker::UpdateProgress, this, &DownloadController::UpdateUIProgress);
connect(this, &DownloadController::StartGemDownload, m_worker, &DownloadWorker::StartDownload);
}
DownloadController::~DownloadController()
{
connect(&m_workerThread, &QThread::finished, m_worker, &DownloadController::deleteLater);
m_workerThread.requestInterruption();
m_workerThread.quit();
m_workerThread.wait();
}
void DownloadController::AddGemDownload(const QString& gemName)
{
m_gemNames.push_back(gemName);
if (m_gemNames.size() == 1)
{
m_worker->SetGemToDownload(m_gemNames[0], false);
m_workerThread.start();
}
}
void DownloadController::CancelGemDownload(const QString& gemName)
{
auto findResult = AZStd::find(m_gemNames.begin(), m_gemNames.end(), gemName);
if (findResult != m_gemNames.end())
{
if (findResult == m_gemNames.begin())
{
// HandleResults will remove the gem upon cancelling
PythonBindingsInterface::Get()->CancelDownload();
}
else
{
m_gemNames.erase(findResult);
}
}
}
void DownloadController::UpdateUIProgress(int progress)
{
m_lastProgress = progress;
emit GemDownloadProgress(progress);
}
void DownloadController::HandleResults(const QString& result)
{
bool succeeded = true;
if (!result.isEmpty())
{
QMessageBox::critical(nullptr, tr("Gem download"), result);
succeeded = false;
}
m_gemNames.erase(m_gemNames.begin());
emit Done(succeeded);
if (!m_gemNames.empty())
{
emit StartGemDownload(m_gemNames[0]);
}
else
{
m_workerThread.quit();
m_workerThread.wait();
}
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,72 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QString>
#include <QThread>
#include <AzCore/std/containers/vector.h>
#endif
QT_FORWARD_DECLARE_CLASS(QProcess)
namespace O3DE::ProjectManager
{
QT_FORWARD_DECLARE_CLASS(DownloadWorker)
class DownloadController : public QObject
{
Q_OBJECT
public:
explicit DownloadController(QWidget* parent = nullptr);
~DownloadController();
void AddGemDownload(const QString& gemName);
void CancelGemDownload(const QString& gemName);
bool IsDownloadQueueEmpty()
{
return m_gemNames.empty();
}
const AZStd::vector<QString>& GetDownloadQueue() const
{
return m_gemNames;
}
const QString& GetCurrentDownloadingGem() const
{
if (!m_gemNames.empty())
{
return m_gemNames[0];
}
else
{
static const QString emptyString;
return emptyString;
}
}
public slots:
void UpdateUIProgress(int progress);
void HandleResults(const QString& result);
signals:
void StartGemDownload(const QString& gemName);
void Done(bool success = true);
void GemDownloadProgress(int percentage);
private:
DownloadWorker* m_worker;
QThread m_workerThread;
QWidget* m_parent;
AZStd::vector<QString> m_gemNames;
int m_lastProgress;
};
} // namespace O3DE::ProjectManager
@@ -0,0 +1,48 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <DownloadController.h>
#include <DownloadWorker.h>
#include <PythonBindings.h>
namespace O3DE::ProjectManager
{
DownloadWorker::DownloadWorker()
: QObject()
{
}
void DownloadWorker::StartDownload()
{
auto gemDownloadProgress = [=](int downloadProgress)
{
m_downloadProgress = downloadProgress;
emit UpdateProgress(downloadProgress);
};
AZ::Outcome<void, AZStd::string> gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress);
if (gemInfoResult.IsSuccess())
{
emit Done("");
}
else
{
emit Done(tr("Gem download failed"));
}
}
void DownloadWorker::SetGemToDownload(const QString& gemName, bool downloadNow)
{
m_gemName = gemName;
if (downloadNow)
{
StartDownload();
}
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,42 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Outcome/Outcome.h>
#endif
QT_FORWARD_DECLARE_CLASS(QProcess)
namespace O3DE::ProjectManager
{
class DownloadWorker : public QObject
{
// Download was cancelled
inline static const QString DownloadCancelled = QObject::tr("Download Cancelled.");
Q_OBJECT
public:
explicit DownloadWorker();
~DownloadWorker() = default;
public slots:
void StartDownload();
void SetGemToDownload(const QString& gemName, bool downloadNow = true);
signals:
void UpdateProgress(int progress);
void Done(QString result = "");
private:
QString m_gemName;
int m_downloadProgress;
};
} // namespace O3DE::ProjectManager
@@ -29,17 +29,17 @@ namespace O3DE::ProjectManager
topBarFrameWidget->setLayout(topBarHLayout);
QTabWidget* tabWidget = new QTabWidget();
tabWidget->setObjectName("engineTab");
tabWidget->tabBar()->setObjectName("engineTabBar");
tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
m_tabWidget = new QTabWidget();
m_tabWidget->setObjectName("engineTab");
m_tabWidget->tabBar()->setObjectName("engineTabBar");
m_tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
m_engineSettingsScreen = new EngineSettingsScreen();
m_gemRepoScreen = new GemRepoScreen();
tabWidget->addTab(m_engineSettingsScreen, tr("General"));
tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories"));
topBarHLayout->addWidget(tabWidget);
m_tabWidget->addTab(m_engineSettingsScreen, tr("General"));
m_tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories"));
topBarHLayout->addWidget(m_tabWidget);
vLayout->addWidget(topBarFrameWidget);
@@ -61,4 +61,28 @@ namespace O3DE::ProjectManager
return true;
}
bool EngineScreenCtrl::ContainsScreen(ProjectManagerScreen screen)
{
if (screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum())
{
return true;
}
return false;
}
void EngineScreenCtrl::GoToScreen(ProjectManagerScreen screen)
{
if (screen == m_engineSettingsScreen->GetScreenEnum())
{
m_tabWidget->setCurrentWidget(m_engineSettingsScreen);
m_engineSettingsScreen->NotifyCurrentScreen();
}
else if (screen == m_gemRepoScreen->GetScreenEnum())
{
m_tabWidget->setCurrentWidget(m_gemRepoScreen);
m_gemRepoScreen->NotifyCurrentScreen();
}
}
} // namespace O3DE::ProjectManager
@@ -11,6 +11,8 @@
#include <ScreenWidget.h>
#endif
QT_FORWARD_DECLARE_CLASS(QTabWidget)
namespace O3DE::ProjectManager
{
QT_FORWARD_DECLARE_CLASS(EngineSettingsScreen)
@@ -26,7 +28,10 @@ namespace O3DE::ProjectManager
QString GetTabText() override;
bool IsTab() override;
bool ContainsScreen(ProjectManagerScreen screen) override;
void GoToScreen(ProjectManagerScreen screen) override;
QTabWidget* m_tabWidget = nullptr;
EngineSettingsScreen* m_engineSettingsScreen = nullptr;
GemRepoScreen* m_gemRepoScreen = nullptr;
};
@@ -12,13 +12,16 @@
#include <QMouseEvent>
#include <QLabel>
#include <QPushButton>
#include <QProgressBar>
#include <TagWidget.h>
#include <QMenu>
namespace O3DE::ProjectManager
{
CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, QWidget* parent)
CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent)
: QWidget(parent)
, m_gemModel(gemModel)
, m_downloadController(downloadController)
{
setObjectName("GemCatalogCart");
@@ -42,6 +45,9 @@ namespace O3DE::ProjectManager
hLayout->addWidget(closeButton);
m_layout->addLayout(hLayout);
// downloading gems
CreateDownloadSection();
// added
CreateGemSection( tr("Gem to be activated"), tr("Gems to be activated"), [=]
{
@@ -149,6 +155,116 @@ namespace O3DE::ProjectManager
update();
}
void CartOverlayWidget::OnCancelDownloadActivated(const QString& gemName)
{
m_downloadController->CancelGemDownload(gemName);
}
void CartOverlayWidget::CreateDownloadSection()
{
QWidget* widget = new QWidget();
widget->setFixedWidth(s_width);
m_layout->addWidget(widget);
QVBoxLayout* layout = new QVBoxLayout();
layout->setAlignment(Qt::AlignTop);
widget->setLayout(layout);
QLabel* titleLabel = new QLabel();
titleLabel->setObjectName("GemCatalogCartOverlaySectionLabel");
layout->addWidget(titleLabel);
titleLabel->setText(tr("Gems to be installed"));
// Create header section
QWidget* downloadingGemsWidget = new QWidget();
downloadingGemsWidget->setObjectName("GemCatalogCartOverlayGemDownloadHeader");
layout->addWidget(downloadingGemsWidget);
QVBoxLayout* gemDownloadLayout = new QVBoxLayout();
gemDownloadLayout->setMargin(0);
gemDownloadLayout->setAlignment(Qt::AlignTop);
downloadingGemsWidget->setLayout(gemDownloadLayout);
QLabel* processingQueueLabel = new QLabel("Processing Queue");
gemDownloadLayout->addWidget(processingQueueLabel);
QWidget* downloadingItemWidget = new QWidget();
downloadingItemWidget->setObjectName("GemCatalogCartOverlayGemDownloadBG");
gemDownloadLayout->addWidget(downloadingItemWidget);
QVBoxLayout* downloadingItemLayout = new QVBoxLayout();
downloadingItemLayout->setAlignment(Qt::AlignTop);
downloadingItemWidget->setLayout(downloadingItemLayout);
auto update = [=](int downloadProgress)
{
if (m_downloadController->IsDownloadQueueEmpty())
{
widget->hide();
}
else
{
widget->setUpdatesEnabled(false);
// remove items
QLayoutItem* layoutItem = nullptr;
while ((layoutItem = downloadingItemLayout->takeAt(0)) != nullptr)
{
if (layoutItem->layout())
{
// Gem info row
QLayoutItem* rowLayoutItem = nullptr;
while ((rowLayoutItem = layoutItem->layout()->takeAt(0)) != nullptr)
{
rowLayoutItem->widget()->deleteLater();
}
layoutItem->layout()->deleteLater();
}
if (layoutItem->widget())
{
layoutItem->widget()->deleteLater();
}
}
// Setup gem download rows
const AZStd::vector<QString>& downloadQueue = m_downloadController->GetDownloadQueue();
QLabel* downloadsInProgessLabel = new QLabel("");
downloadsInProgessLabel->setText(
QString("%1 %2").arg(downloadQueue.size()).arg(downloadQueue.size() == 1 ? tr("download in progress...") : tr("downloads in progress...")));
downloadingItemLayout->addWidget(downloadsInProgessLabel);
for (int downloadingGemNumber = 0; downloadingGemNumber < downloadQueue.size(); ++downloadingGemNumber)
{
QHBoxLayout* nameProgressLayout = new QHBoxLayout();
TagWidget* newTag = new TagWidget(downloadQueue[downloadingGemNumber]);
nameProgressLayout->addWidget(newTag);
QLabel* progress = new QLabel(downloadingGemNumber == 0? QString("%1%").arg(downloadProgress) : tr("Queued"));
nameProgressLayout->addWidget(progress);
QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
nameProgressLayout->addSpacerItem(spacer);
QLabel* cancelText = new QLabel(QString("<a href=\"%1\">Cancel</a>").arg(downloadQueue[downloadingGemNumber]));
cancelText->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
connect(cancelText, &QLabel::linkActivated, this, &CartOverlayWidget::OnCancelDownloadActivated);
nameProgressLayout->addWidget(cancelText);
downloadingItemLayout->addLayout(nameProgressLayout);
QProgressBar* downloadProgessBar = new QProgressBar();
downloadingItemLayout->addWidget(downloadProgessBar);
downloadProgessBar->setValue(downloadingGemNumber == 0 ? downloadProgress : 0);
}
widget->setUpdatesEnabled(true);
widget->show();
}
};
auto downloadEnded = [=](bool /*success*/)
{
update(0); // update the list to remove the gem that has finished
};
// connect to download controller data changed
connect(m_downloadController, &DownloadController::GemDownloadProgress, this, update);
connect(m_downloadController, &DownloadController::Done, this, downloadEnded);
update(0);
}
QStringList CartOverlayWidget::ConvertFromModelIndices(const QVector<QModelIndex>& gems) const
{
QStringList gemNames;
@@ -160,9 +276,10 @@ namespace O3DE::ProjectManager
return gemNames;
}
CartButton::CartButton(GemModel* gemModel, QWidget* parent)
CartButton::CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent)
: QWidget(parent)
, m_gemModel(gemModel)
, m_downloadController(downloadController)
{
m_layout = new QHBoxLayout();
m_layout->setMargin(0);
@@ -239,7 +356,7 @@ namespace O3DE::ProjectManager
delete m_cartOverlay;
}
m_cartOverlay = new CartOverlayWidget(m_gemModel, this);
m_cartOverlay = new CartOverlayWidget(m_gemModel, m_downloadController, this);
connect(m_cartOverlay, &QWidget::destroyed, this, [=]
{
// Reset the overlay pointer on destruction to prevent dangling pointers.
@@ -265,7 +382,7 @@ namespace O3DE::ProjectManager
}
}
GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, QWidget* parent)
GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent)
: QFrame(parent)
{
QHBoxLayout* hLayout = new QHBoxLayout();
@@ -293,8 +410,29 @@ namespace O3DE::ProjectManager
hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
hLayout->addSpacerItem(new QSpacerItem(75, 0, QSizePolicy::Fixed));
CartButton* cartButton = new CartButton(gemModel);
CartButton* cartButton = new CartButton(gemModel, downloadController);
hLayout->addWidget(cartButton);
hLayout->addSpacing(16);
// Separating line
QFrame* vLine = new QFrame();
vLine->setFrameShape(QFrame::VLine);
vLine->setObjectName("verticalSeparatingLine");
hLayout->addWidget(vLine);
hLayout->addSpacing(16);
QMenu* gemMenu = new QMenu(this);
gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); });
gemMenu->addSeparator();
gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); });
QPushButton* gemMenuButton = new QPushButton(this);
gemMenuButton->setObjectName("gemCatalogMenuButton");
gemMenuButton->setMenu(gemMenu);
gemMenuButton->setIcon(QIcon(":/menu.svg"));
gemMenuButton->setIconSize(QSize(36, 24));
hLayout->addWidget(gemMenuButton);
}
void GemCatalogHeaderWidget::ReinitForProject()
@@ -8,21 +8,23 @@
#pragma once
#include <AzCore/std/function/function_fwd.h>
#if !defined(Q_MOC_RUN)
#include <AzCore/std/function/function_fwd.h>
#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 <QHideEvent>
#include <QVBoxLayout>
#include <DownloadController.h>
#endif
QT_FORWARD_DECLARE_CLASS(QPushButton)
QT_FORWARD_DECLARE_CLASS(QLabel)
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
QT_FORWARD_DECLARE_CLASS(QHideEvent)
QT_FORWARD_DECLARE_CLASS(QMoveEvent)
namespace O3DE::ProjectManager
{
class CartOverlayWidget
@@ -31,16 +33,19 @@ namespace O3DE::ProjectManager
Q_OBJECT // AUTOMOC
public:
CartOverlayWidget(GemModel* gemModel, QWidget* parent = nullptr);
CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
private:
QStringList ConvertFromModelIndices(const QVector<QModelIndex>& gems) const;
using GetTagIndicesCallback = AZStd::function<QVector<QModelIndex>()>;
void CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices);
void CreateDownloadSection();
void OnCancelDownloadActivated(const QString& link);
QVBoxLayout* m_layout = nullptr;
GemModel* m_gemModel = nullptr;
DownloadController* m_downloadController = nullptr;
inline constexpr static int s_width = 240;
};
@@ -51,7 +56,7 @@ namespace O3DE::ProjectManager
Q_OBJECT // AUTOMOC
public:
CartButton(GemModel* gemModel, QWidget* parent = nullptr);
CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
~CartButton();
void ShowOverlay();
@@ -64,6 +69,7 @@ namespace O3DE::ProjectManager
QLabel* m_countLabel = nullptr;
QPushButton* m_dropDownButton = nullptr;
CartOverlayWidget* m_cartOverlay = nullptr;
DownloadController* m_downloadController = nullptr;
inline constexpr static int s_iconSize = 24;
inline constexpr static int s_arrowDownIconSize = 8;
@@ -75,11 +81,15 @@ namespace O3DE::ProjectManager
Q_OBJECT // AUTOMOC
public:
explicit GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, QWidget* parent = nullptr);
explicit GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent = nullptr);
~GemCatalogHeaderWidget() = default;
void ReinitForProject();
signals:
void AddGem();
void OpenGemsRepo();
private:
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
inline constexpr static int s_height = 60;
@@ -12,12 +12,17 @@
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <GemCatalog/GemRequirementDialog.h>
#include <GemCatalog/GemDependenciesDialog.h>
#include <DownloadController.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTimer>
#include <PythonBindingsInterface.h>
#include <QMessageBox>
#include <QDir>
#include <QStandardPaths>
#include <QFileDialog>
#include <QMessageBox>
namespace O3DE::ProjectManager
{
@@ -32,9 +37,13 @@ namespace O3DE::ProjectManager
vLayout->setSpacing(0);
setLayout(vLayout);
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel);
m_downloadController = new DownloadController();
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController);
vLayout->addWidget(m_headerWidget);
connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo);
QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setMargin(0);
vLayout->addLayout(hLayout);
@@ -61,11 +70,15 @@ namespace O3DE::ProjectManager
hLayout->addWidget(filterWidget);
hLayout->addLayout(middleVLayout);
hLayout->addWidget(m_gemInspector);
m_notificationsView = AZStd::make_unique<AzToolsFramework::ToastNotificationsView>(this, AZ_CRC("GemCatalogNotificationsView"));
m_notificationsView->SetOffset(QPoint(10, 70));
}
void GemCatalogScreen::ReinitForProject(const QString& projectPath)
{
m_gemModel->clear();
m_gemsToRegisterWithProject.clear();
FillModel(projectPath);
if (m_filterWidget)
@@ -81,6 +94,48 @@ namespace O3DE::ProjectManager
m_headerWidget->ReinitForProject();
connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter);
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
connect(
m_headerWidget, &GemCatalogHeaderWidget::AddGem,
[&]()
{
EngineInfo engineInfo;
QString defaultPath;
AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
if (engineInfoResult.IsSuccess())
{
engineInfo = engineInfoResult.GetValue();
defaultPath = engineInfo.m_defaultGemsFolder;
}
if (defaultPath.isEmpty())
{
defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
}
QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Browse"), defaultPath));
if (!directory.isEmpty())
{
// register the gem to the o3de_manifest.json and to the project after the user confirms
// project creation/update
auto registerResult = PythonBindingsInterface::Get()->RegisterGem(directory);
if(!registerResult)
{
QMessageBox::critical(this, tr("Failed to add gem"), registerResult.GetError().c_str());
}
else
{
m_gemsToRegisterWithProject.insert(directory);
AZ::Outcome<GemInfo, void> gemInfoResult = PythonBindingsInterface::Get()->GetGemInfo(directory);
if (gemInfoResult)
{
m_gemModel->AddGem(gemInfoResult.GetValue<GemInfo>());
m_gemModel->UpdateGemDependencies();
}
}
}
});
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
@@ -89,6 +144,72 @@ namespace O3DE::ProjectManager
});
}
void GemCatalogScreen::OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies)
{
if (m_notificationsEnabled)
{
bool added = GemModel::IsAdded(modelIndex);
bool dependency = GemModel::IsAddedDependency(modelIndex);
bool gemStateChanged = (added && !dependency) || (!added && !dependency);
if (!gemStateChanged && !numChangedDependencies)
{
// no actual changes made
return;
}
QString notification;
if (gemStateChanged)
{
notification = GemModel::GetDisplayName(modelIndex);
if (numChangedDependencies > 0)
{
notification += " " + tr("and") + " ";
}
}
if (numChangedDependencies == 1 )
{
notification += "1 Gem " + tr("dependency");
}
else if (numChangedDependencies > 1)
{
notification += QString("%d Gem ").arg(numChangedDependencies) + tr("dependencies");
}
notification += " " + (added ? tr("activated") : tr("deactivated"));
AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, "");
toastConfiguration.m_customIconImage = ":/gem.svg";
toastConfiguration.m_borderRadius = 4;
toastConfiguration.m_duration = AZStd::chrono::milliseconds(3000);
m_notificationsView->ShowToastNotification(toastConfiguration);
}
}
void GemCatalogScreen::hideEvent(QHideEvent* event)
{
ScreenWidget::hideEvent(event);
m_notificationsView->OnHide();
}
void GemCatalogScreen::showEvent(QShowEvent* event)
{
ScreenWidget::showEvent(event);
m_notificationsView->OnShow();
}
void GemCatalogScreen::resizeEvent(QResizeEvent* event)
{
ScreenWidget::resizeEvent(event);
m_notificationsView->UpdateToastPosition();
}
void GemCatalogScreen::moveEvent(QMoveEvent* event)
{
ScreenWidget::moveEvent(event);
m_notificationsView->UpdateToastPosition();
}
void GemCatalogScreen::FillModel(const QString& projectPath)
{
AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath);
@@ -101,7 +222,22 @@ namespace O3DE::ProjectManager
m_gemModel->AddGem(gemInfo);
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos();
if (allRepoGemInfosResult.IsSuccess())
{
const QVector<GemInfo> allRepoGemInfos = allRepoGemInfosResult.GetValue();
for (const GemInfo& gemInfo : allRepoGemInfos)
{
m_gemModel->AddGem(gemInfo);
}
}
else
{
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems from repos.<br><br>Error:<br>%1").arg(allRepoGemInfosResult.GetError().c_str()));
}
m_gemModel->UpdateGemDependencies();
m_notificationsEnabled = false;
// Gather enabled gems for the given project.
auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath);
@@ -126,12 +262,14 @@ namespace O3DE::ProjectManager
}
else
{
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.<br><br>Error:<br>%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
}
m_notificationsEnabled = true;
}
else
{
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str()));
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.<br><br>Error:<br>%2").arg(projectPath, allGemInfosResult.GetError().c_str()));
}
}
@@ -173,6 +311,12 @@ namespace O3DE::ProjectManager
return EnableDisableGemsResult::Failed;
}
// register external gems that were added with relative paths
if (m_gemsToRegisterWithProject.contains(gemPath))
{
pythonBindings->RegisterGem(QDir(projectPath).relativeFilePath(gemPath), projectPath);
}
}
for (const QModelIndex& modelIndex : toBeRemoved)
@@ -191,6 +335,27 @@ namespace O3DE::ProjectManager
return EnableDisableGemsResult::Success;
}
void GemCatalogScreen::HandleOpenGemRepo()
{
QVector<QModelIndex> gemsToBeAdded = m_gemModel->GatherGemsToBeAdded(true);
QVector<QModelIndex> gemsToBeRemoved = m_gemModel->GatherGemsToBeRemoved(true);
if (!gemsToBeAdded.empty() || !gemsToBeRemoved.empty())
{
QMessageBox::StandardButton warningResult = QMessageBox::warning(
nullptr, "Pending Changes",
"There are some unsaved changes to the gem selection,<br> they will be lost if you change screens.<br> Are you sure?",
QMessageBox::No | QMessageBox::Yes);
if (warningResult != QMessageBox::Yes)
{
return;
}
}
emit ChangeScreenRequest(ProjectManagerScreen::GemRepos);
}
ProjectManagerScreen GemCatalogScreen::GetScreenEnum()
{
return ProjectManagerScreen::GemCatalog;
@@ -10,12 +10,16 @@
#if !defined(Q_MOC_RUN)
#include <ScreenWidget.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzToolsFramework/UI/Notifications/ToastNotificationsView.h>
#include <GemCatalog/GemCatalogHeaderWidget.h>
#include <GemCatalog/GemFilterWidget.h>
#include <GemCatalog/GemListView.h>
#include <GemCatalog/GemInspector.h>
#include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <QSet>
#include <QString>
#endif
namespace O3DE::ProjectManager
@@ -39,10 +43,26 @@ namespace O3DE::ProjectManager
EnableDisableGemsResult EnableDisableGemsForProject(const QString& projectPath);
GemModel* GetGemModel() const { return m_gemModel; }
DownloadController* GetDownloadController() const { return m_downloadController; }
public slots:
void OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies);
protected:
void hideEvent(QHideEvent* event) override;
void showEvent(QShowEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void moveEvent(QMoveEvent* event) override;
private slots:
void HandleOpenGemRepo();
private:
void FillModel(const QString& projectPath);
AZStd::unique_ptr<AzToolsFramework::ToastNotificationsView> m_notificationsView;
GemListView* m_gemListView = nullptr;
GemInspector* m_gemInspector = nullptr;
GemModel* m_gemModel = nullptr;
@@ -50,5 +70,8 @@ namespace O3DE::ProjectManager
GemSortFilterProxyModel* m_proxModel = nullptr;
QVBoxLayout* m_filterWidgetLayout = nullptr;
GemFilterWidget* m_filterWidget = nullptr;
DownloadController* m_downloadController = nullptr;
bool m_notificationsEnabled = true;
QSet<QString> m_gemsToRegisterWithProject;
};
} // namespace O3DE::ProjectManager
@@ -10,6 +10,7 @@
#include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzToolsFramework/UI/Notifications/ToastBus.h>
namespace O3DE::ProjectManager
{
@@ -299,23 +300,50 @@ namespace O3DE::ProjectManager
AZ_Assert(gemModel, "Failed to obtain GemModel");
QVector<QModelIndex> dependencies = gemModel->GatherGemDependencies(modelIndex);
uint32_t numChangedDependencies = 0;
if (IsAdded(modelIndex))
{
for (const QModelIndex& dependency : dependencies)
{
SetIsAddedDependency(*gemModel, dependency, true);
if (!IsAddedDependency(dependency))
{
SetIsAddedDependency(*gemModel, dependency, true);
// if the gem was already added then the state didn't really change
if (!IsAdded(dependency))
{
numChangedDependencies++;
}
}
}
}
else
{
// still a dependency if some added gem depends on this one
SetIsAddedDependency(model, modelIndex, gemModel->HasDependentGems(modelIndex));
bool hasDependentGems = gemModel->HasDependentGems(modelIndex);
if (IsAddedDependency(modelIndex) != hasDependentGems)
{
SetIsAddedDependency(model, modelIndex, hasDependentGems);
}
for (const QModelIndex& dependency : dependencies)
{
SetIsAddedDependency(*gemModel, dependency, gemModel->HasDependentGems(dependency));
hasDependentGems = gemModel->HasDependentGems(dependency);
if (IsAddedDependency(dependency) != hasDependentGems)
{
SetIsAddedDependency(*gemModel, dependency, hasDependentGems);
// if the gem was already added then the state didn't really change
if (!IsAdded(dependency))
{
numChangedDependencies++;
}
}
}
}
gemModel->emit gemStatusChanged(modelIndex, numChangedDependencies);
}
void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded)
@@ -488,5 +516,4 @@ namespace O3DE::ProjectManager
}
return result;
}
} // namespace O3DE::ProjectManager
@@ -77,6 +77,9 @@ namespace O3DE::ProjectManager
int TotalAddedGems(bool includeDependencies = false) const;
signals:
void gemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies);
private:
void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames);
void GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& inOutGems);
@@ -108,7 +108,7 @@ namespace O3DE::ProjectManager
// Draw refresh button
painter->drawPixmap(
repoUpdatedDateRect.left() + repoUpdatedDateRect.width() + s_refreshIconSpacing,
repoUpdatedDateRect.left() + s_updatedMaxWidth + s_refreshIconSpacing,
contentRect.center().y() - s_refreshIconSize / 3, // Dividing size by 3 centers much better
m_refreshIcon);
@@ -150,6 +150,11 @@ namespace O3DE::ProjectManager
emit RemoveRepo(modelIndex);
return true;
}
else if (keyEvent->key() == Qt::Key_R || keyEvent->key() == Qt::Key_F5)
{
emit RefreshRepo(modelIndex);
return true;
}
}
if (event->type() == QEvent::MouseButtonPress)
@@ -160,6 +165,7 @@ namespace O3DE::ProjectManager
CalcRects(option, fullRect, itemRect, contentRect);
const QRect buttonRect = CalcButtonRect(contentRect);
const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect);
const QRect refreshButtonRect = CalcRefreshButtonRect(contentRect, buttonRect);
if (buttonRect.contains(mouseEvent->pos()))
{
@@ -172,6 +178,11 @@ namespace O3DE::ProjectManager
emit RemoveRepo(modelIndex);
return true;
}
else if (refreshButtonRect.contains(mouseEvent->pos()))
{
emit RefreshRepo(modelIndex);
return true;
}
}
return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
@@ -231,6 +242,13 @@ namespace O3DE::ProjectManager
return QRect(topLeft, QSize(s_iconSize, s_iconSize));
}
QRect GemRepoItemDelegate::CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) const
{
const int topLeftX = buttonRect.left() + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 2 + s_refreshIconSpacing;
const QPoint topLeft = QPoint(topLeftX, contentRect.center().y() - s_refreshIconSize / 3);
return QRect(topLeft, QSize(s_refreshIconSize, s_refreshIconSize));
}
void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const
{
painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon);
@@ -68,12 +68,14 @@ namespace O3DE::ProjectManager
signals:
void RemoveRepo(const QModelIndex& modelIndex);
void RefreshRepo(const QModelIndex& modelIndex);
protected:
void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
QRect CalcButtonRect(const QRect& contentRect) const;
QRect CalcDeleteButtonRect(const QRect& contentRect) const;
QRect CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) const;
void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
void DrawEditButtons(QPainter* painter, const QRect& contentRect) const;
@@ -24,6 +24,7 @@ namespace O3DE::ProjectManager
GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, this);
connect(itemDelegate, &GemRepoItemDelegate::RemoveRepo, this, &GemRepoListView::RemoveRepo);
connect(itemDelegate, &GemRepoItemDelegate::RefreshRepo, this, &GemRepoListView::RefreshRepo);
setItemDelegate(itemDelegate);
}
} // namespace O3DE::ProjectManager
@@ -28,5 +28,6 @@ namespace O3DE::ProjectManager
signals:
void RemoveRepo(const QModelIndex& modelIndex);
void RefreshRepo(const QModelIndex& modelIndex);
};
} // namespace O3DE::ProjectManager
@@ -126,6 +126,36 @@ namespace O3DE::ProjectManager
}
}
void GemRepoScreen::HandleRefreshAllButton()
{
bool refreshResult = PythonBindingsInterface::Get()->RefreshAllGemRepos();
Reinit();
if (!refreshResult)
{
QMessageBox::critical(
this, tr("Operation failed"), QString("Some repos failed to refresh."));
}
}
void GemRepoScreen::HandleRefreshRepoButton(const QModelIndex& modelIndex)
{
const QString repoUri = m_gemRepoModel->GetRepoUri(modelIndex);
AZ::Outcome<void, AZStd::string> refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(repoUri);
if (refreshResult.IsSuccess())
{
Reinit();
}
else
{
QMessageBox::critical(
this, tr("Operation failed"),
QString("Failed to refresh gem repo %1<br>Error:<br>%2")
.arg(m_gemRepoModel->GetName(modelIndex), refreshResult.GetError().c_str()));
}
}
void GemRepoScreen::FillModel()
{
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> allGemRepoInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoInfos();
@@ -237,6 +267,8 @@ namespace O3DE::ProjectManager
m_AllUpdateButton->setObjectName("gemRepoHeaderRefreshButton");
topMiddleHLayout->addWidget(m_AllUpdateButton);
connect(m_AllUpdateButton, &QPushButton::clicked, this, &GemRepoScreen::HandleRefreshAllButton);
topMiddleHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
QPushButton* addRepoButton = new QPushButton(tr("Add Repository"), this);
@@ -280,6 +312,7 @@ namespace O3DE::ProjectManager
middleVLayout->addWidget(m_gemRepoListView);
connect(m_gemRepoListView, &GemRepoListView::RemoveRepo, this, &GemRepoScreen::HandleRemoveRepoButton);
connect(m_gemRepoListView, &GemRepoListView::RefreshRepo, this, &GemRepoScreen::HandleRefreshRepoButton);
hLayout->addLayout(middleVLayout);
@@ -40,6 +40,8 @@ namespace O3DE::ProjectManager
public slots:
void HandleAddRepoButton();
void HandleRemoveRepoButton(const QModelIndex& modelIndex);
void HandleRefreshAllButton();
void HandleRefreshRepoButton(const QModelIndex& modelIndex);
private:
void FillModel();
@@ -223,6 +223,7 @@ namespace RedirectOutput
}
} // namespace RedirectOutput
namespace O3DE::ProjectManager
{
PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath)
@@ -301,6 +302,8 @@ namespace O3DE::ProjectManager
m_enableGemProject = pybind11::module::import("o3de.enable_gem");
m_disableGemProject = pybind11::module::import("o3de.disable_gem");
m_editProjectProperties = pybind11::module::import("o3de.project_properties");
m_download = pybind11::module::import("o3de.download");
m_repo = pybind11::module::import("o3de.repo");
m_pathlib = pybind11::module::import("pathlib");
// make sure the engine is registered
@@ -555,6 +558,47 @@ namespace O3DE::ProjectManager
return AZ::Success(AZStd::move(gemNames));
}
AZ::Outcome<void, AZStd::string> PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath)
{
bool registrationResult = false;
auto result = ExecuteWithLockErrorHandling(
[&]
{
auto externalProjectPath = projectPath.isEmpty() ? pybind11::none() : QString_To_Py_Path(projectPath);
auto pythonRegistrationResult = m_register.attr("register")(
pybind11::none(), // engine_path
pybind11::none(), // project_path
QString_To_Py_Path(gemPath), // gem folder
pybind11::none(), // external subdirectory
pybind11::none(), // template_path
pybind11::none(), // restricted folder
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
externalProjectPath // external_subdir_project_path
);
// Returns an exit code so boolify it then invert result
registrationResult = !pythonRegistrationResult.cast<bool>();
});
if (!result.IsSuccess())
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
else if (!registrationResult)
{
return AZ::Failure<AZStd::string>(AZStd::string::format("Failed to register gem path %s", gemPath.toUtf8().constData()));
}
return AZ::Success();
}
bool PythonBindings::AddProject(const QString& path)
{
bool registrationResult = false;
@@ -939,6 +983,46 @@ namespace O3DE::ProjectManager
}
}
AZ::Outcome<void, AZStd::string> PythonBindings::RefreshGemRepo(const QString& repoUri)
{
bool refreshResult = false;
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
[&]
{
auto pyUri = QString_To_Py_String(repoUri);
auto pythonRefreshResult = m_repo.attr("refresh_repo")(pyUri);
// Returns an exit code so boolify it then invert result
refreshResult = !pythonRefreshResult.cast<bool>();
});
if (!result.IsSuccess())
{
return result;
}
else if (!refreshResult)
{
return AZ::Failure<AZStd::string>("Failed to refresh repo.");
}
return AZ::Success();
}
bool PythonBindings::RefreshAllGemRepos()
{
bool refreshResult = false;
bool result = ExecuteWithLock(
[&]
{
auto pythonRefreshResult = m_repo.attr("refresh_repos")();
// Returns an exit code so boolify it then invert result
refreshResult = !pythonRefreshResult.cast<bool>();
});
return result && refreshResult;
}
bool PythonBindings::AddGemRepo(const QString& repoUri)
{
bool registrationResult = false;
@@ -1075,4 +1159,73 @@ namespace O3DE::ProjectManager
std::sort(gemRepos.begin(), gemRepos.end());
return AZ::Success(AZStd::move(gemRepos));
}
AZ::Outcome<void, AZStd::string> PythonBindings::DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback)
{
// This process is currently limited to download a single gem at a time.
bool downloadSucceeded = false;
m_requestCancelDownload = false;
auto result = ExecuteWithLockErrorHandling(
[&]
{
auto downloadResult = m_download.attr("download_gem")(
QString_To_Py_String(gemName), // gem name
pybind11::none(), // destination path
false, // skip auto register
pybind11::cpp_function(
[this, gemProgressCallback](int progress)
{
gemProgressCallback(progress);
return m_requestCancelDownload;
}) // Callback for download progress and cancelling
);
downloadSucceeded = (downloadResult.cast<int>() == 0);
});
if (!result.IsSuccess())
{
return result;
}
else if (!downloadSucceeded)
{
return AZ::Failure<AZStd::string>("Failed to download gem.");
}
return AZ::Success();
}
void PythonBindings::CancelDownload()
{
m_requestCancelDownload = true;
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos()
{
QVector<GemInfo> gemInfos;
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
[&]
{
auto gemPaths = m_repo.attr("get_gem_json_paths_from_all_cached_repos")();
if (pybind11::isinstance<pybind11::set>(gemPaths))
{
for (auto path : gemPaths)
{
GemInfo gemInfo = GemInfoFromPath(path, pybind11::none());
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded;
gemInfos.push_back(gemInfo);
}
}
});
if (!result.IsSuccess())
{
return AZ::Failure(result.GetError());
}
return AZ::Success(AZStd::move(gemInfos));
}
}
@@ -42,6 +42,7 @@ namespace O3DE::ProjectManager
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetEngineGemInfos() override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) override;
AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) override;
AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) override;
// Project
AZ::Outcome<ProjectInfo> CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override;
@@ -58,9 +59,14 @@ namespace O3DE::ProjectManager
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) override;
// Gem Repos
AZ::Outcome<void, AZStd::string> RefreshGemRepo(const QString& repoUri) override;
bool RefreshAllGemRepos() override;
bool AddGemRepo(const QString& repoUri) override;
bool RemoveGemRepo(const QString& repoUri) override;
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) override;
void CancelDownload() override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() override;
private:
AZ_DISABLE_COPY_MOVE(PythonBindings);
@@ -87,6 +93,10 @@ namespace O3DE::ProjectManager
pybind11::handle m_enableGemProject;
pybind11::handle m_disableGemProject;
pybind11::handle m_editProjectProperties;
pybind11::handle m_download;
pybind11::handle m_repo;
pybind11::handle m_pathlib;
bool m_requestCancelDownload = false;
};
}
@@ -91,6 +91,14 @@ namespace O3DE::ProjectManager
*/
virtual AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) = 0;
/**
* Registers the gem to the specified project, or to the o3de_manifest.json if no project path is given
* @param gemPath the path to the gem
* @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json
* @return An outcome with the success flag as well as an error message in case of a failure.
*/
virtual AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0;
// Projects
@@ -168,6 +176,19 @@ namespace O3DE::ProjectManager
// Gem Repos
/**
* Refresh gem repo in the current engine.
* @param repoUri the absolute filesystem path or url to the gem repo.
* @return An outcome with the success flag as well as an error message in case of a failure.
*/
virtual AZ::Outcome<void, AZStd::string> RefreshGemRepo(const QString& repoUri) = 0;
/**
* Refresh all gem repos in the current engine.
* @return true on success, false on failure.
*/
virtual bool RefreshAllGemRepos() = 0;
/**
* Registers this gem repo with the current engine.
* @param repoUri the absolute filesystem path or url to the gem repo.
@@ -187,6 +208,25 @@ namespace O3DE::ProjectManager
* @return A list of gem repo infos.
*/
virtual AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() = 0;
/**
* Downloads and registers a Gem.
* @param gemName the name of the Gem to download
* @param gemProgressCallback a callback function that is called with an int percentage download value
* @return an outcome with a string error message on failure.
*/
virtual AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) = 0;
/**
* Cancels the current download.
*/
virtual void CancelDownload() = 0;
/**
* Gathers all gem infos for all gems registered from repos.
* @return A list of gem infos.
*/
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() = 0;
};
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
@@ -47,6 +47,14 @@ namespace O3DE::ProjectManager
return tr("Missing");
}
virtual bool ContainsScreen([[maybe_unused]] ProjectManagerScreen screen)
{
return false;
}
virtual void GoToScreen([[maybe_unused]] ProjectManagerScreen screen)
{
}
//! Notify this screen it is the current screen
virtual void NotifyCurrentScreen()
{
@@ -55,7 +63,7 @@ namespace O3DE::ProjectManager
signals:
void ChangeScreenRequest(ProjectManagerScreen screen);
void GotoPreviousScreenRequest();
void GoToPreviousScreenRequest();
void ResetScreenRequest(ProjectManagerScreen screen);
void NotifyCurrentProject(const QString& projectPath);
void NotifyBuildProject(const ProjectInfo& projectInfo);
@@ -83,11 +83,28 @@ namespace O3DE::ProjectManager
bool ScreensCtrl::ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit)
{
ScreenWidget* newScreen = nullptr;
const auto iterator = m_screenMap.find(screen);
if (iterator != m_screenMap.end())
{
newScreen = iterator.value();
}
else
{
// Check if screen is contained by another screen
for (ScreenWidget* checkingScreen : m_screenMap)
{
if (checkingScreen->ContainsScreen(screen))
{
newScreen = checkingScreen;
break;
}
}
}
if (newScreen)
{
ScreenWidget* currentScreen = GetCurrentScreen();
ScreenWidget* newScreen = iterator.value();
if (currentScreen != newScreen)
{
@@ -109,6 +126,11 @@ namespace O3DE::ProjectManager
newScreen->NotifyCurrentScreen();
if (iterator == m_screenMap.end())
{
newScreen->GoToScreen(screen);
}
return true;
}
}
@@ -116,7 +138,7 @@ namespace O3DE::ProjectManager
return false;
}
bool ScreensCtrl::GotoPreviousScreen()
bool ScreensCtrl::GoToPreviousScreen()
{
if (!m_screenVisitOrder.isEmpty())
{
@@ -171,7 +193,7 @@ namespace O3DE::ProjectManager
m_screenMap.insert(screen, newScreen);
connect(newScreen, &ScreenWidget::ChangeScreenRequest, this, &ScreensCtrl::ChangeToScreen);
connect(newScreen, &ScreenWidget::GotoPreviousScreenRequest, this, &ScreensCtrl::GotoPreviousScreen);
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);
@@ -41,7 +41,7 @@ namespace O3DE::ProjectManager
public slots:
bool ChangeToScreen(ProjectManagerScreen screen);
bool ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit = true);
bool GotoPreviousScreen();
bool GoToPreviousScreen();
void ResetScreen(ProjectManagerScreen screen);
void ResetAllScreens();
void DeleteScreen(ProjectManagerScreen screen);
@@ -40,6 +40,10 @@ namespace O3DE::ProjectManager
m_updateSettingsScreen = new UpdateProjectSettingsScreen();
m_gemCatalogScreen = new GemCatalogScreen();
connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, [this](ProjectManagerScreen screen){
emit ChangeScreenRequest(screen);
});
m_stack = new QStackedWidget(this);
m_stack->setObjectName("body");
m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
@@ -118,7 +122,7 @@ namespace O3DE::ProjectManager
{
if (UpdateProjectSettings(true))
{
emit GotoPreviousScreenRequest();
emit GoToPreviousScreenRequest();
}
}
}
@@ -136,6 +140,11 @@ namespace O3DE::ProjectManager
}
else if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen)
{
if (!m_gemCatalogScreen->GetDownloadController()->IsDownloadQueueEmpty())
{
QMessageBox::critical(this, tr("Gems downloading"), tr("You must wait for gems to finish downloading before continuing."));
return;
}
// Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project.
const GemCatalogScreen::EnableDisableGemsResult result = m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path);
if (result == GemCatalogScreen::EnableDisableGemsResult::Failed)
@@ -29,6 +29,10 @@ set(FILES
Source/FormImageBrowseEditWidget.cpp
Source/GemsSubWidget.h
Source/GemsSubWidget.cpp
Source/DownloadController.h
Source/DownloadController.cpp
Source/DownloadWorker.h
Source/DownloadWorker.cpp
Source/PathValidator.h
Source/PathValidator.cpp
Source/ProjectManagerWindow.h