Ver código fonte

Project Template details and preview changes

Alex Peterson 4 anos atrás
pai
commit
9b17754278

+ 3 - 0
Code/Tools/ProjectManager/Resources/ArrowBack_Hover.svg

@@ -0,0 +1,3 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4.02554 10L9 15.0362V18L0 9L9 0V2.98885L4 8H18V10H4.02554Z" fill="#1e70eb"/>
+</svg>

+ 3 - 0
Code/Tools/ProjectManager/Resources/DefaultTemplate.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8358f4dad9878c662b9819b2b346622af691eb45f8eddc28fff79a50650ae6cf
+size 2503

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

@@ -7,6 +7,7 @@
         <file>AddOffset.svg</file>
         <file>AddOffset_Hover.svg</file>
         <file>ArrowBack.svg</file>
+        <file>ArrowBack_Hover.svg</file>
         <file>build.svg</file>
         <file>FolderOffset.svg</file>
         <file>FolderOffset_Hover.svg</file>
@@ -18,6 +19,7 @@
         <file>Linux.svg</file>
         <file>macOS.svg</file>
         <file>DefaultProjectImage.png</file>
+        <file>DefaultTemplate.png</file>
         <file>ArrowDownLine.svg</file>
         <file>ArrowUpLine.svg</file>
         <file>o3de.svg</file>

+ 59 - 8
Code/Tools/ProjectManager/Resources/ProjectManager.qss

@@ -136,11 +136,9 @@ QTabBar::tab:pressed
 #header QPushButton:focus {
     border:none;
 }
-#header QPushButton:hover {
-    background:#333333 url(:/ArrowBack.svg) no-repeat center;
-}
+#header QPushButton:hover,
 #header QPushButton:pressed {
-    background:#222222 url(:/ArrowBack.svg) no-repeat center;
+    background:transparent url(:/ArrowBack_Hover.svg) no-repeat center;
 }
 
 #headerTitle {
@@ -210,9 +208,6 @@ QTabBar::tab:pressed
 
 #projectTemplate {
     margin: 55px 0 0 50px;
-    max-width: 780px;
-    min-height:200px;
-    max-height:200px;
 }
 #projectTemplateLabel {
     font-size:16px;
@@ -227,11 +222,67 @@ QTabBar::tab:pressed
 
 #projectTemplateDetails {
     background-color:#444444;
-    max-width:240px;
+    max-width:20%;
     min-width:240px;
     margin-left:30px;
 }
 
+#projectTemplateDetails #displayName,
+#projectTemplateDetails #includedGemsTitle {
+    font-size:18px;
+}
+
+#projectTemplateDetails #moreGems {
+    font-size:14px;
+    margin-top:20px;
+}
+
+#projectTemplateDetails #includedGemsTitle {
+    margin-top:5px;
+    margin-bottom:5px;
+}
+
+#projectTemplateDetails #summary {
+    padding-bottom:0px;
+    border-bottom:2px solid #555555;
+	min-height:80px;
+    qproperty-alignment: AlignTop;
+}
+
+#projectTemplateDetails #browseCatalog {
+    margin:5px 0px 15px 0px;
+}
+
+#projectTemplate QPushButton {
+    qproperty-flat: true;
+    min-width: 96px;
+    max-width: 96px;
+    min-height: 160px;
+    max-height: 160px;
+}
+#projectTemplate #templateLabel {
+    qproperty-alignment: AlignCenter;
+}
+#projectTemplate QPushButton #templateImage {
+    border:3px solid transparent;
+    border-radius: 4px;
+}
+#projectTemplate QPushButton[Checked="true"] #templateImage {
+    border:3px solid #1e70eb;
+}
+#projectTemplate QPushButton[Checked="true"] #templateLabel {
+    font-weight:bold;
+}
+#projectTemplate QPushButton:hover {
+    background-color: #444444;
+}
+#projectTemplate QPushButton:focus {
+    outline: none;
+    border:none;
+}
+
+
+
 #projectSettingsTab::tab-bar {
     left: 60px;
 }

+ 115 - 52
Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp

@@ -15,6 +15,7 @@
 #include <PythonBindingsInterface.h>
 #include <NewProjectSettingsScreen.h>
 #include <ScreenHeaderWidget.h>
+#include <GemCatalog/GemCatalogScreen.h>
 
 #include <QDialogButtonBox>
 #include <QHBoxLayout>
@@ -41,24 +42,33 @@ namespace O3DE::ProjectManager
 
         m_stack = new QStackedWidget(this);
         m_stack->setObjectName("body");
-        m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
-        m_stack->addWidget(new NewProjectSettingsScreen());
-        m_gemCatalog = new GemCatalogScreen();
-        m_stack->addWidget(m_gemCatalog);
+        m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred,QSizePolicy::Expanding));
+
+        m_newProjectSettingsScreen = new NewProjectSettingsScreen(this);
+        m_stack->addWidget(m_newProjectSettingsScreen);
+
+        m_gemCatalogScreen = new GemCatalogScreen(this);
+        m_stack->addWidget(m_gemCatalogScreen);
         vLayout->addWidget(m_stack);
 
-        QDialogButtonBox* backNextButtons = new QDialogButtonBox();
-        backNextButtons->setObjectName("footer");
-        vLayout->addWidget(backNextButtons);
+        QDialogButtonBox* buttons = new QDialogButtonBox();
+        buttons->setObjectName("footer");
+        vLayout->addWidget(buttons);
 
-        m_backButton = backNextButtons->addButton(tr("Back"), QDialogButtonBox::RejectRole);
-        m_backButton->setProperty("secondary", true);
-        m_nextButton = backNextButtons->addButton(tr("Next"), QDialogButtonBox::ApplyRole);
+#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
+        connect(m_newProjectSettingsScreen, &ScreenWidget::ChangeScreenRequest, this, &CreateProjectCtrl::OnChangeScreenRequest);
 
-        connect(m_backButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandleBackButton);
-        connect(m_nextButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandleNextButton);
+        m_secondaryButton = buttons->addButton(tr("Back"), QDialogButtonBox::RejectRole);
+        m_secondaryButton->setProperty("secondary", true);
+        m_secondaryButton->setVisible(false);
+        connect(m_secondaryButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandleSecondaryButton);
 
         Update();
+#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
+
+        m_primaryButton = buttons->addButton(tr("Create Project"), QDialogButtonBox::ApplyRole);
+        connect(m_primaryButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandlePrimaryButton);
+
         setLayout(vLayout);
     }
 
@@ -80,8 +90,10 @@ namespace O3DE::ProjectManager
     {
         if (m_stack->currentIndex() > 0)
         {
-            m_stack->setCurrentIndex(m_stack->currentIndex() - 1);
-            Update();
+#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
+            PreviousScreen();
+#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
+
         }
         else
         {
@@ -89,70 +101,121 @@ namespace O3DE::ProjectManager
         }
     }
 
-    void CreateProjectCtrl::HandleNextButton()
+#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
+    void CreateProjectCtrl::HandleSecondaryButton()
     {
-        ScreenWidget* currentScreen = reinterpret_cast<ScreenWidget*>(m_stack->currentWidget());
-        ProjectManagerScreen screenEnum = currentScreen->GetScreenEnum();
+        if (m_stack->currentIndex() > 0)
+        {
+            // return to Project Settings page
+            PreviousScreen();
+        }
+        else
+        {
+            // Configure Gems
+            NextScreen();
+        }
+    }
+
+    void CreateProjectCtrl::Update()
+    {
+        if (m_stack->currentWidget() == m_gemCatalogScreen)
+        {
+            m_header->setSubTitle(tr("Configure project with Gems"));
+            m_secondaryButton->setVisible(false);
+        }
+        else
+        {
+            m_header->setSubTitle(tr("Enter Project Details"));
+            m_secondaryButton->setVisible(true);
+            m_secondaryButton->setText(tr("Configure Gems"));
+        }
+    }
 
-        if (screenEnum == ProjectManagerScreen::NewProjectSettings)
+    void CreateProjectCtrl::OnChangeScreenRequest(ProjectManagerScreen screen)
+    {
+        if (screen == ProjectManagerScreen::GemCatalog)
+        {
+            HandleSecondaryButton();
+        }
+        else
         {
-            auto newProjectScreen = reinterpret_cast<NewProjectSettingsScreen*>(currentScreen);
-            if (newProjectScreen)
+            emit ChangeScreenRequest(screen);
+        }
+    }
+
+    void CreateProjectCtrl::NextScreen()
+    {
+        if (m_stack->currentIndex() < m_stack->count())
+        {
+            if(CurrentScreenIsValid())
             {
-                if (!newProjectScreen->Validate())
-                {
-                    QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings"));
-                    return;
-                }
+                m_stack->setCurrentIndex(m_stack->currentIndex() + 1);
 
-                m_projectInfo         = newProjectScreen->GetProjectInfo();
-                m_projectTemplatePath = newProjectScreen->GetProjectTemplatePath();
+                QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
+                m_gemCatalogScreen->ReinitForProject(projectTemplatePath + "/Template", /*isNewProject=*/true);
 
-                // The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog.
-                m_gemCatalog->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/true);
+                Update();
+            }
+            else
+            {
+                QMessageBox::warning(this, tr("Invalid project settings"), tr("Please correct the indicated project settings and try again."));
             }
         }
+    }
 
-        if (m_stack->currentIndex() != m_stack->count() - 1)
+    void CreateProjectCtrl::PreviousScreen()
+    {
+        // we don't require the current screen to be valid when moving back
+        if (m_stack->currentIndex() > 0)
         {
-            m_stack->setCurrentIndex(m_stack->currentIndex() + 1);
+            m_stack->setCurrentIndex(m_stack->currentIndex() - 1);
             Update();
         }
-        else
+    }
+#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
+
+    void CreateProjectCtrl::HandlePrimaryButton()
+    {
+        CreateProject();
+    }
+
+    bool CreateProjectCtrl::CurrentScreenIsValid()
+    {
+        if (m_stack->currentWidget() == m_newProjectSettingsScreen)
         {
-            auto result = PythonBindingsInterface::Get()->CreateProject(m_projectTemplatePath, m_projectInfo);
+            return m_newProjectSettingsScreen->Validate();
+        }
+
+        return true;
+    }
+
+    void CreateProjectCtrl::CreateProject()
+    {
+        if (m_newProjectSettingsScreen->Validate())
+        {
+            ProjectInfo projectInfo = m_newProjectSettingsScreen->GetProjectInfo();
+            QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
+
+            auto result = PythonBindingsInterface::Get()->CreateProject(projectTemplatePath, projectInfo);
             if (result.IsSuccess())
             {
                 // automatically register the project
-                PythonBindingsInterface::Get()->AddProject(m_projectInfo.m_path);
+                PythonBindingsInterface::Get()->AddProject(projectInfo.m_path);
+
+#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
+                m_gemCatalogScreen->EnableDisableGemsForProject(projectInfo.m_path);
+#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
 
-                // adding gems is not implemented yet because we don't know what targets to add or how to add them
                 emit ChangeScreenRequest(ProjectManagerScreen::Projects);
             }
             else
             {
                 QMessageBox::critical(this, tr("Project creation failed"), tr("Failed to create project."));
             }
-
-            // Enable/disable gems for the newly created project.
-            m_gemCatalog->EnableDisableGemsForProject(m_projectInfo.m_path);
-        }
-    }
-
-    void CreateProjectCtrl::Update()
-    {
-        ScreenWidget* currentScreen = reinterpret_cast<ScreenWidget*>(m_stack->currentWidget());
-        if (currentScreen && currentScreen->GetScreenEnum() == ProjectManagerScreen::GemCatalog)
-        {
-            m_header->setTitle(tr("Create Project"));
-            m_header->setSubTitle(tr("Configure project with Gems"));
-            m_nextButton->setText(tr("Create Project"));
         }
         else
         {
-            m_header->setTitle(tr("Create Project"));
-            m_header->setSubTitle(tr("Enter Project Details"));
-            m_nextButton->setText(tr("Next"));
+            QMessageBox::warning(this, tr("Invalid project settings"), tr("Please correct the indicated project settings and try again."));
         }
     }
 

+ 28 - 8
Code/Tools/ProjectManager/Source/CreateProjectCtrl.h

@@ -14,9 +14,11 @@
 #if !defined(Q_MOC_RUN)
 #include <ScreenWidget.h>
 #include <ProjectInfo.h>
-#include <GemCatalog/GemCatalogScreen.h>
 #endif
 
+// due to current limitations, customizing template Gems is disabled 
+#define TEMPLATE_GEM_CONFIGURATION_ENABLED
+
 QT_FORWARD_DECLARE_CLASS(QStackedWidget)
 QT_FORWARD_DECLARE_CLASS(QPushButton)
 QT_FORWARD_DECLARE_CLASS(QLabel)
@@ -24,6 +26,8 @@ QT_FORWARD_DECLARE_CLASS(QLabel)
 namespace O3DE::ProjectManager
 {
     QT_FORWARD_DECLARE_CLASS(ScreenHeader)
+    QT_FORWARD_DECLARE_CLASS(NewProjectSettingsScreen)
+    QT_FORWARD_DECLARE_CLASS(GemCatalogScreen)
 
     class CreateProjectCtrl
         : public ScreenWidget
@@ -36,21 +40,37 @@ namespace O3DE::ProjectManager
 
     protected slots:
         void HandleBackButton();
-        void HandleNextButton();
+        void HandlePrimaryButton();
+
+#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
+        void OnChangeScreenRequest(ProjectManagerScreen screen);
+        void HandleSecondaryButton();
+#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
 
     private:
+#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
         void Update();
+        void NextScreen();
+        void PreviousScreen();
+#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
+
+        bool CurrentScreenIsValid();
+        void CreateProject();
 
-        QStackedWidget* m_stack;
-        ScreenHeader* m_header;
+        QStackedWidget* m_stack = nullptr;
+        ScreenHeader* m_header = nullptr;
 
-        QPushButton* m_backButton;
-        QPushButton* m_nextButton;
+        QPushButton* m_primaryButton = nullptr;
+
+#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
+        QPushButton* m_secondaryButton = nullptr;
+#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
 
         QString m_projectTemplatePath;
         ProjectInfo m_projectInfo;
-
-        GemCatalogScreen* m_gemCatalog = nullptr;
+        
+        NewProjectSettingsScreen* m_newProjectSettingsScreen = nullptr;
+        GemCatalogScreen* m_gemCatalogScreen = nullptr;
     };
 
 } // namespace O3DE::ProjectManager

+ 115 - 10
Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp

@@ -14,8 +14,12 @@
 #include <PythonBindingsInterface.h>
 #include <FormLineEditWidget.h>
 #include <FormBrowseEditWidget.h>
+#include <TemplateButtonWidget.h>
 #include <PathValidator.h>
 #include <EngineInfo.h>
+#include <CreateProjectCtrl.h>
+#include <TagWidget.h>
+#include <AzQtComponents/Components/FlowLayout.h>
 
 #include <QVBoxLayout>
 #include <QHBoxLayout>
@@ -28,10 +32,12 @@
 #include <QSpacerItem>
 #include <QStandardPaths>
 #include <QFrame>
+#include <QScrollArea>
+#include <QAbstractButton>
 
 namespace O3DE::ProjectManager
 {
-    constexpr const char* k_pathProperty = "Path";
+    constexpr const char* k_templateIndexProperty = "TemplateIndex";
 
     NewProjectSettingsScreen::NewProjectSettingsScreen(QWidget* parent)
         : ProjectSettingsScreen(parent)
@@ -59,30 +65,69 @@ namespace O3DE::ProjectManager
             projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel");
             containerLayout->addWidget(projectTemplateDetailsLabel);
 
-            QHBoxLayout* templateLayout = new QHBoxLayout(this);
-            containerLayout->addItem(templateLayout);
+
+            // we might have enough templates that we need to scroll
+            QScrollArea* templatesScrollArea = new QScrollArea(this);
+            QWidget* scrollWidget = new QWidget();
+
+            FlowLayout* flowLayout = new FlowLayout(0, s_spacerSize, s_spacerSize);
+            scrollWidget->setLayout(flowLayout);
+
+            templatesScrollArea->setWidget(scrollWidget);
+            templatesScrollArea->setWidgetResizable(true);
 
             m_projectTemplateButtonGroup = new QButtonGroup(this);
             m_projectTemplateButtonGroup->setObjectName("templateButtonGroup");
+
+            // QButtonGroup has overloaded buttonClicked methods so we need the QOverload
+            connect(
+                m_projectTemplateButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
+                [=](QAbstractButton* button)
+                {
+                    if (button && button->property(k_templateIndexProperty).isValid())
+                    {
+                        int projectIndex = button->property(k_templateIndexProperty).toInt();
+                        UpdateTemplateDetails(m_templates.at(projectIndex));
+                    }
+                });
+
             auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates();
             if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty())
             {
-                for (const ProjectTemplateInfo& projectTemplate : templatesResult.GetValue())
+                m_templates = templatesResult.GetValue();
+
+                // sort alphabetically by display name because they could be in any order
+                std::sort(m_templates.begin(), m_templates.end(), [](const ProjectTemplateInfo& arg1, const ProjectTemplateInfo& arg2)
                 {
-                    QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this);
-                    radioButton->setProperty(k_pathProperty, projectTemplate.m_path);
-                    m_projectTemplateButtonGroup->addButton(radioButton);
+                    return arg1.m_displayName.toLower() < arg2.m_displayName.toLower();
+                });
 
-                    containerLayout->addWidget(radioButton);
+                for (int index = 0; index < m_templates.size(); ++index)
+                {
+                    ProjectTemplateInfo projectTemplate = m_templates.at(index);
+                    QString projectPreviewPath = projectTemplate.m_path + "/Template/preview.png";
+                    QFileInfo doesPreviewExist(projectPreviewPath);
+                    if (!doesPreviewExist.exists() || !doesPreviewExist.isFile())
+                    {
+                        projectPreviewPath = ":/DefaultTemplate.png";
+                    }
+                    TemplateButton* templateButton = new TemplateButton(projectPreviewPath, projectTemplate.m_displayName, this);
+                    templateButton->setCheckable(true);
+                    templateButton->setProperty(k_templateIndexProperty, index);
+                    
+                    m_projectTemplateButtonGroup->addButton(templateButton);
+
+                    flowLayout->addWidget(templateButton);
                 }
 
                 m_projectTemplateButtonGroup->buttons().first()->setChecked(true);
             }
+            containerLayout->addWidget(templatesScrollArea);
         }
         projectTemplateWidget->setLayout(containerLayout);
         m_verticalLayout->addWidget(projectTemplateWidget);
 
-        QWidget* projectTemplateDetails = new QWidget(this);
+        QFrame* projectTemplateDetails = CreateTemplateDetails(s_templateDetailsContentMargin);
         projectTemplateDetails->setObjectName("projectTemplateDetails");
         m_horizontalLayout->addWidget(projectTemplateDetails);
     }
@@ -109,11 +154,71 @@ namespace O3DE::ProjectManager
 
     void NewProjectSettingsScreen::NotifyCurrentScreen()
     {
+        if (!m_templates.isEmpty())
+        {
+            UpdateTemplateDetails(m_templates.first());
+        }
+
         Validate();
     }
 
     QString NewProjectSettingsScreen::GetProjectTemplatePath()
     {
-        return m_projectTemplateButtonGroup->checkedButton()->property(k_pathProperty).toString();
+        const int templateIndex = m_projectTemplateButtonGroup->checkedButton()->property(k_templateIndexProperty).toInt();
+        return m_templates.at(templateIndex).m_path;
+    }
+
+    QFrame* NewProjectSettingsScreen::CreateTemplateDetails(int margin)
+    {
+        QFrame* projectTemplateDetails = new QFrame(this);
+        projectTemplateDetails->setObjectName("projectTemplateDetails");
+        QVBoxLayout* templateDetailsLayout = new QVBoxLayout();
+        templateDetailsLayout->setContentsMargins(margin, margin, margin, margin);
+        templateDetailsLayout->setAlignment(Qt::AlignTop);
+        {
+            m_templateDisplayName = new QLabel(this);
+            m_templateDisplayName->setObjectName("displayName");
+            templateDetailsLayout->addWidget(m_templateDisplayName);
+
+            m_templateSummary = new QLabel(this);
+            m_templateSummary->setObjectName("summary");
+            m_templateSummary->setWordWrap(true);
+            templateDetailsLayout->addWidget(m_templateSummary);
+
+            QLabel* includedGemsTitle = new QLabel(tr("Included Gems"), this);
+            includedGemsTitle->setObjectName("includedGemsTitle");
+            templateDetailsLayout->addWidget(includedGemsTitle);
+
+            m_templateIncludedGems = new TagContainerWidget(this);
+            m_templateIncludedGems->setObjectName("includedGems");
+            templateDetailsLayout->addWidget(m_templateIncludedGems);
+
+#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
+            QLabel* moreGemsLabel = new QLabel(tr("Looking for more Gems?"), this);
+            moreGemsLabel->setObjectName("moreGems");
+            templateDetailsLayout->addWidget(moreGemsLabel);
+
+            QLabel* browseCatalogLabel = new QLabel(tr("Browse the  Gems Catalog to further customize your project."), this);
+            browseCatalogLabel->setObjectName("browseCatalog");
+            browseCatalogLabel->setWordWrap(true);
+            templateDetailsLayout->addWidget(browseCatalogLabel);
+
+            QPushButton* configureGemsButton = new QPushButton(tr("Configure with more Gems"), this);
+            connect(configureGemsButton, &QPushButton::clicked, this, [=]()
+                    {
+                        emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
+                    });
+            templateDetailsLayout->addWidget(configureGemsButton);
+#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED 
+        }
+        projectTemplateDetails->setLayout(templateDetailsLayout);
+        return projectTemplateDetails;
+    }
+
+    void NewProjectSettingsScreen::UpdateTemplateDetails(const ProjectTemplateInfo& templateInfo)
+    {
+        m_templateDisplayName->setText(templateInfo.m_displayName);
+        m_templateSummary->setText(templateInfo.m_summary);
+        m_templateIncludedGems->Update(templateInfo.m_includedGems);
     }
 } // namespace O3DE::ProjectManager

+ 14 - 0
Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h

@@ -13,12 +13,17 @@
 
 #if !defined(Q_MOC_RUN)
 #include <ProjectSettingsScreen.h>
+#include <ProjectTemplateInfo.h>
+#include <QVector>
 #endif
 
 QT_FORWARD_DECLARE_CLASS(QButtonGroup)
+QT_FORWARD_DECLARE_CLASS(QLabel)
+QT_FORWARD_DECLARE_CLASS(QFrame)
 
 namespace O3DE::ProjectManager
 {
+    QT_FORWARD_DECLARE_CLASS(TagContainerWidget)
     class NewProjectSettingsScreen
         : public ProjectSettingsScreen
     {
@@ -33,8 +38,17 @@ namespace O3DE::ProjectManager
 
     private:
         QString GetDefaultProjectPath();
+        QFrame* CreateTemplateDetails(int margin);
+        void UpdateTemplateDetails(const ProjectTemplateInfo& templateInfo);
 
         QButtonGroup* m_projectTemplateButtonGroup;
+        QLabel* m_templateDisplayName;
+        QLabel* m_templateSummary;
+        TagContainerWidget* m_templateIncludedGems;
+        QVector<ProjectTemplateInfo> m_templates;
+
+        inline constexpr static int s_spacerSize = 20;
+        inline constexpr static int s_templateDetailsContentMargin = 20;
     };
 
 } // namespace O3DE::ProjectManager

+ 1 - 0
Code/Tools/ProjectManager/Source/ProjectTemplateInfo.h

@@ -31,6 +31,7 @@ namespace O3DE::ProjectManager
         QString m_name;
         QString m_path;
         QString m_summary;
+        QStringList m_includedGems;
         QStringList m_canonicalTags;
         QStringList m_userTags;
     };

+ 8 - 1
Code/Tools/ProjectManager/Source/PythonBindings.cpp

@@ -754,7 +754,7 @@ namespace O3DE::ProjectManager
     ProjectTemplateInfo PythonBindings::ProjectTemplateInfoFromPath(pybind11::handle path)
     {
         ProjectTemplateInfo templateInfo;
-        templateInfo.m_path = Py_To_String(path);
+        templateInfo.m_path = Py_To_String(pybind11::str(path));
 
         auto data = m_manifest.attr("get_template_json_data")(pybind11::none(), path);
         if (pybind11::isinstance<pybind11::dict>(data))
@@ -781,6 +781,13 @@ namespace O3DE::ProjectManager
                         templateInfo.m_canonicalTags.push_back(Py_To_String(tag));
                     }
                 }
+                if (data.contains("included_gems"))
+                {
+                    for (auto gem : data["included_gems"])
+                    {
+                        templateInfo.m_includedGems.push_back(Py_To_String(gem));
+                    }
+                }
             }
             catch ([[maybe_unused]] const std::exception& e)
             {

+ 65 - 0
Code/Tools/ProjectManager/Source/TemplateButtonWidget.cpp

@@ -0,0 +1,65 @@
+/*
+ * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+ * its licensors.
+ *
+ * For complete copyright and license terms please see the LICENSE at the root of this
+ * distribution (the "License"). All use of this software is governed by the License,
+ * or, if provided, by the license below or the license accompanying this file. Do not
+ * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ */
+
+#include <TemplateButtonWidget.h>
+
+#include <QVBoxLayout>
+#include <QLabel>
+#include <QPixmap>
+#include <QAbstractButton>
+#include <QStyle>
+#include <QVariant>
+
+namespace O3DE::ProjectManager
+{
+
+    TemplateButton::TemplateButton(const QString& imagePath, const QString& labelText, QWidget* parent)
+        : QPushButton(parent)
+    {
+        setAutoExclusive(true);
+
+        setObjectName("templateButton");
+
+        QVBoxLayout* vLayout = new QVBoxLayout();
+        vLayout->setSpacing(0);
+        vLayout->setContentsMargins(0, 0, 0, 0);
+        setLayout(vLayout);
+
+        QLabel* image = new QLabel(this);
+        image->setObjectName("templateImage");
+        image->setPixmap(
+            QPixmap(imagePath).scaled(QSize(s_templateImageWidth,s_templateImageHeight) , Qt::KeepAspectRatio, Qt::SmoothTransformation));
+        vLayout->addWidget(image);
+
+        QLabel* label = new QLabel(labelText, this);
+        label->setObjectName("templateLabel");
+        vLayout->addWidget(label);
+
+        connect(this, &QAbstractButton::toggled, this, &TemplateButton::onToggled);
+    }
+
+    void TemplateButton::onToggled()
+    {
+        setProperty("Checked", isChecked());
+
+        // we must unpolish/polish every child after changing a property
+        // or else they won't use the correct stylesheet selector
+        for (auto child : findChildren<QWidget*>())
+        {
+            child->style()->unpolish(child);
+            child->style()->polish(child);
+        }
+
+        style()->unpolish(this);
+        style()->polish(this);
+    }
+} // namespace O3DE::ProjectManager

+ 37 - 0
Code/Tools/ProjectManager/Source/TemplateButtonWidget.h

@@ -0,0 +1,37 @@
+/*
+ * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+ * its licensors.
+ *
+ * For complete copyright and license terms please see the LICENSE at the root of this
+ * distribution (the "License"). All use of this software is governed by the License,
+ * or, if provided, by the license below or the license accompanying this file. Do not
+ * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ */
+
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include <QPushButton>
+#endif
+
+namespace O3DE::ProjectManager
+{
+    class TemplateButton
+        : public QPushButton 
+    {
+        Q_OBJECT // AUTOMOC
+
+    public:
+        explicit TemplateButton(const QString& imagePath, const QString& labelText, QWidget* parent = nullptr);
+        ~TemplateButton() = default;
+
+    protected slots:
+        void onToggled();
+
+    private:
+        inline constexpr static int s_templateImageWidth = 92;
+        inline constexpr static int s_templateImageHeight = 122;
+    };
+} // namespace O3DE::ProjectManager

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

@@ -60,6 +60,8 @@ set(FILES
     Source/LinkWidget.cpp
     Source/TagWidget.h
     Source/TagWidget.cpp
+    Source/TemplateButtonWidget.h
+    Source/TemplateButtonWidget.cpp
     Source/GemCatalog/GemCatalogHeaderWidget.h
     Source/GemCatalog/GemCatalogHeaderWidget.cpp
     Source/GemCatalog/GemCatalogScreen.h

+ 3 - 2
Templates/DefaultProject/template.json

@@ -4,8 +4,9 @@
     "restricted_platform_relative_path": "Templates",
     "origin": "The primary repo for DefaultProject goes here: i.e. http://www.mydomain.com",
     "license": "What license DefaultProject uses goes here: i.e. https://opensource.org/licenses/MIT",
-    "display_name": "DefaultProject",
+    "display_name": "Default",
     "summary": "A short description of DefaultProject.",
+    "included_gems": ["Atom","Camera","EMotionFX","UI","Maestro","Input","ImGui"],
     "canonical_tags": [],
     "user_tags": [
         "DefaultProject"
@@ -651,4 +652,4 @@
             "origin": "Shaders"
         }
     ]
-}
+}