Merge branch 'development' into Prism/RefreshGemRepos
This commit is contained in:
@@ -8,14 +8,13 @@
|
||||
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
#include <TagWidget.h>
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QMenu>
|
||||
#include <QProgressBar>
|
||||
#include <TagWidget.h>
|
||||
#include <QMenu>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -406,7 +405,6 @@ namespace O3DE::ProjectManager
|
||||
|
||||
CartButton* cartButton = new CartButton(gemModel, downloadController);
|
||||
hLayout->addWidget(cartButton);
|
||||
|
||||
hLayout->addSpacing(16);
|
||||
|
||||
// Separating line
|
||||
@@ -418,9 +416,9 @@ namespace O3DE::ProjectManager
|
||||
hLayout->addSpacing(16);
|
||||
|
||||
QMenu* gemMenu = new QMenu(this);
|
||||
m_openGemReposAction = gemMenu->addAction(tr("Show Gem Repos"));
|
||||
|
||||
connect(m_openGemReposAction, &QAction::triggered, this,[this](){ emit OpenGemsRepo(); });
|
||||
gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); });
|
||||
gemMenu->addSeparator();
|
||||
gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); });
|
||||
|
||||
QPushButton* gemMenuButton = new QPushButton(this);
|
||||
gemMenuButton->setObjectName("gemCatalogMenuButton");
|
||||
|
||||
@@ -8,24 +8,23 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/std/function/function_fwd.h>
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <AzCore/std/function/function_fwd.h>
|
||||
#include <AzQtComponents/Components/SearchLineEdit.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <TagWidget.h>
|
||||
#include <DownloadController.h>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QDialog>
|
||||
#include <QMoveEvent>
|
||||
#include <QHideEvent>
|
||||
#include <QVBoxLayout>
|
||||
#include <QAction>
|
||||
#include <DownloadController.h>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QPushButton)
|
||||
QT_FORWARD_DECLARE_CLASS(QLabel)
|
||||
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QHideEvent)
|
||||
QT_FORWARD_DECLARE_CLASS(QMoveEvent)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class CartOverlayWidget
|
||||
@@ -87,12 +86,11 @@ namespace O3DE::ProjectManager
|
||||
void ReinitForProject();
|
||||
|
||||
signals:
|
||||
void AddGem();
|
||||
void OpenGemsRepo();
|
||||
|
||||
|
||||
private:
|
||||
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
|
||||
inline constexpr static int s_height = 60;
|
||||
|
||||
QAction* m_openGemReposAction = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
#include <QTimer>
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <QMessageBox>
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -66,11 +70,15 @@ namespace O3DE::ProjectManager
|
||||
hLayout->addWidget(filterWidget);
|
||||
hLayout->addLayout(middleVLayout);
|
||||
hLayout->addWidget(m_gemInspector);
|
||||
|
||||
m_notificationsView = AZStd::make_unique<AzToolsFramework::ToastNotificationsView>(this, AZ_CRC("GemCatalogNotificationsView"));
|
||||
m_notificationsView->SetOffset(QPoint(10, 70));
|
||||
}
|
||||
|
||||
void GemCatalogScreen::ReinitForProject(const QString& projectPath)
|
||||
{
|
||||
m_gemModel->clear();
|
||||
m_gemsToRegisterWithProject.clear();
|
||||
FillModel(projectPath);
|
||||
|
||||
if (m_filterWidget)
|
||||
@@ -86,6 +94,48 @@ namespace O3DE::ProjectManager
|
||||
m_headerWidget->ReinitForProject();
|
||||
|
||||
connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter);
|
||||
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
|
||||
connect(
|
||||
m_headerWidget, &GemCatalogHeaderWidget::AddGem,
|
||||
[&]()
|
||||
{
|
||||
EngineInfo engineInfo;
|
||||
QString defaultPath;
|
||||
|
||||
AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
|
||||
if (engineInfoResult.IsSuccess())
|
||||
{
|
||||
engineInfo = engineInfoResult.GetValue();
|
||||
defaultPath = engineInfo.m_defaultGemsFolder;
|
||||
}
|
||||
|
||||
if (defaultPath.isEmpty())
|
||||
{
|
||||
defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
||||
}
|
||||
|
||||
QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Browse"), defaultPath));
|
||||
if (!directory.isEmpty())
|
||||
{
|
||||
// register the gem to the o3de_manifest.json and to the project after the user confirms
|
||||
// project creation/update
|
||||
auto registerResult = PythonBindingsInterface::Get()->RegisterGem(directory);
|
||||
if(!registerResult)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to add gem"), registerResult.GetError().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_gemsToRegisterWithProject.insert(directory);
|
||||
AZ::Outcome<GemInfo, void> gemInfoResult = PythonBindingsInterface::Get()->GetGemInfo(directory);
|
||||
if (gemInfoResult)
|
||||
{
|
||||
m_gemModel->AddGem(gemInfoResult.GetValue<GemInfo>());
|
||||
m_gemModel->UpdateGemDependencies();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Select the first entry after everything got correctly sized
|
||||
QTimer::singleShot(200, [=]{
|
||||
@@ -94,6 +144,72 @@ namespace O3DE::ProjectManager
|
||||
});
|
||||
}
|
||||
|
||||
void GemCatalogScreen::OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies)
|
||||
{
|
||||
if (m_notificationsEnabled)
|
||||
{
|
||||
bool added = GemModel::IsAdded(modelIndex);
|
||||
bool dependency = GemModel::IsAddedDependency(modelIndex);
|
||||
|
||||
bool gemStateChanged = (added && !dependency) || (!added && !dependency);
|
||||
if (!gemStateChanged && !numChangedDependencies)
|
||||
{
|
||||
// no actual changes made
|
||||
return;
|
||||
}
|
||||
|
||||
QString notification;
|
||||
if (gemStateChanged)
|
||||
{
|
||||
notification = GemModel::GetDisplayName(modelIndex);
|
||||
if (numChangedDependencies > 0)
|
||||
{
|
||||
notification += " " + tr("and") + " ";
|
||||
}
|
||||
}
|
||||
|
||||
if (numChangedDependencies == 1 )
|
||||
{
|
||||
notification += "1 Gem " + tr("dependency");
|
||||
}
|
||||
else if (numChangedDependencies > 1)
|
||||
{
|
||||
notification += QString("%d Gem ").arg(numChangedDependencies) + tr("dependencies");
|
||||
}
|
||||
notification += " " + (added ? tr("activated") : tr("deactivated"));
|
||||
|
||||
AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, "");
|
||||
toastConfiguration.m_customIconImage = ":/gem.svg";
|
||||
toastConfiguration.m_borderRadius = 4;
|
||||
toastConfiguration.m_duration = AZStd::chrono::milliseconds(3000);
|
||||
m_notificationsView->ShowToastNotification(toastConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::hideEvent(QHideEvent* event)
|
||||
{
|
||||
ScreenWidget::hideEvent(event);
|
||||
m_notificationsView->OnHide();
|
||||
}
|
||||
|
||||
void GemCatalogScreen::showEvent(QShowEvent* event)
|
||||
{
|
||||
ScreenWidget::showEvent(event);
|
||||
m_notificationsView->OnShow();
|
||||
}
|
||||
|
||||
void GemCatalogScreen::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
ScreenWidget::resizeEvent(event);
|
||||
m_notificationsView->UpdateToastPosition();
|
||||
}
|
||||
|
||||
void GemCatalogScreen::moveEvent(QMoveEvent* event)
|
||||
{
|
||||
ScreenWidget::moveEvent(event);
|
||||
m_notificationsView->UpdateToastPosition();
|
||||
}
|
||||
|
||||
void GemCatalogScreen::FillModel(const QString& projectPath)
|
||||
{
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath);
|
||||
@@ -107,6 +223,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
|
||||
m_gemModel->UpdateGemDependencies();
|
||||
m_notificationsEnabled = false;
|
||||
|
||||
// Gather enabled gems for the given project.
|
||||
auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath);
|
||||
@@ -133,6 +250,8 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
|
||||
}
|
||||
|
||||
m_notificationsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -178,6 +297,12 @@ namespace O3DE::ProjectManager
|
||||
|
||||
return EnableDisableGemsResult::Failed;
|
||||
}
|
||||
|
||||
// register external gems that were added with relative paths
|
||||
if (m_gemsToRegisterWithProject.contains(gemPath))
|
||||
{
|
||||
pythonBindings->RegisterGem(QDir(projectPath).relativeFilePath(gemPath), projectPath);
|
||||
}
|
||||
}
|
||||
|
||||
for (const QModelIndex& modelIndex : toBeRemoved)
|
||||
|
||||
@@ -10,12 +10,16 @@
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenWidget.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzToolsFramework/UI/Notifications/ToastNotificationsView.h>
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <GemCatalog/GemFilterWidget.h>
|
||||
#include <GemCatalog/GemListView.h>
|
||||
#include <GemCatalog/GemInspector.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
@@ -41,13 +45,24 @@ namespace O3DE::ProjectManager
|
||||
GemModel* GetGemModel() const { return m_gemModel; }
|
||||
DownloadController* GetDownloadController() const { return m_downloadController; }
|
||||
|
||||
public slots:
|
||||
void OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies);
|
||||
|
||||
protected:
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void moveEvent(QMoveEvent* event) override;
|
||||
|
||||
private slots:
|
||||
void HandleOpenGemRepo();
|
||||
|
||||
private:
|
||||
|
||||
private:
|
||||
void FillModel(const QString& projectPath);
|
||||
|
||||
AZStd::unique_ptr<AzToolsFramework::ToastNotificationsView> m_notificationsView;
|
||||
|
||||
GemListView* m_gemListView = nullptr;
|
||||
GemInspector* m_gemInspector = nullptr;
|
||||
GemModel* m_gemModel = nullptr;
|
||||
@@ -56,5 +71,7 @@ namespace O3DE::ProjectManager
|
||||
QVBoxLayout* m_filterWidgetLayout = nullptr;
|
||||
GemFilterWidget* m_filterWidget = nullptr;
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
bool m_notificationsEnabled = true;
|
||||
QSet<QString> m_gemsToRegisterWithProject;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzToolsFramework/UI/Notifications/ToastBus.h>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -299,23 +300,50 @@ namespace O3DE::ProjectManager
|
||||
AZ_Assert(gemModel, "Failed to obtain GemModel");
|
||||
|
||||
QVector<QModelIndex> dependencies = gemModel->GatherGemDependencies(modelIndex);
|
||||
uint32_t numChangedDependencies = 0;
|
||||
|
||||
if (IsAdded(modelIndex))
|
||||
{
|
||||
for (const QModelIndex& dependency : dependencies)
|
||||
{
|
||||
SetIsAddedDependency(*gemModel, dependency, true);
|
||||
if (!IsAddedDependency(dependency))
|
||||
{
|
||||
SetIsAddedDependency(*gemModel, dependency, true);
|
||||
|
||||
// if the gem was already added then the state didn't really change
|
||||
if (!IsAdded(dependency))
|
||||
{
|
||||
numChangedDependencies++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// still a dependency if some added gem depends on this one
|
||||
SetIsAddedDependency(model, modelIndex, gemModel->HasDependentGems(modelIndex));
|
||||
bool hasDependentGems = gemModel->HasDependentGems(modelIndex);
|
||||
if (IsAddedDependency(modelIndex) != hasDependentGems)
|
||||
{
|
||||
SetIsAddedDependency(model, modelIndex, hasDependentGems);
|
||||
}
|
||||
|
||||
for (const QModelIndex& dependency : dependencies)
|
||||
{
|
||||
SetIsAddedDependency(*gemModel, dependency, gemModel->HasDependentGems(dependency));
|
||||
hasDependentGems = gemModel->HasDependentGems(dependency);
|
||||
if (IsAddedDependency(dependency) != hasDependentGems)
|
||||
{
|
||||
SetIsAddedDependency(*gemModel, dependency, hasDependentGems);
|
||||
|
||||
// if the gem was already added then the state didn't really change
|
||||
if (!IsAdded(dependency))
|
||||
{
|
||||
numChangedDependencies++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gemModel->emit gemStatusChanged(modelIndex, numChangedDependencies);
|
||||
}
|
||||
|
||||
void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded)
|
||||
@@ -488,5 +516,4 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -77,6 +77,9 @@ namespace O3DE::ProjectManager
|
||||
|
||||
int TotalAddedGems(bool includeDependencies = false) const;
|
||||
|
||||
signals:
|
||||
void gemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies);
|
||||
|
||||
private:
|
||||
void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames);
|
||||
void GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& inOutGems);
|
||||
|
||||
@@ -557,6 +557,47 @@ namespace O3DE::ProjectManager
|
||||
return AZ::Success(AZStd::move(gemNames));
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
auto result = ExecuteWithLockErrorHandling(
|
||||
[&]
|
||||
{
|
||||
auto externalProjectPath = projectPath.isEmpty() ? pybind11::none() : QString_To_Py_Path(projectPath);
|
||||
auto pythonRegistrationResult = m_register.attr("register")(
|
||||
pybind11::none(), // engine_path
|
||||
pybind11::none(), // project_path
|
||||
QString_To_Py_Path(gemPath), // gem folder
|
||||
pybind11::none(), // external subdirectory
|
||||
pybind11::none(), // template_path
|
||||
pybind11::none(), // restricted folder
|
||||
pybind11::none(), // repo uri
|
||||
pybind11::none(), // default_engines_folder
|
||||
pybind11::none(), // default_projects_folder
|
||||
pybind11::none(), // default_gems_folder
|
||||
pybind11::none(), // default_templates_folder
|
||||
pybind11::none(), // default_restricted_folder
|
||||
pybind11::none(), // default_third_party_folder
|
||||
pybind11::none(), // external_subdir_engine_path
|
||||
externalProjectPath // external_subdir_project_path
|
||||
);
|
||||
|
||||
// Returns an exit code so boolify it then invert result
|
||||
registrationResult = !pythonRegistrationResult.cast<bool>();
|
||||
});
|
||||
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>(result.GetError().c_str());
|
||||
}
|
||||
else if (!registrationResult)
|
||||
{
|
||||
return AZ::Failure<AZStd::string>(AZStd::string::format("Failed to register gem path %s", gemPath.toUtf8().constData()));
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
bool PythonBindings::AddProject(const QString& path)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetEngineGemInfos() override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) override;
|
||||
AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) override;
|
||||
AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) override;
|
||||
|
||||
// Project
|
||||
AZ::Outcome<ProjectInfo> CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override;
|
||||
|
||||
@@ -91,6 +91,14 @@ namespace O3DE::ProjectManager
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) = 0;
|
||||
|
||||
/**
|
||||
* Registers the gem to the specified project, or to the o3de_manifest.json if no project path is given
|
||||
* @param gemPath the path to the gem
|
||||
* @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json
|
||||
* @return An outcome with the success flag as well as an error message in case of a failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0;
|
||||
|
||||
|
||||
// Projects
|
||||
|
||||
|
||||
Reference in New Issue
Block a user