From 02364b869e20adb9828678f0c76910df34b30285 Mon Sep 17 00:00:00 2001 From: AMZN-Phil Date: Tue, 19 Oct 2021 19:48:36 -0700 Subject: [PATCH] First part of UI feedback for downloading gems Signed-off-by: AMZN-Phil --- .../Resources/ProjectManager.qss | 12 ++ .../GemCatalog/GemCatalogHeaderWidget.cpp | 119 +++++++++++++++++- .../GemCatalog/GemCatalogHeaderWidget.h | 10 +- .../Source/GemCatalog/GemCatalogScreen.cpp | 6 +- .../Source/GemCatalog/GemCatalogScreen.h | 2 + .../ProjectManager/Source/PythonBindings.cpp | 27 ++++ .../ProjectManager/Source/PythonBindings.h | 2 + .../Source/PythonBindingsInterface.h | 2 + .../Source/UpdateProjectCtrl.cpp | 7 +- .../project_manager_files.cmake | 4 + 10 files changed, 181 insertions(+), 10 deletions(-) diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index 5f7826dbac..507cf38726 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -481,6 +481,18 @@ QProgressBar::chunk { font-weight: 600; } +#GemCatalogCartOverlayGemDownloadHeader { + margin:0; + padding: 0px; + background-color: #333333; +} + +#GemCatalogCartOverlayGemDownloadBG { + margin:0; + padding: 0px; + background-color: #444444; +} + #GemCatalogHeaderLabel { font-size: 12px; color: #FFFFFF; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 9fca6040d4..7d25ae180b 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -12,13 +12,15 @@ #include #include #include +#include #include namespace O3DE::ProjectManager { - CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, QWidget* parent) + CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, O3DEObjectDownloadController* downloadController, QWidget* parent) : QWidget(parent) , m_gemModel(gemModel) + , m_downloadController(downloadController) { setObjectName("GemCatalogCart"); @@ -42,6 +44,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 +154,109 @@ namespace O3DE::ProjectManager update(); } + 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* label = new QLabel(); + label->setObjectName("GemCatalogCartOverlaySectionLabel"); + layout->addWidget(label); + + label->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& 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(tr("Cancel")); + 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, &O3DEObjectDownloadController::GemDownloadProgress, this, update); + connect(m_downloadController, &O3DEObjectDownloadController::Done, this, downloadEnded); + update(0); + } + QStringList CartOverlayWidget::ConvertFromModelIndices(const QVector& gems) const { QStringList gemNames; @@ -160,9 +268,10 @@ namespace O3DE::ProjectManager return gemNames; } - CartButton::CartButton(GemModel* gemModel, QWidget* parent) + CartButton::CartButton(GemModel* gemModel, O3DEObjectDownloadController* downloadController, QWidget* parent) : QWidget(parent) , m_gemModel(gemModel) + , m_downloadController(downloadController) { m_layout = new QHBoxLayout(); m_layout->setMargin(0); @@ -239,7 +348,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 +374,7 @@ namespace O3DE::ProjectManager } } - GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, QWidget* parent) + GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, O3DEObjectDownloadController* downloadController, QWidget* parent) : QFrame(parent) { QHBoxLayout* hLayout = new QHBoxLayout(); @@ -293,7 +402,7 @@ 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); } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index 2cfda4c790..3d977e2b15 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -21,6 +21,7 @@ #include #include #include +#include #endif namespace O3DE::ProjectManager @@ -31,16 +32,18 @@ namespace O3DE::ProjectManager Q_OBJECT // AUTOMOC public: - CartOverlayWidget(GemModel* gemModel, QWidget* parent = nullptr); + CartOverlayWidget(GemModel* gemModel, O3DEObjectDownloadController* downloadController, QWidget* parent = nullptr); private: QStringList ConvertFromModelIndices(const QVector& gems) const; using GetTagIndicesCallback = AZStd::function()>; void CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices); + void CreateDownloadSection(); QVBoxLayout* m_layout = nullptr; GemModel* m_gemModel = nullptr; + O3DEObjectDownloadController* m_downloadController = nullptr; inline constexpr static int s_width = 240; }; @@ -51,7 +54,7 @@ namespace O3DE::ProjectManager Q_OBJECT // AUTOMOC public: - CartButton(GemModel* gemModel, QWidget* parent = nullptr); + CartButton(GemModel* gemModel, O3DEObjectDownloadController* downloadController, QWidget* parent = nullptr); ~CartButton(); void ShowOverlay(); @@ -64,6 +67,7 @@ namespace O3DE::ProjectManager QLabel* m_countLabel = nullptr; QPushButton* m_dropDownButton = nullptr; CartOverlayWidget* m_cartOverlay = nullptr; + O3DEObjectDownloadController* m_downloadController = nullptr; inline constexpr static int s_iconSize = 24; inline constexpr static int s_arrowDownIconSize = 8; @@ -75,7 +79,7 @@ namespace O3DE::ProjectManager Q_OBJECT // AUTOMOC public: - explicit GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, QWidget* parent = nullptr); + explicit GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, O3DEObjectDownloadController* downloadController, QWidget* parent = nullptr); ~GemCatalogHeaderWidget() = default; void ReinitForProject(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 945878768d..5a4462d14b 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,10 @@ namespace O3DE::ProjectManager vLayout->setSpacing(0); setLayout(vLayout); - m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel); + m_downloadController = new O3DEObjectDownloadController(); + m_downloadController->Start(); + + m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController); vLayout->addWidget(m_headerWidget); QHBoxLayout* hLayout = new QHBoxLayout(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 72e8d44f65..ad603138f0 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -39,6 +39,7 @@ namespace O3DE::ProjectManager EnableDisableGemsResult EnableDisableGemsForProject(const QString& projectPath); GemModel* GetGemModel() const { return m_gemModel; } + O3DEObjectDownloadController* GetDownloadController() const { return m_downloadController; } private: void FillModel(const QString& projectPath); @@ -50,5 +51,6 @@ namespace O3DE::ProjectManager GemSortFilterProxyModel* m_proxModel = nullptr; QVBoxLayout* m_filterWidgetLayout = nullptr; GemFilterWidget* m_filterWidget = nullptr; + O3DEObjectDownloadController* m_downloadController = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 633116e8b6..bc8773b0c8 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -301,6 +301,7 @@ 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_pathlib = pybind11::module::import("pathlib"); // make sure the engine is registered @@ -1075,4 +1076,30 @@ namespace O3DE::ProjectManager std::sort(gemRepos.begin(), gemRepos.end()); return AZ::Success(AZStd::move(gemRepos)); } + + AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback) + { + bool downloadSucceeded = 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 + ); + downloadSucceeded = (downloadResult.cast() == 0); + }); + + if (!result.IsSuccess()) + { + return result; + } + else if (!downloadSucceeded) + { + return AZ::Failure("Failed to download gem."); + } + + return AZ::Success(); + } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index d0704d0bd1..638ce6b1d4 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -61,6 +61,7 @@ namespace O3DE::ProjectManager bool AddGemRepo(const QString& repoUri) override; bool RemoveGemRepo(const QString& repoUri) override; AZ::Outcome, AZStd::string> GetAllGemRepoInfos() override; + AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) override; private: AZ_DISABLE_COPY_MOVE(PythonBindings); @@ -87,6 +88,7 @@ namespace O3DE::ProjectManager pybind11::handle m_enableGemProject; pybind11::handle m_disableGemProject; pybind11::handle m_editProjectProperties; + pybind11::handle m_download; pybind11::handle m_pathlib; }; } diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 5daf543c11..19442540a4 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -187,6 +187,8 @@ namespace O3DE::ProjectManager * @return A list of gem repo infos. */ virtual AZ::Outcome, AZStd::string> GetAllGemRepoInfos() = 0; + + virtual AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) = 0; }; using PythonBindingsInterface = AZ::Interface; diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index e952ada57a..40f114b82b 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -38,7 +38,7 @@ namespace O3DE::ProjectManager vLayout->addWidget(m_header); m_updateSettingsScreen = new UpdateProjectSettingsScreen(); - m_gemCatalogScreen = new GemCatalogScreen(); + m_gemCatalogScreen = new GemCatalogScreen(nullptr); m_stack = new QStackedWidget(this); m_stack->setObjectName("body"); @@ -136,6 +136,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) diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index fd8389ca4f..d53cf6c8c1 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -29,6 +29,10 @@ set(FILES Source/FormImageBrowseEditWidget.cpp Source/GemsSubWidget.h Source/GemsSubWidget.cpp + Source/O3DEObjectDownloadController.h + Source/O3DEObjectDownloadController.cpp + Source/O3DEObjectDownloadWorker.h + Source/O3DEObjectDownloadWorker.cpp Source/PathValidator.h Source/PathValidator.cpp Source/ProjectManagerWindow.h