From d5ae98496ca8abc139c7cf30ccac04dc8cae45ae Mon Sep 17 00:00:00 2001 From: nggieber Date: Tue, 9 Nov 2021 10:26:37 -0800 Subject: [PATCH 01/14] Full UI Built for Updating and Uninstalling Gems Signed-off-by: nggieber --- .../Resources/ProjectManager.qss | 46 ++++++++++++++ .../Source/GemCatalog/GemCatalogScreen.cpp | 42 ++++++++++--- .../Source/GemCatalog/GemCatalogScreen.h | 3 + .../Source/GemCatalog/GemInfo.h | 2 +- .../Source/GemCatalog/GemInspector.cpp | 32 +++++++++- .../Source/GemCatalog/GemInspector.h | 9 ++- .../Source/GemCatalog/GemUninstallDialog.cpp | 60 +++++++++++++++++++ .../Source/GemCatalog/GemUninstallDialog.h | 25 ++++++++ .../Source/GemCatalog/GemUpdateDialog.cpp | 60 +++++++++++++++++++ .../Source/GemCatalog/GemUpdateDialog.h | 25 ++++++++ .../project_manager_files.cmake | 4 ++ 11 files changed, 297 insertions(+), 11 deletions(-) create mode 100644 Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp create mode 100644 Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.h create mode 100644 Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp create mode 100644 Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index d3ec066be7..426f409581 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -563,6 +563,52 @@ QProgressBar::chunk { margin-top:5px; } +#gemCatalogUpdateGemButton, +#gemCatalogUninstallGemButton +{ + qproperty-flat: true; + min-height:24px; + max-height:24px; + border-radius: 3px; + text-align:center; + font-size:12px; + font-weight:600; +} + +#gemCatalogUpdateGemButton { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #888888, stop: 1.0 #555555); +} +#gemCatalogUpdateGemButton:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #999999, stop: 1.0 #666666); +} +#gemCatalogUpdateGemButton:pressed { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #555555, stop: 1.0 #777777); +} + +#footer > #gemCatalogUninstallGemButton, +#gemCatalogUninstallGemButton { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #E32C27, stop: 1.0 #951D21); +} +#footer > #gemCatalogUninstallGemButton:hover, +#gemCatalogUninstallGemButton:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #FD3129, stop: 1.0 #AF2221); +} +#footer > #gemCatalogUninstallGemButton:pressed, +#gemCatalogUninstallGemButton:pressed { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #951D1F, stop: 1.0 #C92724); +} + +#gemCatalogDialogSubTitle { + font-size:14px; + font-weight:600; +} + /************** Filter Tag widget **************/ #FilterTagWidgetTextLabel { diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 1a3dc03230..1395dce764 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -12,7 +12,10 @@ #include #include #include +#include +#include #include + #include #include #include @@ -55,6 +58,8 @@ namespace O3DE::ProjectManager m_gemInspector->setFixedWidth(240); connect(m_gemInspector, &GemInspector::TagClicked, this, &GemCatalogScreen::SelectGem); + connect(m_gemInspector, &GemInspector::UpdateGem, this, &GemCatalogScreen::UpdateGem); + connect(m_gemInspector, &GemInspector::UninstallGem, this, &GemCatalogScreen::UninstallGem); QWidget* filterWidget = new QWidget(this); filterWidget->setFixedWidth(240); @@ -104,9 +109,10 @@ namespace O3DE::ProjectManager // Select the first entry after everything got correctly sized QTimer::singleShot(200, [=]{ - QModelIndex firstModelIndex = m_gemListView->model()->index(0,0); - m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect); - }); + QModelIndex firstModelIndex = m_gemModel->index(0, 0); // m_gemListView->model()->index(0,0); + //m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect); + m_gemModel->GetSelectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect); + }); } void GemCatalogScreen::OnAddGemClicked() @@ -170,7 +176,7 @@ namespace O3DE::ProjectManager notification = GemModel::GetDisplayName(modelIndex); if (numChangedDependencies > 0) { - notification += " " + tr("and") + " "; + notification += tr(" and "); } if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded) { @@ -178,15 +184,15 @@ namespace O3DE::ProjectManager } } - if (numChangedDependencies == 1 ) + if (numChangedDependencies == 1) { - notification += "1 Gem " + tr("dependency"); + notification += tr("1 Gem dependency"); } else if (numChangedDependencies > 1) { - notification += QString("%1 Gem ").arg(numChangedDependencies) + tr("dependencies"); + notification += tr("%1 Gem %2").arg(QString(numChangedDependencies), tr("dependencies")); } - notification += " " + (added ? tr("activated") : tr("deactivated")); + notification += (added ? tr(" activated") : tr(" deactivated")); AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, ""); toastConfiguration.m_customIconImage = ":/gem.svg"; @@ -210,6 +216,26 @@ namespace O3DE::ProjectManager m_gemListView->scrollTo(proxyIndex); } + void GemCatalogScreen::UpdateGem(const QModelIndex& modelIndex) + { + const QString selectedGemName = m_gemModel->GetDisplayName(modelIndex); + GemUpdateDialog* confirmUpdateDialog = new GemUpdateDialog(selectedGemName, this); + if (confirmUpdateDialog->exec() == QDialog::Accepted) + { + // Update Gem + } + } + + void GemCatalogScreen::UninstallGem(const QModelIndex& modelIndex) + { + const QString selectedGemName = m_gemModel->GetDisplayName(modelIndex); + GemUninstallDialog* confirmUninstallDialog = new GemUninstallDialog(selectedGemName, this); + if (confirmUninstallDialog->exec() == QDialog::Accepted) + { + // Uninstall Gem + } + } + void GemCatalogScreen::hideEvent(QHideEvent* event) { ScreenWidget::hideEvent(event); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 55fbb1befc..5a5a782254 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -49,6 +49,8 @@ namespace O3DE::ProjectManager void OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies); void OnAddGemClicked(); void SelectGem(const QString& gemName); + void UpdateGem(const QModelIndex& modelIndex); + void UninstallGem(const QModelIndex& modelIndex); protected: void hideEvent(QHideEvent* event) override; @@ -62,6 +64,7 @@ namespace O3DE::ProjectManager private: void FillModel(const QString& projectPath); + QModelIndex GetCurrentlySelectedGem(); AZStd::unique_ptr m_notificationsView; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index 12cce5a4ea..bef9f6cd99 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -57,7 +57,7 @@ namespace O3DE::ProjectManager UnknownDownloadStatus = -1, NotDownloaded, Downloading, - Downloaded, + Downloaded }; static QString GetDownloadStatusString(DownloadStatus status); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index b0b8cca29a..7b6947c040 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -70,6 +71,8 @@ namespace O3DE::ProjectManager void GemInspector::Update(const QModelIndex& modelIndex) { + m_curModelIndex = modelIndex; + if (!modelIndex.isValid()) { m_mainWidget->hide(); @@ -122,6 +125,18 @@ namespace O3DE::ProjectManager m_lastUpdatedLabel->setText(tr("Last Updated: %1").arg(m_model->GetLastUpdated(modelIndex))); m_binarySizeLabel->setText(tr("Binary Size: %1 KB").arg(m_model->GetBinarySizeInKB(modelIndex))); + // Update and Uninstall buttons + if (m_model->GetGemOrigin(modelIndex) == GemInfo::Remote && m_model->GetDownloadStatus(modelIndex) == GemInfo::Downloaded) + { + m_updateGemButton->show(); + m_uninstallGemButton->show(); + } + else + { + m_updateGemButton->hide(); + m_uninstallGemButton->hide(); + } + m_mainWidget->adjustSize(); m_mainWidget->show(); } @@ -222,7 +237,7 @@ namespace O3DE::ProjectManager // Depending gems m_dependingGems = new GemsSubWidget(); - connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); }); + connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const QString& tag){ emit TagClicked(tag); }); m_mainLayout->addWidget(m_dependingGems); m_mainLayout->addSpacing(20); @@ -233,5 +248,20 @@ namespace O3DE::ProjectManager m_versionLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor); m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor); m_binarySizeLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor); + + m_mainLayout->addSpacing(20); + + // Update and Uninstall buttons + m_updateGemButton = new QPushButton(tr("Update Gem")); + m_updateGemButton->setObjectName("gemCatalogUpdateGemButton"); + m_mainLayout->addWidget(m_updateGemButton); + connect(m_updateGemButton, &QPushButton::clicked, this , [this]{ emit UpdateGem(m_curModelIndex); }); + + m_mainLayout->addSpacing(10); + + m_uninstallGemButton = new QPushButton(tr("Uninstall Gem")); + m_uninstallGemButton->setObjectName("gemCatalogUninstallGemButton"); + m_mainLayout->addWidget(m_uninstallGemButton); + connect(m_uninstallGemButton, &QPushButton::clicked, this , [this]{ emit UninstallGem(m_curModelIndex); }); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h index c6548527ab..4fb2375d6b 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h @@ -16,11 +16,12 @@ #include #include -#include #endif QT_FORWARD_DECLARE_CLASS(QVBoxLayout) QT_FORWARD_DECLARE_CLASS(QLabel) +QT_FORWARD_DECLARE_CLASS(QSpacerItem) +QT_FORWARD_DECLARE_CLASS(QPushButton) namespace O3DE::ProjectManager { @@ -45,6 +46,8 @@ namespace O3DE::ProjectManager signals: void TagClicked(const QString& tag); + void UpdateGem(const QModelIndex& modelIndex); + void UninstallGem(const QModelIndex& modelIndex); private slots: void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); @@ -55,6 +58,7 @@ namespace O3DE::ProjectManager GemModel* m_model = nullptr; QWidget* m_mainWidget = nullptr; QVBoxLayout* m_mainLayout = nullptr; + QModelIndex m_curModelIndex; // General info (top) section QLabel* m_nameLabel = nullptr; @@ -77,5 +81,8 @@ namespace O3DE::ProjectManager QLabel* m_versionLabel = nullptr; QLabel* m_lastUpdatedLabel = nullptr; QLabel* m_binarySizeLabel = nullptr; + + QPushButton* m_updateGemButton = nullptr; + QPushButton* m_uninstallGemButton = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp new file mode 100644 index 0000000000..6838741417 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp @@ -0,0 +1,60 @@ +/* + * 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 + +#include +#include +#include +#include +#include + +namespace O3DE::ProjectManager +{ + GemUninstallDialog::GemUninstallDialog(const QString& gemName, QWidget* parent) + : QDialog(parent) + { + setWindowTitle(tr("Uninstall Remote Gem")); + setObjectName("GemUninstallDialog"); + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); + + QVBoxLayout* layout = new QVBoxLayout(); + layout->setMargin(30); + layout->setAlignment(Qt::AlignTop); + setLayout(layout); + + // Body + QLabel* subTitleLabel = new QLabel(tr("Are you sure you want to uninstall %1?").arg(gemName)); + subTitleLabel->setObjectName("gemCatalogDialogSubTitle"); + layout->addWidget(subTitleLabel); + + layout->addSpacing(10); + + QLabel* bodyLabel = new QLabel(tr("The Gem and its related files will be uninstalled. This does not affect the Gem’s repository. " + "You can reinstall this Gem from the Catalog, but its contents may be subject to change.")); + bodyLabel->setWordWrap(true); + bodyLabel->setFixedWidth(440); + layout->addWidget(bodyLabel); + + layout->addSpacing(60); + + // Buttons + QDialogButtonBox* dialogButtons = new QDialogButtonBox(); + dialogButtons->setObjectName("footer"); + layout->addWidget(dialogButtons); + + QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); + cancelButton->setProperty("secondary", true); + QPushButton* uninstallButton = dialogButtons->addButton(tr("Uninstall Gem"), QDialogButtonBox::ApplyRole); + uninstallButton->setObjectName("gemCatalogUninstallGemButton"); + + connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); + connect(uninstallButton, &QPushButton::clicked, this, &QDialog::accept); + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.h new file mode 100644 index 0000000000..9e3f4c3f3b --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.h @@ -0,0 +1,25 @@ +/* + * 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 +#endif + +namespace O3DE::ProjectManager +{ + class GemUninstallDialog + : public QDialog + { + Q_OBJECT // AUTOMOC + public: + explicit GemUninstallDialog(const QString& gemName, QWidget *parent = nullptr); + ~GemUninstallDialog() = default; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp new file mode 100644 index 0000000000..8d8490ab40 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp @@ -0,0 +1,60 @@ +/* + * 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 + +#include +#include +#include +#include +#include + +namespace O3DE::ProjectManager +{ + GemUpdateDialog::GemUpdateDialog(const QString& gemName, QWidget* parent) + : QDialog(parent) + { + setWindowTitle(tr("Update Remote Gem")); + setObjectName("GemUpdateDialog"); + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); + + QVBoxLayout* layout = new QVBoxLayout(); + layout->setMargin(30); + layout->setAlignment(Qt::AlignTop); + setLayout(layout); + + // Body + QLabel* subTitleLabel = new QLabel(tr("Update to the latest version of %1?").arg(gemName)); + subTitleLabel->setObjectName("gemCatalogDialogSubTitle"); + layout->addWidget(subTitleLabel); + + layout->addSpacing(10); + + QLabel* bodyLabel = new QLabel(tr("The latest version of this Gem may not be compatible with your engine. " + "Updating this Gem will remove any local changes made to this Gem, " + "and may remove old features that are in use.")); + bodyLabel->setWordWrap(true); + bodyLabel->setFixedWidth(440); + layout->addWidget(bodyLabel); + + layout->addSpacing(60); + + // Buttons + QDialogButtonBox* dialogButtons = new QDialogButtonBox(); + dialogButtons->setObjectName("footer"); + layout->addWidget(dialogButtons); + + QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); + cancelButton->setProperty("secondary", true); + QPushButton* updateButton = dialogButtons->addButton(tr("Update Gem"), QDialogButtonBox::ApplyRole); + + connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); + connect(updateButton, &QPushButton::clicked, this, &QDialog::accept); + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h new file mode 100644 index 0000000000..1a2813d1a2 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h @@ -0,0 +1,25 @@ +/* + * 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 +#endif + +namespace O3DE::ProjectManager +{ + class GemUpdateDialog + : public QDialog + { + Q_OBJECT // AUTOMOC + public : + explicit GemUpdateDialog(const QString& gemName, QWidget* parent = nullptr); + ~GemUpdateDialog() = default; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index e2e35717f6..fcfae2f336 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -96,6 +96,10 @@ set(FILES Source/GemCatalog/GemListHeaderWidget.cpp Source/GemCatalog/GemModel.h Source/GemCatalog/GemModel.cpp + Source/GemCatalog/GemUninstallDialog.h + Source/GemCatalog/GemUninstallDialog.cpp + Source/GemCatalog/GemUpdateDialog.h + Source/GemCatalog/GemUpdateDialog.cpp Source/GemCatalog/GemDependenciesDialog.h Source/GemCatalog/GemDependenciesDialog.cpp Source/GemCatalog/GemRequirementDialog.h From f470351d0c5a32ad9859c6d111d00338f4738311 Mon Sep 17 00:00:00 2001 From: nggieber Date: Wed, 10 Nov 2021 12:33:24 -0800 Subject: [PATCH 02/14] Hook up Gem Updating and Uninstallation and fix lots of minor bugs between gem catalog and gem repos Signed-off-by: nggieber --- .../ProjectManager/Source/DownloadWorker.cpp | 2 +- .../GemCatalog/GemCatalogHeaderWidget.cpp | 1 + .../GemCatalog/GemCatalogHeaderWidget.h | 1 + .../Source/GemCatalog/GemCatalogScreen.cpp | 83 +++++++++++++++++-- .../Source/GemCatalog/GemInfo.h | 1 + .../Source/GemCatalog/GemInspector.cpp | 2 +- .../Source/GemCatalog/GemModel.cpp | 25 ++++++ .../Source/GemCatalog/GemModel.h | 4 +- .../Source/GemCatalog/GemUninstallDialog.cpp | 4 +- .../Source/GemCatalog/GemUpdateDialog.cpp | 18 ++-- .../Source/GemCatalog/GemUpdateDialog.h | 2 +- .../ProjectManager/Source/PythonBindings.cpp | 69 ++++++++++----- .../ProjectManager/Source/PythonBindings.h | 7 +- .../Source/PythonBindingsInterface.h | 41 +++++---- scripts/o3de/o3de/repo.py | 13 +-- scripts/o3de/o3de/utils.py | 6 +- 16 files changed, 206 insertions(+), 73 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp index 9bda1b34cc..5f5c64987b 100644 --- a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp @@ -25,7 +25,7 @@ namespace O3DE::ProjectManager m_downloadProgress = downloadProgress; emit UpdateProgress(downloadProgress); }; - AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress); + AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, true); if (gemInfoResult.IsSuccess()) { emit Done(""); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 8c875e4846..def02ea9ba 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -429,6 +429,7 @@ namespace O3DE::ProjectManager hLayout->addSpacing(16); QMenu* gemMenu = new QMenu(this); + gemMenu->addAction( tr("Refresh"), [this]() { emit RefreshGems(); }); gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); }); gemMenu->addSeparator(); gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); }); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index 6da78cce7a..a0ed7c70f9 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -89,6 +89,7 @@ namespace O3DE::ProjectManager signals: void AddGem(); void OpenGemsRepo(); + void RefreshGems(); private: AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 29f73e5651..6758010cf5 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -50,6 +51,7 @@ namespace O3DE::ProjectManager vLayout->addWidget(m_headerWidget); connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged); + connect(m_headerWidget, &GemCatalogHeaderWidget::RefreshGems, this, &GemCatalogScreen::Refresh); connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo); connect(m_headerWidget, &GemCatalogHeaderWidget::AddGem, this, &GemCatalogScreen::OnAddGemClicked); connect(m_downloadController, &DownloadController::Done, this, &GemCatalogScreen::OnGemDownloadResult); @@ -235,6 +237,9 @@ namespace O3DE::ProjectManager // temporary, until we can refresh filter counts m_proxyModel->ResetFilters(); m_filterWidget->ResetAllFilters(); + + // Reselect the same selection to proc UI updates + m_proxyModel->GetSelectionModel()->select(m_proxyModel->GetSelectionModel()->selection(), QItemSelectionModel::Select); } void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies) @@ -300,21 +305,83 @@ namespace O3DE::ProjectManager void GemCatalogScreen::UpdateGem(const QModelIndex& modelIndex) { - const QString selectedGemName = m_gemModel->GetDisplayName(modelIndex); - GemUpdateDialog* confirmUpdateDialog = new GemUpdateDialog(selectedGemName, this); + const QString selectedGemName = m_gemModel->GetName(modelIndex); + const QString selectedGemLastUpdate = m_gemModel->GetLastUpdated(modelIndex); + const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex); + const QString selectedGemRepoUri = m_gemModel->GetRepoUri(modelIndex); + + // Refresh gem repo + if (!selectedGemRepoUri.isEmpty()) + { + AZ::Outcome refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(selectedGemRepoUri); + if (refreshResult.IsSuccess()) + { + Refresh(); + } + else + { + QMessageBox::critical( + this, tr("Operation failed"), + tr("Failed to refresh gem repo %1
Error:
%2").arg(selectedGemRepoUri, refreshResult.GetError().c_str())); + } + } + // If repo uri isn't specified warn user that repo might not be refreshed + else + { + int result = QMessageBox::warning( + this, tr("Gem Repo Unspecified"), + tr("The repo for %1 is unspecfied. Repo cannot be automatically refreshed. " + "Please ensure this gem's repo is refreshed before attempting to update.") + .arg(selectedDisplayGemName), + QMessageBox::Cancel, QMessageBox::Ok); + + // Allow user to cancel update to manually refresh repo + if (result != QMessageBox::Ok) + { + return; + } + } + + // Check if there is an update avaliable now that repo is refreshed + bool updateAvaliable = PythonBindingsInterface::Get()->IsGemUpdateAvaliable(selectedGemName, selectedGemLastUpdate); + + GemUpdateDialog* confirmUpdateDialog = new GemUpdateDialog(selectedGemName, updateAvaliable, this); if (confirmUpdateDialog->exec() == QDialog::Accepted) { - // Update Gem + m_downloadController->AddGemDownload(selectedGemName); } } void GemCatalogScreen::UninstallGem(const QModelIndex& modelIndex) { - const QString selectedGemName = m_gemModel->GetDisplayName(modelIndex); - GemUninstallDialog* confirmUninstallDialog = new GemUninstallDialog(selectedGemName, this); + const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex); + + GemUninstallDialog* confirmUninstallDialog = new GemUninstallDialog(selectedDisplayGemName, this); if (confirmUninstallDialog->exec() == QDialog::Accepted) { - // Uninstall Gem + const QString selectedGemPath = m_gemModel->GetPath(modelIndex); + + // Unregister the gem + auto unregisterResult = PythonBindingsInterface::Get()->RegisterGem(selectedGemPath, {}, /*remove*/true); + if (!unregisterResult) + { + QMessageBox::critical(this, tr("Failed to unregister gem"), unregisterResult.GetError().c_str()); + } + else + { + // Remove gem from model + m_gemModel->removeRow(modelIndex.row()); + + // Delete uninstalled gem directory + if (!ProjectUtils::DeleteProjectFiles(selectedGemPath, /*force*/true)) + { + QMessageBox::critical( + this, tr("Failed to remove gem directory"), tr("Could not delete gem directory at:
%1").arg(selectedGemPath)); + } + + // Show undownloaded remote gem again + Refresh(); + } } } @@ -351,8 +418,10 @@ namespace O3DE::ProjectManager { // Add all available gems to the model. const QVector& allGemInfos = allGemInfosResult.GetValue(); - for (const GemInfo& gemInfo : allGemInfos) + for (GemInfo gemInfo : allGemInfos) { + // Mark as downloaded because this gem was registered with an existing directory + gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded; m_gemModel->AddGem(gemInfo); } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index bef9f6cd99..b3b29d6ee6 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -85,6 +85,7 @@ namespace O3DE::ProjectManager QString m_licenseLink; QString m_directoryLink; QString m_documentationLink; + QString m_repoUri; QString m_version = "Unknown Version"; QString m_lastUpdatedDate = "Unknown Date"; int m_binarySizeInKB = 0; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index f0f5adebaa..52e078eacd 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -238,7 +238,7 @@ namespace O3DE::ProjectManager // Depending gems m_dependingGems = new GemsSubWidget(); - connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const QString& tag){ emit TagClicked(tag); }); + connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const Tag& tag){ emit TagClicked(tag); }); m_mainLayout->addWidget(m_dependingGems); m_mainLayout->addSpacing(20); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index 88c54de0b3..886b9e57c7 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -61,6 +61,7 @@ namespace O3DE::ProjectManager item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus); item->setData(gemInfo.m_licenseText, RoleLicenseText); item->setData(gemInfo.m_licenseLink, RoleLicenseLink); + item->setData(gemInfo.m_repoUri, RoleRepoUri); appendRow(item); @@ -255,6 +256,11 @@ namespace O3DE::ProjectManager return modelIndex.data(RoleLicenseLink).toString(); } + QString GemModel::GetRepoUri(const QModelIndex& modelIndex) + { + return modelIndex.data(RoleRepoUri).toString(); + } + GemModel* GemModel::GetSourceModel(QAbstractItemModel* model) { GemSortFilterProxyModel* proxyModel = qobject_cast(model); @@ -369,11 +375,30 @@ namespace O3DE::ProjectManager void GemModel::OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) { + bool selectedRowRemoved = false; for (int i = first; i <= last; ++i) { QModelIndex modelIndex = index(i, 0, parent); const QString& gemName = GetName(modelIndex); m_nameToIndexMap.remove(gemName); + + if (GetSelectionModel()->isRowSelected(i)) + { + selectedRowRemoved = true; + } + } + + // Select a valid row if currently selected row was removed + if (selectedRowRemoved) + { + for (const QModelIndex& index : m_nameToIndexMap) + { + if (index.isValid()) + { + GetSelectionModel()->select(index, QItemSelectionModel::ClearAndSelect); + break; + } + } } } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index e25a1c7703..87a718d8c7 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -51,7 +51,8 @@ namespace O3DE::ProjectManager RoleRequirement, RoleDownloadStatus, RoleLicenseText, - RoleLicenseLink + RoleLicenseLink, + RoleRepoUri }; void AddGem(const GemInfo& gemInfo); @@ -80,6 +81,7 @@ namespace O3DE::ProjectManager static QString GetRequirement(const QModelIndex& modelIndex); static QString GetLicenseText(const QModelIndex& modelIndex); static QString GetLicenseLink(const QModelIndex& modelIndex); + static QString GetRepoUri(const QModelIndex& modelIndex); static GemModel* GetSourceModel(QAbstractItemModel* model); static const GemModel* GetSourceModel(const QAbstractItemModel* model); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp index 6838741417..6b2f14e551 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp @@ -39,10 +39,10 @@ namespace O3DE::ProjectManager QLabel* bodyLabel = new QLabel(tr("The Gem and its related files will be uninstalled. This does not affect the Gem’s repository. " "You can reinstall this Gem from the Catalog, but its contents may be subject to change.")); bodyLabel->setWordWrap(true); - bodyLabel->setFixedWidth(440); + bodyLabel->setFixedSize(QSize(440, 80)); layout->addWidget(bodyLabel); - layout->addSpacing(60); + layout->addSpacing(40); // Buttons QDialogButtonBox* dialogButtons = new QDialogButtonBox(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp index 8d8490ab40..dab4a2a0fe 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp @@ -16,7 +16,7 @@ namespace O3DE::ProjectManager { - GemUpdateDialog::GemUpdateDialog(const QString& gemName, QWidget* parent) + GemUpdateDialog::GemUpdateDialog(const QString& gemName, bool updateAvaliable, QWidget* parent) : QDialog(parent) { setWindowTitle(tr("Update Remote Gem")); @@ -30,20 +30,23 @@ namespace O3DE::ProjectManager setLayout(layout); // Body - QLabel* subTitleLabel = new QLabel(tr("Update to the latest version of %1?").arg(gemName)); + QLabel* subTitleLabel = new QLabel(tr("%1Update to the latest version of %2?").arg( + updateAvaliable ? "" : tr("Force "), gemName)); subTitleLabel->setObjectName("gemCatalogDialogSubTitle"); layout->addWidget(subTitleLabel); layout->addSpacing(10); - QLabel* bodyLabel = new QLabel(tr("The latest version of this Gem may not be compatible with your engine. " + QLabel* bodyLabel = new QLabel(tr("%1The latest version of this Gem may not be compatible with your engine. " "Updating this Gem will remove any local changes made to this Gem, " - "and may remove old features that are in use.")); + "and may remove old features that are in use.").arg( + updateAvaliable ? "" : tr("No update detected for Gem. " + "This will force a redownload of the gem anyways. "))); bodyLabel->setWordWrap(true); - bodyLabel->setFixedWidth(440); + bodyLabel->setFixedSize(QSize(440, 80)); layout->addWidget(bodyLabel); - layout->addSpacing(60); + layout->addSpacing(40); // Buttons QDialogButtonBox* dialogButtons = new QDialogButtonBox(); @@ -52,7 +55,8 @@ namespace O3DE::ProjectManager QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); cancelButton->setProperty("secondary", true); - QPushButton* updateButton = dialogButtons->addButton(tr("Update Gem"), QDialogButtonBox::ApplyRole); + QPushButton* updateButton = + dialogButtons->addButton(tr("%1Update Gem").arg(updateAvaliable ? "" : tr("Force ")), QDialogButtonBox::ApplyRole); connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); connect(updateButton, &QPushButton::clicked, this, &QDialog::accept); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h index 1a2813d1a2..cf34abfb3d 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h @@ -19,7 +19,7 @@ namespace O3DE::ProjectManager { Q_OBJECT // AUTOMOC public : - explicit GemUpdateDialog(const QString& gemName, QWidget* parent = nullptr); + explicit GemUpdateDialog(const QString& gemName, bool updateAvaliable = true, QWidget* parent = nullptr); ~GemUpdateDialog() = default; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 021066e1c7..ef7daa761e 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -560,7 +560,7 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemNames)); } - AZ::Outcome PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath) + AZ::Outcome PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath, bool remove) { bool registrationResult = false; auto result = ExecuteWithLockErrorHandling( @@ -582,7 +582,8 @@ namespace O3DE::ProjectManager pybind11::none(), // default_restricted_folder pybind11::none(), // default_third_party_folder pybind11::none(), // external_subdir_engine_path - externalProjectPath // external_subdir_project_path + externalProjectPath, // external_subdir_project_path + remove // remove ); // Returns an exit code so boolify it then invert result @@ -715,6 +716,7 @@ namespace O3DE::ProjectManager gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", ""); gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License"); gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", ""); + gemInfo.m_repoUri = Py_To_String_Optional(data, "repo_uri", ""); if (gemInfo.m_creator.contains("Open 3D Engine")) { @@ -728,6 +730,11 @@ namespace O3DE::ProjectManager { gemInfo.m_gemOrigin = GemInfo::GemOrigin::Remote; } + // If no origin was provided this cannot be remote and would be specified if O3DE so it should be local + else + { + gemInfo.m_gemOrigin = GemInfo::GemOrigin::Local; + } // As long Base Open3DEngine gems are installed before first startup non-remote gems will be downloaded if (gemInfo.m_gemOrigin != GemInfo::GemOrigin::Remote) @@ -1166,7 +1173,34 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemRepos)); } - AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback) + AZ::Outcome, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos() + { + QVector gemInfos; + AZ::Outcome result = ExecuteWithLockErrorHandling( + [&] + { + auto gemPaths = m_repo.attr("get_gem_json_paths_from_all_cached_repos")(); + + if (pybind11::isinstance(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)); + } + + AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback, bool force) { // This process is currently limited to download a single gem at a time. bool downloadSucceeded = false; @@ -1179,7 +1213,7 @@ namespace O3DE::ProjectManager QString_To_Py_String(gemName), // gem name pybind11::none(), // destination path false, // skip auto register - false, // force + force, // force overwrite pybind11::cpp_function( [this, gemProgressCallback](int progress) { @@ -1209,30 +1243,19 @@ namespace O3DE::ProjectManager m_requestCancelDownload = true; } - AZ::Outcome, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos() + bool PythonBindings::IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) { - QVector gemInfos; - AZ::Outcome result = ExecuteWithLockErrorHandling( + bool updateAvaliableResult = false; + bool result = ExecuteWithLock( [&] { - auto gemPaths = m_repo.attr("get_gem_json_paths_from_all_cached_repos")(); + auto pyGemName = QString_To_Py_String(gemName); + auto pyLastUpdated = QString_To_Py_String(lastUpdated); + auto pythonUpdateAvaliableResult = m_download.attr("is_o3de_gem_update_available")(pyGemName, pyLastUpdated); - if (pybind11::isinstance(gemPaths)) - { - for (auto path : gemPaths) - { - GemInfo gemInfo = GemInfoFromPath(path, pybind11::none()); - gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded; - gemInfos.push_back(gemInfo); - } - } + updateAvaliableResult = pythonUpdateAvaliableResult.cast(); }); - if (!result.IsSuccess()) - { - return AZ::Failure(result.GetError()); - } - - return AZ::Success(AZStd::move(gemInfos)); + return result && updateAvaliableResult; } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 4375d56d02..8a22344596 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -42,7 +42,7 @@ namespace O3DE::ProjectManager AZ::Outcome, AZStd::string> GetEngineGemInfos() override; AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) override; AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) override; - AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}) override; + AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}, bool remove = false) override; // Project AZ::Outcome CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override; @@ -64,9 +64,10 @@ 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; - void CancelDownload() override; AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() override; + AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback, bool force = false) override; + void CancelDownload() override; + bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) override; private: AZ_DISABLE_COPY_MOVE(PythonBindings); diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 1134804f1f..07d4551c60 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -94,10 +94,11 @@ namespace O3DE::ProjectManager /** * 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 + * @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json + * @param remove Unregister instead of registering this gem * @return An outcome with the success flag as well as an error message in case of a failure. */ - virtual AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0; + virtual AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}, bool remove = false) = 0; // Projects @@ -209,24 +210,34 @@ namespace O3DE::ProjectManager */ virtual AZ::Outcome, 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 DownloadGem(const QString& gemName, std::function 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, AZStd::string> GetAllGemRepoGemsInfos() = 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. + * @param force should we forcibly overwrite the old version of the gem. + * @return an outcome with a string error message on failure. + */ + virtual AZ::Outcome DownloadGem( + const QString& gemName, std::function gemProgressCallback, bool force = false) = 0; + + /** + * Cancels the current download. + */ + virtual void CancelDownload() = 0; + + /** + * Checks if there is an update avaliable for a gem on a repo. + * @param gemName the name of the gem to check. + * @param lastUpdated last time the gem was update. + * @return true if update is avaliable, false if not. + */ + virtual bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) = 0; }; using PythonBindingsInterface = AZ::Interface; diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index a6b1505761..1462015630 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -74,9 +74,9 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') - if not cache_file.is_file(): + if cache_file.is_file(): parsed_uri = urllib.parse.urlparse(manifest_json_uri) - download_file_result = utils.download_file(parsed_uri, cache_file) + download_file_result = utils.download_file(parsed_uri, cache_file, True) if download_file_result != 0: return download_file_result @@ -96,7 +96,7 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') if cache_file.is_file(): cache_file.unlink() - download_file_result = utils.download_file(parsed_uri, cache_file) + download_file_result = utils.download_file(parsed_uri, cache_file, True) if download_file_result != 0: return download_file_result @@ -165,7 +165,7 @@ def refresh_repo(repo_uri: str, repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - download_file_result = utils.download_file(parsed_uri, cache_file) + download_file_result = utils.download_file(parsed_uri, cache_file, True) if download_file_result != 0: return download_file_result @@ -178,12 +178,7 @@ def refresh_repo(repo_uri: str, def refresh_repos() -> int: json_data = manifest.load_o3de_manifest() - - # clear the cache cache_folder = manifest.get_o3de_cache_folder() - shutil.rmtree(cache_folder) - cache_folder = manifest.get_o3de_cache_folder() # will recreate it - result = 0 # set will stop circular references diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py index 56f9ae4bcd..71929af7b5 100755 --- a/scripts/o3de/o3de/utils.py +++ b/scripts/o3de/o3de/utils.py @@ -117,7 +117,7 @@ def backup_folder(folder: str or pathlib.Path) -> None: if backup_folder_name.is_dir(): renamed = True -def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite, download_progress_callback = None) -> int: +def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite: bool = False, download_progress_callback = None) -> int: """ :param parsed_uri: uniform resource identifier to zip file to download :param download_path: location path on disk to download file @@ -128,7 +128,7 @@ def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite, down logger.warn(f'File already downloaded to {download_path}.') else: try: - shutil.rmtree(download_path) + os.unlink(download_path) except OSError: logger.error(f'Could not remove existing download path {download_path}.') return 1 @@ -162,7 +162,7 @@ def download_zip_file(parsed_uri, download_zip_path: pathlib.Path, download_prog :param parsed_uri: uniform resource identifier to zip file to download :param download_zip_path: path to output zip file """ - download_file_result = download_file(parsed_uri, download_zip_path, download_progress_callback) + download_file_result = download_file(parsed_uri, download_zip_path, True, download_progress_callback) if download_file_result != 0: return download_file_result From d9443ec42ce8039f3e7c75a26835b1ab8c12426a Mon Sep 17 00:00:00 2001 From: nggieber Date: Wed, 10 Nov 2021 15:29:20 -0800 Subject: [PATCH 03/14] Addressed some PR feedback and continue filtering with same search string even after refresh Signed-off-by: nggieber --- Code/Tools/ProjectManager/Source/DownloadWorker.cpp | 4 +++- .../Source/GemCatalog/GemCatalogScreen.cpp | 10 +++++----- .../Source/GemCatalog/GemSortFilterProxyModel.cpp | 7 +++++-- .../Source/GemCatalog/GemSortFilterProxyModel.h | 2 +- .../Source/GemCatalog/GemUpdateDialog.cpp | 6 +++--- scripts/o3de/o3de/repo.py | 10 +++++----- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp index 5f5c64987b..a71a8dd486 100644 --- a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp @@ -25,7 +25,9 @@ namespace O3DE::ProjectManager m_downloadProgress = downloadProgress; emit UpdateProgress(downloadProgress); }; - AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, true); + AZ::Outcome gemInfoResult = + PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, /*force*/true); + if (gemInfoResult.IsSuccess()) { emit Done(""); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 3a513fdc1d..cff9a28db3 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -106,7 +106,7 @@ namespace O3DE::ProjectManager FillModel(projectPath); - m_proxyModel->ResetFilters(); + m_proxyModel->ResetFilters(false); m_proxyModel->sort(/*column=*/0); if (m_filterWidget) @@ -235,7 +235,7 @@ namespace O3DE::ProjectManager m_proxyModel->sort(/*column=*/0); // temporary, until we can refresh filter counts - m_proxyModel->ResetFilters(); + m_proxyModel->ResetFilters(false); m_filterWidget->ResetAllFilters(); // Reselect the same selection to proc UI updates @@ -323,15 +323,15 @@ namespace O3DE::ProjectManager { QMessageBox::critical( this, tr("Operation failed"), - tr("Failed to refresh gem repo %1
Error:
%2").arg(selectedGemRepoUri, refreshResult.GetError().c_str())); + tr("Failed to refresh gem repository %1
Error:
%2").arg(selectedGemRepoUri, refreshResult.GetError().c_str())); } } // If repo uri isn't specified warn user that repo might not be refreshed else { int result = QMessageBox::warning( - this, tr("Gem Repo Unspecified"), - tr("The repo for %1 is unspecfied. Repo cannot be automatically refreshed. " + this, tr("Gem Repository Unspecified"), + tr("The repo for %1 is unspecfied. Repository cannot be automatically refreshed. " "Please ensure this gem's repo is refreshed before attempting to update.") .arg(selectedDisplayGemName), QMessageBox::Cancel, QMessageBox::Ok); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp index 32d0e2fee9..a32492cf1e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp @@ -204,9 +204,12 @@ namespace O3DE::ProjectManager emit OnInvalidated(); } - void GemSortFilterProxyModel::ResetFilters() + void GemSortFilterProxyModel::ResetFilters(bool clearSearchString) { - m_searchString.clear(); + if (clearSearchString) + { + m_searchString.clear(); + } m_gemSelectedFilter = GemSelected::NoFilter; m_gemActiveFilter = GemActive::NoFilter; m_gemOriginFilter = {}; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h index ab739e62f9..0c58d66ccf 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h @@ -70,7 +70,7 @@ namespace O3DE::ProjectManager void SetFeatures(const QSet& features) { m_featureFilter = features; InvalidateFilter(); } void InvalidateFilter(); - void ResetFilters(); + void ResetFilters(bool clearSearchString = true); signals: void OnInvalidated(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp index dab4a2a0fe..3e68c597de 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp @@ -30,8 +30,8 @@ namespace O3DE::ProjectManager setLayout(layout); // Body - QLabel* subTitleLabel = new QLabel(tr("%1Update to the latest version of %2?").arg( - updateAvaliable ? "" : tr("Force "), gemName)); + QLabel* subTitleLabel = new QLabel(tr("%1 to the latest version of %2?").arg( + updateAvaliable ? tr("Update") : tr("Force update"), gemName)); subTitleLabel->setObjectName("gemCatalogDialogSubTitle"); layout->addWidget(subTitleLabel); @@ -46,7 +46,7 @@ namespace O3DE::ProjectManager bodyLabel->setFixedSize(QSize(440, 80)); layout->addWidget(bodyLabel); - layout->addSpacing(40); + layout->addSpacing(); // Buttons QDialogButtonBox* dialogButtons = new QDialogButtonBox(); diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 1462015630..c8fac38605 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -74,11 +74,11 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') - if cache_file.is_file(): - parsed_uri = urllib.parse.urlparse(manifest_json_uri) - download_file_result = utils.download_file(parsed_uri, cache_file, True) - if download_file_result != 0: - return download_file_result + + parsed_uri = urllib.parse.urlparse(manifest_json_uri) + download_file_result = utils.download_file(parsed_uri, cache_file, True) + if download_file_result != 0: + return download_file_result # Having a repo is also optional repo_list = [] From d71196102f875150b03cd0c9c550fbdc356bb989 Mon Sep 17 00:00:00 2001 From: nggieber Date: Thu, 11 Nov 2021 10:20:46 -0800 Subject: [PATCH 04/14] More PR feedback changes Signed-off-by: nggieber --- .../Source/GemCatalog/GemCatalogScreen.cpp | 6 ++---- .../Source/GemCatalog/GemUpdateDialog.cpp | 4 ++-- .../ProjectManager/Source/PythonBindings.cpp | 21 ++++++++++++++++--- .../ProjectManager/Source/PythonBindings.h | 4 +++- .../Source/PythonBindingsInterface.h | 11 ++++++++-- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index cff9a28db3..7004fe24d4 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -363,7 +363,7 @@ namespace O3DE::ProjectManager const QString selectedGemPath = m_gemModel->GetPath(modelIndex); // Unregister the gem - auto unregisterResult = PythonBindingsInterface::Get()->RegisterGem(selectedGemPath, {}, /*remove*/true); + auto unregisterResult = PythonBindingsInterface::Get()->UnregisterGem(selectedGemPath); if (!unregisterResult) { QMessageBox::critical(this, tr("Failed to unregister gem"), unregisterResult.GetError().c_str()); @@ -419,10 +419,8 @@ namespace O3DE::ProjectManager { // Add all available gems to the model. const QVector& allGemInfos = allGemInfosResult.GetValue(); - for (GemInfo gemInfo : allGemInfos) + for (const GemInfo& gemInfo : allGemInfos) { - // Mark as downloaded because this gem was registered with an existing directory - gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded; m_gemModel->AddGem(gemInfo); } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp index 3e68c597de..82d205aab5 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp @@ -41,12 +41,12 @@ namespace O3DE::ProjectManager "Updating this Gem will remove any local changes made to this Gem, " "and may remove old features that are in use.").arg( updateAvaliable ? "" : tr("No update detected for Gem. " - "This will force a redownload of the gem anyways. "))); + "This will force a re-download of the gem. "))); bodyLabel->setWordWrap(true); bodyLabel->setFixedSize(QSize(440, 80)); layout->addWidget(bodyLabel); - layout->addSpacing(); + layout->addSpacing(40); // Buttons QDialogButtonBox* dialogButtons = new QDialogButtonBox(); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index ef7daa761e..e0208a266c 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -515,7 +515,11 @@ namespace O3DE::ProjectManager auto pyProjectPath = QString_To_Py_Path(projectPath); for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath)) { - gems.push_back(GemInfoFromPath(path, pyProjectPath)); + GemInfo gemInfo = GemInfoFromPath(path, pyProjectPath); + // Mark as downloaded because this gem was registered with an existing directory + gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded; + + gems.push_back(AZStd::move(gemInfo)); } }); if (!result.IsSuccess()) @@ -560,7 +564,7 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemNames)); } - AZ::Outcome PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath, bool remove) + AZ::Outcome PythonBindings::GemRegistration(const QString& gemPath, const QString& projectPath, bool remove) { bool registrationResult = false; auto result = ExecuteWithLockErrorHandling( @@ -596,12 +600,23 @@ namespace O3DE::ProjectManager } else if (!registrationResult) { - return AZ::Failure(AZStd::string::format("Failed to register gem path %s", gemPath.toUtf8().constData())); + return AZ::Failure(AZStd::string::format( + "Failed to %s gem path %s", remove ? "unregister" : "register", gemPath.toUtf8().constData())); } return AZ::Success(); } + AZ::Outcome PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath) + { + return GemRegistration(gemPath, projectPath); + } + + AZ::Outcome PythonBindings::UnregisterGem(const QString& gemPath, const QString& projectPath) + { + return GemRegistration(gemPath, projectPath, /*remove*/true); + } + bool PythonBindings::AddProject(const QString& path) { bool registrationResult = false; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 8a22344596..d258898fae 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -42,7 +42,8 @@ namespace O3DE::ProjectManager AZ::Outcome, AZStd::string> GetEngineGemInfos() override; AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) override; AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) override; - AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}, bool remove = false) override; + AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}) override; + AZ::Outcome UnregisterGem(const QString& gemPath, const QString& projectPath = {}) override; // Project AZ::Outcome CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override; @@ -78,6 +79,7 @@ namespace O3DE::ProjectManager GemRepoInfo GetGemRepoInfo(pybind11::handle repoUri); ProjectInfo ProjectInfoFromPath(pybind11::handle path); ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath); + AZ::Outcome GemRegistration(const QString& gemPath, const QString& projectPath, bool remove = false); bool RegisterThisEngine(); bool StopPython(); diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 07d4551c60..6c8b0c89a6 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -95,10 +95,17 @@ namespace O3DE::ProjectManager * 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 - * @param remove Unregister instead of registering this gem * @return An outcome with the success flag as well as an error message in case of a failure. */ - virtual AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}, bool remove = false) = 0; + virtual AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0; + + /** + * Unregisters the gem from the specified project, or from 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 unregister 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 UnregisterGem(const QString& gemPath, const QString& projectPath = {}) = 0; // Projects From 2b248ac4859a4d35bed04b6bd4d21a38316c5edc Mon Sep 17 00:00:00 2001 From: nggieber Date: Thu, 11 Nov 2021 12:09:24 -0800 Subject: [PATCH 05/14] Fix merge issue and validation issue Signed-off-by: nggieber --- Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp | 4 +++- .../ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index 52e078eacd..26844b43e6 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -127,7 +127,9 @@ namespace O3DE::ProjectManager m_binarySizeLabel->setText(tr("Binary Size: %1").arg(binarySize ? tr("%1 KB").arg(binarySize) : tr("Unknown"))); // Update and Uninstall buttons - if (m_model->GetGemOrigin(modelIndex) == GemInfo::Remote && m_model->GetDownloadStatus(modelIndex) == GemInfo::Downloaded) + if (m_model->GetGemOrigin(modelIndex) == GemInfo::Remote && + (m_model->GetDownloadStatus(modelIndex) == GemInfo::Downloaded || + m_model->GetDownloadStatus(modelIndex) == GemInfo::DownloadSuccessful)) { m_updateGemButton->show(); m_uninstallGemButton->show(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp index 6b2f14e551..1408e29b6d 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp @@ -36,7 +36,7 @@ namespace O3DE::ProjectManager layout->addSpacing(10); - QLabel* bodyLabel = new QLabel(tr("The Gem and its related files will be uninstalled. This does not affect the Gem’s repository. " + QLabel* bodyLabel = new QLabel(tr("The Gem and its related files will be uninstalled. This does not affect the Gem's repository. " "You can reinstall this Gem from the Catalog, but its contents may be subject to change.")); bodyLabel->setWordWrap(true); bodyLabel->setFixedSize(QSize(440, 80)); From 71eedb895d69314abf3a999fc10a44e7edd5ed59 Mon Sep 17 00:00:00 2001 From: rgba16f <82187279+rgba16f@users.noreply.github.com> Date: Wed, 10 Nov 2021 10:26:03 -0600 Subject: [PATCH 06/14] Move PipelineStateCache validation of set uniqeness to only be active in debug builds (#5472) Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com> --- Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp b/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp index 0c06887dd6..79c05c37da 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp @@ -41,9 +41,12 @@ namespace AZ AZ_Assert(readOnlyCache.empty(), "Inactive library has pipeline states in its global entry."); } +#if defined(AZ_DEBUG_BUILD) + // the PipelineStateSet is expensive to duplicate, only do this in debug. PipelineStateSet readOnlyCacheCopy = readOnlyCache; AZ_Assert(AZStd::unique(readOnlyCacheCopy.begin(), readOnlyCacheCopy.end()) == readOnlyCacheCopy.end(), "'%d' Duplicates existed in the read-only cache!", readOnlyCache.size() - readOnlyCacheCopy.size()); +#endif } m_threadLibrarySet.ForEach([this](const ThreadLibrarySet& threadLibrarySet) From 394ba9a8bcb061be377a115f596dac834b753f85 Mon Sep 17 00:00:00 2001 From: rgba16f <82187279+rgba16f@users.noreply.github.com> Date: Wed, 10 Nov 2021 14:08:55 -0600 Subject: [PATCH 07/14] Atom CPU threading optimization (#5481) * Small change to make the RasterPass Scope jobs split the work more evenly over the cores Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com> * Update with PR feedback. Remove scale of EstimatedItemCount, modify the command list cost threshold instead Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com> --- .../Include/Atom/RHI.Reflect/DX12/PlatformLimitsDescriptor.h | 2 +- .../Include/Atom/RHI.Reflect/Metal/PlatformLimitsDescriptor.h | 2 +- .../Include/Atom/RHI.Reflect/Vulkan/PlatformLimitsDescriptor.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gems/Atom/RHI/DX12/Code/Include/Atom/RHI.Reflect/DX12/PlatformLimitsDescriptor.h b/Gems/Atom/RHI/DX12/Code/Include/Atom/RHI.Reflect/DX12/PlatformLimitsDescriptor.h index 63c2d69ea2..1a447693c2 100644 --- a/Gems/Atom/RHI/DX12/Code/Include/Atom/RHI.Reflect/DX12/PlatformLimitsDescriptor.h +++ b/Gems/Atom/RHI/DX12/Code/Include/Atom/RHI.Reflect/DX12/PlatformLimitsDescriptor.h @@ -40,7 +40,7 @@ namespace AZ uint32_t m_swapChainsPerCommandList = 8; // The maximum cost that can be associated with a single command list. - uint32_t m_commandListCostThresholdMin = 1000; + uint32_t m_commandListCostThresholdMin = 250; // The maximum number of command lists per scope. uint32_t m_commandListsPerScopeMax = 16; diff --git a/Gems/Atom/RHI/Metal/Code/Include/Atom/RHI.Reflect/Metal/PlatformLimitsDescriptor.h b/Gems/Atom/RHI/Metal/Code/Include/Atom/RHI.Reflect/Metal/PlatformLimitsDescriptor.h index 375b532d39..bd52c37a67 100644 --- a/Gems/Atom/RHI/Metal/Code/Include/Atom/RHI.Reflect/Metal/PlatformLimitsDescriptor.h +++ b/Gems/Atom/RHI/Metal/Code/Include/Atom/RHI.Reflect/Metal/PlatformLimitsDescriptor.h @@ -31,7 +31,7 @@ namespace AZ uint32_t m_swapChainsPerCommandList = 8; // The maximum cost that can be associated with a single command list. - uint32_t m_commandListCostThresholdMin = 1000; + uint32_t m_commandListCostThresholdMin = 250; // The maximum number of command lists per scope. uint32_t m_commandListsPerScopeMax = 16; diff --git a/Gems/Atom/RHI/Vulkan/Code/Include/Atom/RHI.Reflect/Vulkan/PlatformLimitsDescriptor.h b/Gems/Atom/RHI/Vulkan/Code/Include/Atom/RHI.Reflect/Vulkan/PlatformLimitsDescriptor.h index 5e41da9627..eaa4356796 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Include/Atom/RHI.Reflect/Vulkan/PlatformLimitsDescriptor.h +++ b/Gems/Atom/RHI/Vulkan/Code/Include/Atom/RHI.Reflect/Vulkan/PlatformLimitsDescriptor.h @@ -33,7 +33,7 @@ namespace AZ uint32_t m_swapChainsPerCommandList = 8; // The maximum cost that can be associated with a single command list. - uint32_t m_commandListCostThresholdMin = 1000; + uint32_t m_commandListCostThresholdMin = 250; // The maximum number of command lists per scope. uint32_t m_commandListsPerScopeMax = 16; From e0d736dd844a82b0c232739540a168078e63e276 Mon Sep 17 00:00:00 2001 From: galibzon <66021303+galibzon@users.noreply.github.com> Date: Thu, 11 Nov 2021 16:04:22 -0600 Subject: [PATCH 08/14] Fixed the other call to CreateVariable (#5561) in favor of CreateVariableEx Signed-off-by: galibzon <66021303+galibzon@users.noreply.github.com> --- Code/Framework/AzCore/AzCore/Name/NameDictionary.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzCore/AzCore/Name/NameDictionary.cpp b/Code/Framework/AzCore/AzCore/Name/NameDictionary.cpp index 6cba54a17f..1b39eb81bd 100644 --- a/Code/Framework/AzCore/AzCore/Name/NameDictionary.cpp +++ b/Code/Framework/AzCore/AzCore/Name/NameDictionary.cpp @@ -32,7 +32,12 @@ namespace AZ if (!s_instance) { - s_instance = AZ::Environment::CreateVariable(NameDictionaryInstanceName); + // Because the NameDictionary allocates memory using the AZ::Allocator and it is created + // in the executable memory space, it's ownership cannot be transferred to other module memory spaces + // Otherwise this could cause the the NameDictionary to be destroyed in static de-init + // after the AZ::Allocators have been destroyed + // Therefore we supply the isTransferOwnership value of false using CreateVariableEx + s_instance = AZ::Environment::CreateVariableEx(NameDictionaryInstanceName, true, false); } } From 6ffee4b0a8292b5ad6bb9a35b49655c62b2b5b3b Mon Sep 17 00:00:00 2001 From: Allen Jackson <23512001+jackalbe@users.noreply.github.com> Date: Thu, 11 Nov 2021 16:07:02 -0600 Subject: [PATCH 09/14] {lyn7352} adding more logging around mock_asset_builder.py (#5103) (#5566) o3de\AutomatedTesting\Gem\PythonTests\PythonAssetBuilder\mock_asset_builder.py - adding more logging - updated keys for platforms (pc, server) Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com> --- .../Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py index 443a420b61..a6e8b97b63 100644 --- a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py +++ b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py @@ -23,7 +23,7 @@ def create_jobs(request): jobDescriptorList = [] for platformInfo in request.enabledPlatforms: jobDesc = azlmbr.asset.builder.JobDescriptor() - jobDesc.jobKey = jobKeyName + jobDesc.jobKey = f'{jobKeyName}-{platformInfo.identifier}' jobDesc.set_platform_identifier(platformInfo.identifier) jobDescriptorList.append(jobDesc) @@ -38,7 +38,7 @@ def on_create_jobs(args): return create_jobs(request) except: log_exception_traceback() - # returing back a default CreateJobsResponse() records an asset error + # returning back a default CreateJobsResponse() records an asset error return azlmbr.asset.builder.CreateJobsResponse() def process_file(request): @@ -58,6 +58,7 @@ def process_file(request): fileOutput = open(tempFilename, "w") fileOutput.write('{}') fileOutput.close() + print(f'Wrote mock asset file: {tempFilename}') # generate a product asset file entry subId = binascii.crc32(mockFilename.encode()) From b8f7767cd6ca0201cd5ae0a44c37b75d42f22299 Mon Sep 17 00:00:00 2001 From: Junbo Liang <68558268+junbo75@users.noreply.github.com> Date: Thu, 11 Nov 2021 16:11:52 -0800 Subject: [PATCH 10/14] [Resource Mapping Tool] make top-level Account id optional in ResourceMapping tool schema (#5569) * [Resource Mapping Tool] make top-level Account id optional in ResourceMapping tool schema Signed-off-by: junbo75 <68558268+junbo75@users.noreply.github.com> --- .../controller/view_edit_controller.py | 6 +-- .../controller/test_view_edit_controller.py | 52 +++++++++++++++++-- .../tests/unit/utils/test_json_utils.py | 5 ++ .../ResourceMappingTool/utils/json_utils.py | 2 +- 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/controller/view_edit_controller.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/controller/view_edit_controller.py index bdfe7fa11e..39f7b5ccf1 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/controller/view_edit_controller.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/controller/view_edit_controller.py @@ -69,17 +69,13 @@ class ViewEditController(QObject): json_dict: Dict[str, any] = \ json_utils.convert_resources_to_json_dict(self._proxy_model.get_resources(), self._config_file_json_source) - configuration: Configuration = self._configuration_manager.configuration - if json_dict.get(json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME) == \ - json_utils.RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE: - json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = configuration.account_id - if json_dict == self._config_file_json_source: # skip because no difference found against existing json file return True # try to write in memory json content into json file try: + configuration: Configuration = self._configuration_manager.configuration config_file_full_path: str = file_utils.join_path(configuration.config_directory, config_file_name) json_utils.write_into_json_file(config_file_full_path, json_dict) self._config_file_json_source = json_dict diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/controller/test_view_edit_controller.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/controller/test_view_edit_controller.py index c73bddcd9c..41d4490c44 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/controller/test_view_edit_controller.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/controller/test_view_edit_controller.py @@ -420,8 +420,30 @@ class TestViewEditController(TestCase): self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \ TestViewEditController._expected_config_file_name expected_json_dict: Dict[str, any] = { - "dummyKey": "dummyValue", - self._expected_account_id_attribute_name: self._expected_account_id_template_vale} + "dummyKey": "dummyValue" + } + mock_json_utils.validate_resources_according_to_json_schema.return_value = [] + mock_json_utils.convert_resources_to_json_dict.return_value = expected_json_dict + mock_file_utils.join_path.return_value = TestViewEditController._expected_config_file_full_path + mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0] + + mocked_call_args[0]() # triggering save_changes_button connected function + mock_json_utils.convert_resources_to_json_dict.assert_called_once() + mock_json_utils.write_into_json_file.assert_called_once_with( + TestViewEditController._expected_config_file_full_path, expected_json_dict) + self._mocked_proxy_model.override_all_resources_status.assert_called_once_with( + ResourceMappingAttributesStatus(ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE, + [ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE])) + + @patch("controller.view_edit_controller.file_utils") + @patch("controller.view_edit_controller.json_utils") + def test_page_save_changes_button_json_file_saved_and_template_account_id_unchanged( + self, mock_json_utils: MagicMock, mock_file_utils: MagicMock) -> None: + self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \ + TestViewEditController._expected_config_file_name + expected_json_dict: Dict[str, any] = { + self._expected_account_id_attribute_name: self._expected_account_id_template_vale + } mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME = self._expected_account_id_attribute_name mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE = self._expected_account_id_template_vale mock_json_utils.validate_resources_according_to_json_schema.return_value = [] @@ -430,7 +452,31 @@ class TestViewEditController(TestCase): mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0] mocked_call_args[0]() # triggering save_changes_button connected function - assert expected_json_dict["AccountId"] == self._mocked_configuration_manager.configuration.account_id + assert expected_json_dict[mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] == self._expected_account_id_template_vale + mock_json_utils.convert_resources_to_json_dict.assert_called_once() + mock_json_utils.write_into_json_file.assert_called_once_with( + TestViewEditController._expected_config_file_full_path, expected_json_dict) + self._mocked_proxy_model.override_all_resources_status.assert_called_once_with( + ResourceMappingAttributesStatus(ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE, + [ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE])) + + @patch("controller.view_edit_controller.file_utils") + @patch("controller.view_edit_controller.json_utils") + def test_page_save_changes_button_json_file_saved_and_empty_account_id_unchanged( + self, mock_json_utils: MagicMock, mock_file_utils: MagicMock) -> None: + self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \ + TestViewEditController._expected_config_file_name + expected_json_dict: Dict[str, any] = { + self._expected_account_id_attribute_name: '' + } + mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME = self._expected_account_id_attribute_name + mock_json_utils.validate_resources_according_to_json_schema.return_value = [] + mock_json_utils.convert_resources_to_json_dict.return_value = expected_json_dict + mock_file_utils.join_path.return_value = TestViewEditController._expected_config_file_full_path + mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0] + + mocked_call_args[0]() # triggering save_changes_button connected function + assert expected_json_dict[mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] == '' mock_json_utils.convert_resources_to_json_dict.assert_called_once() mock_json_utils.write_into_json_file.assert_called_once_with( TestViewEditController._expected_config_file_full_path, expected_json_dict) diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_json_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_json_utils.py index 7eb92ff222..9fe99461ba 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_json_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_json_utils.py @@ -103,6 +103,11 @@ class TestJsonUtils(TestCase): invalid_json_dict.pop(json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME) self.assertRaises(KeyError, json_utils.validate_json_dict_according_to_json_schema, invalid_json_dict) + def test_validate_json_dict_according_to_json_schema_raise_error_when_json_dict_has_empty_accountid(self) -> None: + valid_json_dict: Dict[str, any] = copy.deepcopy(TestJsonUtils._expected_json_dict) + valid_json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = '' + json_utils.validate_json_dict_according_to_json_schema(valid_json_dict) + def test_validate_json_dict_according_to_json_schema_pass_when_json_dict_has_template_accountid(self) -> None: valid_json_dict: Dict[str, any] = copy.deepcopy(TestJsonUtils._expected_json_dict) valid_json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = \ diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py index 5b9377ada3..7926cca970 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py @@ -28,7 +28,7 @@ _RESOURCE_MAPPING_JSON_FORMAT_VERSION: str = "1.0.0" RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME: str = "AccountId" RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE: str = "EMPTY" -_RESOURCE_MAPPING_ACCOUNTID_PATTERN: str = f"^[0-9]{{12}}|{RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE}$" +_RESOURCE_MAPPING_ACCOUNTID_PATTERN: str = f"^[0-9]{{12}}$|{RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE}|^$" _RESOURCE_MAPPING_REGION_PATTERN: str = "^[a-z]{2}-[a-z]{4,9}-[0-9]{1}$" _RESOURCE_MAPPING_VERSION_PATTERN: str = "^[0-9]{1}.[0-9]{1}.[0-9]{1}$" From 2fa3c134e04c89b61b1e47bf0b44bdc74b840866 Mon Sep 17 00:00:00 2001 From: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> Date: Thu, 11 Nov 2021 17:18:36 -0800 Subject: [PATCH 11/14] Terrain depends on SurfaceData and GradientSignal (#5476) Signed-off-by: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> --- Gems/Terrain/Code/CMakeLists.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Gems/Terrain/Code/CMakeLists.txt b/Gems/Terrain/Code/CMakeLists.txt index 4b2f32e172..d532284350 100644 --- a/Gems/Terrain/Code/CMakeLists.txt +++ b/Gems/Terrain/Code/CMakeLists.txt @@ -26,8 +26,6 @@ ly_add_target( Gem::GradientSignal Gem::SurfaceData Gem::LmbrCentral - - ) ly_add_target( @@ -49,14 +47,14 @@ ly_add_target( ) # the above module is for use in all client/server types -ly_create_alias(NAME Terrain.Servers NAMESPACE Gem TARGETS Gem::Terrain) -ly_create_alias(NAME Terrain.Clients NAMESPACE Gem TARGETS Gem::Terrain) +ly_create_alias(NAME Terrain.Servers NAMESPACE Gem TARGETS Gem::Terrain Gem::SurfaceData.Servers Gem::GradientSignal.Servers) +ly_create_alias(NAME Terrain.Clients NAMESPACE Gem TARGETS Gem::Terrain Gem::SurfaceData.Clients Gem::GradientSignal.Clients) # If we are on a host platform, we want to add the host tools targets like the Terrain.Editor target which # will also depend on Terrain.Static if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( - NAME Terrain.Editor MODULE + NAME Terrain.Editor GEM_MODULE NAMESPACE Gem AUTOMOC FILES_CMAKE @@ -78,8 +76,8 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) ) # the above module is for use in dev tool situations - ly_create_alias(NAME Terrain.Builders NAMESPACE Gem TARGETS Gem::Terrain.Editor) - ly_create_alias(NAME Terrain.Tools NAMESPACE Gem TARGETS Gem::Terrain.Editor) + ly_create_alias(NAME Terrain.Builders NAMESPACE Gem TARGETS Gem::Terrain.Editor Gem::SurfaceData.Builders Gem::GradientSignal.Builders) + ly_create_alias(NAME Terrain.Tools NAMESPACE Gem TARGETS Gem::Terrain.Editor Gem::SurfaceData.Tools Gem::GradientSignal.Tools) endif() ################################################################################ From a544800536a5bebda96812951befa4d43b0474d3 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Thu, 11 Nov 2021 17:36:12 -0800 Subject: [PATCH 12/14] LYN-4946 | Merge Game Mode buttons in the Play Controls toolbar (#5557) * LYN-4946 Double play buttons - Tranformer style into one. Signed-off-by: sphrose <82213493+sphrose@users.noreply.github.com> * compile fix Signed-off-by: sphrose <82213493+sphrose@users.noreply.github.com> * Changes as per review. Signed-off-by: sphrose <82213493+sphrose@users.noreply.github.com> * Address minor confusion points. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Fix rename operation missing some matches... Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Remove extra newline. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Co-authored-by: sphrose <82213493+sphrose@users.noreply.github.com> --- Code/Editor/CryEdit.cpp | 12 +++++++---- Code/Editor/CryEdit.h | 1 + Code/Editor/MainWindow.cpp | 20 +++++++++-------- Code/Editor/Resource.h | 1 + Code/Editor/ToolbarManager.cpp | 39 ++++++++++++++++++++++++++++++---- Code/Editor/ToolbarManager.h | 6 ++++++ 6 files changed, 62 insertions(+), 17 deletions(-) diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index 0b40390a18..4008710786 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -371,10 +371,8 @@ void CCryEditApp::RegisterActionHandlers() ON_COMMAND(ID_EDIT_FETCH, OnEditFetch) ON_COMMAND(ID_FILE_EXPORTTOGAMENOSURFACETEXTURE, OnFileExportToGameNoSurfaceTexture) ON_COMMAND(ID_VIEW_SWITCHTOGAME, OnViewSwitchToGame) - MainWindow::instance()->GetActionManager()->RegisterActionHandler(ID_VIEW_SWITCHTOGAME_FULLSCREEN, [this]() { - ed_previewGameInFullscreen_once = true; - OnViewSwitchToGame(); - }); + ON_COMMAND(ID_VIEW_SWITCHTOGAME_VIEWPORT, OnViewSwitchToGame) + ON_COMMAND(ID_VIEW_SWITCHTOGAME_FULLSCREEN, OnViewSwitchToGameFullScreen) ON_COMMAND(ID_MOVE_OBJECT, OnMoveObject) ON_COMMAND(ID_RENAME_OBJ, OnRenameObj) ON_COMMAND(ID_UNDO, OnUndo) @@ -2575,6 +2573,12 @@ void CCryEditApp::OnViewSwitchToGame() GetIEditor()->SetInGameMode(inGame); } +void CCryEditApp::OnViewSwitchToGameFullScreen() +{ + ed_previewGameInFullscreen_once = true; + OnViewSwitchToGame(); +} + ////////////////////////////////////////////////////////////////////////// void CCryEditApp::OnExportSelectedObjects() { diff --git a/Code/Editor/CryEdit.h b/Code/Editor/CryEdit.h index f68cfdc33d..dd597dcc55 100644 --- a/Code/Editor/CryEdit.h +++ b/Code/Editor/CryEdit.h @@ -212,6 +212,7 @@ public: void OnEditFetch(); void OnFileExportToGameNoSurfaceTexture(); void OnViewSwitchToGame(); + void OnViewSwitchToGameFullScreen(); void OnViewDeploy(); void DeleteSelectedEntities(bool includeDescendants); void OnMoveObject(); diff --git a/Code/Editor/MainWindow.cpp b/Code/Editor/MainWindow.cpp index ed72cd9170..1c5b6c567a 100644 --- a/Code/Editor/MainWindow.cpp +++ b/Code/Editor/MainWindow.cpp @@ -939,27 +939,27 @@ void MainWindow::InitActions() .Connect(&QAction::triggered, this, &MainWindow::OnRefreshAudioSystem); // Game actions - am->AddAction(ID_VIEW_SWITCHTOGAME, tr("Play &Game")) + am->AddAction(ID_VIEW_SWITCHTOGAME, tr("Play Game")) .SetIcon(QIcon(":/stylesheet/img/UI20/toolbar/Play.svg")) + .SetToolTip(tr("Play Game")) + .SetStatusTip(tr("Activate the game input mode")) + .SetCheckable(true) + .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame); + am->AddAction(ID_VIEW_SWITCHTOGAME_VIEWPORT, tr("Play Game")) .SetShortcut(tr("Ctrl+G")) .SetToolTip(tr("Play Game (Ctrl+G)")) .SetStatusTip(tr("Activate the game input mode")) - .SetApplyHoverEffect() - .SetCheckable(true) .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame); - am->AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, tr("Play &Game (Maximized)")) + am->AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, tr("Play Game (Maximized)")) .SetShortcut(tr("Ctrl+Shift+G")) .SetStatusTip(tr("Activate the game input mode (maximized)")) - .SetIcon(Style::icon("Play")) - .SetApplyHoverEffect() - .SetCheckable(true); + .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame); am->AddAction(ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL, tr("Play Controls")) .SetText(tr("Play Controls")); am->AddAction(ID_SWITCH_PHYSICS, tr("Simulate")) .SetIcon(QIcon(":/stylesheet/img/UI20/toolbar/Simulate_Physics.svg")) .SetShortcut(tr("Ctrl+P")) .SetToolTip(tr("Simulate (Ctrl+P)")) - .SetCheckable(true) .SetStatusTip(tr("Enable processing of Physics and AI.")) .SetApplyHoverEffect() .SetCheckable(true) @@ -1266,7 +1266,9 @@ void MainWindow::OnGameModeChanged(bool inGameMode) // block signals on the switch to game actions before setting the checked state, as // setting the checked state triggers the action, which will re-enter this function // and result in an infinite loop - AZStd::vector actions = { m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME), m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN) }; + AZStd::vector actions = { m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_VIEWPORT), + m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN), + m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME)}; for (auto action : actions) { action->blockSignals(true); diff --git a/Code/Editor/Resource.h b/Code/Editor/Resource.h index b3640fac70..ba3cd39fe7 100644 --- a/Code/Editor/Resource.h +++ b/Code/Editor/Resource.h @@ -104,6 +104,7 @@ #define ID_FILE_EXPORTTOGAMENOSURFACETEXTURE 33473 #define ID_VIEW_SWITCHTOGAME 33477 #define ID_VIEW_SWITCHTOGAME_FULLSCREEN 33478 +#define ID_VIEW_SWITCHTOGAME_VIEWPORT 33479 #define ID_MOVE_OBJECT 33481 #define ID_RENAME_OBJ 33483 #define ID_FETCH 33496 diff --git a/Code/Editor/ToolbarManager.cpp b/Code/Editor/ToolbarManager.cpp index 00b7992ef0..6b610d23ce 100644 --- a/Code/Editor/ToolbarManager.cpp +++ b/Code/Editor/ToolbarManager.cpp @@ -590,6 +590,16 @@ AmazonToolbar ToolbarManager::GetObjectToolbar() const return t; } +QMenu* ToolbarManager::CreatePlayButtonMenu() const +{ + QMenu* playButtonMenu = new QMenu("Play Game"); + + playButtonMenu->addAction(m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_VIEWPORT)); + playButtonMenu->addAction(m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN)); + + return playButtonMenu; +} + AmazonToolbar ToolbarManager::GetPlayConsoleToolbar() const { AmazonToolbar t = AmazonToolbar("PlayConsole", QObject::tr("Play Controls")); @@ -598,8 +608,17 @@ AmazonToolbar ToolbarManager::GetPlayConsoleToolbar() const t.AddAction(ID_TOOLBAR_WIDGET_SPACER_RIGHT, ORIGINAL_TOOLBAR_VERSION); t.AddAction(ID_TOOLBAR_SEPARATOR, ORIGINAL_TOOLBAR_VERSION); t.AddAction(ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL, ORIGINAL_TOOLBAR_VERSION); - t.AddAction(ID_VIEW_SWITCHTOGAME, TOOLBARS_WITH_PLAY_GAME); - t.AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, TOOLBARS_WITH_PLAY_GAME); + + QAction* playAction = m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME); + QToolButton* playButton = new QToolButton(t.Toolbar()); + + QMenu* menu = CreatePlayButtonMenu(); + menu->setParent(t.Toolbar()); + playAction->setMenu(menu); + + playButton->setDefaultAction(playAction); + t.AddWidget(playButton, ID_VIEW_SWITCHTOGAME, ORIGINAL_TOOLBAR_VERSION); + t.AddAction(ID_TOOLBAR_SEPARATOR, ORIGINAL_TOOLBAR_VERSION); t.AddAction(ID_SWITCH_PHYSICS, TOOLBARS_WITH_PLAY_GAME); return t; @@ -728,7 +747,14 @@ void AmazonToolbar::SetActionsOnInternalToolbar(ActionManager* actionManager) { if (actionManager->HasAction(actionId)) { - m_toolbar->addAction(actionManager->GetAction(actionId)); + if (actionData.widget != nullptr) + { + m_toolbar->addWidget(actionData.widget); + } + else + { + m_toolbar->addAction(actionManager->GetAction(actionId)); + } } } } @@ -1367,7 +1393,12 @@ void AmazonToolbar::InstantiateToolbar(QMainWindow* mainWindow, ToolbarManager* void AmazonToolbar::AddAction(int actionId, int toolbarVersionAdded) { - m_actions.push_back({ actionId, toolbarVersionAdded }); + AddWidget(nullptr, actionId, toolbarVersionAdded); +} + +void AmazonToolbar::AddWidget(QWidget* widget, int actionId, int toolbarVersionAdded) +{ + m_actions.push_back({ actionId, toolbarVersionAdded, widget }); } void AmazonToolbar::Clear() diff --git a/Code/Editor/ToolbarManager.h b/Code/Editor/ToolbarManager.h index be537533b6..ae6b0c7296 100644 --- a/Code/Editor/ToolbarManager.h +++ b/Code/Editor/ToolbarManager.h @@ -87,6 +87,7 @@ public: const QString& GetTranslatedName() const { return m_translatedName; } void AddAction(int actionId, int toolbarVersionAdded = 0); + void AddWidget(QWidget* widget, int actionId, int toolbarVersionAdded = 0); QToolBar* Toolbar() const { return m_toolbar; } @@ -117,6 +118,7 @@ private: { int actionId; int toolbarVersionAdded; + QWidget* widget; bool operator ==(const AmazonToolbar::ActionData& other) const { @@ -133,7 +135,9 @@ private: class AmazonToolBarExpanderWatcher; class ToolbarManager + : public QObject { + Q_OBJECT public: explicit ToolbarManager(ActionManager* actionManager, MainWindow* mainWindow); ~ToolbarManager(); @@ -178,6 +182,8 @@ private: void UpdateAllowedAreas(QToolBar* toolbar); bool IsDirty(const AmazonToolbar& toolbar) const; + QMenu* CreatePlayButtonMenu() const; + const AmazonToolbar* FindDefaultToolbar(const QString& toolbarName) const; AmazonToolbar* FindToolbar(const QString& toolbarName); From bceadf7597a4af57d37a18c4cb2f81b5e840dbb6 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Fri, 12 Nov 2021 08:40:10 -0800 Subject: [PATCH 13/14] Fixes for CMake 3.22rc (#5314) (#5580) Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> --- .../Platform/Common/MSVC/Configurations_msvc.cmake | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake index a4d8533626..66a5b7b01f 100644 --- a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake +++ b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake @@ -139,11 +139,20 @@ endif() # Configure system includes ly_set(LY_CXX_SYSTEM_INCLUDE_CONFIGURATION_FLAG - /experimental:external # Turns on "external" headers feature for MSVC compilers + /experimental:external # Turns on "external" headers feature for MSVC compilers, required for MSVC < 16.10 /external:W0 # Set warning level in external headers to 0. This is used to suppress warnings 3rdParty libraries which uses the "system_includes" option in their json configuration ) + +# CMake 3.22rc added a definition for CMAKE_INCLUDE_SYSTEM_FLAG_CXX. However, its defined as "-external:I ", that space causes +# issues when trying to use in TargetIncludeSystemDirectories_unsupported.cmake. +# CMake 3.22rc has also not added support for external directories in MSVC through target_include_directories(... SYSTEM +# So we will just fix the flag that was added by 3.22rc so it works with our TargetIncludeSystemDirectories_unsupported.cmake +# Once target_include_directories(... SYSTEM is supported, we can branch and use TargetIncludeSystemDirectories_supported.cmake +# Reported this here: https://gitlab.kitware.com/cmake/cmake/-/issues/17904#note_1078281 if(NOT CMAKE_INCLUDE_SYSTEM_FLAG_CXX) - ly_set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX /external:I) + ly_set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "/external:I") +else() + string(STRIP ${CMAKE_INCLUDE_SYSTEM_FLAG_CXX} CMAKE_INCLUDE_SYSTEM_FLAG_CXX) endif() include(cmake/Platform/Common/TargetIncludeSystemDirectories_unsupported.cmake) From b44ce82435b7f737bca73bcf25e553d6c7b23f27 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Fri, 12 Nov 2021 08:40:56 -0800 Subject: [PATCH 14/14] Private dependencies are not propagated to other targets in generated install layout (#5581) * transfering private dependencies as runtime dependencies for the generated targets in the install layout Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> * Update cmake/Platform/Common/Install_common.cmake Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Co-authored-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Update cmake/Platform/Common/Install_common.cmake Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Co-authored-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Co-authored-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> --- cmake/Platform/Common/Install_common.cmake | 30 +++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index f22f4c43c9..f844b1bec9 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -167,18 +167,16 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar endforeach() list(JOIN INCLUDE_DIRECTORIES_PLACEHOLDER "\n" INCLUDE_DIRECTORIES_PLACEHOLDER) - string(REPEAT " " 8 PLACEHOLDER_INDENT) - get_target_property(RUNTIME_DEPENDENCIES_PLACEHOLDER ${TARGET_NAME} MANUALLY_ADDED_DEPENDENCIES) - if(RUNTIME_DEPENDENCIES_PLACEHOLDER) # not found properties return the name of the variable with a "-NOTFOUND" at the end, here we set it to empty if not found - set(RUNTIME_DEPENDENCIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${RUNTIME_DEPENDENCIES_PLACEHOLDER}") - list(JOIN RUNTIME_DEPENDENCIES_PLACEHOLDER "\n${PLACEHOLDER_INDENT}" RUNTIME_DEPENDENCIES_PLACEHOLDER) - else() - unset(RUNTIME_DEPENDENCIES_PLACEHOLDER) - endif() - string(REPEAT " " 12 PLACEHOLDER_INDENT) get_property(interface_build_dependencies_props TARGET ${TARGET_NAME} PROPERTY LY_DELAYED_LINK) unset(INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) + # We can have private build dependencies that contains direct or indirect runtime dependencies. + # Since imported targets cannot contain build dependencies, we need another way to propagate the runtime dependencies. + # We dont want to put such dependencies in the interface because a user can mistakenly use a symbol that is not available + # when using the engine from source (and that the author of the target didn't want to set public). + # To overcome this, we will actually expose the private build dependencies as runtime dependencies. Our runtime dependency + # algorithm will walk recursively also through static libraries and will only copy binaries to the output. + unset(RUNTIME_DEPENDENCIES_PLACEHOLDER) if(interface_build_dependencies_props) cmake_parse_arguments(build_deps "" "" "PRIVATE;PUBLIC;INTERFACE" ${interface_build_dependencies_props}) # Interface and public dependencies should always be exposed @@ -191,6 +189,8 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar if("${target_type}" STREQUAL "STATIC_LIBRARY") set(build_deps_target "${build_deps_target};${build_deps_PRIVATE}") endif() + # But we will also pass the private dependencies as runtime dependencies (note the comment above) + set(RUNTIME_DEPENDENCIES_PLACEHOLDER ${build_deps_PRIVATE}) foreach(build_dependency IN LISTS build_deps_target) # Skip wrapping produced when targets are not created in the same directory if(build_dependency) @@ -200,6 +200,18 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar endif() list(JOIN INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "\n" INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) + string(REPEAT " " 8 PLACEHOLDER_INDENT) + get_target_property(manually_added_dependencies ${TARGET_NAME} MANUALLY_ADDED_DEPENDENCIES) + if(manually_added_dependencies) # not found properties return the name of the variable with a "-NOTFOUND" at the end, here we set it to empty if not found + list(APPEND RUNTIME_DEPENDENCIES_PLACEHOLDER ${manually_added_dependencies}) + endif() + if(RUNTIME_DEPENDENCIES_PLACEHOLDER) + set(RUNTIME_DEPENDENCIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${RUNTIME_DEPENDENCIES_PLACEHOLDER}") + list(JOIN RUNTIME_DEPENDENCIES_PLACEHOLDER "\n${PLACEHOLDER_INDENT}" RUNTIME_DEPENDENCIES_PLACEHOLDER) + else() + unset(RUNTIME_DEPENDENCIES_PLACEHOLDER) + endif() + string(REPEAT " " 8 PLACEHOLDER_INDENT) # If a target has an LY_PROJECT_NAME property, forward that property to new target get_target_property(target_project_association ${TARGET_NAME} LY_PROJECT_NAME)