diff --git a/Code/Tools/ProjectManager/Resources/Download.svg b/Code/Tools/ProjectManager/Resources/Download.svg
new file mode 100644
index 0000000000..c2b0c2ce3c
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/Download.svg
@@ -0,0 +1,3 @@
+
diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
index 30bcc1ace5..aeaf9a9248 100644
--- a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
+++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
@@ -34,9 +34,11 @@
Warning.svg
Backgrounds/DefaultBackground.jpg
Backgrounds/FtueBackground.jpg
- FeatureTagClose.svg
+ X.svg
Refresh.svg
Edit.svg
Delete.svg
+ Download.svg
+ in_progress.gif
diff --git a/Code/Tools/ProjectManager/Resources/FeatureTagClose.svg b/Code/Tools/ProjectManager/Resources/X.svg
similarity index 100%
rename from Code/Tools/ProjectManager/Resources/FeatureTagClose.svg
rename to Code/Tools/ProjectManager/Resources/X.svg
diff --git a/Code/Tools/ProjectManager/Resources/in_progress.gif b/Code/Tools/ProjectManager/Resources/in_progress.gif
new file mode 100644
index 0000000000..eb392a9b89
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/in_progress.gif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:64985a78205da45f4bb92b040c348d96fe7cd7277549c1f79c430469a0d3bab7
+size 166393
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterTagWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterTagWidget.cpp
index 138880f44e..69a883d169 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterTagWidget.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterTagWidget.cpp
@@ -33,7 +33,7 @@ namespace O3DE::ProjectManager
m_closeButton = new QPushButton();
m_closeButton->setFlat(true);
- m_closeButton->setIcon(QIcon(":/FeatureTagClose.svg"));
+ m_closeButton->setIcon(QIcon(":/X.svg"));
m_closeButton->setIconSize(QSize(12, 12));
m_closeButton->setStyleSheet("QPushButton { background-color: transparent; border: 0px }");
layout->addWidget(m_closeButton);
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
index 771d644617..cbdcf64162 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
@@ -8,6 +8,8 @@
#include "GemInfo.h"
+#include
+
namespace O3DE::ProjectManager
{
GemInfo::GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded)
@@ -29,17 +31,17 @@ namespace O3DE::ProjectManager
switch (platform)
{
case Android:
- return "Android";
+ return QObject::tr("Android");
case iOS:
- return "iOS";
+ return QObject::tr("iOS");
case Linux:
- return "Linux";
+ return QObject::tr("Linux");
case macOS:
- return "macOS";
+ return QObject::tr("macOS");
case Windows:
- return "Windows";
+ return QObject::tr("Windows");
default:
- return "";
+ return QObject::tr("");
}
}
@@ -48,13 +50,13 @@ namespace O3DE::ProjectManager
switch (type)
{
case Asset:
- return "Asset";
+ return QObject::tr("Asset");
case Code:
- return "Code";
+ return QObject::tr("Code");
case Tool:
- return "Tool";
+ return QObject::tr("Tool");
default:
- return "";
+ return QObject::tr("");
}
}
@@ -62,15 +64,33 @@ namespace O3DE::ProjectManager
{
switch (origin)
{
- case Open3DEEngine:
- return "Open 3D Engine";
+ case Open3DEngine:
+ return QObject::tr("Open 3D Engine");
case Local:
- return "Local";
+ return QObject::tr("Local");
+ case Remote:
+ return QObject::tr("Remote");
default:
- return "";
+ return QObject::tr("");
}
}
+ QString GemInfo::GetDownloadStatusString(DownloadStatus status)
+ {
+ switch (status)
+ {
+ case NotDownloaded:
+ return QObject::tr("Not Downloaded");
+ case Downloading:
+ return QObject::tr("Downloading");
+ case Downloaded:
+ return QObject::tr("Downloaded");
+ case UnknownDownloadStatus:
+ default:
+ return QObject::tr("");
+ }
+ };
+
bool GemInfo::IsPlatformSupported(Platform platform) const
{
return (m_platforms & platform);
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
index 311eeb93f6..8c6d40505a 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
@@ -44,13 +44,23 @@ namespace O3DE::ProjectManager
enum GemOrigin
{
- Open3DEEngine = 1 << 0,
+ Open3DEngine = 1 << 0,
Local = 1 << 1,
- NumGemOrigins = 2
+ Remote = 1 << 2,
+ NumGemOrigins = 3
};
Q_DECLARE_FLAGS(GemOrigins, GemOrigin)
static QString GetGemOriginString(GemOrigin origin);
+ enum DownloadStatus
+ {
+ UnknownDownloadStatus = -1,
+ NotDownloaded,
+ Downloading,
+ Downloaded,
+ };
+ static QString GetDownloadStatusString(DownloadStatus status);
+
GemInfo() = default;
GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded);
bool IsPlatformSupported(Platform platform) const;
@@ -68,6 +78,7 @@ namespace O3DE::ProjectManager
QString m_summary = "No summary provided.";
Platforms m_platforms;
Types m_types; //! Asset and/or Code and/or Tool
+ DownloadStatus m_downloadStatus = UnknownDownloadStatus;
QStringList m_features;
QString m_requirement;
QString m_directoryLink;
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp
index 2c7f17db32..e15c4b3b39 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+
#include
#include
#include
@@ -20,6 +21,7 @@
#include
#include
#include
+#include
namespace O3DE::ProjectManager
{
@@ -32,6 +34,11 @@ namespace O3DE::ProjectManager
AddPlatformIcon(GemInfo::Linux, ":/Linux.svg");
AddPlatformIcon(GemInfo::macOS, ":/macOS.svg");
AddPlatformIcon(GemInfo::Windows, ":/Windows.svg");
+
+ SetStatusIcon(m_notDownloadedPixmap, ":/Download.svg");
+ SetStatusIcon(m_unknownStatusPixmap, ":/X.svg");
+
+ m_downloadingMovie = new QMovie(":/in_progress.gif");
}
void GemItemDelegate::AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath)
@@ -41,6 +48,25 @@ namespace O3DE::ProjectManager
m_platformIcons.insert(platform, QIcon(iconPath).pixmap(static_cast(static_cast(s_platformIconSize) * aspectRatio), s_platformIconSize));
}
+ void GemItemDelegate::SetStatusIcon(QPixmap& m_iconPixmap, const QString& iconPath)
+ {
+ QPixmap pixmap(iconPath);
+ float aspectRatio = static_cast(pixmap.width()) / pixmap.height();
+ int xScaler = s_statusIconSize;
+ int yScaler = s_statusIconSize;
+
+ if (aspectRatio > 1.0f)
+ {
+ yScaler = static_cast(1.0f / aspectRatio * s_statusIconSize);
+ }
+ else if (aspectRatio < 1.0f)
+ {
+ xScaler = static_cast(aspectRatio * s_statusIconSize);
+ }
+
+ m_iconPixmap = QPixmap(QIcon(iconPath).pixmap(xScaler, yScaler));
+ }
+
void GemItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
{
if (!modelIndex.isValid())
@@ -56,6 +82,8 @@ namespace O3DE::ProjectManager
QRect fullRect, itemRect, contentRect;
CalcRects(options, fullRect, itemRect, contentRect);
+ QRect buttonRect = CalcButtonRect(contentRect);
+
QFont standardFont(options.font);
standardFont.setPixelSize(static_cast(s_fontSize));
QFontMetrics standardFontMetrics(standardFont);
@@ -114,7 +142,8 @@ namespace O3DE::ProjectManager
const QRect summaryRect = CalcSummaryRect(contentRect, hasTags);
DrawText(summary, painter, summaryRect, standardFont);
- DrawButton(painter, contentRect, modelIndex);
+ DrawDownloadStatusIcon(painter, contentRect, buttonRect, modelIndex);
+ DrawButton(painter, buttonRect, modelIndex);
DrawPlatformIcons(painter, contentRect, modelIndex);
DrawFeatureTags(painter, contentRect, featureTags, standardFont, summaryRect);
@@ -270,7 +299,7 @@ namespace O3DE::ProjectManager
QRect GemItemDelegate::CalcButtonRect(const QRect& contentRect) const
{
- const QPoint topLeft = QPoint(contentRect.right() - s_buttonWidth - s_itemMargins.right(), contentRect.top() + contentRect.height() / 2 - s_buttonHeight / 2);
+ const QPoint topLeft = QPoint(contentRect.right() - s_buttonWidth, contentRect.center().y() - s_buttonHeight / 2);
const QSize size = QSize(s_buttonWidth, s_buttonHeight);
return QRect(topLeft, size);
}
@@ -378,10 +407,9 @@ namespace O3DE::ProjectManager
painter->restore();
}
- void GemItemDelegate::DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const
+ void GemItemDelegate::DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const
{
painter->save();
- const QRect buttonRect = CalcButtonRect(contentRect);
QPoint circleCenter;
if (GemModel::IsAdded(modelIndex))
@@ -427,4 +455,45 @@ namespace O3DE::ProjectManager
return QString();
}
+
+ void GemItemDelegate::DrawDownloadStatusIcon(QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const
+ {
+ const GemInfo::DownloadStatus downloadStatus = GemModel::GetDownloadStatus(modelIndex);
+
+ // Show no icon if gem is already downloaded
+ if (downloadStatus == GemInfo::DownloadStatus::Downloaded)
+ {
+ return;
+ }
+
+ QPixmap currentFrame;
+ const QPixmap* statusPixmap;
+ if (downloadStatus == GemInfo::DownloadStatus::Downloading)
+ {
+ if (m_downloadingMovie->state() != QMovie::Running)
+ {
+ m_downloadingMovie->start();
+ emit MovieStartedPlaying(m_downloadingMovie);
+ }
+
+ currentFrame = m_downloadingMovie->currentPixmap();
+ currentFrame = currentFrame.scaled(s_statusIconSize, s_statusIconSize);
+ statusPixmap = ¤tFrame;
+ }
+ else if (downloadStatus == GemInfo::DownloadStatus::NotDownloaded)
+ {
+ statusPixmap = &m_notDownloadedPixmap;
+ }
+ else
+ {
+ statusPixmap = &m_unknownStatusPixmap;
+ }
+
+ QSize statusSize = statusPixmap->size();
+
+ painter->drawPixmap(
+ buttonRect.left() - s_statusButtonSpacing - statusSize.width(),
+ contentRect.center().y() - statusSize.height() / 2,
+ *statusPixmap);
+ }
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h
index 52b5a4f58e..c013be0d9e 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h
@@ -49,13 +49,13 @@ namespace O3DE::ProjectManager
// Margin and borders
inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/16, /*top=*/8, /*right=*/16, /*bottom=*/8); // Item border distances
- inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/12, /*right=*/15, /*bottom=*/12); // Distances of the elements within an item to the item borders
+ inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/12, /*right=*/20, /*bottom=*/12); // Distances of the elements within an item to the item borders
inline constexpr static int s_borderWidth = 4;
// Button
- inline constexpr static int s_buttonWidth = 55;
- inline constexpr static int s_buttonHeight = 18;
- inline constexpr static int s_buttonBorderRadius = 9;
+ inline constexpr static int s_buttonWidth = 32;
+ inline constexpr static int s_buttonHeight = 16;
+ inline constexpr static int s_buttonBorderRadius = s_buttonHeight / 2;
inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 2;
inline constexpr static qreal s_buttonFontSize = 10.0;
@@ -65,6 +65,9 @@ namespace O3DE::ProjectManager
inline constexpr static int s_featureTagBorderMarginY = 3;
inline constexpr static int s_featureTagSpacing = 7;
+ signals:
+ void MovieStartedPlaying(const QMovie* playingMovie) const;
+
protected:
bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) override;
bool helpEvent(QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index) override;
@@ -74,9 +77,10 @@ namespace O3DE::ProjectManager
QRect CalcButtonRect(const QRect& contentRect) const;
QRect CalcSummaryRect(const QRect& contentRect, bool hasTags) const;
void DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
- void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
+ void DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const;
void DrawFeatureTags(QPainter* painter, const QRect& contentRect, const QStringList& featureTags, const QFont& standardFont, const QRect& summaryRect) const;
void DrawText(const QString& text, QPainter* painter, const QRect& rect, const QFont& standardFont) const;
+ void DrawDownloadStatusIcon(QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const;
QAbstractItemModel* m_model = nullptr;
@@ -85,5 +89,14 @@ namespace O3DE::ProjectManager
void AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath);
inline constexpr static int s_platformIconSize = 12;
QHash m_platformIcons;
+
+ // Status icons
+ void SetStatusIcon(QPixmap& m_iconPixmap, const QString& iconPath);
+ inline constexpr static int s_statusIconSize = 16;
+ inline constexpr static int s_statusButtonSpacing = 5;
+
+ QPixmap m_unknownStatusPixmap;
+ QPixmap m_notDownloadedPixmap;
+ QMovie* m_downloadingMovie = nullptr;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp
index ab51c7511c..10ff31f33b 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp
@@ -103,11 +103,11 @@ namespace O3DE::ProjectManager
QSpacerItem* horizontalSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
columnHeaderLayout->addSpacerItem(horizontalSpacer);
- QLabel* gemSelectedLabel = new QLabel(tr("Selected"));
+ QLabel* gemSelectedLabel = new QLabel(tr("Status"));
gemSelectedLabel->setObjectName("GemCatalogHeaderLabel");
columnHeaderLayout->addWidget(gemSelectedLabel);
- columnHeaderLayout->addSpacing(65);
+ columnHeaderLayout->addSpacing(72);
vLayout->addLayout(columnHeaderLayout);
}
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp
index f5b54a364b..cfdf7fa5b3 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp
@@ -9,6 +9,8 @@
#include
#include
+#include
+
namespace O3DE::ProjectManager
{
GemListView::GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent)
@@ -19,6 +21,17 @@ namespace O3DE::ProjectManager
setModel(model);
setSelectionModel(selectionModel);
- setItemDelegate(new GemItemDelegate(model, this));
+ GemItemDelegate* itemDelegate = new GemItemDelegate(model, this);
+
+ connect(itemDelegate, &GemItemDelegate::MovieStartedPlaying, [=](const QMovie* playingMovie)
+ {
+ // Force redraw when movie is playing so animation is smooth
+ connect(playingMovie, &QMovie::frameChanged, this, [=]
+ {
+ this->viewport()->repaint();
+ });
+ });
+
+ setItemDelegate(itemDelegate);
}
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
index c8911de360..35491f4ddd 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
@@ -48,6 +48,7 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_features, RoleFeatures);
item->setData(gemInfo.m_path, RolePath);
item->setData(gemInfo.m_requirement, RoleRequirement);
+ item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus);
appendRow(item);
@@ -132,6 +133,11 @@ namespace O3DE::ProjectManager
return static_cast(modelIndex.data(RoleTypes).toInt());
}
+ GemInfo::DownloadStatus GemModel::GetDownloadStatus(const QModelIndex& modelIndex)
+ {
+ return static_cast(modelIndex.data(RoleDownloadStatus).toInt());
+ }
+
QString GemModel::GetSummary(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleSummary).toString();
@@ -373,6 +379,11 @@ namespace O3DE::ProjectManager
return previouslyAdded && !added;
}
+ void GemModel::SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status)
+ {
+ model.setData(modelIndex, status, RoleDownloadStatus);
+ }
+
bool GemModel::HasRequirement(const QModelIndex& modelIndex)
{
return !modelIndex.data(RoleRequirement).toString().isEmpty();
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
index ef2d1a903d..0d1c225f74 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
@@ -40,6 +40,7 @@ namespace O3DE::ProjectManager
static GemInfo::GemOrigin GetGemOrigin(const QModelIndex& modelIndex);
static GemInfo::Platforms GetPlatforms(const QModelIndex& modelIndex);
static GemInfo::Types GetTypes(const QModelIndex& modelIndex);
+ static GemInfo::DownloadStatus GetDownloadStatus(const QModelIndex& modelIndex);
static QString GetSummary(const QModelIndex& modelIndex);
static QString GetDirectoryLink(const QModelIndex& modelIndex);
static QString GetDocLink(const QModelIndex& modelIndex);
@@ -64,6 +65,7 @@ namespace O3DE::ProjectManager
static bool NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies = false);
static bool HasRequirement(const QModelIndex& modelIndex);
static void UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex);
+ static void SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status);
bool DoGemsToBeAddedHaveRequirements() const;
bool HasDependentGemsToRemove() const;
@@ -101,7 +103,8 @@ namespace O3DE::ProjectManager
RoleFeatures,
RoleTypes,
RolePath,
- RoleRequirement
+ RoleRequirement,
+ RoleDownloadStatus
};
QHash m_nameToIndexMap;
diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
index 83f93630ac..d91f08c73e 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp
+++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
@@ -668,7 +668,21 @@ namespace O3DE::ProjectManager
if (gemInfo.m_creator.contains("Open 3D Engine"))
{
- gemInfo.m_gemOrigin = GemInfo::GemOrigin::Open3DEEngine;
+ gemInfo.m_gemOrigin = GemInfo::GemOrigin::Open3DEngine;
+ }
+ else if (gemInfo.m_creator.contains("Amazon Web Services"))
+ {
+ gemInfo.m_gemOrigin = GemInfo::GemOrigin::Local;
+ }
+ else if (data.contains("origin"))
+ {
+ gemInfo.m_gemOrigin = GemInfo::GemOrigin::Remote;
+ }
+
+ // As long Base Open3DEngine gems are installed before first startup non-remote gems will be downloaded
+ if (gemInfo.m_gemOrigin != GemInfo::GemOrigin::Remote)
+ {
+ gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded;
}
if (data.contains("user_tags"))