Ver código fonte

Project Manager Toolbar Update

- use flow control for projects page for automatic updates when resizing
- made the first time screen only display the first time
Alex Peterson 4 anos atrás
pai
commit
13de9de3c1
39 arquivos alterados com 1103 adições e 633 exclusões
  1. 2 2
      AutomatedTesting/preview.png
  2. 5 0
      Code/Tools/ProjectManager/Resources/AddOffset.svg
  3. 5 0
      Code/Tools/ProjectManager/Resources/AddOffset_Hover.svg
  4. 3 0
      Code/Tools/ProjectManager/Resources/ArrowBack.svg
  5. 5 0
      Code/Tools/ProjectManager/Resources/FolderOffset.svg
  6. 5 0
      Code/Tools/ProjectManager/Resources/FolderOffset_Hover.svg
  7. 9 0
      Code/Tools/ProjectManager/Resources/ProjectManager.qrc
  8. 267 8
      Code/Tools/ProjectManager/Resources/ProjectManager.qss
  9. 11 0
      Code/Tools/ProjectManager/Resources/build.svg
  10. 5 0
      Code/Tools/ProjectManager/Resources/menu.svg
  11. 5 0
      Code/Tools/ProjectManager/Resources/menu_hover.svg
  12. 3 0
      Code/Tools/ProjectManager/Resources/o3de.svg
  13. 46 36
      Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp
  14. 10 6
      Code/Tools/ProjectManager/Source/CreateProjectCtrl.h
  15. 10 0
      Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp
  16. 3 0
      Code/Tools/ProjectManager/Source/EngineSettingsScreen.h
  17. 0 95
      Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp
  18. 0 49
      Code/Tools/ProjectManager/Source/FirstTimeUseScreen.h
  19. 75 68
      Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp
  20. 5 3
      Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h
  21. 17 7
      Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp
  22. 0 1
      Code/Tools/ProjectManager/Source/ProjectButtonWidget.h
  23. 18 33
      Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
  24. 0 14
      Code/Tools/ProjectManager/Source/ProjectManagerWindow.h
  25. 0 67
      Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui
  26. 0 206
      Code/Tools/ProjectManager/Source/ProjectsHomeScreen.cpp
  27. 347 0
      Code/Tools/ProjectManager/Source/ProjectsScreen.cpp
  28. 24 6
      Code/Tools/ProjectManager/Source/ProjectsScreen.h
  29. 1 2
      Code/Tools/ProjectManager/Source/ScreenDefs.h
  30. 3 7
      Code/Tools/ProjectManager/Source/ScreenFactory.cpp
  31. 62 0
      Code/Tools/ProjectManager/Source/ScreenHeaderWidget.cpp
  32. 42 0
      Code/Tools/ProjectManager/Source/ScreenHeaderWidget.h
  33. 15 0
      Code/Tools/ProjectManager/Source/ScreenWidget.h
  34. 83 14
      Code/Tools/ProjectManager/Source/ScreensCtrl.cpp
  35. 4 0
      Code/Tools/ProjectManager/Source/ScreensCtrl.h
  36. 1 1
      Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp
  37. 6 1
      Code/Tools/ProjectManager/Source/main.cpp
  38. 4 5
      Code/Tools/ProjectManager/project_manager_files.cmake
  39. 2 2
      Templates/DefaultProject/Template/preview.png

+ 2 - 2
AutomatedTesting/preview.png

@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a18fae4040a22d2bb359a8ca642b97bb8f6468eeb52e2826b3b029bd8f1350b6
-size 5466
+oid sha256:40949893ed7009eeaa90b7ce6057cb6be9dfaf7b162e3c26ba9dadf985939d7d
+size 2038

+ 5 - 0
Code/Tools/ProjectManager/Resources/AddOffset.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="64" viewBox="0 0 24 64" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(0,40)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13 3H11V11H3V13H11V21H13V13H21V11H13V3Z" fill="white"/>
+</g>
+</svg>

+ 5 - 0
Code/Tools/ProjectManager/Resources/AddOffset_Hover.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="64" viewBox="0 0 24 64" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(0,40)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13 3H11V11H3V13H11V21H13V13H21V11H13V3Z" fill="#1e70eb"/>
+</g>
+</svg>

+ 3 - 0
Code/Tools/ProjectManager/Resources/ArrowBack.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="white"/>
+</svg>

+ 5 - 0
Code/Tools/ProjectManager/Resources/FolderOffset.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="64" viewBox="0 0 24 64" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(0,40)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.98407 4H2V6V7V19.8145H2.07539L6.02059 7.99123H19V6H11.328L9.98407 4ZM19 19.7473L22.0766 10H7.05436L3.68763 19.8329H18.973L18.9788 19.8145H19V19.7473Z" fill="white"/>
+</g>
+</svg>

+ 5 - 0
Code/Tools/ProjectManager/Resources/FolderOffset_Hover.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="64" viewBox="0 0 24 64" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(0,40)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.98407 4H2V6V7V19.8145H2.07539L6.02059 7.99123H19V6H11.328L9.98407 4ZM19 19.7473L22.0766 10H7.05436L3.68763 19.8329H18.973L18.9788 19.8145H19V19.7473Z" fill="#1e70eb"/>
+</g>
+</svg>

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

@@ -4,6 +4,12 @@
     </qresource>
     <qresource prefix="/">
         <file>Add.svg</file>
+        <file>AddOffset.svg</file>
+        <file>AddOffset_Hover.svg</file>
+        <file>ArrowBack.svg</file>
+        <file>build.svg</file>
+        <file>FolderOffset.svg</file>
+        <file>FolderOffset_Hover.svg</file>
         <file>Select_Folder.svg</file>
         <file>o3de_editor.ico</file>
         <file>Windows.svg</file>
@@ -14,6 +20,9 @@
         <file>DefaultProjectImage.png</file>
         <file>ArrowDownLine.svg</file>
         <file>ArrowUpLine.svg</file>
+        <file>o3de.svg</file>
+        <file>menu.svg</file>
+        <file>menu_hover.svg</file>
         <file>Backgrounds/FirstTimeBackgroundImage.jpg</file>
         <file>ArrowDownLine.svg</file>
         <file>ArrowUpLine.svg</file>

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

@@ -1,29 +1,69 @@
 /************** General (MainWindow) **************/
 QMainWindow {
-    background-color: #333333;
+    background:#131313 url(:/o3de.svg) no-repeat top left;
+    /* position the logo using padding and background-origin, Qt does not support background-position pixels */
+    background-origin:content;
+    padding:25px 16px;
+    margin:0;
 }
 
-
 QPushButton:focus {
     outline: none;
     border:1px solid #1e70eb;
 }
 
+QTabBar {
+    background-color: transparent;
+}
+QTabWidget::tab-bar
+{
+    left: 78px; /* make room for the logo */
+}
+QTabBar::tab {
+    height:82px;
+    background-color: transparent;
+    font-size:24px;
+    min-width:100px;
+    margin-right:40px;
+    border-bottom: 3px solid transparent;
+}
+QTabBar::tab:text
+{
+    text-align:left;
+}
+QTabWidget::pane {
+    background-color: #333333;
+    border:0 none;
+}
+QTabBar::tab:selected
+{
+    border-bottom: 3px solid #1e70eb;
+    color: #1e70eb;
+}
+QTabBar::tab:hover
+{
+    color: #1e70eb;
+}
+QTabBar::tab:pressed
+{
+    color: #0e60eb;
+}
+
 /************** General (Forms) **************/
 
 #formLineEditWidget,
 #formBrowseEditWidget {
-    max-width: 780px;
+    max-width: 890px;
 }
 
 #formFrame {
-    max-width: 720px;
+    max-width: 840px;
     background-color: #444444;
     border:1px solid #dddddd;
     border-radius: 4px;
     padding: 0px 10px 2px 6px;
     margin-top:10px;
-    margin-left:30px;
+    margin-left:50px;
 }
 
 #formFrame[Focus="true"] {
@@ -59,16 +99,235 @@ QPushButton:focus {
     padding-top: -4px;
 }
 
+
 #formErrorLabel {
     color: #ec3030;
     font-size: 14px;
-    margin-left: 40px;
+    margin-left: 50px;
 }
 
 #formTitleLabel {
     font-size:21px;
     color:#ffffff;
-    margin: 10px 0 10px 30px;
+    margin: 24px 0 10px 50px;
+}
+
+/************** General (Modal windows) **************/
+
+#header {
+	background-color:#111111;
+    min-height:80px;
+    max-height:80px;
+}
+
+#header QPushButton {
+    /* settings min/max lets us use a fixed size */
+    min-width: 24px;
+    max-width: 24px;
+    min-height: 24px;
+    max-height: 24px;
+    margin: 20px 10px 0px 10px;
+    background:transparent url(:/ArrowBack.svg) no-repeat center;
+    background-origin:content;
+    qproperty-flat: true;
+    qproperty-iconSize: 50px;
+}
+
+#header QPushButton:focus {
+    border:none;
+}
+#header QPushButton:hover {
+    background:#333333 url(:/ArrowBack.svg) no-repeat center;
+}
+#header QPushButton:pressed {
+    background:#222222 url(:/ArrowBack.svg) no-repeat center;
+}
+
+#headerTitle {
+    font-size:14px;
+    text-align:left;
+    margin:0;
+    padding-top:10px;
+    padding-bottom:-5px;
+    min-height:15px;
+    max-height:15px;
+}
+#headerSubTitle {
+    font-size:24px;
+    text-align:left;
+    margin:0;
+    min-height:42px;
+    max-height:42px;
+}
+
+#body {
+    background-color:#333333;
+}
+#footer {
+    /* settings min/max lets us use a fixed size */
+    min-width: 50px;
+    min-height:54px;
+    max-height:54px;
+}
+
+#footer > QPushButton {
+    qproperty-flat: true;
+	background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+							stop: 0 #0095f2, stop: 1.0 #1e70eb);
+    border-radius: 3px;
+    min-height: 28px;
+    max-height: 28px;
+    min-width: 150px;
+    margin-right:30px;
+}
+#footer > QPushButton:hover {
+	background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+							stop: 0 #10A5f2, stop: 1.0 #2e80eb);
+}
+#footer > QPushButton:pressed {
+	background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+							stop: 0 #0085e2, stop: 1.0 #0e60db);
+}
+
+#footer > QPushButton[secondary="true"] {
+    margin-right: 10px;
+	background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+							stop: 0 #888888, stop: 1.0 #555555);
+}
+#footer > QPushButton[secondary="true"]:hover {
+	background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+							stop: 0 #999999, stop: 1.0 #666666);
+}
+#footer > QPushButton[secondary="true"]:pressed {
+	background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+							stop: 0 #555555, stop: 1.0 #777777);
+}
+
+/************** Project Settings **************/
+#projectSettings {
+    margin-top:42px;
+}
+
+#projectTemplate {
+    margin: 55px 0 0 50px;
+    max-width: 780px;
+    min-height:200px;
+    max-height:200px;
+}
+#projectTemplateLabel {
+    font-size:16px;
+    font-weight:100;
+}
+
+#projectTemplateDetailsLabel {
+    font-size:14px;
+    min-height:40px;
+    margin-bottom:20px;
+}
+
+#projectTemplateDetails {
+    background-color:#444444;
+    max-width:240px;
+    min-width:240px;
+    margin-left:30px;
+}
+
+/************** Projects **************/
+#firstTimeContent > #titleLabel {
+    font-size:60px;
+    margin:73px 0px 0px 0px;
+    qproperty-indent: 0;
+}
+
+#firstTimeContent > #introLabel {
+    font-size:14px;
+    margin:10px 0 60px 0;
+    qproperty-indent: 0;
+}
+
+#firstTimeContent > QPushButton {
+    min-width: 210px;
+    max-width: 210px;
+    min-height: 276px;
+    max-height: 276px;
+    qproperty-flat: true;
+    background-origin:content;
+    font-size:14px;
+    border: 1px solid #ffffff;
+}
+
+#firstTimeContent > QPushButton:hover {
+    border: 1px solid #1e70eb;
+    color: #1e70eb;
+}
+
+#firstTimeContent > QPushButton:pressed {
+    border: 1px solid #0e60eb;
+    color: #0e60eb;
+}
+
+#createProjectButton {
+    background:rgba(0,0,0,180) url(:/AddOffset.svg) no-repeat center center;
+}
+#createProjectButton:hover,
+#createProjectButton:pressed {
+    background:rgba(0,0,0,180) url(:/AddOffset_Hover.svg) no-repeat center center;
+}
+
+#addProjectButton {
+    background:rgba(0,0,0,180) url(:/FolderOffset.svg) no-repeat center center;
+}
+#addProjectButton:hover,
+#addProjectButton:pressed {
+    background:rgba(0,0,0,180) url(:/FolderOffset_Hover.svg) no-repeat center center;
+}
+
+#projectsContent > QFrame  {
+    margin-top:60px;
+}
+
+#projectsContent > QFrame > #titleLabel {
+    font-size:24px;
+    qproperty-indent: 0;
+}
+
+#projectsContent > QScrollArea {
+    margin-top:40px;
+    margin-bottom:5px;
+}
+
+#projectButton > #labelButton  {
+    border:1px solid white;
+}
+#projectButton > #labelButton:hover,
+#projectButton > #labelButton:pressed  {
+    border:1px solid #1e70eb;
+}
+
+#projectButton > QFrame  {
+    margin-top:6px;
+}
+
+#projectButton > QFrame > QLabel {
+    font-weight:bold;
+    font-size:14px;
+    qproperty-indent: 0;
+}
+
+#projectMenuButton {
+    qproperty-flat: true;
+    background:transparent url(:/menu.svg) no-repeat center center;
+    max-width:30px;
+    min-width:30px;
+    max-height:14px;
+    min-height:14px;
+}
+
+#projectsContent > QFrame > #newProjectButton {
+    min-width:150px;
+    max-width:150px;
+    min-height:26px;
+    max-height:26px;
 }
 
 #labelButtonOverlay {
@@ -77,4 +336,4 @@ QPushButton:focus {
     max-width:210px;;
     min-height:278px;
     max-height:278px;
-}
+}

+ 11 - 0
Code/Tools/ProjectManager/Resources/build.svg

@@ -0,0 +1,11 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="3" y="16" width="5" height="5" fill="white"/>
+<rect x="9.5" y="16" width="5" height="5" fill="white"/>
+<rect x="16" y="16" width="5" height="5" fill="white"/>
+<rect x="3" y="9.5" width="5" height="5" fill="white"/>
+<rect x="9.5" y="9.5" width="5" height="5" fill="white"/>
+<rect x="19.415" y="8.58496" width="5" height="5" transform="rotate(60 19.415 8.58496)" fill="white"/>
+<rect x="3" y="3" width="5" height="5" fill="white"/>
+<rect x="8.58496" y="6.41504" width="5" height="5" transform="rotate(-60 8.58496 6.41504)" fill="white"/>
+<rect x="19.5" y="0.964844" width="5" height="5" transform="rotate(45 19.5 0.964844)" fill="white"/>
+</svg>

+ 5 - 0
Code/Tools/ProjectManager/Resources/menu.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="1.3335" y="2.66675" width="13.3333" height="1.33333" fill="white"/>
+<rect x="1.3335" y="7.33325" width="13.3333" height="1.33333" fill="white"/>
+<rect x="1.3335" y="12" width="13.3333" height="1.33333" fill="white"/>
+</svg>

+ 5 - 0
Code/Tools/ProjectManager/Resources/menu_hover.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="1.3335" y="2.66675" width="13.3333" height="1.33333" fill="#1e70eb"/>
+<rect x="1.3335" y="7.33325" width="13.3333" height="1.33333" fill="#1e70eb"/>
+<rect x="1.3335" y="12" width="13.3333" height="1.33333" fill="#1e70eb"/>
+</svg>

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

@@ -0,0 +1,3 @@
+<svg width="38" height="32" viewBox="0 0 38 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M33.0335 27.6364C30.2006 30.4503 26.4498 32 22.4729 32H22.4719L22.4007 31.9999C18.3948 31.9813 14.6226 30.396 11.7792 27.5358C7.98134 23.7148 6.67845 18.3374 7.86239 13.445H13.291C12.8522 14.5772 12.6238 15.7937 12.6313 17.0465C12.6466 19.6692 13.682 22.1248 15.5465 23.9605C17.4134 25.7987 19.8692 26.811 22.462 26.811L22.4897 26.8108C25.0998 26.8025 27.555 25.7736 29.4067 23.9136C31.2794 22.0326 32.3023 19.5505 32.2872 16.9246C32.2718 14.2963 31.2187 11.8234 29.3215 9.96131C27.4791 8.153 25.0581 7.16098 22.4942 7.16098C22.4558 7.16098 22.4177 7.16127 22.379 7.16171C20.9514 7.17701 19.5695 7.49947 18.3061 8.09355V2.5551C19.6438 2.16896 21.0401 1.96569 22.4658 1.96569L22.5394 1.96598C26.5556 1.98507 30.3331 3.58486 33.1758 6.47058C35.976 9.31332 37.5035 13.0777 37.4771 17.0703C37.4508 21.0641 35.8727 24.8164 33.0335 27.6364ZM1.73437 2.98423H4.71974V0H1.73437V2.98423ZM0 11.9513H4.68388V7.26925H0V11.9513ZM7.65831 10.0451H14.8796V2.82656H7.65831V10.0451Z" fill="white"/>
+</svg>

+ 46 - 36
Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp

@@ -14,11 +14,17 @@
 #include <ScreensCtrl.h>
 #include <PythonBindingsInterface.h>
 #include <NewProjectSettingsScreen.h>
+#include <ScreenHeaderWidget.h>
+#include <GemCatalog/GemCatalogScreen.h>
 
 #include <QDialogButtonBox>
+#include <QHBoxLayout>
 #include <QVBoxLayout>
 #include <QPushButton>
 #include <QMessageBox>
+#include <QStackedWidget>
+#include <QLabel>
+#include <QSizePolicy>
 
 namespace O3DE::ProjectManager
 {
@@ -26,29 +32,34 @@ namespace O3DE::ProjectManager
         : ScreenWidget(parent)
     {
         QVBoxLayout* vLayout = new QVBoxLayout();
-        setLayout(vLayout);
+        vLayout->setContentsMargins(0,0,0,0);
+
+        m_header = new ScreenHeader(this);
+        m_header->setTitle(tr("Create a New Project"));
+        m_header->setSubTitle(tr("Enter Project Details"));
+        connect(m_header->backButton(), &QPushButton::clicked, this, &CreateProjectCtrl::HandleBackButton);
+        vLayout->addWidget(m_header);
 
-        m_screensCtrl = new ScreensCtrl();
-        vLayout->addWidget(m_screensCtrl);
+        m_stack = new QStackedWidget(this);
+        m_stack->setObjectName("body");
+        m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred,QSizePolicy::Expanding));
+        m_stack->addWidget(new NewProjectSettingsScreen());
+        m_stack->addWidget(new GemCatalogScreen());
+        vLayout->addWidget(m_stack);
 
         QDialogButtonBox* backNextButtons = new QDialogButtonBox();
+        backNextButtons->setObjectName("footer");
         vLayout->addWidget(backNextButtons);
 
         m_backButton = backNextButtons->addButton(tr("Back"), QDialogButtonBox::RejectRole);
+        m_backButton->setProperty("secondary", true);
         m_nextButton = backNextButtons->addButton(tr("Next"), QDialogButtonBox::ApplyRole);
 
-        connect(m_backButton, &QPushButton::pressed, this, &CreateProjectCtrl::HandleBackButton);
-        connect(m_nextButton, &QPushButton::pressed, this, &CreateProjectCtrl::HandleNextButton);
-
-        m_screensOrder =
-        {
-            ProjectManagerScreen::NewProjectSettings,
-            ProjectManagerScreen::GemCatalog
-        };
-        m_screensCtrl->BuildScreens(m_screensOrder);
-        m_screensCtrl->ForceChangeToScreen(ProjectManagerScreen::NewProjectSettings, false);
+        connect(m_backButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandleBackButton);
+        connect(m_nextButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandleNextButton);
 
-        UpdateNextButtonText();
+        Update();
+        setLayout(vLayout);
     }
 
     ProjectManagerScreen CreateProjectCtrl::GetScreenEnum()
@@ -58,28 +69,20 @@ namespace O3DE::ProjectManager
 
     void CreateProjectCtrl::HandleBackButton()
     {
-        if (!m_screensCtrl->GotoPreviousScreen())
+        if (m_stack->currentIndex() > 0)
         {
-            emit GotoPreviousScreenRequest();
+            m_stack->setCurrentIndex(m_stack->currentIndex() - 1);
+            Update();
         }
         else
         {
-            UpdateNextButtonText();
+            emit GotoPreviousScreenRequest();
         }
     }
     void CreateProjectCtrl::HandleNextButton()
     {
-        ScreenWidget* currentScreen = m_screensCtrl->GetCurrentScreen();
+        ScreenWidget* currentScreen = reinterpret_cast<ScreenWidget*>(m_stack->currentWidget());
         ProjectManagerScreen screenEnum = currentScreen->GetScreenEnum();
-        auto screenOrderIter = m_screensOrder.begin();
-        for (; screenOrderIter != m_screensOrder.end(); ++screenOrderIter)
-        {
-            if (*screenOrderIter == screenEnum)
-            {
-                ++screenOrderIter;
-                break;
-            }
-        }
 
         if (screenEnum == ProjectManagerScreen::NewProjectSettings)
         {
@@ -97,10 +100,10 @@ namespace O3DE::ProjectManager
             }
         }
 
-        if (screenOrderIter != m_screensOrder.end())
+        if (m_stack->currentIndex() != m_stack->count() - 1)
         {
-            m_screensCtrl->ChangeToScreen(*screenOrderIter);
-            UpdateNextButtonText();
+            m_stack->setCurrentIndex(m_stack->currentIndex() + 1);
+            Update();
         }
         else
         {
@@ -108,7 +111,7 @@ namespace O3DE::ProjectManager
             if (result.IsSuccess())
             {
                 // adding gems is not implemented yet because we don't know what targets to add or how to add them
-                emit ChangeScreenRequest(ProjectManagerScreen::ProjectsHome);
+                emit ChangeScreenRequest(ProjectManagerScreen::Projects);
             }
             else
             {
@@ -117,14 +120,21 @@ namespace O3DE::ProjectManager
         }
     }
 
-    void CreateProjectCtrl::UpdateNextButtonText()
+    void CreateProjectCtrl::Update()
     {
-        QString nextButtonText = tr("Next");
-        if (m_screensCtrl->GetCurrentScreen()->GetScreenEnum() == ProjectManagerScreen::GemCatalog)
+        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
         {
-            nextButtonText = tr("Create Project");
+            m_header->setTitle(tr("Create Project"));
+            m_header->setSubTitle(tr("Enter Project Details"));
+            m_nextButton->setText(tr("Next"));
         }
-        m_nextButton->setText(nextButtonText);
     }
 
 } // namespace O3DE::ProjectManager

+ 10 - 6
Code/Tools/ProjectManager/Source/CreateProjectCtrl.h

@@ -12,15 +12,18 @@
 #pragma once
 
 #if !defined(Q_MOC_RUN)
-#include "ProjectInfo.h"
 #include <ScreenWidget.h>
-#include <ScreensCtrl.h>
-#include <QPushButton>
+#include <ProjectInfo.h>
 #endif
 
+QT_FORWARD_DECLARE_CLASS(QStackedWidget)
+QT_FORWARD_DECLARE_CLASS(QPushButton)
+QT_FORWARD_DECLARE_CLASS(QLabel)
 
 namespace O3DE::ProjectManager
 {
+    QT_FORWARD_DECLARE_CLASS(ScreenHeader)
+
     class CreateProjectCtrl
         : public ScreenWidget
     {
@@ -34,12 +37,13 @@ namespace O3DE::ProjectManager
         void HandleNextButton();
 
     private:
-        void UpdateNextButtonText();
+        void Update();
+
+        QStackedWidget* m_stack;
+        ScreenHeader* m_header;
 
-        ScreensCtrl* m_screensCtrl;
         QPushButton* m_backButton;
         QPushButton* m_nextButton;
-        QVector<ProjectManagerScreen> m_screensOrder;
 
         QString m_projectTemplatePath;
         ProjectInfo m_projectInfo;

+ 10 - 0
Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp

@@ -82,6 +82,16 @@ namespace O3DE::ProjectManager
         return ProjectManagerScreen::EngineSettings;
     }
 
+    QString EngineSettingsScreen::GetTabText()
+    {
+        return tr("Engine");
+    }
+
+    bool EngineSettingsScreen::IsTab()
+    {
+        return true;
+    }
+
     void EngineSettingsScreen::OnTextChanged()
     {
         // save engine settings

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

@@ -26,7 +26,10 @@ namespace O3DE::ProjectManager
     public:
         explicit EngineSettingsScreen(QWidget* parent = nullptr);
         ~EngineSettingsScreen() = default;
+
         ProjectManagerScreen GetScreenEnum() override;
+        QString GetTabText() override;
+        bool IsTab() override;
 
     protected slots:
         void OnTextChanged();

+ 0 - 95
Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp

@@ -1,95 +0,0 @@
-/*
- * 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 <FirstTimeUseScreen.h>
-
-#include <QVBoxLayout>
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QPushButton>
-#include <QIcon>
-#include <QSpacerItem>
-
-namespace O3DE::ProjectManager
-{
-    FirstTimeUseScreen::FirstTimeUseScreen(QWidget* parent)
-        : ScreenWidget(parent)
-    {
-        QVBoxLayout* vLayout = new QVBoxLayout();
-        setLayout(vLayout);
-        vLayout->setContentsMargins(s_contentMargins, s_contentMargins, s_contentMargins, s_contentMargins);
-
-        QLabel* titleLabel = new QLabel(this);
-        titleLabel->setText(tr("Ready. Set. Create!"));
-        titleLabel->setStyleSheet("font-size: 60px");
-        vLayout->addWidget(titleLabel);
-
-        QLabel* introLabel = new QLabel(this);
-        introLabel->setTextFormat(Qt::AutoText);
-        introLabel->setText(tr("<html><head/><body><p>Welcome to O3DE! Start something new by creating a project. Not sure what to create? </p><p>Explore what\342\200\231s available by downloading our sample project.</p></body></html>"));
-        introLabel->setStyleSheet("font-size: 14px");
-        vLayout->addWidget(introLabel);
-
-        QHBoxLayout* buttonLayout = new QHBoxLayout();
-        buttonLayout->setSpacing(s_buttonSpacing);
-
-        m_createProjectButton = CreateLargeBoxButton(QIcon(":/Add.svg"), tr("Create Project"), this);
-        m_createProjectButton->setIconSize(QSize(s_iconSize, s_iconSize));
-        buttonLayout->addWidget(m_createProjectButton);
-
-        m_addProjectButton = CreateLargeBoxButton(QIcon(":/Select_Folder.svg"), tr("Add a Project"), this);
-        m_addProjectButton->setIconSize(QSize(s_iconSize, s_iconSize));
-        buttonLayout->addWidget(m_addProjectButton);
-
-        QSpacerItem* buttonSpacer = new QSpacerItem(s_spacerSize, s_spacerSize, QSizePolicy::Expanding, QSizePolicy::Minimum);
-        buttonLayout->addItem(buttonSpacer);
-
-        vLayout->addItem(buttonLayout);
-
-        QSpacerItem* verticalSpacer = new QSpacerItem(s_spacerSize, s_spacerSize, QSizePolicy::Minimum, QSizePolicy::Expanding);
-        vLayout->addItem(verticalSpacer);
-
-        // Using border-image allows for scaling options background-image does not support
-        setStyleSheet("O3DE--ProjectManager--ScreenWidget { border-image: url(:/Backgrounds/FirstTimeBackgroundImage.jpg) repeat repeat; }");
-
-        connect(m_createProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleNewProjectButton);
-        connect(m_addProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleAddProjectButton);
-    }
-
-    ProjectManagerScreen FirstTimeUseScreen::GetScreenEnum()
-    {
-        return ProjectManagerScreen::FirstTimeUse;
-    }
-
-    void FirstTimeUseScreen::HandleNewProjectButton()
-    {
-        emit ResetScreenRequest(ProjectManagerScreen::CreateProject);
-        emit ChangeScreenRequest(ProjectManagerScreen::CreateProject);
-    }
-    void FirstTimeUseScreen::HandleAddProjectButton()
-    {
-        emit ChangeScreenRequest(ProjectManagerScreen::ProjectsHome);
-    }
-
-    QPushButton* FirstTimeUseScreen::CreateLargeBoxButton(const QIcon& icon, const QString& text, QWidget* parent)
-    {
-        QPushButton* largeBoxButton = new QPushButton(icon, text, parent);
-
-        largeBoxButton->setFixedSize(s_boxButtonWidth, s_boxButtonHeight);
-        largeBoxButton->setFlat(true);
-        largeBoxButton->setFocusPolicy(Qt::FocusPolicy::NoFocus);
-        largeBoxButton->setStyleSheet("QPushButton { font-size: 14px; background-color: rgba(0, 0, 0, 191); }");
-
-        return largeBoxButton;
-    }
-
-} // namespace O3DE::ProjectManager

+ 0 - 49
Code/Tools/ProjectManager/Source/FirstTimeUseScreen.h

@@ -1,49 +0,0 @@
-/*
- * 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 <ScreenWidget.h>
-#endif
-
-QT_FORWARD_DECLARE_CLASS(QIcon)
-QT_FORWARD_DECLARE_CLASS(QPushButton)
-
-namespace O3DE::ProjectManager
-{
-    class FirstTimeUseScreen
-        : public ScreenWidget
-    {
-    public:
-        explicit FirstTimeUseScreen(QWidget* parent = nullptr);
-        ~FirstTimeUseScreen() = default;
-        ProjectManagerScreen GetScreenEnum() override;
-
-    protected slots:
-        void HandleNewProjectButton();
-        void HandleAddProjectButton();
-
-    private:
-        QPushButton* CreateLargeBoxButton(const QIcon& icon, const QString& text, QWidget* parent = nullptr);
-
-        QPushButton* m_createProjectButton;
-        QPushButton* m_addProjectButton;
-
-        inline constexpr static int s_contentMargins = 80;
-        inline constexpr static int s_buttonSpacing = 30;
-        inline constexpr static int s_iconSize = 24;
-        inline constexpr static int s_spacerSize = 20;
-        inline constexpr static int s_boxButtonWidth = 210;
-        inline constexpr static int s_boxButtonHeight = 280;
-    };
-
-} // namespace O3DE::ProjectManager

+ 75 - 68
Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp

@@ -12,6 +12,9 @@
 
 #include <NewProjectSettingsScreen.h>
 #include <PythonBindingsInterface.h>
+#include <FormLineEditWidget.h>
+#include <FormBrowseEditWidget.h>
+#include <PathValidator.h>
 
 #include <QVBoxLayout>
 #include <QHBoxLayout>
@@ -23,6 +26,7 @@
 #include <QPushButton>
 #include <QSpacerItem>
 #include <QStandardPaths>
+#include <QFrame>
 
 namespace O3DE::ProjectManager
 {
@@ -31,64 +35,81 @@ namespace O3DE::ProjectManager
     NewProjectSettingsScreen::NewProjectSettingsScreen(QWidget* parent)
         : ScreenWidget(parent)
     {
-        QHBoxLayout* hLayout = new QHBoxLayout();
-        this->setLayout(hLayout);
-
+        QHBoxLayout* hLayout = new QHBoxLayout(this);
+        hLayout->setAlignment(Qt::AlignLeft);
+        hLayout->setContentsMargins(0,0,0,0);
+
+        // if we don't provide a parent for this box layout the stylesheet doesn't take
+        // if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally
+        QFrame* projectSettingsFrame = new QFrame(this);
+        projectSettingsFrame->setObjectName("projectSettings");
         QVBoxLayout* vLayout = new QVBoxLayout(this);
 
-        QLabel* projectNameLabel = new QLabel(tr("Project Name"), this);
-        vLayout->addWidget(projectNameLabel);
-
-        m_projectNameLineEdit = new QLineEdit(tr("New Project"), this);
-        vLayout->addWidget(m_projectNameLineEdit);
-
-        QLabel* projectPathLabel = new QLabel(tr("Project Location"), this);
-        vLayout->addWidget(projectPathLabel);
-
+        // you cannot remove content margins in qss
+        vLayout->setContentsMargins(0,0,0,0);
+        vLayout->setAlignment(Qt::AlignTop);
         {
-            QHBoxLayout* projectPathLayout = new QHBoxLayout(this);
-
-            m_projectPathLineEdit = new QLineEdit(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), this);
-            projectPathLayout->addWidget(m_projectPathLineEdit);
-
-            QPushButton* browseButton = new QPushButton(tr("Browse"), this);
-            connect(browseButton, &QPushButton::pressed, this, &NewProjectSettingsScreen::HandleBrowseButton);
-            projectPathLayout->addWidget(browseButton);
-
-            vLayout->addLayout(projectPathLayout);
-        }
-
-        QLabel* projectTemplateLabel = new QLabel(this);
-        projectTemplateLabel->setText("Project Template");
-        vLayout->addWidget(projectTemplateLabel);
-
-        QHBoxLayout* templateLayout = new QHBoxLayout(this);
-        vLayout->addItem(templateLayout);
-
-        m_projectTemplateButtonGroup = new QButtonGroup(this);
-        auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates();
-        if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty())
-        {
-            for (auto projectTemplate : templatesResult.GetValue())
+            m_projectName = new FormLineEditWidget(tr("Project name"), tr("New Project"), this);
+            m_projectName->setErrorLabelText(
+                tr("A project with this name already exists at this location. Please choose a new name or location."));
+            vLayout->addWidget(m_projectName);
+
+            m_projectPath =
+                new FormBrowseEditWidget(tr("Project Location"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), this);
+            m_projectPath->lineEdit()->setReadOnly(true);
+            m_projectPath->setErrorLabelText(tr("Please provide a valid path to a folder that exists"));
+            m_projectPath->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this));
+            vLayout->addWidget(m_projectPath);
+
+            // if we don't use a QFrame we cannot "contain" the widgets inside and move them around
+            // as a group
+            QFrame* projectTemplateWidget = new QFrame(this);
+            projectTemplateWidget->setObjectName("projectTemplate");
+            QVBoxLayout* containerLayout = new QVBoxLayout();
+            containerLayout->setAlignment(Qt::AlignTop);
             {
-                QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this);
-                radioButton->setProperty(k_pathProperty, projectTemplate.m_path);
-                m_projectTemplateButtonGroup->addButton(radioButton);
-
-                templateLayout->addWidget(radioButton);
+                QLabel* projectTemplateLabel = new QLabel(tr("Select a Project Template"));
+                projectTemplateLabel->setObjectName("projectTemplateLabel");
+                containerLayout->addWidget(projectTemplateLabel);
+
+                QLabel* projectTemplateDetailsLabel = new QLabel(tr("Project templates are pre-configured with relevant Gems that provide "
+                                                                    "additional functionality and content to the project."));
+                projectTemplateDetailsLabel->setWordWrap(true);
+                projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel");
+                containerLayout->addWidget(projectTemplateDetailsLabel);
+
+                QHBoxLayout* templateLayout = new QHBoxLayout(this);
+                containerLayout->addItem(templateLayout);
+
+                m_projectTemplateButtonGroup = new QButtonGroup(this);
+                m_projectTemplateButtonGroup->setObjectName("templateButtonGroup");
+                auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates();
+                if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty())
+                {
+                    for (auto projectTemplate : templatesResult.GetValue())
+                    {
+                        QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this);
+                        radioButton->setProperty(k_pathProperty, projectTemplate.m_path);
+                        m_projectTemplateButtonGroup->addButton(radioButton);
+
+                        containerLayout->addWidget(radioButton);
+                    }
+
+                    m_projectTemplateButtonGroup->buttons().first()->setChecked(true);
+                }
             }
-
-            m_projectTemplateButtonGroup->buttons().first()->setChecked(true);
+            projectTemplateWidget->setLayout(containerLayout);
+            vLayout->addWidget(projectTemplateWidget);
         }
+        projectSettingsFrame->setLayout(vLayout);
 
-        QSpacerItem* verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
-        vLayout->addItem(verticalSpacer);
+        hLayout->addWidget(projectSettingsFrame);
 
-        hLayout->addItem(vLayout);
+        QWidget* projectTemplateDetails = new QWidget(this);
+        projectTemplateDetails->setObjectName("projectTemplateDetails");
+        hLayout->addWidget(projectTemplateDetails);
 
-        QWidget* gemsListPlaceholder = new QWidget(this);
-        gemsListPlaceholder->setFixedWidth(250);
-        hLayout->addWidget(gemsListPlaceholder);
+        this->setLayout(hLayout);
     }
 
     ProjectManagerScreen NewProjectSettingsScreen::GetScreenEnum()
@@ -96,26 +117,12 @@ namespace O3DE::ProjectManager
         return ProjectManagerScreen::NewProjectSettings;
     }
 
-    void NewProjectSettingsScreen::HandleBrowseButton()
-    {
-        QString defaultPath = m_projectPathLineEdit->text();
-        if (defaultPath.isEmpty())
-        {
-            defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
-        }
-
-        QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("New project path"), defaultPath));
-        if (!directory.isEmpty())
-        {
-            m_projectPathLineEdit->setText(directory);
-        }
-    }
 
     ProjectInfo NewProjectSettingsScreen::GetProjectInfo()
     {
         ProjectInfo projectInfo;
-        projectInfo.m_projectName = m_projectNameLineEdit->text();
-        projectInfo.m_path        = QDir::toNativeSeparators(m_projectPathLineEdit->text() + "/" + projectInfo.m_projectName);
+        projectInfo.m_projectName = m_projectName->lineEdit()->text();
+        projectInfo.m_path        = QDir::toNativeSeparators(m_projectPath->lineEdit()->text() + "/" + projectInfo.m_projectName);
         return projectInfo;
     }
 
@@ -127,18 +134,18 @@ namespace O3DE::ProjectManager
     bool NewProjectSettingsScreen::Validate()
     {
         bool projectNameIsValid = true;
-        if (m_projectNameLineEdit->text().isEmpty())
+        if (m_projectName->lineEdit()->text().isEmpty())
         {
             projectNameIsValid = false;
         }
 
         bool projectPathIsValid = true;
-        if (m_projectPathLineEdit->text().isEmpty())
+        if (m_projectPath->lineEdit()->text().isEmpty())
         {
             projectPathIsValid = false;
         }
 
-        QDir path(QDir::toNativeSeparators(m_projectPathLineEdit->text() + "/" + m_projectNameLineEdit->text()));
+        QDir path(QDir::toNativeSeparators(m_projectPath->lineEdit()->text() + "/" + m_projectName->lineEdit()->text()));
         if (path.exists() && !path.isEmpty())
         {
             projectPathIsValid = false;

+ 5 - 3
Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h

@@ -17,10 +17,12 @@
 #endif
 
 QT_FORWARD_DECLARE_CLASS(QButtonGroup)
-QT_FORWARD_DECLARE_CLASS(QLineEdit)
 
 namespace O3DE::ProjectManager
 {
+    QT_FORWARD_DECLARE_CLASS(FormLineEditWidget)
+    QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget)
+
     class NewProjectSettingsScreen
         : public ScreenWidget
     {
@@ -38,8 +40,8 @@ namespace O3DE::ProjectManager
         void HandleBrowseButton();
 
     private:
-        QLineEdit* m_projectNameLineEdit;
-        QLineEdit* m_projectPathLineEdit;
+        FormLineEditWidget* m_projectName;
+        FormBrowseEditWidget* m_projectPath;
         QButtonGroup* m_projectTemplateButtonGroup;
     };
 

+ 17 - 7
Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp

@@ -31,6 +31,7 @@ namespace O3DE::ProjectManager
     LabelButton::LabelButton(QWidget* parent)
         : QLabel(parent)
     {
+        setObjectName("labelButton");
         m_overlayLabel = new QLabel("", this);
         m_overlayLabel->setObjectName("labelButtonOverlay");
         m_overlayLabel->setWordWrap(true);
@@ -75,6 +76,8 @@ namespace O3DE::ProjectManager
 
     void ProjectButton::Setup()
     {
+        setObjectName("projectButton");
+
         QVBoxLayout* vLayout = new QVBoxLayout();
         vLayout->setSpacing(0);
         vLayout->setContentsMargins(0, 0, 0, 0);
@@ -98,14 +101,21 @@ namespace O3DE::ProjectManager
         m_deleteProjectAction = newProjectMenu->addAction(tr("Delete the Project"));
 #endif
 
-        m_projectSettingsMenuButton = new QPushButton(this);
-        m_projectSettingsMenuButton->setText(m_projectName);
-        m_projectSettingsMenuButton->setMenu(newProjectMenu);
-        m_projectSettingsMenuButton->setFocusPolicy(Qt::FocusPolicy::NoFocus);
-        m_projectSettingsMenuButton->setStyleSheet("font-size: 14px; text-align:left;");
-        vLayout->addWidget(m_projectSettingsMenuButton);
+        QFrame* footer = new QFrame(this);
+        QHBoxLayout* hLayout = new QHBoxLayout();
+        hLayout->setContentsMargins(0, 0, 0, 0);
+        footer->setLayout(hLayout);
+        {
+            QLabel* projectNameLabel = new QLabel(m_projectName, this);
+            hLayout->addWidget(projectNameLabel);
+
+            QPushButton* projectMenuButton = new QPushButton(this);
+            projectMenuButton->setObjectName("projectMenuButton");
+            projectMenuButton->setMenu(newProjectMenu);
+            hLayout->addWidget(projectMenuButton);
+        }
 
-        setFixedSize(s_projectImageWidth, s_projectImageHeight + m_projectSettingsMenuButton->height());
+        vLayout->addWidget(footer);
 
         connect(m_projectImageLabel, &LabelButton::triggered, [this]() { emit OpenProject(m_projectName); });
         connect(m_editProjectAction, &QAction::triggered, [this]() { emit EditProject(m_projectName); });

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

@@ -73,7 +73,6 @@ namespace O3DE::ProjectManager
         QString m_projectName;
         QString m_projectImagePath;
         LabelButton* m_projectImageLabel;
-        QPushButton* m_projectSettingsMenuButton;
         QAction* m_editProjectAction;
         QAction* m_editProjectGemsAction;
         QAction* m_copyProjectAction;

+ 18 - 33
Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp

@@ -11,52 +11,46 @@
  */
 
 #include <ProjectManagerWindow.h>
-#include <ScreenFactory.h>
+#include <ScreensCtrl.h>
 
 #include <AzQtComponents/Components/StyleManager.h>
 #include <AzCore/IO/Path/Path.h>
 
 #include <QDir>
 
-#include <Source/ui_ProjectManagerWindow.h>
-
 namespace O3DE::ProjectManager
 {
     ProjectManagerWindow::ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath)
         : QMainWindow(parent)
-        , m_ui(new Ui::ProjectManagerWindowClass())
     {
-        m_ui->setupUi(this);
-        QLayout* layout = m_ui->centralWidget->layout();
-        layout->setMargin(0);
-        layout->setSpacing(0);
-        layout->setContentsMargins(0, 0, 0, 0);
-
         m_pythonBindings = AZStd::make_unique<PythonBindings>(engineRootPath);
 
-        m_screensCtrl = new ScreensCtrl();
-        m_ui->verticalLayout->addWidget(m_screensCtrl);
+        setWindowTitle(tr("O3DE Project Manager"));
 
-        connect(m_ui->projectsMenu, &QMenu::aboutToShow, this, &ProjectManagerWindow::HandleProjectsMenu);
-        connect(m_ui->engineMenu, &QMenu::aboutToShow, this, &ProjectManagerWindow::HandleEngineMenu);
+        ScreensCtrl* screensCtrl = new ScreensCtrl();
 
+        // currently the tab order on the home page is based on the order of this list
+        QVector<ProjectManagerScreen> screenEnums =
+        {
+            ProjectManagerScreen::Projects,
+            ProjectManagerScreen::EngineSettings,
+            ProjectManagerScreen::CreateProject,
+            ProjectManagerScreen::UpdateProject
+        };
+        screensCtrl->BuildScreens(screenEnums);
+
+        setCentralWidget(screensCtrl);
+
+        // setup stylesheets and hot reloading 
         QDir rootDir = QString::fromUtf8(engineRootPath.Native().data(), aznumeric_cast<int>(engineRootPath.Native().size()));
         const auto pathOnDisk = rootDir.absoluteFilePath("Code/Tools/ProjectManager/Resources");
         const auto qrcPath = QStringLiteral(":/ProjectManager/style");
         AzQtComponents::StyleManager::addSearchPaths("style", pathOnDisk, qrcPath, engineRootPath);
 
+        // set stylesheet after creating the screens or their styles won't get updated
         AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("style:ProjectManager.qss"));
 
-        QVector<ProjectManagerScreen> screenEnums =
-        {
-            ProjectManagerScreen::FirstTimeUse,
-            ProjectManagerScreen::CreateProject,
-            ProjectManagerScreen::ProjectsHome,
-            ProjectManagerScreen::UpdateProject,
-            ProjectManagerScreen::EngineSettings
-        };
-        m_screensCtrl->BuildScreens(screenEnums);
-        m_screensCtrl->ForceChangeToScreen(ProjectManagerScreen::FirstTimeUse, false);
+        screensCtrl->ForceChangeToScreen(ProjectManagerScreen::Projects, false);
     }
 
     ProjectManagerWindow::~ProjectManagerWindow()
@@ -64,13 +58,4 @@ namespace O3DE::ProjectManager
         m_pythonBindings.reset();
     }
 
-    void ProjectManagerWindow::HandleProjectsMenu()
-    {
-        m_screensCtrl->ChangeToScreen(ProjectManagerScreen::ProjectsHome);
-    }
-    void ProjectManagerWindow::HandleEngineMenu()
-    {
-        m_screensCtrl->ChangeToScreen(ProjectManagerScreen::EngineSettings);
-    }
-
 } // namespace O3DE::ProjectManager

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

@@ -13,17 +13,9 @@
 
 #if !defined(Q_MOC_RUN)
 #include <QMainWindow>
-
-#include <ScreensCtrl.h>
-
 #include <PythonBindings.h>
 #endif
 
-namespace Ui
-{
-    class ProjectManagerWindowClass;
-}
-
 namespace O3DE::ProjectManager
 {
     class ProjectManagerWindow
@@ -35,13 +27,7 @@ namespace O3DE::ProjectManager
         explicit ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath);
         ~ProjectManagerWindow();
 
-    protected slots:
-        void HandleProjectsMenu();
-        void HandleEngineMenu();
-
     private:
-        QScopedPointer<Ui::ProjectManagerWindowClass> m_ui;
-        ScreensCtrl* m_screensCtrl;
         AZStd::unique_ptr<PythonBindings> m_pythonBindings;
     };
 

+ 0 - 67
Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui

@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ProjectManagerWindowClass</class>
- <widget class="QMainWindow" name="ProjectManagerWindowClass">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>1200</width>
-    <height>800</height>
-   </rect>
-  </property>
-  <property name="sizePolicy">
-   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-    <horstretch>0</horstretch>
-    <verstretch>0</verstretch>
-   </sizepolicy>
-  </property>
-  <property name="windowTitle">
-   <string>O3DE Project Manager</string>
-  </property>
-  <widget class="QWidget" name="centralWidget">
-   <layout class="QVBoxLayout" name="verticalLayout"/>
-  </widget>
-  <widget class="QMenuBar" name="menuBar">
-   <property name="geometry">
-    <rect>
-     <x>0</x>
-     <y>0</y>
-     <width>1200</width>
-     <height>36</height>
-    </rect>
-   </property>
-   <property name="font">
-    <font>
-     <pointsize>16</pointsize>
-    </font>
-   </property>
-   <widget class="QMenu" name="iconMenu">
-    <property name="title">
-     <string>Icon</string>
-    </property>
-    <property name="icon">
-     <iconset resource="../Resources/ProjectManager.qrc">
-      <normaloff>:/o3de_editor.ico</normaloff>:/o3de_editor.ico</iconset>
-    </property>
-   </widget>
-   <widget class="QMenu" name="projectsMenu">
-    <property name="title">
-     <string>Projects</string>
-    </property>
-   </widget>
-   <widget class="QMenu" name="engineMenu">
-    <property name="title">
-     <string>Engine</string>
-    </property>
-   </widget>
-   <addaction name="iconMenu"/>
-   <addaction name="projectsMenu"/>
-   <addaction name="engineMenu"/>
-  </widget>
- </widget>
- <resources>
-  <include location="../Resources/ProjectManager.qrc"/>
- </resources>
- <connections/>
-</ui>

+ 0 - 206
Code/Tools/ProjectManager/Source/ProjectsHomeScreen.cpp

@@ -1,206 +0,0 @@
-/*
- * 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 <ProjectsHomeScreen.h>
-
-#include <ProjectButtonWidget.h>
-#include <PythonBindingsInterface.h>
-#include <AzCore/Platform.h>
-#include <AzCore/IO/SystemFile.h>
-#include <AzFramework/AzFramework_Traits_Platform.h>
-#include <AzFramework/Process/ProcessCommon.h>
-#include <AzFramework/Process/ProcessWatcher.h>
-#include <AzCore/Utils/Utils.h>
-
-#include <QVBoxLayout>
-#include <QHBoxLayout>
-#include <QGridLayout>
-#include <QLabel>
-#include <QPushButton>
-#include <QMenu>
-#include <QListView>
-#include <QSpacerItem>
-#include <QListWidget>
-#include <QListWidgetItem>
-#include <QFileInfo>
-#include <QScrollArea>
-#include <QMessageBox>
-#include <QTimer>
-
-namespace O3DE::ProjectManager
-{
-    ProjectsHomeScreen::ProjectsHomeScreen(QWidget* parent)
-        : ScreenWidget(parent)
-    {
-        QVBoxLayout* vLayout = new QVBoxLayout();
-        setLayout(vLayout);
-        vLayout->setContentsMargins(s_contentMargins, s_contentMargins, s_contentMargins, s_contentMargins);
-
-        QHBoxLayout* topLayout = new QHBoxLayout();
-
-        QLabel* titleLabel = new QLabel(this);
-        titleLabel->setText("My Projects");
-        titleLabel->setStyleSheet("font-size: 24px");
-        topLayout->addWidget(titleLabel);
-
-        QSpacerItem* topSpacer = new QSpacerItem(s_spacerSize, s_spacerSize, QSizePolicy::Expanding, QSizePolicy::Minimum);
-        topLayout->addItem(topSpacer);
-
-        QMenu* newProjectMenu = new QMenu(this);
-        m_createNewProjectAction = newProjectMenu->addAction("Create New Project");
-        m_addExistingProjectAction = newProjectMenu->addAction("Add Existing Project");
-
-        QPushButton* newProjectMenuButton = new QPushButton(this);
-        newProjectMenuButton->setText("New Project...");
-        newProjectMenuButton->setMenu(newProjectMenu);
-        newProjectMenuButton->setFixedWidth(s_newProjectButtonWidth);
-        newProjectMenuButton->setStyleSheet("font-size: 14px;");
-        topLayout->addWidget(newProjectMenuButton);
-
-        vLayout->addLayout(topLayout);
-
-        // Get all projects and create a horizontal scrolling list of them
-        auto projectsResult = PythonBindingsInterface::Get()->GetProjects();
-        if (projectsResult.IsSuccess() && !projectsResult.GetValue().isEmpty())
-        {
-            QScrollArea* projectsScrollArea = new QScrollArea(this);
-            QWidget* scrollWidget = new QWidget();
-            QGridLayout* projectGridLayout = new QGridLayout();
-            scrollWidget->setLayout(projectGridLayout);
-            projectsScrollArea->setWidget(scrollWidget);
-            projectsScrollArea->setWidgetResizable(true);
-
-            int gridIndex = 0;
-            for (auto project : projectsResult.GetValue())
-            {
-                ProjectButton* projectButton;
-                QString projectPreviewPath = project.m_path + m_projectPreviewImagePath;
-                QFileInfo doesPreviewExist(projectPreviewPath);
-                if (doesPreviewExist.exists() && doesPreviewExist.isFile())
-                {
-                    projectButton = new ProjectButton(project.m_projectName, projectPreviewPath, this);
-                }
-                else
-                {
-                    projectButton = new ProjectButton(project.m_projectName, this);
-                }
-
-                // Create rows of projects buttons s_projectButtonRowCount buttons wide
-                projectGridLayout->addWidget(projectButton, gridIndex / s_projectButtonRowCount, gridIndex % s_projectButtonRowCount);
-
-                connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsHomeScreen::HandleOpenProject);
-                connect(projectButton, &ProjectButton::EditProject, this, &ProjectsHomeScreen::HandleEditProject);
-
-#ifdef SHOW_ALL_PROJECT_ACTIONS
-                connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsHomeScreen::HandleEditProjectGems);
-                connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsHomeScreen::HandleCopyProject);
-                connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsHomeScreen::HandleRemoveProject);
-                connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsHomeScreen::HandleDeleteProject);
-#endif
-                ++gridIndex;
-            }
-
-            vLayout->addWidget(projectsScrollArea);
-        }
-
-        // Using border-image allows for scaling options background-image does not support
-        setStyleSheet("O3DE--ProjectManager--ScreenWidget { border-image: url(:/Backgrounds/FirstTimeBackgroundImage.jpg) repeat repeat; }");
-
-        connect(m_createNewProjectAction, &QAction::triggered, this, &ProjectsHomeScreen::HandleNewProjectButton);
-        connect(m_addExistingProjectAction, &QAction::triggered, this, &ProjectsHomeScreen::HandleAddProjectButton);
-    }
-
-    ProjectManagerScreen ProjectsHomeScreen::GetScreenEnum()
-    {
-        return ProjectManagerScreen::ProjectsHome;
-    }
-
-    void ProjectsHomeScreen::HandleNewProjectButton()
-    {
-        emit ResetScreenRequest(ProjectManagerScreen::CreateProject);
-        emit ChangeScreenRequest(ProjectManagerScreen::CreateProject);
-    }
-    void ProjectsHomeScreen::HandleAddProjectButton()
-    {
-        // Do nothing for now
-    }
-    void ProjectsHomeScreen::HandleOpenProject(const QString& projectPath)
-    {
-        if (!projectPath.isEmpty())
-        {
-            AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
-            AZStd::string executableFilename = "Editor";
-            AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
-            auto cmdPath = AZ::IO::FixedMaxPathString::format("%s -regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(), projectPath.toStdString().c_str());
-
-            AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
-            processLaunchInfo.m_commandlineParameters = cmdPath;
-            bool launchSucceeded = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
-            if (!launchSucceeded)
-            {
-                AZ_Error("ProjectManager", false, "Failed to launch editor");
-                QMessageBox::critical( this, tr("Error"), tr("Failed to launch the Editor, please verify the project settings are valid."));
-            }
-            else
-            {
-                // prevent the user from accidentally pressing the button while the editor is launching
-                // and let them know what's happening
-                ProjectButton* button = qobject_cast<ProjectButton*>(sender());
-                if (button)
-                {
-                    button->SetButtonEnabled(false);
-                    button->SetButtonOverlayText(tr("Opening Editor..."));
-                }
-
-                // enable the button after 3 seconds 
-                constexpr int waitTimeInMs = 3000;
-                QTimer::singleShot(waitTimeInMs, this, [this, button] {
-                        if (button)
-                        {
-                            button->SetButtonEnabled(true);
-                        }
-                    });
-            }
-        }
-        else
-        {
-            AZ_Error("ProjectManager", false, "Cannot open editor because an empty project path was provided");
-            QMessageBox::critical( this, tr("Error"), tr("Failed to launch the Editor because the project path is invalid."));
-        }
-
-    }
-    void ProjectsHomeScreen::HandleEditProject(const QString& projectPath)
-    {
-        emit NotifyCurrentProject(projectPath);
-        emit ResetScreenRequest(ProjectManagerScreen::UpdateProject);
-        emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
-    }
-    void ProjectsHomeScreen::HandleEditProjectGems(const QString& projectPath)
-    {
-        emit NotifyCurrentProject(projectPath);
-        emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
-    }
-    void ProjectsHomeScreen::HandleCopyProject([[maybe_unused]] const QString& projectPath)
-    {
-        // Open file dialog and choose location for copied project then register copy with O3DE
-    }
-    void ProjectsHomeScreen::HandleRemoveProject([[maybe_unused]] const QString& projectPath)
-    {
-        // Unregister Project from O3DE 
-    }
-    void ProjectsHomeScreen::HandleDeleteProject([[maybe_unused]] const QString& projectPath)
-    {
-        // Remove project from 03DE and delete from disk
-        ProjectsHomeScreen::HandleRemoveProject(projectPath);
-    }
-
-} // namespace O3DE::ProjectManager

+ 347 - 0
Code/Tools/ProjectManager/Source/ProjectsScreen.cpp

@@ -0,0 +1,347 @@
+/*
+ * 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 <ProjectsScreen.h>
+
+#include <ProjectButtonWidget.h>
+#include <PythonBindingsInterface.h>
+
+#include <AzQtComponents/Components/FlowLayout.h>
+#include <AzCore/Platform.h>
+#include <AzCore/IO/SystemFile.h>
+#include <AzFramework/AzFramework_Traits_Platform.h>
+#include <AzFramework/Process/ProcessCommon.h>
+#include <AzFramework/Process/ProcessWatcher.h>
+#include <AzCore/Utils/Utils.h>
+
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QPushButton>
+#include <QMenu>
+#include <QListView>
+#include <QSpacerItem>
+#include <QListWidget>
+#include <QListWidgetItem>
+#include <QFileInfo>
+#include <QScrollArea>
+#include <QStackedWidget>
+#include <QFrame>
+#include <QIcon>
+#include <QPixmap>
+#include <QSettings>
+#include <QMessageBox>
+#include <QTimer>
+
+//#define DISPLAY_PROJECT_DEV_DATA true 
+
+namespace O3DE::ProjectManager
+{
+    ProjectsScreen::ProjectsScreen(QWidget* parent)
+        : ScreenWidget(parent)
+    {
+        QVBoxLayout* vLayout = new QVBoxLayout();
+        vLayout->setAlignment(Qt::AlignTop);
+        vLayout->setContentsMargins(s_contentMargins, 0, s_contentMargins, 0);
+        setLayout(vLayout);
+
+        m_background.load(":/Backgrounds/FirstTimeBackgroundImage.jpg");
+
+        m_stack = new QStackedWidget(this);
+
+        m_firstTimeContent = CreateFirstTimeContent();
+        m_stack->addWidget(m_firstTimeContent);
+
+        m_projectsContent = CreateProjectsContent();
+        m_stack->addWidget(m_projectsContent);
+
+        vLayout->addWidget(m_stack);
+
+        connect(m_createNewProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleNewProjectButton);
+        connect(m_addExistingProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleAddProjectButton);
+    }
+
+    QFrame* ProjectsScreen::CreateFirstTimeContent()
+    {
+        QFrame* frame = new QFrame(this);
+        frame->setObjectName("firstTimeContent");
+        {
+            QVBoxLayout* layout = new QVBoxLayout(this);
+            layout->setContentsMargins(0, 0, 0, 0);
+            layout->setAlignment(Qt::AlignTop);
+            frame->setLayout(layout);
+
+            QLabel* titleLabel = new QLabel(tr("Ready. Set. Create."), this);
+            titleLabel->setObjectName("titleLabel");
+            layout->addWidget(titleLabel);
+
+            QLabel* introLabel = new QLabel(this);
+            introLabel->setObjectName("introLabel");
+            introLabel->setText(tr("Welcome to O3DE! Start something new by creating a project. Not sure what to create? \nExplore what's "
+                                   "available by downloading our sample project."));
+            layout->addWidget(introLabel);
+
+            QHBoxLayout* buttonLayout = new QHBoxLayout(this);
+            buttonLayout->setAlignment(Qt::AlignLeft);
+            buttonLayout->setSpacing(s_spacerSize);
+
+            // use a newline to force the text up
+            QPushButton* createProjectButton = new QPushButton(tr("Create a Project\n"), this);
+            createProjectButton->setObjectName("createProjectButton");
+            buttonLayout->addWidget(createProjectButton);
+
+            QPushButton* addProjectButton = new QPushButton(tr("Add a Project\n"), this);
+            addProjectButton->setObjectName("addProjectButton");
+            buttonLayout->addWidget(addProjectButton);
+
+            connect(createProjectButton, &QPushButton::clicked, this, &ProjectsScreen::HandleNewProjectButton);
+            connect(addProjectButton, &QPushButton::clicked, this, &ProjectsScreen::HandleAddProjectButton);
+
+            layout->addLayout(buttonLayout);
+        }
+
+        return frame;
+    }
+
+    QFrame* ProjectsScreen::CreateProjectsContent()
+    {
+        QFrame* frame = new QFrame(this);
+        frame->setObjectName("projectsContent");
+        {
+            QVBoxLayout* layout = new QVBoxLayout();
+            layout->setAlignment(Qt::AlignTop);
+            layout->setContentsMargins(0, 0, 0, 0);
+            frame->setLayout(layout);
+
+            QFrame* header = new QFrame(this);
+            QHBoxLayout* headerLayout = new QHBoxLayout();
+            {
+                QLabel* titleLabel = new QLabel(tr("My Projects"), this);
+                titleLabel->setObjectName("titleLabel");
+                headerLayout->addWidget(titleLabel);
+
+                QMenu* newProjectMenu = new QMenu(this);
+                m_createNewProjectAction = newProjectMenu->addAction("Create New Project");
+                m_addExistingProjectAction = newProjectMenu->addAction("Add Existing Project");
+
+                connect(m_createNewProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleNewProjectButton);
+                connect(m_addExistingProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleAddProjectButton);
+
+                QPushButton* newProjectMenuButton = new QPushButton(tr("New Project..."), this);
+                newProjectMenuButton->setObjectName("newProjectButton");
+                newProjectMenuButton->setMenu(newProjectMenu);
+                newProjectMenuButton->setDefault(true);
+                headerLayout->addWidget(newProjectMenuButton);
+            }
+            header->setLayout(headerLayout);
+
+            layout->addWidget(header);
+
+            // Get all projects and create a horizontal scrolling list of them
+            auto projectsResult = PythonBindingsInterface::Get()->GetProjects();
+            if (projectsResult.IsSuccess() && !projectsResult.GetValue().isEmpty())
+            {
+                QScrollArea* projectsScrollArea = new QScrollArea(this);
+                QWidget* scrollWidget = new QWidget();
+
+                FlowLayout* flowLayout = new FlowLayout(0, s_spacerSize, s_spacerSize);
+                scrollWidget->setLayout(flowLayout);
+
+                projectsScrollArea->setWidget(scrollWidget);
+                projectsScrollArea->setWidgetResizable(true);
+
+#ifndef DISPLAY_PROJECT_DEV_DATA
+                for (auto project : projectsResult.GetValue())
+#else
+                ProjectInfo project = projectsResult.GetValue().at(0);
+                for (int i = 0; i < 15; i++)
+#endif
+                {
+                    ProjectButton* projectButton;
+                    QString projectPreviewPath = project.m_path + m_projectPreviewImagePath;
+                    QFileInfo doesPreviewExist(projectPreviewPath);
+                    if (doesPreviewExist.exists() && doesPreviewExist.isFile())
+                    {
+                        projectButton = new ProjectButton(project.m_projectName, projectPreviewPath, this);
+                    }
+                    else
+                    {
+                        projectButton = new ProjectButton(project.m_projectName, this);
+                    }
+
+                    flowLayout->addWidget(projectButton);
+
+                    connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject);
+                    connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject);
+
+    #ifdef DISPLAY_PROJECT_DEV_DATA
+                    connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems);
+                    connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject);
+                    connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject);
+                    connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject);
+    #endif
+                }
+
+                layout->addWidget(projectsScrollArea);
+            }
+        }
+
+        return frame;
+    }
+
+    ProjectManagerScreen ProjectsScreen::GetScreenEnum()
+    {
+        return ProjectManagerScreen::Projects;
+    }
+
+    bool ProjectsScreen::IsTab()
+    {
+        return true;
+    }
+
+    QString ProjectsScreen::GetTabText()
+    {
+        return tr("Projects");
+    }
+
+    void ProjectsScreen::paintEvent([[maybe_unused]] QPaintEvent* event)
+    {
+        // we paint the background here because qss does not support background cover scaling
+        QPainter painter(this);
+
+        auto winSize = size();
+        auto pixmapRatio = (float)m_background.width() / m_background.height();
+        auto windowRatio = (float)winSize.width() / winSize.height();
+
+        if (pixmapRatio > windowRatio)
+        {
+            auto newWidth = (int)(winSize.height() * pixmapRatio);
+            auto offset = (newWidth - winSize.width()) / -2;
+            painter.drawPixmap(offset, 0, newWidth, winSize.height(), m_background);
+        }
+        else
+        {
+            auto newHeight = (int)(winSize.width() / pixmapRatio);
+            painter.drawPixmap(0, 0, winSize.width(), newHeight, m_background);
+        }
+    }
+
+    void ProjectsScreen::HandleNewProjectButton()
+    {
+        emit ResetScreenRequest(ProjectManagerScreen::CreateProject);
+        emit ChangeScreenRequest(ProjectManagerScreen::CreateProject);
+    }
+    void ProjectsScreen::HandleAddProjectButton()
+    {
+        // Do nothing for now
+    }
+    void ProjectsScreen::HandleOpenProject(const QString& projectPath)
+    {
+        if (!projectPath.isEmpty())
+        {
+            AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
+            AZStd::string executableFilename = "Editor";
+            AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
+            auto cmdPath = AZ::IO::FixedMaxPathString::format("%s -regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(), projectPath.toStdString().c_str());
+
+            AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
+            processLaunchInfo.m_commandlineParameters = cmdPath;
+            bool launchSucceeded = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
+            if (!launchSucceeded)
+            {
+                AZ_Error("ProjectManager", false, "Failed to launch editor");
+                QMessageBox::critical( this, tr("Error"), tr("Failed to launch the Editor, please verify the project settings are valid."));
+            }
+            else
+            {
+                // prevent the user from accidentally pressing the button while the editor is launching
+                // and let them know what's happening
+                ProjectButton* button = qobject_cast<ProjectButton*>(sender());
+                if (button)
+                {
+                    button->SetButtonEnabled(false);
+                    button->SetButtonOverlayText(tr("Opening Editor..."));
+                }
+
+                // enable the button after 3 seconds 
+                constexpr int waitTimeInMs = 3000;
+                QTimer::singleShot(waitTimeInMs, this, [this, button] {
+                        if (button)
+                        {
+                            button->SetButtonEnabled(true);
+                        }
+                    });
+            }
+        }
+        else
+        {
+            AZ_Error("ProjectManager", false, "Cannot open editor because an empty project path was provided");
+            QMessageBox::critical( this, tr("Error"), tr("Failed to launch the Editor because the project path is invalid."));
+        }
+
+    }
+    void ProjectsScreen::HandleEditProject(const QString& projectPath)
+    {
+        emit NotifyCurrentProject(projectPath);
+        emit ResetScreenRequest(ProjectManagerScreen::UpdateProject);
+        emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
+    }
+    void ProjectsScreen::HandleEditProjectGems(const QString& projectPath)
+    {
+        emit NotifyCurrentProject(projectPath);
+        emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
+    }
+    void ProjectsScreen::HandleCopyProject([[maybe_unused]] const QString& projectPath)
+    {
+        // Open file dialog and choose location for copied project then register copy with O3DE
+    }
+    void ProjectsScreen::HandleRemoveProject([[maybe_unused]] const QString& projectPath)
+    {
+        // Unregister Project from O3DE 
+    }
+    void ProjectsScreen::HandleDeleteProject([[maybe_unused]] const QString& projectPath)
+    {
+        // Remove project from 03DE and delete from disk
+        ProjectsScreen::HandleRemoveProject(projectPath);
+    }
+
+    void ProjectsScreen::NotifyCurrentScreen()
+    {
+        if (ShouldDisplayFirstTimeContent())
+        {
+            m_stack->setCurrentWidget(m_firstTimeContent);
+        }
+        else
+        {
+            m_stack->setCurrentWidget(m_projectsContent);
+        }
+    }
+
+    bool ProjectsScreen::ShouldDisplayFirstTimeContent()
+    {
+        auto projectsResult = PythonBindingsInterface::Get()->GetProjects();
+        if (!projectsResult.IsSuccess() || projectsResult.GetValue().isEmpty())
+        {
+            return true;
+        }
+
+        QSettings settings;
+        bool displayFirstTimeContent = settings.value("displayFirstTimeContent", true).toBool();
+        if (displayFirstTimeContent)
+        {
+            settings.setValue("displayFirstTimeContent", false);
+        }
+
+        return displayFirstTimeContent;
+    }
+
+} // namespace O3DE::ProjectManager

+ 24 - 6
Code/Tools/ProjectManager/Source/ProjectsHomeScreen.h → Code/Tools/ProjectManager/Source/ProjectsScreen.h

@@ -15,16 +15,26 @@
 #include <ScreenWidget.h>
 #endif
 
+QT_FORWARD_DECLARE_CLASS(QPaintEvent)
+QT_FORWARD_DECLARE_CLASS(QFrame)
+QT_FORWARD_DECLARE_CLASS(QStackedWidget)
+
 namespace O3DE::ProjectManager
 {
-    class ProjectsHomeScreen
+    class ProjectsScreen
         : public ScreenWidget
     {
 
     public:
-        explicit ProjectsHomeScreen(QWidget* parent = nullptr);
-        ~ProjectsHomeScreen() = default;
+        explicit ProjectsScreen(QWidget* parent = nullptr);
+        ~ProjectsScreen() = default;
+
         ProjectManagerScreen GetScreenEnum() override;
+        QString GetTabText() override;
+        bool IsTab() override;
+
+    protected:
+        void NotifyCurrentScreen() override;
 
     protected slots:
         void HandleNewProjectButton();
@@ -36,16 +46,24 @@ namespace O3DE::ProjectManager
         void HandleRemoveProject(const QString& projectPath);
         void HandleDeleteProject(const QString& projectPath);
 
+        void paintEvent(QPaintEvent* event) override;
+
     private:
+        QFrame* CreateFirstTimeContent();
+        QFrame* CreateProjectsContent();
+        bool ShouldDisplayFirstTimeContent();
+
         QAction* m_createNewProjectAction;
         QAction* m_addExistingProjectAction;
+        QPixmap m_background;
+        QFrame* m_firstTimeContent;
+        QFrame* m_projectsContent;
+        QStackedWidget* m_stack;
 
         const QString m_projectPreviewImagePath = "/preview.png";
+
         inline constexpr static int s_contentMargins = 80;
         inline constexpr static int s_spacerSize = 20;
-        inline constexpr static int s_projectButtonRowCount = 4;
-        inline constexpr static int s_newProjectButtonWidth = 156;
-
     };
 
 } // namespace O3DE::ProjectManager

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

@@ -17,11 +17,10 @@ namespace O3DE::ProjectManager
     {
         Invalid = -1,
         Empty,
-        FirstTimeUse,
         CreateProject,
         NewProjectSettings,
         GemCatalog,
-        ProjectsHome,
+        Projects,
         UpdateProject,
         ProjectSettings,
         EngineSettings

+ 3 - 7
Code/Tools/ProjectManager/Source/ScreenFactory.cpp

@@ -11,12 +11,11 @@
  */
 #include <ScreenFactory.h>
 
-#include <FirstTimeUseScreen.h>
 #include <CreateProjectCtrl.h>
 #include <UpdateProjectCtrl.h>
 #include <NewProjectSettingsScreen.h>
 #include <GemCatalog/GemCatalogScreen.h>
-#include <ProjectsHomeScreen.h>
+#include <ProjectsScreen.h>
 #include <ProjectSettingsScreen.h>
 #include <EngineSettingsScreen.h>
 
@@ -28,9 +27,6 @@ namespace O3DE::ProjectManager
 
         switch(screen)
         {
-        case (ProjectManagerScreen::FirstTimeUse):
-            newScreen = new FirstTimeUseScreen(parent);
-            break;
         case (ProjectManagerScreen::CreateProject):
             newScreen = new CreateProjectCtrl(parent);
             break;
@@ -40,8 +36,8 @@ namespace O3DE::ProjectManager
         case (ProjectManagerScreen::GemCatalog):
             newScreen = new GemCatalogScreen(parent);
             break;
-        case (ProjectManagerScreen::ProjectsHome):
-            newScreen = new ProjectsHomeScreen(parent);
+        case (ProjectManagerScreen::Projects):
+            newScreen = new ProjectsScreen(parent);
             break;
         case (ProjectManagerScreen::UpdateProject):
             newScreen = new UpdateProjectCtrl(parent);

+ 62 - 0
Code/Tools/ProjectManager/Source/ScreenHeaderWidget.cpp

@@ -0,0 +1,62 @@
+/*
+* 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 <ScreenHeaderWidget.h>
+#include <QHBoxLayout>
+#include <QPushButton>
+#include <QLabel>
+
+namespace O3DE::ProjectManager
+{
+    ScreenHeader::ScreenHeader(QWidget* parent)
+        : QFrame(parent)
+    {
+        setObjectName("header");
+
+        QHBoxLayout* layout = new QHBoxLayout();
+        layout->setAlignment(Qt::AlignLeft);
+        layout->setContentsMargins(0,0,0,0);
+
+        m_backButton = new QPushButton();
+        m_backButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
+        layout->addWidget(m_backButton);
+
+        QVBoxLayout* titleLayout = new QVBoxLayout();
+        m_title = new QLabel();
+        m_title->setObjectName("headerTitle");
+        titleLayout->addWidget(m_title);
+
+        m_subTitle = new QLabel();
+        m_subTitle->setObjectName("headerSubTitle");
+        titleLayout->addWidget(m_subTitle);
+
+        layout->addLayout(titleLayout);
+
+        setLayout(layout);
+    }
+
+    void ScreenHeader::setTitle(const QString& text)
+    {
+        m_title->setText(text);
+    }
+
+    void ScreenHeader::setSubTitle(const QString& text)
+    {
+        m_subTitle->setText(text);
+    }
+
+    QPushButton* ScreenHeader::backButton()
+    {
+        return m_backButton;
+    }
+
+} // namespace O3DE::ProjectManager

+ 42 - 0
Code/Tools/ProjectManager/Source/ScreenHeaderWidget.h

@@ -0,0 +1,42 @@
+/*
+* 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 <QFrame>
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QLabel)
+QT_FORWARD_DECLARE_CLASS(QPushButton)
+
+namespace O3DE::ProjectManager
+{
+    class ScreenHeader 
+        : public QFrame 
+    {
+        Q_OBJECT // AUTOMOC
+
+    public:
+        ScreenHeader(QWidget* parent = nullptr);
+
+        void setTitle(const QString& text);
+        void setSubTitle(const QString& text);
+
+        QPushButton* backButton();
+
+    private:
+        QLabel* m_title;
+        QLabel* m_subTitle;
+        QPushButton* m_backButton;
+    };
+} // namespace O3DE::ProjectManager

+ 15 - 0
Code/Tools/ProjectManager/Source/ScreenWidget.h

@@ -41,12 +41,27 @@ namespace O3DE::ProjectManager
         {
             return true;
         }
+        virtual bool IsTab()
+        {
+            return false;
+        }
+        virtual QString GetTabText()
+        {
+            return tr("Missing");
+        }
+
+        //! Notify this screen it is the current screen 
+        virtual void NotifyCurrentScreen()
+        {
+
+        }
 
     signals:
         void ChangeScreenRequest(ProjectManagerScreen screen);
         void GotoPreviousScreenRequest();
         void ResetScreenRequest(ProjectManagerScreen screen);
         void NotifyCurrentProject(const QString& projectPath);
+
     };
 
 } // namespace O3DE::ProjectManager

+ 83 - 14
Code/Tools/ProjectManager/Source/ScreensCtrl.cpp

@@ -14,6 +14,7 @@
 #include <ScreenFactory.h>
 #include <ScreenWidget.h>
 
+#include <QTabWidget>
 #include <QVBoxLayout>
 
 namespace O3DE::ProjectManager
@@ -21,17 +22,19 @@ namespace O3DE::ProjectManager
     ScreensCtrl::ScreensCtrl(QWidget* parent)
         : QWidget(parent)
     {
+        setObjectName("ScreensCtrl");
+
         QVBoxLayout* vLayout = new QVBoxLayout();
-        vLayout->setMargin(0);
-        vLayout->setSpacing(0);
         vLayout->setContentsMargins(0, 0, 0, 0);
         setLayout(vLayout);
 
         m_screenStack = new QStackedWidget();
         vLayout->addWidget(m_screenStack);
 
-        //Track the bottom of the stack
-        m_screenVisitOrder.push(ProjectManagerScreen::Invalid);
+        // add a tab widget at the bottom of the stack
+        m_tabWidget = new QTabWidget();
+        m_screenStack->addWidget(m_tabWidget);
+        connect(m_tabWidget, &QTabWidget::currentChanged, this, &ScreensCtrl::TabChanged);
     }
 
     void ScreensCtrl::BuildScreens(QVector<ProjectManagerScreen> screens)
@@ -57,7 +60,14 @@ namespace O3DE::ProjectManager
 
     ScreenWidget* ScreensCtrl::GetCurrentScreen()
     {
-        return reinterpret_cast<ScreenWidget*>(m_screenStack->currentWidget());
+        if (m_screenStack->currentWidget() == m_tabWidget)
+        {
+            return reinterpret_cast<ScreenWidget*>(m_tabWidget->currentWidget());
+        }
+        else
+        {
+            return reinterpret_cast<ScreenWidget*>(m_screenStack->currentWidget());
+        }
     }
 
     bool ScreensCtrl::ChangeToScreen(ProjectManagerScreen screen)
@@ -79,13 +89,28 @@ namespace O3DE::ProjectManager
         if (iterator != m_screenMap.end())
         {
             ScreenWidget* currentScreen = GetCurrentScreen();
-            if (currentScreen != iterator.value())
+            ScreenWidget* newScreen = iterator.value();
+
+            if (currentScreen != newScreen)
             {
                 if (addVisit)
                 {
-                    m_screenVisitOrder.push(currentScreen->GetScreenEnum());
+                    ProjectManagerScreen oldScreen = currentScreen->GetScreenEnum();
+                    m_screenVisitOrder.push(oldScreen);
+                }
+
+                if (newScreen->IsTab())
+                {
+                    m_tabWidget->setCurrentWidget(newScreen);
+                    m_screenStack->setCurrentWidget(m_tabWidget);
                 }
-                m_screenStack->setCurrentWidget(iterator.value());
+                else
+                {
+                    m_screenStack->setCurrentWidget(newScreen);
+                }
+
+                newScreen->NotifyCurrentScreen();
+
                 return true;
             }
         }
@@ -95,23 +120,46 @@ namespace O3DE::ProjectManager
 
     bool ScreensCtrl::GotoPreviousScreen()
     {
-        // Don't go back if we are on the first set screen
-        if (m_screenVisitOrder.top() != ProjectManagerScreen::Invalid)
+        if (!m_screenVisitOrder.isEmpty())
         {
             // We do not check with screen if we can go back, we should always be able to go back
-            return ForceChangeToScreen(m_screenVisitOrder.pop(), false);
+            ProjectManagerScreen previousScreen = m_screenVisitOrder.pop();
+            return ForceChangeToScreen(previousScreen, false);
         }
         return false;
     }
 
     void ScreensCtrl::ResetScreen(ProjectManagerScreen screen)
     {
+        bool shouldRestoreCurrentScreen = false;
+        if (GetCurrentScreen() && GetCurrentScreen()->GetScreenEnum() == screen)
+        {
+            shouldRestoreCurrentScreen = true;
+        }
+
         // Delete old screen if it exists to start fresh
         DeleteScreen(screen);
 
         // Add new screen
         ScreenWidget* newScreen = BuildScreen(this, screen);
-        m_screenStack->addWidget(newScreen);
+        if (newScreen->IsTab())
+        {
+            m_tabWidget->addTab(newScreen, newScreen->GetTabText());
+            if (shouldRestoreCurrentScreen)
+            {
+                m_tabWidget->setCurrentWidget(newScreen);
+                m_screenStack->setCurrentWidget(m_tabWidget);
+            }
+        }
+        else
+        {
+            m_screenStack->addWidget(newScreen);
+            if (shouldRestoreCurrentScreen)
+            {
+                m_screenStack->setCurrentWidget(newScreen);
+            }
+        }
+
         m_screenMap.insert(screen, newScreen);
 
         connect(newScreen, &ScreenWidget::ChangeScreenRequest, this, &ScreensCtrl::ChangeToScreen);
@@ -134,8 +182,21 @@ namespace O3DE::ProjectManager
         const auto iter = m_screenMap.find(screen);
         if (iter != m_screenMap.end())
         {
-            m_screenStack->removeWidget(iter.value());
-            iter.value()->deleteLater();
+            ScreenWidget* screenToDelete = iter.value();
+            if (screenToDelete->IsTab())
+            {
+                int tabIndex = m_tabWidget->indexOf(screenToDelete);
+                if (tabIndex > -1)
+                {
+                    m_tabWidget->removeTab(tabIndex);
+                }
+            }
+            else
+            {
+                // if the screen we delete is the current widget, a new one will
+                // be selected automatically (randomly?)
+                m_screenStack->removeWidget(screenToDelete);
+            }
 
             // Erase does not cause a rehash so interators remain valid
             m_screenMap.erase(iter);
@@ -150,4 +211,12 @@ namespace O3DE::ProjectManager
         }
     }
 
+    void ScreensCtrl::TabChanged([[maybe_unused]] int index)
+    {
+        ScreenWidget* screen = reinterpret_cast<ScreenWidget*>(m_tabWidget->currentWidget());
+        if (screen)
+        {
+            screen->NotifyCurrentScreen();
+        }
+    }
 } // namespace O3DE::ProjectManager

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

@@ -18,6 +18,8 @@
 #include <QStack>
 #endif
 
+QT_FORWARD_DECLARE_CLASS(QTabWidget)
+
 namespace O3DE::ProjectManager
 {
     class ScreenWidget;
@@ -46,11 +48,13 @@ namespace O3DE::ProjectManager
         void ResetAllScreens();
         void DeleteScreen(ProjectManagerScreen screen);
         void DeleteAllScreens();
+        void TabChanged(int index);
 
     private:
         QStackedWidget* m_screenStack;
         QHash<ProjectManagerScreen, ScreenWidget*> m_screenMap;
         QStack<ProjectManagerScreen> m_screenVisitOrder;
+        QTabWidget* m_tabWidget;
     };
 
 } // namespace O3DE::ProjectManager

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

@@ -108,7 +108,7 @@ namespace O3DE::ProjectManager
             auto result = PythonBindingsInterface::Get()->UpdateProject(m_projectInfo);
             if (result)
             {
-                emit ChangeScreenRequest(ProjectManagerScreen::ProjectsHome);
+                emit ChangeScreenRequest(ProjectManagerScreen::Projects);
             }
             else
             {

+ 6 - 1
Code/Tools/ProjectManager/Source/main.cpp

@@ -35,7 +35,6 @@ int main(int argc, char* argv[])
     QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
     AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware);
 
-
     AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
     int runSuccess = 0;
     {
@@ -55,6 +54,12 @@ int main(int argc, char* argv[])
         O3DE::ProjectManager::ProjectManagerWindow window(nullptr, engineRootPath);
         window.show();
 
+        // somethings is preventing us from moving the window to the center of the
+        // primary screen - likely an Az style or component helper
+        constexpr int width = 1200;
+        constexpr int height = 800;
+        window.resize(width, height);
+
         runSuccess = app.exec();
     }
     AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();

+ 4 - 5
Code/Tools/ProjectManager/project_manager_files.cmake

@@ -21,8 +21,6 @@ set(FILES
     Source/ScreenWidget.h
     Source/EngineInfo.h
     Source/EngineInfo.cpp
-    Source/FirstTimeUseScreen.h
-    Source/FirstTimeUseScreen.cpp
     Source/FormLineEditWidget.h
     Source/FormLineEditWidget.cpp
     Source/FormBrowseEditWidget.h
@@ -33,7 +31,6 @@ set(FILES
     Source/ProjectManagerWindow.cpp
     Source/ProjectTemplateInfo.h
     Source/ProjectTemplateInfo.cpp
-    Source/ProjectManagerWindow.ui
     Source/PythonBindings.h
     Source/PythonBindings.cpp
     Source/PythonBindingsInterface.h
@@ -45,8 +42,8 @@ set(FILES
     Source/CreateProjectCtrl.cpp
     Source/UpdateProjectCtrl.h
     Source/UpdateProjectCtrl.cpp
-    Source/ProjectsHomeScreen.h
-    Source/ProjectsHomeScreen.cpp
+    Source/ProjectsScreen.h
+    Source/ProjectsScreen.cpp
     Source/ProjectSettingsScreen.h
     Source/ProjectSettingsScreen.cpp
     Source/ProjectSettingsScreen.ui
@@ -54,6 +51,8 @@ set(FILES
     Source/EngineSettingsScreen.cpp
     Source/ProjectButtonWidget.h
     Source/ProjectButtonWidget.cpp
+    Source/ScreenHeaderWidget.h
+    Source/ScreenHeaderWidget.cpp
     Source/LinkWidget.h
     Source/LinkWidget.cpp
     Source/TagWidget.h

+ 2 - 2
Templates/DefaultProject/Template/preview.png

@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a18fae4040a22d2bb359a8ca642b97bb8f6468eeb52e2826b3b029bd8f1350b6
-size 5466
+oid sha256:40949893ed7009eeaa90b7ce6057cb6be9dfaf7b162e3c26ba9dadf985939d7d
+size 2038