Procházet zdrojové kódy

Add Initial Gem Repository Management Screen (#4172)

* Adds the gem repo screen with the UI built but with mocked data and not connected to the o3de scripts

Signed-off-by: nggieber <[email protected]>

* Changed name of added to enabled, disabled define, removed unused functions

Signed-off-by: nggieber <[email protected]>

* Added eof nl to qss

Signed-off-by: nggieber <[email protected]>
AMZN-nggieber před 3 roky
rodič
revize
357e823323
26 změnil soubory, kde provedl 1054 přidání a 21 odebrání
  1. 4 0
      Code/Tools/ProjectManager/Resources/Delete.svg
  2. 7 0
      Code/Tools/ProjectManager/Resources/Edit.svg
  3. 3 0
      Code/Tools/ProjectManager/Resources/ProjectManager.qrc
  4. 79 0
      Code/Tools/ProjectManager/Resources/ProjectManager.qss
  5. 4 0
      Code/Tools/ProjectManager/Resources/Refresh.svg
  6. 64 0
      Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp
  7. 34 0
      Code/Tools/ProjectManager/Source/EngineScreenCtrl.h
  8. 5 14
      Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp
  9. 0 2
      Code/Tools/ProjectManager/Source/EngineSettingsScreen.h
  10. 32 0
      Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.cpp
  11. 37 0
      Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h
  12. 222 0
      Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp
  13. 82 0
      Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h
  14. 23 0
      Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp
  15. 28 0
      Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h
  16. 94 0
      Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp
  17. 58 0
      Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h
  18. 145 0
      Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp
  19. 50 0
      Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h
  20. 1 1
      Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
  21. 41 0
      Code/Tools/ProjectManager/Source/PythonBindings.cpp
  22. 4 0
      Code/Tools/ProjectManager/Source/PythonBindings.h
  23. 11 2
      Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
  24. 6 2
      Code/Tools/ProjectManager/Source/ScreenDefs.h
  25. 8 0
      Code/Tools/ProjectManager/Source/ScreenFactory.cpp
  26. 12 0
      Code/Tools/ProjectManager/project_manager_files.cmake

+ 4 - 0
Code/Tools/ProjectManager/Resources/Delete.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.33301 6H12.6663V14.6667H3.33301V6ZM5.33301 7.33333H6.66634V13.3333H5.33301V7.33333ZM10.6663 7.33333H9.33301V13.3333H10.6663V7.33333Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M10.6667 2H5.33333V3.33333H2V4.66667H14V3.33333H10.6667V2Z" fill="#E9E9E9"/>
+</svg>

+ 7 - 0
Code/Tools/ProjectManager/Resources/Edit.svg

@@ -0,0 +1,7 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="2" y="2" width="1.33333" height="12" fill="white"/>
+<rect x="2" y="12.6666" width="12" height="1.33333" fill="white"/>
+<rect x="5.33301" y="9.20911" width="10.6667" height="2" transform="rotate(-45 5.33301 9.20911)" fill="white"/>
+<rect x="2" y="2" width="6.66667" height="1.33333" fill="white"/>
+<rect width="1.33333" height="6.66667" transform="matrix(-1 0 0 1 14 7.33337)" fill="white"/>
+</svg>

+ 3 - 0
Code/Tools/ProjectManager/Resources/ProjectManager.qrc

@@ -35,5 +35,8 @@
         <file>Backgrounds/DefaultBackground.jpg</file>
         <file>Backgrounds/FtueBackground.jpg</file>
         <file>FeatureTagClose.svg</file>
+        <file>Refresh.svg</file>
+        <file>Edit.svg</file>
+        <file>Delete.svg</file>
     </qresource>
 </RCC>

+ 79 - 0
Code/Tools/ProjectManager/Resources/ProjectManager.qss

@@ -518,3 +518,82 @@ QProgressBar::chunk {
     font-size: 12px;
     font-weight: 600;
 }
+
+/************** Engine **************/
+
+#engineTab::tab-bar {
+    left: 60px;
+}
+
+#engineTabBar::tab {
+    height: 50px;
+    background-color: transparent;
+    font-weight: 400;
+    font-size: 18px;
+    min-width: 160px;
+}
+
+#engineTabBar::tab:selected {
+    border-bottom: 3px solid #94D2FF;
+    color: #94D2FF;
+    font-weight: 600;
+}
+#engineTabBar::tab:hover {
+    color: #94D2FF;
+    font-weight: 600;
+}
+#engineTabBar::tab:pressed {
+    color: #66bcfa;
+}
+
+#engineTopFrame {
+    background-color:#1E252F;
+}
+
+/************** Gem Repo **************/
+
+#gemRepoHeaderLabel {
+    font-size: 12px;
+}
+
+#gemRepoHeaderRefreshButton {
+    background-color: transparent;
+    qproperty-flat: true;
+    qproperty-iconSize: 14px;
+}
+
+#gemRepoHeaderAddButton {
+    background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+                            stop: 0 #888888, stop: 1.0 #555555);
+    qproperty-flat: true;
+    margin-right:30px;
+    min-width:120px;
+    max-width:120px;
+    min-height:24px;
+    max-height:24px;
+    border-radius: 3px;
+    text-align:center;
+    font-size:12px;
+    font-weight:600;
+}
+#gemRepoHeaderAddButton:hover {
+    background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+                            stop: 0 #999999, stop: 1.0 #666666);
+}
+#gemRepoHeaderAddButton:pressed {
+    background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+                            stop: 0 #555555, stop: 1.0 #777777);
+}
+
+#gemRepoHeaderTable {
+    background-color: transparent;
+    max-height: 30px;
+}
+
+#gemRepoListHeader {
+    background-color: transparent;
+}
+
+#gemRepoInspector {
+    background: #444444;
+}

+ 4 - 0
Code/Tools/ProjectManager/Resources/Refresh.svg

@@ -0,0 +1,4 @@
+<svg width="15" height="12" viewBox="0 0 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M13.046 5.85448C13.046 5.869 13.0436 5.66561 13.0436 5.68014H14.5278L12.3341 8.22252L10.1114 5.68014H11.6481C11.6481 5.66561 11.6513 5.869 11.6513 5.85448C11.6513 3.42833 9.77724 1.46707 7.46731 1.46707C6.42131 1.46707 5.46247 1.87385 4.73608 2.54213L3.8063 1.43801C4.79419 0.537286 6.07264 -0.000244141 7.46731 -0.000244141C10.5472 -0.000244141 13.046 2.6293 13.046 5.85448Z" fill="white"/>
+<path d="M1.48184 6.14503C1.48184 6.13051 1.48428 6.3339 1.48428 6.31937H0L2.1937 3.777L4.41646 6.31937H2.87975C2.87975 6.3339 2.87651 6.13051 2.87651 6.14503C2.87651 8.57118 4.7506 10.5324 7.06053 10.5324C8.10654 10.5324 9.06537 10.1257 9.79177 9.45738L10.7215 10.5615C9.73366 11.4622 8.4552 11.9998 7.06053 11.9998C3.98063 11.9998 1.48184 9.37022 1.48184 6.14503Z" fill="white"/>
+</svg>

+ 64 - 0
Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp

@@ -0,0 +1,64 @@
+/*
+ * 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 <EngineScreenCtrl.h>
+#include <GemRepo/GemRepoScreen.h>
+#include <EngineSettingsScreen.h>
+
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QTabWidget>
+
+namespace O3DE::ProjectManager
+{
+    EngineScreenCtrl::EngineScreenCtrl(QWidget* parent)
+        : ScreenWidget(parent)
+    {
+        QVBoxLayout* vLayout = new QVBoxLayout();
+        vLayout->setContentsMargins(0, 0, 0, 0);
+
+        QFrame* topBarFrameWidget = new QFrame(this);
+        topBarFrameWidget->setObjectName("engineTopFrame");
+        QHBoxLayout* topBarHLayout = new QHBoxLayout();
+        topBarHLayout->setContentsMargins(0, 0, 0, 0);
+        
+        topBarFrameWidget->setLayout(topBarHLayout);
+
+        QTabWidget* tabWidget = new QTabWidget();
+        tabWidget->setObjectName("engineTab");
+        tabWidget->tabBar()->setObjectName("engineTabBar");
+        tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
+
+        m_engineSettingsScreen = new EngineSettingsScreen();
+        m_gemRepoScreen = new GemRepoScreen();
+
+        tabWidget->addTab(m_engineSettingsScreen, tr("General"));
+        tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories"));
+        topBarHLayout->addWidget(tabWidget);
+
+        vLayout->addWidget(topBarFrameWidget);
+
+        setLayout(vLayout);
+    }
+
+    ProjectManagerScreen EngineScreenCtrl::GetScreenEnum()
+    {
+        return ProjectManagerScreen::UpdateProject;
+    }
+
+    QString EngineScreenCtrl::GetTabText()
+    {
+        return tr("Engine");
+    }
+
+    bool EngineScreenCtrl::IsTab()
+    {
+        return true;
+    }
+
+} // namespace O3DE::ProjectManager

+ 34 - 0
Code/Tools/ProjectManager/Source/EngineScreenCtrl.h

@@ -0,0 +1,34 @@
+/*
+ * 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 <ScreenWidget.h>
+#endif
+
+namespace O3DE::ProjectManager
+{
+    QT_FORWARD_DECLARE_CLASS(EngineSettingsScreen)
+    QT_FORWARD_DECLARE_CLASS(GemRepoScreen)
+
+    class EngineScreenCtrl
+        : public ScreenWidget
+    {
+    public:
+        explicit EngineScreenCtrl(QWidget* parent = nullptr);
+        ~EngineScreenCtrl() = default;
+        ProjectManagerScreen GetScreenEnum() override;
+
+        QString GetTabText() override;
+        bool IsTab() override;
+
+        EngineSettingsScreen* m_engineSettingsScreen = nullptr;
+        GemRepoScreen* m_gemRepoScreen = nullptr;
+    };
+
+} // namespace O3DE::ProjectManager

+ 5 - 14
Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp

@@ -7,15 +7,16 @@
  */
 
 #include <EngineSettingsScreen.h>
-#include <QVBoxLayout>
-#include <QLabel>
-#include <QLineEdit>
-#include <QMessageBox>
 #include <FormLineEditWidget.h>
 #include <FormFolderBrowseEditWidget.h>
 #include <PythonBindingsInterface.h>
 #include <PathValidator.h>
 
+#include <QVBoxLayout>
+#include <QLabel>
+#include <QLineEdit>
+#include <QMessageBox>
+
 namespace O3DE::ProjectManager
 {
     EngineSettingsScreen::EngineSettingsScreen(QWidget* parent)
@@ -78,16 +79,6 @@ namespace O3DE::ProjectManager
         return ProjectManagerScreen::EngineSettings;
     }
 
-    QString EngineSettingsScreen::GetTabText()
-    {
-        return tr("Engine");
-    }
-
-    bool EngineSettingsScreen::IsTab()
-    {
-        return true;
-    }
-
     void EngineSettingsScreen::OnTextChanged()
     {
         // save engine settings

+ 0 - 2
Code/Tools/ProjectManager/Source/EngineSettingsScreen.h

@@ -24,8 +24,6 @@ namespace O3DE::ProjectManager
         ~EngineSettingsScreen() = default;
 
         ProjectManagerScreen GetScreenEnum() override;
-        QString GetTabText() override;
-        bool IsTab() override;
 
     protected slots:
         void OnTextChanged();

+ 32 - 0
Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.cpp

@@ -0,0 +1,32 @@
+/*
+ * 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 <GemRepo/GemRepoInfo.h>
+
+namespace O3DE::ProjectManager
+{
+    GemRepoInfo::GemRepoInfo(
+        const QString& name, const QString& creator, const QString& summary, const QDateTime& lastUpdated, bool isEnabled = true)
+        : m_name(name)
+        , m_creator(creator)
+        , m_summary(summary)
+        , m_lastUpdated(lastUpdated)
+        , m_isEnabled(isEnabled)
+    {
+    }
+    
+    bool GemRepoInfo::IsValid() const
+    {
+        return !m_name.isEmpty();
+    }
+
+    bool GemRepoInfo::operator<(const GemRepoInfo& gemRepoInfo) const
+    {
+        return (m_lastUpdated < gemRepoInfo.m_lastUpdated);
+    }
+} // namespace O3DE::ProjectManager

+ 37 - 0
Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h

@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include <QString>
+#include <QDateTime>
+#endif
+
+namespace O3DE::ProjectManager
+{
+    class GemRepoInfo
+    {
+    public:
+        GemRepoInfo() = default;
+        GemRepoInfo(const QString& name, const QString& creator, const QString& summary, const QDateTime& lastUpdated, bool isEnabled);
+
+        bool IsValid() const;
+
+        bool operator<(const GemRepoInfo& gemRepoInfo) const;
+
+        QString m_path;
+        QString m_name = "Unknown Gem Repo Name";
+        QString m_creator = "Unknown Creator";
+        bool m_isEnabled = false; //! Is the repo currently enabled for this engine?
+        QString m_summary = "No summary provided.";
+        QString m_directoryLink;
+        QString m_repoLink;
+        QDateTime m_lastUpdated;
+    };
+} // namespace O3DE::ProjectManager

+ 222 - 0
Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp

@@ -0,0 +1,222 @@
+/*
+ * 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 <GemRepo/GemRepoItemDelegate.h>
+#include <GemRepo/GemRepoModel.h>
+
+#include <QEvent>
+#include <QPainter>
+#include <QMouseEvent>
+
+namespace O3DE::ProjectManager
+{
+    GemRepoItemDelegate::GemRepoItemDelegate(QAbstractItemModel* model, QObject* parent)
+        : QStyledItemDelegate(parent)
+        , m_model(model)
+    {
+        m_refreshIcon   = QIcon(":/Refresh.svg").pixmap(s_refreshIconSize, s_refreshIconSize);
+        m_editIcon      = QIcon(":/Edit.svg").pixmap(s_iconSize, s_iconSize);
+        m_deleteIcon    = QIcon(":/Delete.svg").pixmap(s_iconSize, s_iconSize);
+    }
+
+    void GemRepoItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
+    {
+        if (!modelIndex.isValid())
+        {
+            return;
+        }
+
+        QStyleOptionViewItem options(option);
+        initStyleOption(&options, modelIndex);
+
+        painter->setRenderHint(QPainter::Antialiasing);
+
+        QRect fullRect, itemRect, contentRect;
+        CalcRects(options, fullRect, itemRect, contentRect);
+        QRect buttonRect = CalcButtonRect(contentRect);
+
+        QFont standardFont(options.font);
+        standardFont.setPixelSize(static_cast<int>(s_fontSize));
+        QFontMetrics standardFontMetrics(standardFont);
+
+        painter->save();
+        painter->setClipping(true);
+        painter->setClipRect(fullRect);
+        painter->setFont(standardFont);
+        painter->setPen(m_textColor);
+
+        // Draw background
+        painter->fillRect(fullRect, m_backgroundColor);
+
+        // Draw item background
+        const QColor itemBackgroundColor = options.state & QStyle::State_MouseOver ? m_itemBackgroundColor.lighter(120) : m_itemBackgroundColor;
+        painter->fillRect(itemRect, itemBackgroundColor);
+
+        // Draw border
+        if (options.state & QStyle::State_Selected)
+        {
+            painter->save();
+            QPen borderPen(m_borderColor);
+            borderPen.setWidth(s_borderWidth);
+            painter->setPen(borderPen);
+            painter->drawRect(itemRect);
+
+            painter->restore();
+        }
+
+        // Repo enabled
+        DrawButton(painter, buttonRect, modelIndex);
+
+        // Repo name
+        QString repoName = GemRepoModel::GetName(modelIndex);
+        repoName = QFontMetrics(standardFont).elidedText(repoName, Qt::TextElideMode::ElideRight, s_nameMaxWidth);
+
+        QRect repoNameRect = GetTextRect(standardFont, repoName, s_fontSize);
+        int currentHorizontalOffset = buttonRect.left() + s_buttonWidth + s_buttonSpacing;
+        repoNameRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoNameRect.height() / 2);
+        repoNameRect = painter->boundingRect(repoNameRect, Qt::TextSingleLine, repoName);
+
+        painter->drawText(repoNameRect, Qt::TextSingleLine, repoName);
+
+        // Rem repo creator
+        QString repoCreator = GemRepoModel::GetCreator(modelIndex);
+        repoCreator = standardFontMetrics.elidedText(repoCreator, Qt::TextElideMode::ElideRight, s_creatorMaxWidth);
+
+        QRect repoCreatorRect = GetTextRect(standardFont, repoCreator, s_fontSize);
+        currentHorizontalOffset += s_nameMaxWidth + s_contentSpacing;
+        repoCreatorRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoCreatorRect.height() / 2);
+        repoCreatorRect = painter->boundingRect(repoCreatorRect, Qt::TextSingleLine, repoCreator);
+
+        painter->drawText(repoCreatorRect, Qt::TextSingleLine, repoCreator);
+
+        // Repo update
+        QString repoUpdatedDate = GemRepoModel::GetLastUpdated(modelIndex).toString("dd/MM/yyyy hh:mmap");
+        repoUpdatedDate = standardFontMetrics.elidedText(repoUpdatedDate, Qt::TextElideMode::ElideRight, s_updatedMaxWidth);
+
+        QRect repoUpdatedDateRect = GetTextRect(standardFont, repoUpdatedDate, s_fontSize);
+        currentHorizontalOffset += s_creatorMaxWidth + s_contentSpacing;
+        repoUpdatedDateRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoUpdatedDateRect.height() / 2);
+        repoUpdatedDateRect = painter->boundingRect(repoUpdatedDateRect, Qt::TextSingleLine, repoUpdatedDate);
+
+        painter->drawText(repoUpdatedDateRect, Qt::TextSingleLine, repoUpdatedDate);
+
+        // Draw refresh button
+        painter->drawPixmap(
+            repoUpdatedDateRect.left() + repoUpdatedDateRect.width() + s_refreshIconSpacing,
+            contentRect.center().y() - s_refreshIconSize / 3, // Dividing size by 3 centers much better
+            m_refreshIcon);
+
+        if (options.state & QStyle::State_MouseOver)
+        {
+            DrawEditButtons(painter, contentRect);
+        }
+
+        painter->restore();
+    }
+
+    QSize GemRepoItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
+    {
+        QStyleOptionViewItem options(option);
+        initStyleOption(&options, modelIndex);
+
+        int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right();
+        return QSize(marginsHorizontal + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 3, s_height);
+    }
+
+    bool GemRepoItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex)
+    {
+        if (!modelIndex.isValid())
+        {
+            return false;
+        }
+
+        if (event->type() == QEvent::KeyPress)
+        {
+            auto keyEvent = static_cast<const QKeyEvent*>(event);
+            if (keyEvent->key() == Qt::Key_Space)
+            {
+                const bool isAdded = GemRepoModel::IsEnabled(modelIndex);
+                GemRepoModel::SetEnabled(*model, modelIndex, !isAdded);
+                return true;
+            }
+        }
+
+        if (event->type() == QEvent::MouseButtonPress)
+        {
+            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
+
+            QRect fullRect, itemRect, contentRect;
+            CalcRects(option, fullRect, itemRect, contentRect);
+            const QRect buttonRect = CalcButtonRect(contentRect);
+
+            if (buttonRect.contains(mouseEvent->pos()))
+            {
+                const bool isAdded = GemRepoModel::IsEnabled(modelIndex);
+                GemRepoModel::SetEnabled(*model, modelIndex, !isAdded);
+                return true;
+            }
+        }
+
+        return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
+    }
+
+    void GemRepoItemDelegate::CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const
+    {
+        outFullRect = QRect(option.rect);
+        outItemRect = QRect(outFullRect.adjusted(s_itemMargins.left(), s_itemMargins.top(), -s_itemMargins.right(), -s_itemMargins.bottom()));
+        outContentRect = QRect(outItemRect.adjusted(s_contentMargins.left(), s_contentMargins.top(), -s_contentMargins.right(), -s_contentMargins.bottom()));
+    }
+
+    QRect GemRepoItemDelegate::GetTextRect(QFont& font, const QString& text, qreal fontSize) const
+    {
+        font.setPixelSize(static_cast<int>(fontSize));
+        return QFontMetrics(font).boundingRect(text);
+    }
+
+    QRect GemRepoItemDelegate::CalcButtonRect(const QRect& contentRect) const
+    {
+        const QPoint topLeft = QPoint(contentRect.left(), contentRect.top() + contentRect.height() / 2 - s_buttonHeight / 2);
+        const QSize size = QSize(s_buttonWidth, s_buttonHeight);
+        return QRect(topLeft, size);
+    }
+
+    void GemRepoItemDelegate::DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const
+    {
+        painter->save();
+        QPoint circleCenter;
+
+        const bool isEnabled = GemRepoModel::IsEnabled(modelIndex);
+        if (isEnabled)
+        {
+            painter->setBrush(m_buttonEnabledColor);
+            painter->setPen(m_buttonEnabledColor);
+
+            circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1);
+        }
+        else
+        {
+            circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius + 1, 1);
+        }
+
+        // Rounded rect
+        painter->drawRoundedRect(buttonRect, s_buttonBorderRadius, s_buttonBorderRadius);
+
+        // Circle
+        painter->setBrush(m_textColor);
+        painter->drawEllipse(circleCenter, s_buttonCircleRadius, s_buttonCircleRadius);
+
+        painter->restore();
+    }
+
+    void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const
+    {
+        painter->drawPixmap(contentRect.right() - s_iconSize * 2 - s_iconSpacing, contentRect.center().y() - s_iconSize / 2, m_editIcon);
+        painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon);
+    }
+
+} // namespace O3DE::ProjectManager

+ 82 - 0
Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h

@@ -0,0 +1,82 @@
+/*
+ * 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 <QStyledItemDelegate>
+#include <GemRepo/GemRepoInfo.h>
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
+QT_FORWARD_DECLARE_CLASS(QEvent)
+
+namespace O3DE::ProjectManager
+{
+    class GemRepoItemDelegate
+        : public QStyledItemDelegate
+    {
+        Q_OBJECT // AUTOMOC
+
+    public:
+        explicit GemRepoItemDelegate(QAbstractItemModel* model, QObject* parent = nullptr);
+        ~GemRepoItemDelegate() = default;
+
+        void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
+        bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) override;
+        QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
+
+        // Colors
+        const QColor m_textColor = QColor("#FFFFFF");
+        const QColor m_backgroundColor = QColor("#333333"); // Outside of the actual repo item
+        const QColor m_itemBackgroundColor = QColor("#404040"); // Background color of the repo item
+        const QColor m_borderColor = QColor("#1E70EB");
+        const QColor m_buttonEnabledColor = QColor("#1E70EB");
+
+        // Item
+        inline constexpr static int s_height = 72; // Repo item total height
+        inline constexpr static qreal s_fontSize = 12.0;
+
+        // Margin and borders
+        inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/0, /*top=*/8, /*right=*/60, /*bottom=*/8); // Item border distances
+        inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/20, /*right=*/20, /*bottom=*/20); // Distances of the elements within an item to the item borders
+        inline constexpr static int s_borderWidth = 4;
+
+        // Content
+        inline constexpr static int s_contentSpacing = 5;
+        inline constexpr static int s_nameMaxWidth = 145;
+        inline constexpr static int s_creatorMaxWidth = 115;
+        inline constexpr static int s_updatedMaxWidth = 125;
+
+        // Button
+        inline constexpr static int s_buttonWidth = 32;
+        inline constexpr static int s_buttonHeight = 16;
+        inline constexpr static int s_buttonBorderRadius = 8;
+        inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 2;
+        inline constexpr static int s_buttonSpacing = 20;
+
+        // Icon
+        inline constexpr static int s_iconSize = 24;
+        inline constexpr static int s_iconSpacing = 16;
+        inline constexpr static int s_refreshIconSize = 14;
+        inline constexpr static int s_refreshIconSpacing = 10;
+
+    protected:
+        void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
+        QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
+        QRect CalcButtonRect(const QRect& contentRect) const;
+        void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
+        void DrawEditButtons(QPainter* painter, const QRect& contentRect) const;
+
+        QAbstractItemModel* m_model = nullptr;
+
+        QPixmap m_refreshIcon;
+        QPixmap m_editIcon;
+        QPixmap m_deleteIcon;
+    };
+} // namespace O3DE::ProjectManager

+ 23 - 0
Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp

@@ -0,0 +1,23 @@
+/*
+ * 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 <GemRepo/GemRepoListView.h>
+#include <GemRepo/GemRepoItemDelegate.h>
+
+namespace O3DE::ProjectManager
+{
+    GemRepoListView::GemRepoListView(QAbstractItemModel* model, QWidget* parent)
+        : QListView(parent)
+    {
+        setObjectName("gemRepoListView");
+        setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
+
+        setModel(model);
+        setItemDelegate(new GemRepoItemDelegate(model, this));
+    }
+} // namespace O3DE::ProjectManager

+ 28 - 0
Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h

@@ -0,0 +1,28 @@
+/*
+ * 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 <QListView>
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
+
+namespace O3DE::ProjectManager
+{
+    class GemRepoListView
+        : public QListView
+    {
+        Q_OBJECT // AUTOMOC
+
+    public:
+        explicit GemRepoListView(QAbstractItemModel* model, QWidget* parent = nullptr);
+        ~GemRepoListView() = default;
+    };
+} // namespace O3DE::ProjectManager

+ 94 - 0
Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp

@@ -0,0 +1,94 @@
+/*
+ * 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 <GemRepo/GemRepoModel.h>
+
+#include <QItemSelectionModel>
+
+namespace O3DE::ProjectManager
+{
+    GemRepoModel::GemRepoModel(QObject* parent)
+        : QStandardItemModel(parent)
+    {
+        m_selectionModel = new QItemSelectionModel(this, parent);
+    }
+
+    QItemSelectionModel* GemRepoModel::GetSelectionModel() const
+    {
+        return m_selectionModel;
+    }
+
+    void GemRepoModel::AddGemRepo(const GemRepoInfo& gemRepoInfo)
+    {
+        QStandardItem* item = new QStandardItem();
+
+        item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+
+        item->setData(gemRepoInfo.m_name, RoleName);
+        item->setData(gemRepoInfo.m_creator, RoleCreator);
+        item->setData(gemRepoInfo.m_summary, RoleSummary);
+        item->setData(gemRepoInfo.m_isEnabled, RoleIsEnabled);
+        item->setData(gemRepoInfo.m_directoryLink, RoleDirectoryLink);
+        item->setData(gemRepoInfo.m_repoLink, RoleRepoLink);
+        item->setData(gemRepoInfo.m_lastUpdated, RoleLastUpdated);
+        item->setData(gemRepoInfo.m_path, RolePath);
+
+        appendRow(item);
+    }
+
+    void GemRepoModel::Clear()
+    {
+        clear();
+    }
+
+    QString GemRepoModel::GetName(const QModelIndex& modelIndex)
+    {
+        return modelIndex.data(RoleName).toString();
+    }
+
+    QString GemRepoModel::GetCreator(const QModelIndex& modelIndex)
+    {
+        return modelIndex.data(RoleCreator).toString();
+    }
+
+    QString GemRepoModel::GetSummary(const QModelIndex& modelIndex)
+    {
+        return modelIndex.data(RoleSummary).toString();
+    }
+
+    QString GemRepoModel::GetDirectoryLink(const QModelIndex& modelIndex)
+    {
+        return modelIndex.data(RoleDirectoryLink).toString();
+    }
+
+    QString GemRepoModel::GetRepoLink(const QModelIndex& modelIndex)
+    {
+        return modelIndex.data(RoleRepoLink).toString();
+    }
+
+    QDateTime GemRepoModel::GetLastUpdated(const QModelIndex& modelIndex)
+    {
+        return modelIndex.data(RoleLastUpdated).toDateTime();
+    }
+
+    QString GemRepoModel::GetPath(const QModelIndex& modelIndex)
+    {
+        return modelIndex.data(RolePath).toString();
+    }
+
+    bool GemRepoModel::IsEnabled(const QModelIndex& modelIndex)
+    {
+        return modelIndex.data(RoleIsEnabled).toBool();
+    }
+
+    void GemRepoModel::SetEnabled(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isEnabled)
+    {
+        model.setData(modelIndex, isEnabled, RoleIsEnabled);
+    }
+
+} // namespace O3DE::ProjectManager

+ 58 - 0
Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h

@@ -0,0 +1,58 @@
+/*
+ * 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 <QStandardItemModel>
+#include <GemRepo/GemRepoInfo.h>
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QItemSelectionModel)
+
+namespace O3DE::ProjectManager
+{
+    class GemRepoModel
+        : public QStandardItemModel
+    {
+        Q_OBJECT // AUTOMOC
+
+    public:
+        explicit GemRepoModel(QObject* parent = nullptr);
+        QItemSelectionModel* GetSelectionModel() const;
+
+        void AddGemRepo(const GemRepoInfo& gemInfo);
+        void Clear();
+
+        static QString GetName(const QModelIndex& modelIndex);
+        static QString GetCreator(const QModelIndex& modelIndex);
+        static QString GetSummary(const QModelIndex& modelIndex);
+        static QString GetDirectoryLink(const QModelIndex& modelIndex);
+        static QString GetRepoLink(const QModelIndex& modelIndex);
+        static QDateTime GetLastUpdated(const QModelIndex& modelIndex);
+        static QString GetPath(const QModelIndex& modelIndex);
+
+        static bool IsEnabled(const QModelIndex& modelIndex);
+        static void SetEnabled(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isEnabled);
+
+    private:
+        enum UserRole
+        {
+            RoleName = Qt::UserRole,
+            RoleCreator,
+            RoleSummary,
+            RoleIsEnabled,
+            RoleDirectoryLink,
+            RoleRepoLink,
+            RoleLastUpdated,
+            RolePath
+        };
+
+        QItemSelectionModel* m_selectionModel = nullptr;
+    };
+} // namespace O3DE::ProjectManager

+ 145 - 0
Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp

@@ -0,0 +1,145 @@
+/*
+ * 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 <GemRepo/GemRepoScreen.h>
+#include <GemRepo/GemRepoItemDelegate.h>
+#include <GemRepo/GemRepoListView.h>
+#include <GemRepo/GemRepoModel.h>
+#include <PythonBindingsInterface.h>
+
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QPushButton>
+#include <QTimer>
+#include <QMessageBox>
+#include <QLabel>
+#include <QHeaderView>
+#include <QTableWidget>
+
+namespace O3DE::ProjectManager
+{
+    GemRepoScreen::GemRepoScreen(QWidget* parent)
+        : ScreenWidget(parent)
+    {
+        m_gemRepoModel = new GemRepoModel(this);
+
+        QVBoxLayout* vLayout = new QVBoxLayout();
+        vLayout->setMargin(0);
+        vLayout->setSpacing(0);
+        setLayout(vLayout);
+
+        QHBoxLayout* hLayout = new QHBoxLayout();
+        hLayout->setMargin(0);
+        hLayout->setSpacing(0);
+        vLayout->addLayout(hLayout);
+
+        hLayout->addSpacing(60);
+
+        m_gemRepoInspector = new QFrame(this);
+        m_gemRepoInspector->setObjectName(tr("gemRepoInspector"));
+        m_gemRepoInspector->setFixedWidth(240);
+
+        QVBoxLayout* middleVLayout = new QVBoxLayout();
+        middleVLayout->setMargin(0);
+        middleVLayout->setSpacing(0);
+
+        middleVLayout->addSpacing(30);
+
+        QHBoxLayout* topMiddleHLayout = new QHBoxLayout();
+        topMiddleHLayout->setMargin(0);
+        topMiddleHLayout->setSpacing(0);
+
+        m_lastAllUpdateLabel = new QLabel(tr("Last Updated: Never"), this);
+        m_lastAllUpdateLabel->setObjectName("gemRepoHeaderLabel");
+        topMiddleHLayout->addWidget(m_lastAllUpdateLabel);
+
+        topMiddleHLayout->addSpacing(20);
+
+        m_AllUpdateButton = new QPushButton(QIcon(":/Refresh.svg"), tr("Update All"), this);
+        m_AllUpdateButton->setObjectName("gemRepoHeaderRefreshButton");
+        topMiddleHLayout->addWidget(m_AllUpdateButton);
+
+        topMiddleHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
+
+        m_AddRepoButton = new QPushButton(tr("Add Repository"), this);
+        m_AddRepoButton->setObjectName("gemRepoHeaderAddButton");
+        topMiddleHLayout->addWidget(m_AddRepoButton);
+
+        middleVLayout->addLayout(topMiddleHLayout);
+
+        middleVLayout->addSpacing(30);
+
+        // Create a QTableWidget just for its header
+        // Using a seperate model allows the setup  of a header exactly as needed
+        m_gemRepoHeaderTable = new QTableWidget(this);
+        m_gemRepoHeaderTable->setObjectName("gemRepoHeaderTable");
+        m_gemRepoListHeader = m_gemRepoHeaderTable->horizontalHeader();
+        m_gemRepoListHeader->setObjectName("gemRepoListHeader");
+        m_gemRepoListHeader->setSectionResizeMode(QHeaderView::ResizeMode::Fixed);
+
+        // Insert columns so the header labels will show up
+        m_gemRepoHeaderTable->insertColumn(0);
+        m_gemRepoHeaderTable->insertColumn(1);
+        m_gemRepoHeaderTable->insertColumn(2);
+        m_gemRepoHeaderTable->insertColumn(3);
+        m_gemRepoHeaderTable->setHorizontalHeaderLabels({ tr("Enabled"), tr("Repository Name"), tr("Creator"), tr("Updated") });
+
+        const int headerExtraMargin = 10;
+        m_gemRepoListHeader->resizeSection(0, GemRepoItemDelegate::s_buttonWidth + GemRepoItemDelegate::s_buttonSpacing - 3);
+        m_gemRepoListHeader->resizeSection(1, GemRepoItemDelegate::s_nameMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin);
+        m_gemRepoListHeader->resizeSection(2, GemRepoItemDelegate::s_creatorMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin);
+        m_gemRepoListHeader->resizeSection(3, GemRepoItemDelegate::s_updatedMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin);
+
+        // Required to set stylesheet in code as it will not be respected if set in qss
+        m_gemRepoHeaderTable->horizontalHeader()->setStyleSheet("QHeaderView::section { background-color:transparent; color:white; font-size:12px; text-align:left; border-style:none; }");
+        middleVLayout->addWidget(m_gemRepoHeaderTable);
+
+        m_gemRepoListView = new GemRepoListView(m_gemRepoModel, this);
+        middleVLayout->addWidget(m_gemRepoListView);
+
+        hLayout->addLayout(middleVLayout);
+        hLayout->addWidget(m_gemRepoInspector);
+
+        Reinit();
+    }
+
+    void GemRepoScreen::Reinit()
+    {
+        m_gemRepoModel->clear();
+        FillModel();
+
+        // Select the first entry after everything got correctly sized
+        QTimer::singleShot(200, [=]{
+            QModelIndex firstModelIndex = m_gemRepoListView->model()->index(0,0);
+            m_gemRepoListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
+            });
+    }
+
+    void GemRepoScreen::FillModel()
+    {
+        AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> allGemRepoInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoInfos();
+        if (allGemRepoInfosResult.IsSuccess())
+        {
+            // Add all available repos to the model
+            const QVector<GemRepoInfo> allGemRepoInfos = allGemRepoInfosResult.GetValue();
+            for (const GemRepoInfo& gemRepoInfo : allGemRepoInfos)
+            {
+                m_gemRepoModel->AddGemRepo(gemRepoInfo);
+            }
+        }
+        else
+        {
+            QMessageBox::critical(this, tr("Operation failed"), QString("Cannot retrieve gem repos for engine.\n\nError:\n%2").arg(allGemRepoInfosResult.GetError().c_str()));
+        }
+    }
+
+    ProjectManagerScreen GemRepoScreen::GetScreenEnum()
+    {
+        return ProjectManagerScreen::GemRepos;
+    }
+} // namespace O3DE::ProjectManager

+ 50 - 0
Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h

@@ -0,0 +1,50 @@
+/*
+ * 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 <ScreenWidget.h>
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QLabel)
+QT_FORWARD_DECLARE_CLASS(QPushButton)
+QT_FORWARD_DECLARE_CLASS(QHeaderView)
+QT_FORWARD_DECLARE_CLASS(QTableWidget)
+
+namespace O3DE::ProjectManager
+{
+    QT_FORWARD_DECLARE_CLASS(GemRepoListView)
+    QT_FORWARD_DECLARE_CLASS(GemRepoModel)
+
+    class GemRepoScreen
+        : public ScreenWidget
+    {
+    public:
+        explicit GemRepoScreen(QWidget* parent = nullptr);
+        ~GemRepoScreen() = default;
+        ProjectManagerScreen GetScreenEnum() override;
+
+        void Reinit();
+
+        GemRepoModel* GetGemRepoModel() const { return m_gemRepoModel; }
+
+    private:
+        void FillModel();
+
+        QTableWidget* m_gemRepoHeaderTable = nullptr;
+        QHeaderView* m_gemRepoListHeader = nullptr;
+        GemRepoListView* m_gemRepoListView = nullptr;
+        QFrame* m_gemRepoInspector = nullptr;
+        GemRepoModel* m_gemRepoModel = nullptr;
+
+        QLabel* m_lastAllUpdateLabel;
+        QPushButton* m_AllUpdateButton;
+        QPushButton* m_AddRepoButton;
+    };
+} // namespace O3DE::ProjectManager

+ 1 - 1
Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp

@@ -22,7 +22,7 @@ namespace O3DE::ProjectManager
         QVector<ProjectManagerScreen> screenEnums =
         {
             ProjectManagerScreen::Projects,
-            ProjectManagerScreen::EngineSettings,
+            ProjectManagerScreen::Engine,
             ProjectManagerScreen::CreateProject,
             ProjectManagerScreen::UpdateProject
         };

+ 41 - 0
Code/Tools/ProjectManager/Source/PythonBindings.cpp

@@ -912,4 +912,45 @@ namespace O3DE::ProjectManager
             return AZ::Success(AZStd::move(templates));
         }
     }
+
+    GemRepoInfo PythonBindings::GemRepoInfoFromPath(pybind11::handle path, pybind11::handle pyEnginePath)
+    {
+        /* Placeholder Logic */
+        (void)path;
+        (void)pyEnginePath;
+
+        return GemRepoInfo();
+    }
+
+//#define MOCK_GEM_REPO_INFO true
+
+    AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> PythonBindings::GetAllGemRepoInfos()
+    {
+        QVector<GemRepoInfo> gemRepos;
+
+#ifndef MOCK_GEM_REPO_INFO
+        auto result = ExecuteWithLockErrorHandling(
+            [&]
+            {
+                /* Placeholder Logic, o3de scripts need method added
+                * 
+                for (auto path : m_manifest.attr("get_gem_repos")())
+                {
+                    gemRepos.push_back(GemRepoInfoFromPath(path, pybind11::none()));
+                }
+                *
+                */
+            });
+        if (!result.IsSuccess())
+        {
+            return AZ::Failure<AZStd::string>(result.GetError().c_str());
+        }
+#else
+        gemRepos.push_back(GemRepoInfo("JohnCreates", "John Smith", "", QDateTime(QDate(2021, 8, 31), QTime(11, 57)), true));
+        gemRepos.push_back(GemRepoInfo("JanesGems", "Jane Doe", "", QDateTime(QDate(2021, 9, 10), QTime(18, 23)), false));
+#endif // MOCK_GEM_REPO_INFO
+
+        std::sort(gemRepos.begin(), gemRepos.end());
+        return AZ::Success(AZStd::move(gemRepos));
+    }
 }

+ 4 - 0
Code/Tools/ProjectManager/Source/PythonBindings.h

@@ -56,12 +56,16 @@ namespace O3DE::ProjectManager
         // ProjectTemplate
         AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) override;
 
+        // Gem Repos
+        AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
+
     private:
         AZ_DISABLE_COPY_MOVE(PythonBindings);
 
         AZ::Outcome<void, AZStd::string> ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback);
         bool ExecuteWithLock(AZStd::function<void()> executionCallback);
         GemInfo GemInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
+        GemRepoInfo GemRepoInfoFromPath(pybind11::handle path, pybind11::handle pyEnginePath);
         ProjectInfo ProjectInfoFromPath(pybind11::handle path);
         ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
         bool RegisterThisEngine();

+ 11 - 2
Code/Tools/ProjectManager/Source/PythonBindingsInterface.h

@@ -17,6 +17,7 @@
 #include <GemCatalog/GemInfo.h>
 #include <ProjectInfo.h>
 #include <ProjectTemplateInfo.h>
+#include <GemRepo/GemRepoInfo.h>
 
 namespace O3DE::ProjectManager
 {
@@ -56,14 +57,14 @@ namespace O3DE::ProjectManager
 
         /**
          * Get info about a Gem 
-         * @param path the absolute path to the Gem 
+         * @param projectPath the absolute path to the Gem 
          * @return an outcome with GemInfo on success 
          */
         virtual AZ::Outcome<GemInfo> GetGemInfo(const QString& path, const QString& projectPath = {}) = 0;
 
         /**
          * Get all available gem infos. This concatenates gems registered by the engine and the project.
-         * @param path The absolute path to the project.
+         * @param projectPath The absolute path to the project.
          * @return A list of gem infos.
          */
         virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) = 0;
@@ -155,6 +156,14 @@ namespace O3DE::ProjectManager
          * @return an outcome with ProjectTemplateInfos on success 
          */
         virtual AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) = 0;
+
+        // Gem Repos
+
+        /**
+         * Get all available gem repo infos. Gathers all repos registered with the engine.
+         * @return A list of gem repo infos.
+         */
+        virtual AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() = 0;
     };
 
     using PythonBindingsInterface = AZ::Interface<IPythonBindings>;

+ 6 - 2
Code/Tools/ProjectManager/Source/ScreenDefs.h

@@ -23,7 +23,9 @@ namespace O3DE::ProjectManager
         Projects,
         UpdateProject,
         UpdateProjectSettings,
-        EngineSettings
+        Engine,
+        EngineSettings,
+        GemRepos
     };
 
     static QHash<QString, ProjectManagerScreen> s_ProjectManagerStringNames = {
@@ -34,7 +36,9 @@ namespace O3DE::ProjectManager
         { "Projects", ProjectManagerScreen::Projects},
         { "UpdateProject", ProjectManagerScreen::UpdateProject},
         { "UpdateProjectSettings", ProjectManagerScreen::UpdateProjectSettings},
-        { "EngineSettings", ProjectManagerScreen::EngineSettings}
+        { "Engine", ProjectManagerScreen::Engine},
+        { "EngineSettings", ProjectManagerScreen::EngineSettings},
+        { "GemRepos", ProjectManagerScreen::GemRepos}
     };
 
     // need to define qHash for ProjectManagerScreen when using scoped enums

+ 8 - 0
Code/Tools/ProjectManager/Source/ScreenFactory.cpp

@@ -13,7 +13,9 @@
 #include <GemCatalog/GemCatalogScreen.h>
 #include <ProjectsScreen.h>
 #include <UpdateProjectSettingsScreen.h>
+#include <EngineScreenCtrl.h>
 #include <EngineSettingsScreen.h>
+#include <GemRepo/GemRepoScreen.h>
 
 namespace O3DE::ProjectManager
 {
@@ -41,9 +43,15 @@ namespace O3DE::ProjectManager
         case (ProjectManagerScreen::UpdateProjectSettings):
             newScreen = new UpdateProjectSettingsScreen(parent);
             break;
+        case (ProjectManagerScreen::Engine):
+            newScreen = new EngineScreenCtrl(parent);
+            break;
         case (ProjectManagerScreen::EngineSettings):
             newScreen = new EngineSettingsScreen(parent);
             break;
+        case (ProjectManagerScreen::GemRepos):
+            newScreen = new GemRepoScreen(parent);
+            break;
         case (ProjectManagerScreen::Empty):
         default:
             newScreen = new ScreenWidget(parent);

+ 12 - 0
Code/Tools/ProjectManager/project_manager_files.cmake

@@ -56,6 +56,8 @@ set(FILES
     Source/ProjectsScreen.cpp
     Source/ProjectSettingsScreen.h
     Source/ProjectSettingsScreen.cpp
+    Source/EngineScreenCtrl.h
+    Source/EngineScreenCtrl.cpp
     Source/EngineSettingsScreen.h
     Source/EngineSettingsScreen.cpp
     Source/ProjectButtonWidget.h
@@ -98,4 +100,14 @@ set(FILES
     Source/GemCatalog/GemRequirementListView.cpp
     Source/GemCatalog/GemSortFilterProxyModel.h
     Source/GemCatalog/GemSortFilterProxyModel.cpp
+    Source/GemRepo/GemRepoScreen.h
+    Source/GemRepo/GemRepoScreen.cpp
+    Source/GemRepo/GemRepoInfo.h
+    Source/GemRepo/GemRepoInfo.cpp
+    Source/GemRepo/GemRepoItemDelegate.h
+    Source/GemRepo/GemRepoItemDelegate.cpp
+    Source/GemRepo/GemRepoListView.h
+    Source/GemRepo/GemRepoListView.cpp
+    Source/GemRepo/GemRepoModel.h
+    Source/GemRepo/GemRepoModel.cpp
 )