NewProjectSettingsScreen.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <NewProjectSettingsScreen.h>
  9. #include <ProjectManagerDefs.h>
  10. #include <PythonBindingsInterface.h>
  11. #include <FormBrowseEditWidget.h>
  12. #include <FormLineEditWidget.h>
  13. #include <TemplateButtonWidget.h>
  14. #include <PathValidator.h>
  15. #include <EngineInfo.h>
  16. #include <CreateProjectCtrl.h>
  17. #include <TagWidget.h>
  18. #include <AzCore/Math/Uuid.h>
  19. #include <AzQtComponents/Components/FlowLayout.h>
  20. #include <QVBoxLayout>
  21. #include <QHBoxLayout>
  22. #include <QFileDialog>
  23. #include <QLabel>
  24. #include <QLineEdit>
  25. #include <QRadioButton>
  26. #include <QButtonGroup>
  27. #include <QPushButton>
  28. #include <QSpacerItem>
  29. #include <QStandardPaths>
  30. #include <QFrame>
  31. #include <QScrollArea>
  32. #include <QAbstractButton>
  33. namespace O3DE::ProjectManager
  34. {
  35. constexpr const char* k_templateIndexProperty = "TemplateIndex";
  36. NewProjectSettingsScreen::NewProjectSettingsScreen(QWidget* parent)
  37. : ProjectSettingsScreen(parent)
  38. {
  39. const QString defaultName = GetDefaultProjectName();
  40. const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + defaultName);
  41. m_projectName->lineEdit()->setText(defaultName);
  42. m_projectPath->lineEdit()->setText(defaultPath);
  43. // if we don't use a QFrame we cannot "contain" the widgets inside and move them around
  44. // as a group
  45. QFrame* projectTemplateWidget = new QFrame(this);
  46. projectTemplateWidget->setObjectName("projectTemplate");
  47. QVBoxLayout* containerLayout = new QVBoxLayout();
  48. containerLayout->setAlignment(Qt::AlignTop);
  49. {
  50. QLabel* projectTemplateLabel = new QLabel(tr("Select a Project Template"));
  51. projectTemplateLabel->setObjectName("projectTemplateLabel");
  52. containerLayout->addWidget(projectTemplateLabel);
  53. QLabel* projectTemplateDetailsLabel = new QLabel(tr("Project templates are pre-configured with relevant Gems that provide "
  54. "additional functionality and content to the project."));
  55. projectTemplateDetailsLabel->setWordWrap(true);
  56. projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel");
  57. containerLayout->addWidget(projectTemplateDetailsLabel);
  58. // we might have enough templates that we need to scroll
  59. QScrollArea* templatesScrollArea = new QScrollArea(this);
  60. QWidget* scrollWidget = new QWidget();
  61. FlowLayout* flowLayout = new FlowLayout(0, s_spacerSize, s_spacerSize);
  62. scrollWidget->setLayout(flowLayout);
  63. templatesScrollArea->setWidget(scrollWidget);
  64. templatesScrollArea->setWidgetResizable(true);
  65. m_projectTemplateButtonGroup = new QButtonGroup(this);
  66. m_projectTemplateButtonGroup->setObjectName("templateButtonGroup");
  67. // QButtonGroup has overloaded buttonClicked methods so we need the QOverload
  68. connect(
  69. m_projectTemplateButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
  70. [=](QAbstractButton* button)
  71. {
  72. if (button && button->property(k_templateIndexProperty).isValid())
  73. {
  74. int projectTemplateIndex = button->property(k_templateIndexProperty).toInt();
  75. if (m_selectedTemplateIndex != projectTemplateIndex)
  76. {
  77. const int oldIndex = m_selectedTemplateIndex;
  78. m_selectedTemplateIndex = projectTemplateIndex;
  79. UpdateTemplateDetails(m_templates.at(m_selectedTemplateIndex));
  80. emit OnTemplateSelectionChanged(/*oldIndex=*/oldIndex, /*newIndex=*/m_selectedTemplateIndex);
  81. }
  82. }
  83. });
  84. auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates();
  85. if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty())
  86. {
  87. m_templates = templatesResult.GetValue();
  88. // sort alphabetically by display name (but putting Standard first) because they could be in any order
  89. std::sort(m_templates.begin(), m_templates.end(), [](const ProjectTemplateInfo& arg1, const ProjectTemplateInfo& arg2)
  90. {
  91. if (arg1.m_displayName == "Standard")
  92. {
  93. return true;
  94. }
  95. else if (arg2.m_displayName == "Standard")
  96. {
  97. return false;
  98. }
  99. else
  100. {
  101. return arg1.m_displayName.toLower() < arg2.m_displayName.toLower();
  102. }
  103. });
  104. for (int index = 0; index < m_templates.size(); ++index)
  105. {
  106. ProjectTemplateInfo projectTemplate = m_templates.at(index);
  107. QString projectPreviewPath = QDir(projectTemplate.m_path).filePath(ProjectPreviewImagePath);
  108. QFileInfo doesPreviewExist(projectPreviewPath);
  109. if (!doesPreviewExist.exists() || !doesPreviewExist.isFile())
  110. {
  111. projectPreviewPath = ":/DefaultTemplate.png";
  112. }
  113. TemplateButton* templateButton = new TemplateButton(projectPreviewPath, projectTemplate.m_displayName, this);
  114. templateButton->setCheckable(true);
  115. templateButton->setProperty(k_templateIndexProperty, index);
  116. m_projectTemplateButtonGroup->addButton(templateButton);
  117. flowLayout->addWidget(templateButton);
  118. }
  119. // Select the first project template (default selection).
  120. SelectProjectTemplate(0, /*blockSignals=*/true);
  121. }
  122. containerLayout->addWidget(templatesScrollArea);
  123. }
  124. projectTemplateWidget->setLayout(containerLayout);
  125. m_verticalLayout->addWidget(projectTemplateWidget);
  126. QFrame* projectTemplateDetails = CreateTemplateDetails(s_templateDetailsContentMargin);
  127. projectTemplateDetails->setObjectName("projectTemplateDetails");
  128. m_horizontalLayout->addWidget(projectTemplateDetails);
  129. }
  130. QString NewProjectSettingsScreen::GetDefaultProjectPath()
  131. {
  132. QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
  133. AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
  134. if (engineInfoResult.IsSuccess())
  135. {
  136. QDir path(QDir::toNativeSeparators(engineInfoResult.GetValue().m_defaultProjectsFolder));
  137. if (path.exists())
  138. {
  139. defaultPath = path.absolutePath();
  140. }
  141. }
  142. return defaultPath;
  143. }
  144. QString NewProjectSettingsScreen::GetDefaultProjectName()
  145. {
  146. return "NewProject";
  147. }
  148. QString NewProjectSettingsScreen::GetProjectAutoPath()
  149. {
  150. const QString projectName = m_projectName->lineEdit()->text();
  151. return QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + projectName);
  152. }
  153. ProjectManagerScreen NewProjectSettingsScreen::GetScreenEnum()
  154. {
  155. return ProjectManagerScreen::NewProjectSettings;
  156. }
  157. void NewProjectSettingsScreen::NotifyCurrentScreen()
  158. {
  159. if (!m_templates.isEmpty())
  160. {
  161. UpdateTemplateDetails(m_templates.first());
  162. }
  163. Validate();
  164. }
  165. QString NewProjectSettingsScreen::GetProjectTemplatePath()
  166. {
  167. AZ_Assert(m_selectedTemplateIndex == m_projectTemplateButtonGroup->checkedButton()->property(k_templateIndexProperty).toInt(),
  168. "Selected template index not in sync with the currently checked project template button.");
  169. return m_templates.at(m_selectedTemplateIndex).m_path;
  170. }
  171. QFrame* NewProjectSettingsScreen::CreateTemplateDetails(int margin)
  172. {
  173. QFrame* projectTemplateDetails = new QFrame(this);
  174. projectTemplateDetails->setObjectName("projectTemplateDetails");
  175. QVBoxLayout* templateDetailsLayout = new QVBoxLayout();
  176. templateDetailsLayout->setContentsMargins(margin, margin, margin, margin);
  177. templateDetailsLayout->setAlignment(Qt::AlignTop);
  178. {
  179. m_templateDisplayName = new QLabel(this);
  180. m_templateDisplayName->setObjectName("displayName");
  181. templateDetailsLayout->addWidget(m_templateDisplayName);
  182. m_templateSummary = new QLabel(this);
  183. m_templateSummary->setObjectName("summary");
  184. m_templateSummary->setWordWrap(true);
  185. templateDetailsLayout->addWidget(m_templateSummary);
  186. QLabel* includedGemsTitle = new QLabel(tr("Included Gems"), this);
  187. includedGemsTitle->setObjectName("includedGemsTitle");
  188. templateDetailsLayout->addWidget(includedGemsTitle);
  189. m_templateIncludedGems = new TagContainerWidget(this);
  190. m_templateIncludedGems->setObjectName("includedGems");
  191. templateDetailsLayout->addWidget(m_templateIncludedGems);
  192. #ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
  193. QLabel* moreGemsLabel = new QLabel(tr("Looking for more Gems?"), this);
  194. moreGemsLabel->setObjectName("moreGems");
  195. templateDetailsLayout->addWidget(moreGemsLabel);
  196. QLabel* browseCatalogLabel = new QLabel(tr("Browse the Gems Catalog to further customize your project."), this);
  197. browseCatalogLabel->setObjectName("browseCatalog");
  198. browseCatalogLabel->setWordWrap(true);
  199. templateDetailsLayout->addWidget(browseCatalogLabel);
  200. QPushButton* configureGemsButton = new QPushButton(tr("Configure with more Gems"), this);
  201. connect(configureGemsButton, &QPushButton::clicked, this, [=]()
  202. {
  203. emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
  204. });
  205. templateDetailsLayout->addWidget(configureGemsButton);
  206. #endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
  207. }
  208. projectTemplateDetails->setLayout(templateDetailsLayout);
  209. return projectTemplateDetails;
  210. }
  211. void NewProjectSettingsScreen::UpdateTemplateDetails(const ProjectTemplateInfo& templateInfo)
  212. {
  213. m_templateDisplayName->setText(templateInfo.m_displayName);
  214. m_templateSummary->setText(templateInfo.m_summary);
  215. m_templateIncludedGems->Update(templateInfo.m_includedGems);
  216. }
  217. void NewProjectSettingsScreen::SelectProjectTemplate(int index, bool blockSignals)
  218. {
  219. const QList<QAbstractButton*> buttons = m_projectTemplateButtonGroup->buttons();
  220. if (index >= buttons.size())
  221. {
  222. return;
  223. }
  224. if (blockSignals)
  225. {
  226. m_projectTemplateButtonGroup->blockSignals(true);
  227. }
  228. QAbstractButton* button = buttons.at(index);
  229. button->setChecked(true);
  230. m_selectedTemplateIndex = button->property(k_templateIndexProperty).toInt();
  231. if (blockSignals)
  232. {
  233. m_projectTemplateButtonGroup->blockSignals(false);
  234. }
  235. }
  236. void NewProjectSettingsScreen::OnProjectNameUpdated()
  237. {
  238. if (ValidateProjectName() && !m_userChangedProjectPath)
  239. {
  240. m_projectPath->setText(GetProjectAutoPath());
  241. }
  242. }
  243. void NewProjectSettingsScreen::OnProjectPathUpdated()
  244. {
  245. const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + GetDefaultProjectName());
  246. const QString autoPath = GetProjectAutoPath();
  247. const QString path = m_projectPath->lineEdit()->text();
  248. m_userChangedProjectPath = path != defaultPath && path != autoPath;
  249. ValidateProjectPath();
  250. }
  251. } // namespace O3DE::ProjectManager