ProjectsScreen.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  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 <AzQtComponents/Components/FlowLayout.h>
  16. #include <AzCore/Platform.h>
  17. #include <AzCore/IO/SystemFile.h>
  18. #include <AzFramework/AzFramework_Traits_Platform.h>
  19. #include <AzFramework/Process/ProcessCommon.h>
  20. #include <AzFramework/Process/ProcessWatcher.h>
  21. #include <AzCore/Utils/Utils.h>
  22. #include <QVBoxLayout>
  23. #include <QHBoxLayout>
  24. #include <QLabel>
  25. #include <QPushButton>
  26. #include <QMenu>
  27. #include <QListView>
  28. #include <QSpacerItem>
  29. #include <QListWidget>
  30. #include <QListWidgetItem>
  31. #include <QFileInfo>
  32. #include <QScrollArea>
  33. #include <QStackedWidget>
  34. #include <QFrame>
  35. #include <QIcon>
  36. #include <QPixmap>
  37. #include <QSettings>
  38. #include <QMessageBox>
  39. #include <QTimer>
  40. //#define DISPLAY_PROJECT_DEV_DATA true
  41. namespace O3DE::ProjectManager
  42. {
  43. ProjectsScreen::ProjectsScreen(QWidget* parent)
  44. : ScreenWidget(parent)
  45. {
  46. QVBoxLayout* vLayout = new QVBoxLayout();
  47. vLayout->setAlignment(Qt::AlignTop);
  48. vLayout->setContentsMargins(s_contentMargins, 0, s_contentMargins, 0);
  49. setLayout(vLayout);
  50. m_background.load(":/Backgrounds/FirstTimeBackgroundImage.jpg");
  51. m_stack = new QStackedWidget(this);
  52. m_firstTimeContent = CreateFirstTimeContent();
  53. m_stack->addWidget(m_firstTimeContent);
  54. m_projectsContent = CreateProjectsContent();
  55. m_stack->addWidget(m_projectsContent);
  56. vLayout->addWidget(m_stack);
  57. connect(m_createNewProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleNewProjectButton);
  58. connect(m_addExistingProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleAddProjectButton);
  59. }
  60. QFrame* ProjectsScreen::CreateFirstTimeContent()
  61. {
  62. QFrame* frame = new QFrame(this);
  63. frame->setObjectName("firstTimeContent");
  64. {
  65. QVBoxLayout* layout = new QVBoxLayout(this);
  66. layout->setContentsMargins(0, 0, 0, 0);
  67. layout->setAlignment(Qt::AlignTop);
  68. frame->setLayout(layout);
  69. QLabel* titleLabel = new QLabel(tr("Ready. Set. Create."), this);
  70. titleLabel->setObjectName("titleLabel");
  71. layout->addWidget(titleLabel);
  72. QLabel* introLabel = new QLabel(this);
  73. introLabel->setObjectName("introLabel");
  74. introLabel->setText(tr("Welcome to O3DE! Start something new by creating a project. Not sure what to create? \nExplore what's "
  75. "available by downloading our sample project."));
  76. layout->addWidget(introLabel);
  77. QHBoxLayout* buttonLayout = new QHBoxLayout(this);
  78. buttonLayout->setAlignment(Qt::AlignLeft);
  79. buttonLayout->setSpacing(s_spacerSize);
  80. // use a newline to force the text up
  81. QPushButton* createProjectButton = new QPushButton(tr("Create a Project\n"), this);
  82. createProjectButton->setObjectName("createProjectButton");
  83. buttonLayout->addWidget(createProjectButton);
  84. QPushButton* addProjectButton = new QPushButton(tr("Add a Project\n"), this);
  85. addProjectButton->setObjectName("addProjectButton");
  86. buttonLayout->addWidget(addProjectButton);
  87. connect(createProjectButton, &QPushButton::clicked, this, &ProjectsScreen::HandleNewProjectButton);
  88. connect(addProjectButton, &QPushButton::clicked, this, &ProjectsScreen::HandleAddProjectButton);
  89. layout->addLayout(buttonLayout);
  90. }
  91. return frame;
  92. }
  93. QFrame* ProjectsScreen::CreateProjectsContent()
  94. {
  95. QFrame* frame = new QFrame(this);
  96. frame->setObjectName("projectsContent");
  97. {
  98. QVBoxLayout* layout = new QVBoxLayout();
  99. layout->setAlignment(Qt::AlignTop);
  100. layout->setContentsMargins(0, 0, 0, 0);
  101. frame->setLayout(layout);
  102. QFrame* header = new QFrame(this);
  103. QHBoxLayout* headerLayout = new QHBoxLayout();
  104. {
  105. QLabel* titleLabel = new QLabel(tr("My Projects"), this);
  106. titleLabel->setObjectName("titleLabel");
  107. headerLayout->addWidget(titleLabel);
  108. QMenu* newProjectMenu = new QMenu(this);
  109. m_createNewProjectAction = newProjectMenu->addAction("Create New Project");
  110. m_addExistingProjectAction = newProjectMenu->addAction("Add Existing Project");
  111. connect(m_createNewProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleNewProjectButton);
  112. connect(m_addExistingProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleAddProjectButton);
  113. QPushButton* newProjectMenuButton = new QPushButton(tr("New Project..."), this);
  114. newProjectMenuButton->setObjectName("newProjectButton");
  115. newProjectMenuButton->setMenu(newProjectMenu);
  116. newProjectMenuButton->setDefault(true);
  117. headerLayout->addWidget(newProjectMenuButton);
  118. }
  119. header->setLayout(headerLayout);
  120. layout->addWidget(header);
  121. // Get all projects and create a horizontal scrolling list of them
  122. auto projectsResult = PythonBindingsInterface::Get()->GetProjects();
  123. if (projectsResult.IsSuccess() && !projectsResult.GetValue().isEmpty())
  124. {
  125. QScrollArea* projectsScrollArea = new QScrollArea(this);
  126. QWidget* scrollWidget = new QWidget();
  127. FlowLayout* flowLayout = new FlowLayout(0, s_spacerSize, s_spacerSize);
  128. scrollWidget->setLayout(flowLayout);
  129. projectsScrollArea->setWidget(scrollWidget);
  130. projectsScrollArea->setWidgetResizable(true);
  131. #ifndef DISPLAY_PROJECT_DEV_DATA
  132. for (auto project : projectsResult.GetValue())
  133. #else
  134. ProjectInfo project = projectsResult.GetValue().at(0);
  135. for (int i = 0; i < 15; i++)
  136. #endif
  137. {
  138. ProjectButton* projectButton;
  139. QString projectPreviewPath = project.m_path + m_projectPreviewImagePath;
  140. QFileInfo doesPreviewExist(projectPreviewPath);
  141. if (doesPreviewExist.exists() && doesPreviewExist.isFile())
  142. {
  143. projectButton = new ProjectButton(project.m_projectName, projectPreviewPath, this);
  144. }
  145. else
  146. {
  147. projectButton = new ProjectButton(project.m_projectName, this);
  148. }
  149. flowLayout->addWidget(projectButton);
  150. connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject);
  151. connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject);
  152. #ifdef DISPLAY_PROJECT_DEV_DATA
  153. connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems);
  154. connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject);
  155. connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject);
  156. connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject);
  157. #endif
  158. }
  159. layout->addWidget(projectsScrollArea);
  160. }
  161. }
  162. return frame;
  163. }
  164. ProjectManagerScreen ProjectsScreen::GetScreenEnum()
  165. {
  166. return ProjectManagerScreen::Projects;
  167. }
  168. bool ProjectsScreen::IsTab()
  169. {
  170. return true;
  171. }
  172. QString ProjectsScreen::GetTabText()
  173. {
  174. return tr("Projects");
  175. }
  176. void ProjectsScreen::paintEvent([[maybe_unused]] QPaintEvent* event)
  177. {
  178. // we paint the background here because qss does not support background cover scaling
  179. QPainter painter(this);
  180. auto winSize = size();
  181. auto pixmapRatio = (float)m_background.width() / m_background.height();
  182. auto windowRatio = (float)winSize.width() / winSize.height();
  183. if (pixmapRatio > windowRatio)
  184. {
  185. auto newWidth = (int)(winSize.height() * pixmapRatio);
  186. auto offset = (newWidth - winSize.width()) / -2;
  187. painter.drawPixmap(offset, 0, newWidth, winSize.height(), m_background);
  188. }
  189. else
  190. {
  191. auto newHeight = (int)(winSize.width() / pixmapRatio);
  192. painter.drawPixmap(0, 0, winSize.width(), newHeight, m_background);
  193. }
  194. }
  195. void ProjectsScreen::HandleNewProjectButton()
  196. {
  197. emit ResetScreenRequest(ProjectManagerScreen::CreateProject);
  198. emit ChangeScreenRequest(ProjectManagerScreen::CreateProject);
  199. }
  200. void ProjectsScreen::HandleAddProjectButton()
  201. {
  202. // Do nothing for now
  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([[maybe_unused]] const QString& projectPath)
  258. {
  259. // Open file dialog and choose location for copied project then register copy with O3DE
  260. }
  261. void ProjectsScreen::HandleRemoveProject([[maybe_unused]] const QString& projectPath)
  262. {
  263. // Unregister Project from O3DE
  264. }
  265. void ProjectsScreen::HandleDeleteProject([[maybe_unused]] const QString& projectPath)
  266. {
  267. // Remove project from 03DE and delete from disk
  268. ProjectsScreen::HandleRemoveProject(projectPath);
  269. }
  270. void ProjectsScreen::NotifyCurrentScreen()
  271. {
  272. if (ShouldDisplayFirstTimeContent())
  273. {
  274. m_stack->setCurrentWidget(m_firstTimeContent);
  275. }
  276. else
  277. {
  278. m_stack->setCurrentWidget(m_projectsContent);
  279. }
  280. }
  281. bool ProjectsScreen::ShouldDisplayFirstTimeContent()
  282. {
  283. auto projectsResult = PythonBindingsInterface::Get()->GetProjects();
  284. if (!projectsResult.IsSuccess() || projectsResult.GetValue().isEmpty())
  285. {
  286. return true;
  287. }
  288. QSettings settings;
  289. bool displayFirstTimeContent = settings.value("displayFirstTimeContent", true).toBool();
  290. if (displayFirstTimeContent)
  291. {
  292. settings.setValue("displayFirstTimeContent", false);
  293. }
  294. return displayFirstTimeContent;
  295. }
  296. } // namespace O3DE::ProjectManager