ProjectsScreen.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. /*
  2. * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
  3. * its licensors.
  4. *
  5. * For complete copyright and license terms please see the LICENSE at the root of this
  6. * distribution (the "License"). All use of this software is governed by the License,
  7. * or, if provided, by the license below or the license accompanying this file. Do not
  8. * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
  9. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. *
  11. */
  12. #include <ProjectsScreen.h>
  13. #include <ProjectButtonWidget.h>
  14. #include <PythonBindingsInterface.h>
  15. #include <ProjectUtils.h>
  16. #include <AzQtComponents/Components/FlowLayout.h>
  17. #include <AzCore/Platform.h>
  18. #include <AzCore/IO/SystemFile.h>
  19. #include <AzFramework/AzFramework_Traits_Platform.h>
  20. #include <AzFramework/Process/ProcessCommon.h>
  21. #include <AzFramework/Process/ProcessWatcher.h>
  22. #include <AzCore/Utils/Utils.h>
  23. #include <QVBoxLayout>
  24. #include <QHBoxLayout>
  25. #include <QLabel>
  26. #include <QPushButton>
  27. #include <QMenu>
  28. #include <QListView>
  29. #include <QSpacerItem>
  30. #include <QListWidget>
  31. #include <QListWidgetItem>
  32. #include <QFileInfo>
  33. #include <QScrollArea>
  34. #include <QStackedWidget>
  35. #include <QFrame>
  36. #include <QIcon>
  37. #include <QPixmap>
  38. #include <QSettings>
  39. #include <QMessageBox>
  40. #include <QTimer>
  41. //#define DISPLAY_PROJECT_DEV_DATA true
  42. namespace O3DE::ProjectManager
  43. {
  44. ProjectsScreen::ProjectsScreen(QWidget* parent)
  45. : ScreenWidget(parent)
  46. {
  47. QVBoxLayout* vLayout = new QVBoxLayout();
  48. vLayout->setAlignment(Qt::AlignTop);
  49. vLayout->setContentsMargins(s_contentMargins, 0, s_contentMargins, 0);
  50. setLayout(vLayout);
  51. m_background.load(":/Backgrounds/FirstTimeBackgroundImage.jpg");
  52. m_stack = new QStackedWidget(this);
  53. m_firstTimeContent = CreateFirstTimeContent();
  54. m_stack->addWidget(m_firstTimeContent);
  55. m_projectsContent = CreateProjectsContent();
  56. m_stack->addWidget(m_projectsContent);
  57. vLayout->addWidget(m_stack);
  58. }
  59. QFrame* ProjectsScreen::CreateFirstTimeContent()
  60. {
  61. QFrame* frame = new QFrame(this);
  62. frame->setObjectName("firstTimeContent");
  63. {
  64. QVBoxLayout* layout = new QVBoxLayout(this);
  65. layout->setContentsMargins(0, 0, 0, 0);
  66. layout->setAlignment(Qt::AlignTop);
  67. frame->setLayout(layout);
  68. QLabel* titleLabel = new QLabel(tr("Ready. Set. Create."), this);
  69. titleLabel->setObjectName("titleLabel");
  70. layout->addWidget(titleLabel);
  71. QLabel* introLabel = new QLabel(this);
  72. introLabel->setObjectName("introLabel");
  73. introLabel->setText(tr("Welcome to O3DE! Start something new by creating a project. Not sure what to create? \nExplore what's "
  74. "available by downloading our sample project."));
  75. layout->addWidget(introLabel);
  76. QHBoxLayout* buttonLayout = new QHBoxLayout(this);
  77. buttonLayout->setAlignment(Qt::AlignLeft);
  78. buttonLayout->setSpacing(s_spacerSize);
  79. // use a newline to force the text up
  80. QPushButton* createProjectButton = new QPushButton(tr("Create a Project\n"), this);
  81. createProjectButton->setObjectName("createProjectButton");
  82. buttonLayout->addWidget(createProjectButton);
  83. QPushButton* addProjectButton = new QPushButton(tr("Add a Project\n"), this);
  84. addProjectButton->setObjectName("addProjectButton");
  85. buttonLayout->addWidget(addProjectButton);
  86. connect(createProjectButton, &QPushButton::clicked, this, &ProjectsScreen::HandleNewProjectButton);
  87. connect(addProjectButton, &QPushButton::clicked, this, &ProjectsScreen::HandleAddProjectButton);
  88. layout->addLayout(buttonLayout);
  89. }
  90. return frame;
  91. }
  92. QFrame* ProjectsScreen::CreateProjectsContent()
  93. {
  94. QFrame* frame = new QFrame(this);
  95. frame->setObjectName("projectsContent");
  96. {
  97. QVBoxLayout* layout = new QVBoxLayout();
  98. layout->setAlignment(Qt::AlignTop);
  99. layout->setContentsMargins(0, 0, 0, 0);
  100. frame->setLayout(layout);
  101. QFrame* header = new QFrame(this);
  102. QHBoxLayout* headerLayout = new QHBoxLayout();
  103. {
  104. QLabel* titleLabel = new QLabel(tr("My Projects"), this);
  105. titleLabel->setObjectName("titleLabel");
  106. headerLayout->addWidget(titleLabel);
  107. QMenu* newProjectMenu = new QMenu(this);
  108. m_createNewProjectAction = newProjectMenu->addAction("Create New Project");
  109. m_addExistingProjectAction = newProjectMenu->addAction("Add Existing Project");
  110. connect(m_createNewProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleNewProjectButton);
  111. connect(m_addExistingProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleAddProjectButton);
  112. QPushButton* newProjectMenuButton = new QPushButton(tr("New Project..."), this);
  113. newProjectMenuButton->setObjectName("newProjectButton");
  114. newProjectMenuButton->setMenu(newProjectMenu);
  115. newProjectMenuButton->setDefault(true);
  116. headerLayout->addWidget(newProjectMenuButton);
  117. }
  118. header->setLayout(headerLayout);
  119. layout->addWidget(header);
  120. // Get all projects and create a horizontal scrolling list of them
  121. auto projectsResult = PythonBindingsInterface::Get()->GetProjects();
  122. if (projectsResult.IsSuccess() && !projectsResult.GetValue().isEmpty())
  123. {
  124. QScrollArea* projectsScrollArea = new QScrollArea(this);
  125. QWidget* scrollWidget = new QWidget();
  126. FlowLayout* flowLayout = new FlowLayout(0, s_spacerSize, s_spacerSize);
  127. scrollWidget->setLayout(flowLayout);
  128. projectsScrollArea->setWidget(scrollWidget);
  129. projectsScrollArea->setWidgetResizable(true);
  130. #ifndef DISPLAY_PROJECT_DEV_DATA
  131. for (auto project : projectsResult.GetValue())
  132. #else
  133. ProjectInfo project = projectsResult.GetValue().at(0);
  134. for (int i = 0; i < 15; i++)
  135. #endif
  136. {
  137. ProjectButton* projectButton;
  138. QString projectPreviewPath = project.m_path + m_projectPreviewImagePath;
  139. QFileInfo doesPreviewExist(projectPreviewPath);
  140. if (doesPreviewExist.exists() && doesPreviewExist.isFile())
  141. {
  142. project.m_imagePath = projectPreviewPath;
  143. }
  144. projectButton = new ProjectButton(project, this);
  145. flowLayout->addWidget(projectButton);
  146. connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject);
  147. connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject);
  148. connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject);
  149. connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject);
  150. connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject);
  151. #ifdef SHOW_ALL_PROJECT_ACTIONS
  152. connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems);
  153. #endif
  154. }
  155. layout->addWidget(projectsScrollArea);
  156. }
  157. }
  158. return frame;
  159. }
  160. ProjectManagerScreen ProjectsScreen::GetScreenEnum()
  161. {
  162. return ProjectManagerScreen::Projects;
  163. }
  164. bool ProjectsScreen::IsTab()
  165. {
  166. return true;
  167. }
  168. QString ProjectsScreen::GetTabText()
  169. {
  170. return tr("Projects");
  171. }
  172. void ProjectsScreen::paintEvent([[maybe_unused]] QPaintEvent* event)
  173. {
  174. // we paint the background here because qss does not support background cover scaling
  175. QPainter painter(this);
  176. auto winSize = size();
  177. auto pixmapRatio = (float)m_background.width() / m_background.height();
  178. auto windowRatio = (float)winSize.width() / winSize.height();
  179. if (pixmapRatio > windowRatio)
  180. {
  181. auto newWidth = (int)(winSize.height() * pixmapRatio);
  182. auto offset = (newWidth - winSize.width()) / -2;
  183. painter.drawPixmap(offset, 0, newWidth, winSize.height(), m_background);
  184. }
  185. else
  186. {
  187. auto newHeight = (int)(winSize.width() / pixmapRatio);
  188. painter.drawPixmap(0, 0, winSize.width(), newHeight, m_background);
  189. }
  190. }
  191. void ProjectsScreen::HandleNewProjectButton()
  192. {
  193. emit ResetScreenRequest(ProjectManagerScreen::CreateProject);
  194. emit ChangeScreenRequest(ProjectManagerScreen::CreateProject);
  195. }
  196. void ProjectsScreen::HandleAddProjectButton()
  197. {
  198. if (ProjectUtils::AddProjectDialog(this))
  199. {
  200. emit ResetScreenRequest(ProjectManagerScreen::Projects);
  201. emit ChangeScreenRequest(ProjectManagerScreen::Projects);
  202. }
  203. }
  204. void ProjectsScreen::HandleOpenProject(const QString& projectPath)
  205. {
  206. if (!projectPath.isEmpty())
  207. {
  208. AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
  209. AZStd::string executableFilename = "Editor";
  210. AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
  211. auto cmdPath = AZ::IO::FixedMaxPathString::format("%s -regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(), projectPath.toStdString().c_str());
  212. AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
  213. processLaunchInfo.m_commandlineParameters = cmdPath;
  214. bool launchSucceeded = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
  215. if (!launchSucceeded)
  216. {
  217. AZ_Error("ProjectManager", false, "Failed to launch editor");
  218. QMessageBox::critical( this, tr("Error"), tr("Failed to launch the Editor, please verify the project settings are valid."));
  219. }
  220. else
  221. {
  222. // prevent the user from accidentally pressing the button while the editor is launching
  223. // and let them know what's happening
  224. ProjectButton* button = qobject_cast<ProjectButton*>(sender());
  225. if (button)
  226. {
  227. button->SetButtonEnabled(false);
  228. button->SetButtonOverlayText(tr("Opening Editor..."));
  229. }
  230. // enable the button after 3 seconds
  231. constexpr int waitTimeInMs = 3000;
  232. QTimer::singleShot(waitTimeInMs, this, [this, button] {
  233. if (button)
  234. {
  235. button->SetButtonEnabled(true);
  236. }
  237. });
  238. }
  239. }
  240. else
  241. {
  242. AZ_Error("ProjectManager", false, "Cannot open editor because an empty project path was provided");
  243. QMessageBox::critical( this, tr("Error"), tr("Failed to launch the Editor because the project path is invalid."));
  244. }
  245. }
  246. void ProjectsScreen::HandleEditProject(const QString& projectPath)
  247. {
  248. emit NotifyCurrentProject(projectPath);
  249. emit ResetScreenRequest(ProjectManagerScreen::UpdateProject);
  250. emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
  251. }
  252. void ProjectsScreen::HandleEditProjectGems(const QString& projectPath)
  253. {
  254. emit NotifyCurrentProject(projectPath);
  255. emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
  256. }
  257. void ProjectsScreen::HandleCopyProject(const QString& projectPath)
  258. {
  259. // Open file dialog and choose location for copied project then register copy with O3DE
  260. if (ProjectUtils::CopyProjectDialog(projectPath, this))
  261. {
  262. emit ResetScreenRequest(ProjectManagerScreen::Projects);
  263. emit ChangeScreenRequest(ProjectManagerScreen::Projects);
  264. }
  265. }
  266. void ProjectsScreen::HandleRemoveProject(const QString& projectPath)
  267. {
  268. // Unregister Project from O3DE and reload projects
  269. if (ProjectUtils::UnregisterProject(projectPath))
  270. {
  271. emit ResetScreenRequest(ProjectManagerScreen::Projects);
  272. emit ChangeScreenRequest(ProjectManagerScreen::Projects);
  273. }
  274. }
  275. void ProjectsScreen::HandleDeleteProject(const QString& projectPath)
  276. {
  277. QMessageBox::StandardButton warningResult = QMessageBox::warning(
  278. this, tr("Delete Project"), tr("Are you sure?\nProject will be removed from O3DE and directory will be deleted!"),
  279. QMessageBox::No | QMessageBox::Yes);
  280. if (warningResult == QMessageBox::Yes)
  281. {
  282. // Remove project from O3DE and delete from disk
  283. HandleRemoveProject(projectPath);
  284. ProjectUtils::DeleteProjectFiles(projectPath);
  285. }
  286. }
  287. void ProjectsScreen::NotifyCurrentScreen()
  288. {
  289. if (ShouldDisplayFirstTimeContent())
  290. {
  291. m_stack->setCurrentWidget(m_firstTimeContent);
  292. }
  293. else
  294. {
  295. // refresh the projects content by re-creating it for now
  296. if (m_projectsContent)
  297. {
  298. m_stack->removeWidget(m_projectsContent);
  299. m_projectsContent->deleteLater();
  300. }
  301. m_projectsContent = CreateProjectsContent();
  302. m_stack->addWidget(m_projectsContent);
  303. m_stack->setCurrentWidget(m_projectsContent);
  304. }
  305. }
  306. bool ProjectsScreen::ShouldDisplayFirstTimeContent()
  307. {
  308. auto projectsResult = PythonBindingsInterface::Get()->GetProjects();
  309. if (!projectsResult.IsSuccess() || projectsResult.GetValue().isEmpty())
  310. {
  311. return true;
  312. }
  313. QSettings settings;
  314. bool displayFirstTimeContent = settings.value("displayFirstTimeContent", true).toBool();
  315. if (displayFirstTimeContent)
  316. {
  317. settings.setValue("displayFirstTimeContent", false);
  318. }
  319. return displayFirstTimeContent;
  320. }
  321. } // namespace O3DE::ProjectManager