GemCatalogHeaderWidget.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  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 <GemCatalog/GemCatalogHeaderWidget.h>
  9. #include <TagWidget.h>
  10. #include <AzCore/std/functional.h>
  11. #include <QHBoxLayout>
  12. #include <QMouseEvent>
  13. #include <QLabel>
  14. #include <QPushButton>
  15. #include <QProgressBar>
  16. #include <QMenu>
  17. #include <QLocale>
  18. #include <QMovie>
  19. #include <QPainter>
  20. #include <QPainterPath>
  21. namespace O3DE::ProjectManager
  22. {
  23. GemCartWidget::GemCartWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent)
  24. : QScrollArea(parent)
  25. , m_gemModel(gemModel)
  26. , m_downloadController(downloadController)
  27. {
  28. setObjectName("GemCatalogCart");
  29. setWidgetResizable(true);
  30. setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  31. setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
  32. m_layout = new QVBoxLayout();
  33. m_layout->setSpacing(0);
  34. m_layout->setMargin(5);
  35. m_layout->setAlignment(Qt::AlignTop);
  36. setLayout(m_layout);
  37. setMinimumHeight(400);
  38. QHBoxLayout* hLayout = new QHBoxLayout();
  39. QPushButton* closeButton = new QPushButton();
  40. closeButton->setFlat(true);
  41. closeButton->setFocusPolicy(Qt::NoFocus);
  42. closeButton->setIcon(QIcon(":/WindowClose.svg"));
  43. connect(closeButton, &QPushButton::clicked, this, [=]
  44. {
  45. deleteLater();
  46. });
  47. hLayout->addSpacerItem(new QSpacerItem(10, 0, QSizePolicy::Expanding));
  48. hLayout->addWidget(closeButton);
  49. m_layout->addLayout(hLayout);
  50. // downloading gems
  51. CreateDownloadSection();
  52. // added
  53. CreateGemSection( tr("Gem to be activated"), tr("Gems to be activated"), [=]
  54. {
  55. QVector<QModelIndex> gems;
  56. const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/false);
  57. // don't include gems that were already active because they were dependencies
  58. for (const QModelIndex& modelIndex : toBeAdded)
  59. {
  60. if (!GemModel::WasPreviouslyAddedDependency(modelIndex))
  61. {
  62. gems.push_back(modelIndex);
  63. }
  64. }
  65. return gems;
  66. });
  67. // removed
  68. CreateGemSection( tr("Gem to be deactivated"), tr("Gems to be deactivated"), [=]
  69. {
  70. QVector<QModelIndex> gems;
  71. const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/false);
  72. // don't include gems that are still active because they are dependencies
  73. for (const QModelIndex& modelIndex : toBeAdded)
  74. {
  75. if (!GemModel::IsAddedDependency(modelIndex))
  76. {
  77. gems.push_back(modelIndex);
  78. }
  79. }
  80. return gems;
  81. });
  82. // added dependencies
  83. CreateGemSection( tr("Dependency to be activated"), tr("Dependencies to be activated"), [=]
  84. {
  85. QVector<QModelIndex> dependencies;
  86. const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
  87. // only include gems that are dependencies and not explicitly added
  88. for (const QModelIndex& modelIndex : toBeAdded)
  89. {
  90. if (GemModel::IsAddedDependency(modelIndex) && !GemModel::IsAdded(modelIndex))
  91. {
  92. dependencies.push_back(modelIndex);
  93. }
  94. }
  95. return dependencies;
  96. });
  97. // removed dependencies
  98. CreateGemSection( tr("Dependency to be deactivated"), tr("Dependencies to be deactivated"), [=]
  99. {
  100. QVector<QModelIndex> dependencies;
  101. const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
  102. // don't include gems that were explicitly removed - those are listed in a different section
  103. for (const QModelIndex& modelIndex : toBeRemoved)
  104. {
  105. if (!GemModel::WasPreviouslyAdded(modelIndex))
  106. {
  107. dependencies.push_back(modelIndex);
  108. }
  109. }
  110. return dependencies;
  111. });
  112. }
  113. void GemCartWidget::CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices)
  114. {
  115. QWidget* widget = new QWidget();
  116. widget->setFixedWidth(s_width);
  117. m_layout->addWidget(widget);
  118. QVBoxLayout* layout = new QVBoxLayout();
  119. layout->setAlignment(Qt::AlignTop);
  120. widget->setLayout(layout);
  121. QLabel* label = new QLabel();
  122. label->setObjectName("GemCatalogCartOverlaySectionLabel");
  123. layout->addWidget(label);
  124. TagContainerWidget* tagContainer = new TagContainerWidget();
  125. layout->addWidget(tagContainer);
  126. auto update = [=]()
  127. {
  128. const QVector<QModelIndex> tagIndices = getTagIndices();
  129. if (tagIndices.isEmpty())
  130. {
  131. widget->hide();
  132. }
  133. else
  134. {
  135. tagContainer->Update(GetTagsFromModelIndices(tagIndices));
  136. label->setText(QString("%1 %2").arg(tagIndices.size()).arg(tagIndices.size() == 1 ? singularTitle : pluralTitle));
  137. widget->show();
  138. }
  139. };
  140. connect(m_gemModel, &GemModel::dataChanged, this, update);
  141. update();
  142. }
  143. void GemCartWidget::OnCancelDownloadActivated(const QString& gemName)
  144. {
  145. m_downloadController->CancelObjectDownload(gemName, DownloadController::DownloadObjectType::Gem);
  146. }
  147. void GemCartWidget::CreateDownloadSection()
  148. {
  149. m_downloadSectionWidget = new QWidget();
  150. m_downloadSectionWidget->setFixedWidth(s_width);
  151. m_layout->addWidget(m_downloadSectionWidget);
  152. QVBoxLayout* layout = new QVBoxLayout();
  153. layout->setAlignment(Qt::AlignTop);
  154. m_downloadSectionWidget->setLayout(layout);
  155. QLabel* titleLabel = new QLabel();
  156. titleLabel->setObjectName("GemCatalogCartOverlaySectionLabel");
  157. layout->addWidget(titleLabel);
  158. titleLabel->setText(tr("Gems to be installed"));
  159. // Create header section
  160. QWidget* downloadingGemsWidget = new QWidget();
  161. downloadingGemsWidget->setObjectName("GemCatalogCartOverlayGemDownloadHeader");
  162. layout->addWidget(downloadingGemsWidget);
  163. QVBoxLayout* gemDownloadLayout = new QVBoxLayout();
  164. gemDownloadLayout->setMargin(0);
  165. gemDownloadLayout->setAlignment(Qt::AlignTop);
  166. downloadingGemsWidget->setLayout(gemDownloadLayout);
  167. QLabel* processingQueueLabel = new QLabel("Processing Queue");
  168. gemDownloadLayout->addWidget(processingQueueLabel);
  169. m_downloadingListWidget = new QWidget();
  170. m_downloadingListWidget->setObjectName("GemCatalogCartOverlayGemDownloadBG");
  171. gemDownloadLayout->addWidget(m_downloadingListWidget);
  172. QVBoxLayout* downloadingItemLayout = new QVBoxLayout();
  173. downloadingItemLayout->setAlignment(Qt::AlignTop);
  174. m_downloadingListWidget->setLayout(downloadingItemLayout);
  175. QLabel* downloadsInProgessLabel = new QLabel("");
  176. downloadsInProgessLabel->setObjectName("NumDownloadsInProgressLabel");
  177. downloadingItemLayout->addWidget(downloadsInProgessLabel);
  178. if (m_downloadController->IsDownloadQueueEmpty())
  179. {
  180. m_downloadSectionWidget->hide();
  181. }
  182. else
  183. {
  184. // Setup gem download rows for gems that are already in the queue
  185. const AZStd::deque<DownloadController::DownloadableObject>& downloadQueue = m_downloadController->GetDownloadQueue();
  186. for (const DownloadController::DownloadableObject& o3deObject : downloadQueue)
  187. {
  188. if (o3deObject.m_objectType == DownloadController::DownloadObjectType::Gem)
  189. {
  190. ObjectDownloadAdded(o3deObject.m_objectName, o3deObject.m_objectType);
  191. }
  192. }
  193. }
  194. // connect to download controller data changed
  195. connect(m_downloadController, &DownloadController::ObjectDownloadAdded, this, &GemCartWidget::ObjectDownloadAdded);
  196. connect(m_downloadController, &DownloadController::ObjectDownloadRemoved, this, &GemCartWidget::ObjectDownloadRemoved);
  197. connect(m_downloadController, &DownloadController::ObjectDownloadProgress, this, &GemCartWidget::ObjectDownloadProgress);
  198. }
  199. void GemCartWidget::ObjectDownloadAdded(const QString& gemName, DownloadController::DownloadObjectType objectType)
  200. {
  201. if (objectType != DownloadController::DownloadObjectType::Gem)
  202. {
  203. return;
  204. }
  205. // Containing widget for the current download item
  206. QWidget* newGemDownloadWidget = new QWidget();
  207. newGemDownloadWidget->setObjectName(gemName);
  208. QVBoxLayout* downloadingGemLayout = new QVBoxLayout(newGemDownloadWidget);
  209. newGemDownloadWidget->setLayout(downloadingGemLayout);
  210. // Gem name, progress string, cancel
  211. QHBoxLayout* nameProgressLayout = new QHBoxLayout(newGemDownloadWidget);
  212. TagWidget* newTag = new TagWidget({gemName, gemName}, newGemDownloadWidget);
  213. nameProgressLayout->addWidget(newTag);
  214. QLabel* progress = new QLabel(tr("Queued"), newGemDownloadWidget);
  215. progress->setObjectName("DownloadProgressLabel");
  216. nameProgressLayout->addWidget(progress);
  217. nameProgressLayout->addStretch();
  218. QLabel* cancelText = new QLabel(tr("<a href=\"%1\">Cancel</a>").arg(gemName), newGemDownloadWidget);
  219. cancelText->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
  220. connect(cancelText, &QLabel::linkActivated, this, &GemCartWidget::OnCancelDownloadActivated);
  221. nameProgressLayout->addWidget(cancelText);
  222. downloadingGemLayout->addLayout(nameProgressLayout);
  223. // Progress bar
  224. QProgressBar* downloadProgessBar = new QProgressBar(newGemDownloadWidget);
  225. downloadProgessBar->setObjectName("DownloadProgressBar");
  226. downloadingGemLayout->addWidget(downloadProgessBar);
  227. downloadProgessBar->setValue(0);
  228. m_downloadingListWidget->layout()->addWidget(newGemDownloadWidget);
  229. const AZStd::deque<DownloadController::DownloadableObject>& downloadQueue = m_downloadController->GetDownloadQueue();
  230. QLabel* numDownloads = m_downloadingListWidget->findChild<QLabel*>("NumDownloadsInProgressLabel");
  231. numDownloads->setText(QString("%1 %2")
  232. .arg(downloadQueue.size())
  233. .arg(downloadQueue.size() == 1 ? tr("download in progress...") : tr("downloads in progress...")));
  234. m_downloadingListWidget->show();
  235. }
  236. void GemCartWidget::ObjectDownloadRemoved(const QString& gemName, DownloadController::DownloadObjectType objectType)
  237. {
  238. if (objectType != DownloadController::DownloadObjectType::Gem)
  239. {
  240. return;
  241. }
  242. QWidget* gemToRemove = m_downloadingListWidget->findChild<QWidget*>(gemName);
  243. if (gemToRemove)
  244. {
  245. gemToRemove->deleteLater();
  246. }
  247. if (m_downloadController->IsDownloadQueueEmpty())
  248. {
  249. m_downloadSectionWidget->hide();
  250. }
  251. else
  252. {
  253. size_t downloadQueueSize = m_downloadController->GetDownloadQueue().size();
  254. QLabel* numDownloads = m_downloadingListWidget->findChild<QLabel*>("NumDownloadsInProgressLabel");
  255. numDownloads->setText(QString("%1 %2")
  256. .arg(downloadQueueSize)
  257. .arg(downloadQueueSize == 1 ? tr("download in progress...") : tr("downloads in progress...")));
  258. }
  259. }
  260. void GemCartWidget::ObjectDownloadProgress(const QString& gemName, DownloadController::DownloadObjectType objectType, int bytesDownloaded, int totalBytes)
  261. {
  262. if (objectType != DownloadController::DownloadObjectType::Gem)
  263. {
  264. return;
  265. }
  266. QWidget* gemToUpdate = m_downloadingListWidget->findChild<QWidget*>(gemName);
  267. if (gemToUpdate)
  268. {
  269. QLabel* progressLabel = gemToUpdate->findChild<QLabel*>("DownloadProgressLabel");
  270. QProgressBar* progressBar = gemToUpdate->findChild<QProgressBar*>("DownloadProgressBar");
  271. // totalBytes can be 0 if the server does not return a content-length for the object
  272. if (totalBytes != 0)
  273. {
  274. int downloadPercentage = static_cast<int>((bytesDownloaded / static_cast<float>(totalBytes)) * 100);
  275. if (progressLabel)
  276. {
  277. progressLabel->setText(QString("%1%").arg(downloadPercentage));
  278. }
  279. if (progressBar)
  280. {
  281. progressBar->setValue(downloadPercentage);
  282. }
  283. }
  284. else
  285. {
  286. if (progressLabel)
  287. {
  288. progressLabel->setText(QLocale::system().formattedDataSize(bytesDownloaded));
  289. }
  290. if (progressBar)
  291. {
  292. progressBar->setRange(0, 0);
  293. }
  294. }
  295. }
  296. }
  297. QVector<Tag> GemCartWidget::GetTagsFromModelIndices(const QVector<QModelIndex>& gems) const
  298. {
  299. QVector<Tag> tags;
  300. tags.reserve(gems.size());
  301. for (const QModelIndex& modelIndex : gems)
  302. {
  303. const GemInfo& gemInfo = GemModel::GetGemInfo(modelIndex);
  304. if(gemInfo.m_isEngineGem)
  305. {
  306. // don't show engine gem versions
  307. tags.push_back({ gemInfo.m_displayName, gemInfo.m_name });
  308. }
  309. else
  310. {
  311. // show non-engine gem versions if available
  312. QString version = GemModel::GetNewVersion(modelIndex);
  313. if (version.isEmpty())
  314. {
  315. version = gemInfo.m_version;
  316. }
  317. if (version.isEmpty() || version.contains("Unknown", Qt::CaseInsensitive) || gemInfo.m_displayName.contains(version))
  318. {
  319. tags.push_back({ gemInfo.m_displayName, gemInfo.m_name });
  320. }
  321. else
  322. {
  323. const QString& title = QString("%1 %2").arg(gemInfo.m_displayName, version);
  324. tags.push_back({ title, gemInfo.m_name });
  325. }
  326. }
  327. }
  328. return tags;
  329. }
  330. CartButton::CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent)
  331. : QWidget(parent)
  332. , m_gemModel(gemModel)
  333. , m_downloadController(downloadController)
  334. {
  335. m_layout = new QHBoxLayout();
  336. m_layout->setMargin(0);
  337. setLayout(m_layout);
  338. QPushButton* iconButton = new QPushButton();
  339. iconButton->setFlat(true);
  340. iconButton->setFocusPolicy(Qt::NoFocus);
  341. iconButton->setIcon(QIcon(":/Summary.svg"));
  342. iconButton->setFixedSize(s_iconSize, s_iconSize);
  343. connect(iconButton, &QPushButton::clicked, this, &CartButton::ShowGemCart);
  344. m_layout->addWidget(iconButton);
  345. m_countLabel = new QLabel("0");
  346. m_countLabel->setObjectName("GemCatalogCartCountLabel");
  347. m_countLabel->setFixedHeight(s_iconSize - 1); // Compensate for the empty icon space by using a slightly smaller label height.
  348. m_layout->addWidget(m_countLabel);
  349. m_dropDownButton = new QPushButton();
  350. m_dropDownButton->setFlat(true);
  351. m_dropDownButton->setFocusPolicy(Qt::NoFocus);
  352. m_dropDownButton->setIcon(QIcon(":/CarrotArrowDown.svg"));
  353. m_dropDownButton->setFixedSize(s_arrowDownIconSize, s_arrowDownIconSize);
  354. connect(m_dropDownButton, &QPushButton::clicked, this, &CartButton::ShowGemCart);
  355. m_layout->addWidget(m_dropDownButton);
  356. // Adjust the label text whenever the model gets updated.
  357. connect(gemModel, &GemModel::dataChanged, [=]
  358. {
  359. const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
  360. const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
  361. const int count = toBeAdded.size() + toBeRemoved.size();
  362. m_countLabel->setText(QString::number(count));
  363. m_dropDownButton->setVisible(!toBeAdded.isEmpty() || !toBeRemoved.isEmpty());
  364. // Automatically close the overlay window in case there are no gems to be activated or deactivated anymore.
  365. if (m_gemCart && toBeAdded.isEmpty() && toBeRemoved.isEmpty())
  366. {
  367. m_gemCart->deleteLater();
  368. m_gemCart = nullptr;
  369. }
  370. });
  371. }
  372. void CartButton::mousePressEvent([[maybe_unused]] QMouseEvent* event)
  373. {
  374. ShowGemCart();
  375. }
  376. void CartButton::hideEvent(QHideEvent*)
  377. {
  378. if (m_gemCart)
  379. {
  380. m_gemCart->hide();
  381. }
  382. }
  383. void CartButton::ShowGemCart()
  384. {
  385. const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
  386. const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
  387. if (toBeAdded.isEmpty() && toBeRemoved.isEmpty() && m_downloadController->IsDownloadQueueEmpty())
  388. {
  389. return;
  390. }
  391. if (m_gemCart)
  392. {
  393. // Directly delete the former overlay before creating the new one.
  394. // Don't use deleteLater() here. This might overwrite the new overlay pointer
  395. // depending on the event queue.
  396. delete m_gemCart;
  397. }
  398. m_gemCart = new GemCartWidget(m_gemModel, m_downloadController, this);
  399. connect(m_gemCart, &QWidget::destroyed, this, [=]
  400. {
  401. // Reset the overlay pointer on destruction to prevent dangling pointers.
  402. m_gemCart = nullptr;
  403. // Tell header gem cart is no longer open
  404. UpdateGemCart(nullptr);
  405. });
  406. m_gemCart->show();
  407. emit UpdateGemCart(m_gemCart);
  408. }
  409. CartButton::~CartButton()
  410. {
  411. // Make sure the overlay window is automatically closed in case the gem catalog is destroyed.
  412. if (m_gemCart)
  413. {
  414. m_gemCart->deleteLater();
  415. }
  416. }
  417. GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent)
  418. : QFrame(parent)
  419. , m_downloadController(downloadController)
  420. {
  421. QHBoxLayout* hLayout = new QHBoxLayout();
  422. hLayout->setAlignment(Qt::AlignLeft);
  423. hLayout->setContentsMargins(10, 7, 10, 7);
  424. setLayout(hLayout);
  425. setObjectName("GemCatalogHeaderWidget");
  426. setFixedHeight(s_height);
  427. QLabel* titleLabel = new QLabel(tr("Gem Catalog"));
  428. titleLabel->setObjectName("GemCatalogTitle");
  429. hLayout->addWidget(titleLabel);
  430. hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
  431. m_filterLineEdit = new AzQtComponents::SearchLineEdit();
  432. connect(m_filterLineEdit, &QLineEdit::textChanged, this, [=](const QString& text)
  433. {
  434. filterProxyModel->SetSearchString(text);
  435. });
  436. hLayout->addWidget(m_filterLineEdit);
  437. hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
  438. hLayout->addSpacerItem(new QSpacerItem(75, 0, QSizePolicy::Fixed));
  439. // spinner
  440. m_downloadSpinnerMovie = new QMovie(":/in_progress.gif");
  441. m_downloadSpinner = new QLabel(this);
  442. m_downloadSpinner->setScaledContents(true);
  443. m_downloadSpinner->setMaximumSize(16, 16);
  444. m_downloadSpinner->setMovie(m_downloadSpinnerMovie);
  445. hLayout->addWidget(m_downloadSpinner);
  446. hLayout->addSpacing(8);
  447. // downloading label
  448. m_downloadLabel = new QLabel(tr("Downloading"));
  449. hLayout->addWidget(m_downloadLabel);
  450. m_downloadSpinner->hide();
  451. m_downloadLabel->hide();
  452. hLayout->addSpacing(16);
  453. m_cartButton = new CartButton(gemModel, downloadController);
  454. hLayout->addWidget(m_cartButton);
  455. hLayout->addSpacing(16);
  456. // Separating line
  457. QFrame* vLine = new QFrame();
  458. vLine->setFrameShape(QFrame::VLine);
  459. vLine->setObjectName("verticalSeparatingLine");
  460. hLayout->addWidget(vLine);
  461. hLayout->addSpacing(16);
  462. QMenu* gemMenu = new QMenu(this);
  463. gemMenu->addAction(tr("Refresh"), [this]() { emit RefreshGems(/*refreshRemoteRepos*/true); });
  464. gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); });
  465. gemMenu->addSeparator();
  466. gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); });
  467. gemMenu->addAction( tr("Create New Gem"), [this]() { emit CreateGem(); });
  468. QPushButton* gemMenuButton = new QPushButton(this);
  469. gemMenuButton->setObjectName("gemCatalogMenuButton");
  470. gemMenuButton->setMenu(gemMenu);
  471. gemMenuButton->setIcon(QIcon(":/menu.svg"));
  472. gemMenuButton->setIconSize(QSize(36, 24));
  473. hLayout->addWidget(gemMenuButton);
  474. connect(m_downloadController, &DownloadController::ObjectDownloadAdded, this, &GemCatalogHeaderWidget::GemDownloadAdded);
  475. connect(m_downloadController, &DownloadController::ObjectDownloadRemoved, this, &GemCatalogHeaderWidget::GemDownloadRemoved);
  476. connect(
  477. m_cartButton, &CartButton::UpdateGemCart, this,
  478. [this](QWidget* gemCart)
  479. {
  480. GemCartShown(gemCart);
  481. if (gemCart)
  482. {
  483. emit UpdateGemCart(gemCart);
  484. }
  485. });
  486. }
  487. void GemCatalogHeaderWidget::GemDownloadAdded(const QString& /*gemName*/, DownloadController::DownloadObjectType objectType)
  488. {
  489. if (objectType != DownloadController::DownloadObjectType::Gem)
  490. {
  491. return;
  492. }
  493. m_downloadSpinner->show();
  494. m_downloadLabel->show();
  495. m_downloadSpinnerMovie->start();
  496. m_cartButton->ShowGemCart();
  497. }
  498. void GemCatalogHeaderWidget::GemDownloadRemoved(const QString& /*gemName*/, DownloadController::DownloadObjectType objectType)
  499. {
  500. if (objectType == DownloadController::DownloadObjectType::Gem && m_downloadController->IsDownloadQueueEmpty())
  501. {
  502. m_downloadSpinner->hide();
  503. m_downloadLabel->hide();
  504. m_downloadSpinnerMovie->stop();
  505. }
  506. }
  507. void GemCatalogHeaderWidget::GemCartShown(bool state)
  508. {
  509. m_showGemCart = state;
  510. repaint();
  511. }
  512. void GemCatalogHeaderWidget::ReinitForProject()
  513. {
  514. m_filterLineEdit->setText({});
  515. }
  516. void GemCatalogHeaderWidget::paintEvent([[maybe_unused]] QPaintEvent* event)
  517. {
  518. // Only show triangle when cart is shown
  519. if (!m_showGemCart)
  520. {
  521. return;
  522. }
  523. const QPoint buttonPos = m_cartButton->pos();
  524. const QSize buttonSize = m_cartButton->size();
  525. // Draw isosceles triangle with top point touching bottom of cartButton
  526. // Bottom aligned with header bottom and top of right panel
  527. const QPoint topPoint(buttonPos.x() + buttonSize.width() / 2, buttonPos.y() + buttonSize.height());
  528. const QPoint bottomLeftPoint(topPoint.x() - 20, height());
  529. const QPoint bottomRightPoint(topPoint.x() + 20, height());
  530. QPainterPath trianglePath;
  531. trianglePath.moveTo(topPoint);
  532. trianglePath.lineTo(bottomLeftPoint);
  533. trianglePath.lineTo(bottomRightPoint);
  534. trianglePath.lineTo(topPoint);
  535. QPainter painter(this);
  536. painter.setRenderHint(QPainter::Antialiasing, true);
  537. painter.setPen(Qt::NoPen);
  538. painter.fillPath(trianglePath, QBrush(QColor("#555555")));
  539. }
  540. } // namespace O3DE::ProjectManager