GemItemDelegate.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  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/GemItemDelegate.h>
  9. #include <GemCatalog/GemModel.h>
  10. #include <GemCatalog/GemSortFilterProxyModel.h>
  11. #include <AdjustableHeaderWidget.h>
  12. #include <ProjectManagerDefs.h>
  13. #include <AzCore/std/smart_ptr/unique_ptr.h>
  14. #include <QEvent>
  15. #include <QAbstractItemView>
  16. #include <QPainter>
  17. #include <QMouseEvent>
  18. #include <QHelpEvent>
  19. #include <QToolTip>
  20. #include <QHoverEvent>
  21. #include <QTextDocument>
  22. #include <QAbstractTextDocumentLayout>
  23. #include <QDesktopServices>
  24. #include <QMovie>
  25. #include <QHeaderView>
  26. #include <QDir>
  27. namespace O3DE::ProjectManager
  28. {
  29. GemItemDelegate::GemItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent)
  30. : QStyledItemDelegate(parent)
  31. , m_model(model)
  32. , m_headerWidget(header)
  33. {
  34. AddPlatformIcon(GemInfo::Android, ":/Android.svg");
  35. AddPlatformIcon(GemInfo::iOS, ":/iOS.svg");
  36. AddPlatformIcon(GemInfo::Linux, ":/Linux.svg");
  37. AddPlatformIcon(GemInfo::macOS, ":/macOS.svg");
  38. AddPlatformIcon(GemInfo::Windows, ":/Windows.svg");
  39. SetStatusIcon(m_notDownloadedPixmap, ":/Download.svg");
  40. SetStatusIcon(m_unknownStatusPixmap, ":/X.svg");
  41. SetStatusIcon(m_downloadSuccessfulPixmap, ":/checkmark.svg");
  42. SetStatusIcon(m_downloadFailedPixmap, ":/Warning.svg");
  43. m_downloadingMovie = new QMovie(":/in_progress.gif");
  44. }
  45. void GemItemDelegate::AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath)
  46. {
  47. QPixmap pixmap(iconPath);
  48. qreal aspectRatio = static_cast<qreal>(pixmap.width()) / pixmap.height();
  49. m_platformIcons.insert(platform, QIcon(iconPath).pixmap(static_cast<int>(static_cast<qreal>(s_platformIconSize) * aspectRatio), s_platformIconSize));
  50. }
  51. void GemItemDelegate::SetStatusIcon(QPixmap& m_iconPixmap, const QString& iconPath)
  52. {
  53. QPixmap pixmap(iconPath);
  54. float aspectRatio = static_cast<float>(pixmap.width()) / pixmap.height();
  55. int xScaler = s_statusIconSize;
  56. int yScaler = s_statusIconSize;
  57. if (aspectRatio > 1.0f)
  58. {
  59. yScaler = static_cast<int>(1.0f / aspectRatio * s_statusIconSize);
  60. }
  61. else if (aspectRatio < 1.0f)
  62. {
  63. xScaler = static_cast<int>(aspectRatio * s_statusIconSize);
  64. }
  65. m_iconPixmap = QPixmap(QIcon(iconPath).pixmap(xScaler, yScaler));
  66. }
  67. void GemItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
  68. {
  69. if (!modelIndex.isValid())
  70. {
  71. return;
  72. }
  73. QStyleOptionViewItem options(option);
  74. initStyleOption(&options, modelIndex);
  75. painter->setRenderHint(QPainter::Antialiasing);
  76. QRect fullRect, itemRect, contentRect;
  77. CalcRects(options, fullRect, itemRect, contentRect);
  78. QRect buttonRect = CalcButtonRect(contentRect);
  79. QFont standardFont(options.font);
  80. standardFont.setPixelSize(static_cast<int>(s_fontSize));
  81. QFontMetrics standardFontMetrics(standardFont);
  82. painter->save();
  83. painter->setClipping(true);
  84. painter->setClipRect(fullRect);
  85. painter->setFont(options.font);
  86. // Draw background
  87. painter->fillRect(fullRect, m_backgroundColor);
  88. // Draw item background
  89. const QColor itemBackgroundColor = options.state & QStyle::State_MouseOver ? m_itemBackgroundColor.lighter(120) : m_itemBackgroundColor;
  90. painter->fillRect(itemRect, itemBackgroundColor);
  91. // Draw border
  92. if (options.state & QStyle::State_Selected)
  93. {
  94. painter->save();
  95. QPen borderPen(m_borderColor);
  96. borderPen.setWidth(s_borderWidth);
  97. painter->setPen(borderPen);
  98. painter->drawRect(itemRect);
  99. painter->restore();
  100. }
  101. // Gem preview
  102. QString previewPath = QDir(GemModel::GetPath(modelIndex)).filePath(ProjectPreviewImagePath);
  103. QPixmap gemPreviewImage(previewPath);
  104. QRect gemPreviewRect(
  105. contentRect.left() + AdjustableHeaderWidget::s_headerTextIndent,
  106. contentRect.center().y() - GemPreviewImageHeight / 2,
  107. GemPreviewImageWidth, GemPreviewImageHeight);
  108. painter->drawPixmap(gemPreviewRect, gemPreviewImage);
  109. // Gem name
  110. QString gemName = GemModel::GetDisplayName(modelIndex);
  111. QFont gemNameFont(options.font);
  112. QPair<int, int> nameXBounds = CalcColumnXBounds(HeaderOrder::Name);
  113. const int nameStartX = nameXBounds.first;
  114. const int nameColumnTextStartX = s_itemMargins.left() + nameStartX + AdjustableHeaderWidget::s_headerTextIndent;
  115. const int nameColumnMaxTextWidth = nameXBounds.second - nameStartX - AdjustableHeaderWidget::s_headerTextIndent;
  116. gemNameFont.setPixelSize(static_cast<int>(s_gemNameFontSize));
  117. gemNameFont.setBold(true);
  118. gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, nameColumnMaxTextWidth);
  119. QRect gemNameRect = GetTextRect(gemNameFont, gemName, s_gemNameFontSize);
  120. gemNameRect.moveTo(nameColumnTextStartX, contentRect.top());
  121. painter->setFont(gemNameFont);
  122. painter->setPen(m_textColor);
  123. gemNameRect = painter->boundingRect(gemNameRect, Qt::TextSingleLine, gemName);
  124. painter->drawText(gemNameRect, Qt::TextSingleLine, gemName);
  125. // Gem creator
  126. QString gemCreator = GemModel::GetCreator(modelIndex);
  127. gemCreator = standardFontMetrics.elidedText(gemCreator, Qt::TextElideMode::ElideRight, nameColumnMaxTextWidth);
  128. QRect gemCreatorRect = GetTextRect(standardFont, gemCreator, s_fontSize);
  129. gemCreatorRect.moveTo(nameColumnTextStartX, contentRect.top() + gemNameRect.height());
  130. painter->setFont(standardFont);
  131. gemCreatorRect = painter->boundingRect(gemCreatorRect, Qt::TextSingleLine, gemCreator);
  132. painter->drawText(gemCreatorRect, Qt::TextSingleLine, gemCreator);
  133. // Gem summary
  134. const QStringList featureTags = GemModel::GetFeatures(modelIndex);
  135. const bool hasTags = !featureTags.isEmpty();
  136. const QString summary = GemModel::GetSummary(modelIndex);
  137. const QRect summaryRect = CalcSummaryRect(contentRect, hasTags);
  138. DrawText(summary, painter, summaryRect, standardFont);
  139. DrawDownloadStatusIcon(painter, contentRect, buttonRect, modelIndex);
  140. DrawButton(painter, buttonRect, modelIndex);
  141. DrawPlatformIcons(painter, contentRect, modelIndex);
  142. DrawFeatureTags(painter, contentRect, featureTags, standardFont, summaryRect);
  143. painter->restore();
  144. }
  145. QRect GemItemDelegate::CalcSummaryRect(const QRect& contentRect, bool hasTags) const
  146. {
  147. const int featureTagAreaHeight = 40;
  148. const int summaryHeight = contentRect.height() - (hasTags * featureTagAreaHeight);
  149. const auto [summaryStartX, summaryEndX] = CalcColumnXBounds(HeaderOrder::Summary);
  150. const QSize summarySize =
  151. QSize(summaryEndX - summaryStartX - AdjustableHeaderWidget::s_headerTextIndent - s_extraSummarySpacing,
  152. summaryHeight);
  153. return QRect(
  154. QPoint(s_itemMargins.left() + summaryStartX + AdjustableHeaderWidget::s_headerTextIndent, contentRect.top()), summarySize);
  155. }
  156. QSize GemItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
  157. {
  158. QStyleOptionViewItem options(option);
  159. initStyleOption(&options, modelIndex);
  160. int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right();
  161. return QSize(marginsHorizontal + s_buttonWidth + s_defaultSummaryStartX, s_height);
  162. }
  163. bool GemItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex)
  164. {
  165. if (!modelIndex.isValid())
  166. {
  167. return false;
  168. }
  169. if (event->type() == QEvent::KeyPress)
  170. {
  171. auto keyEvent = static_cast<const QKeyEvent*>(event);
  172. if (keyEvent->key() == Qt::Key_Space)
  173. {
  174. const bool isAdded = GemModel::IsAdded(modelIndex);
  175. GemModel::SetIsAdded(*model, modelIndex, !isAdded);
  176. return true;
  177. }
  178. }
  179. else if (event->type() == QEvent::MouseButtonPress)
  180. {
  181. QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
  182. QRect fullRect, itemRect, contentRect;
  183. CalcRects(option, fullRect, itemRect, contentRect);
  184. const QRect buttonRect = CalcButtonRect(contentRect);
  185. if (buttonRect.contains(mouseEvent->pos()))
  186. {
  187. const bool isAdded = GemModel::IsAdded(modelIndex);
  188. GemModel::SetIsAdded(*model, modelIndex, !isAdded);
  189. return true;
  190. }
  191. // we must manually handle html links because we aren't using QLabels
  192. const QStringList featureTags = GemModel::GetFeatures(modelIndex);
  193. const bool hasTags = !featureTags.isEmpty();
  194. const QRect summaryRect = CalcSummaryRect(contentRect, hasTags);
  195. if (summaryRect.contains(mouseEvent->pos()))
  196. {
  197. const QString html = GemModel::GetSummary(modelIndex);
  198. QString anchor = anchorAt(html, mouseEvent->pos(), summaryRect);
  199. if (!anchor.isEmpty())
  200. {
  201. QDesktopServices::openUrl(QUrl(anchor));
  202. return true;
  203. }
  204. }
  205. }
  206. return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
  207. }
  208. QString GetGemNameList(const QVector<QModelIndex> modelIndices)
  209. {
  210. QString gemNameList;
  211. for (int i = 0; i < modelIndices.size(); ++i)
  212. {
  213. if (!gemNameList.isEmpty())
  214. {
  215. if (i == modelIndices.size() - 1)
  216. {
  217. gemNameList.append(" and ");
  218. }
  219. else
  220. {
  221. gemNameList.append(", ");
  222. }
  223. }
  224. gemNameList.append(GemModel::GetDisplayName(modelIndices[i]));
  225. }
  226. return gemNameList;
  227. }
  228. bool GemItemDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index)
  229. {
  230. if (event->type() == QEvent::ToolTip)
  231. {
  232. QRect fullRect, itemRect, contentRect;
  233. CalcRects(option, fullRect, itemRect, contentRect);
  234. const QRect buttonRect = CalcButtonRect(contentRect);
  235. if (buttonRect.contains(event->pos()))
  236. {
  237. if (!QToolTip::isVisible())
  238. {
  239. if(GemModel::IsAddedDependency(index) && !GemModel::IsAdded(index))
  240. {
  241. const GemModel* gemModel = GemModel::GetSourceModel(index.model());
  242. AZ_Assert(gemModel, "Failed to obtain GemModel");
  243. // we only want to display the gems that must be de-selected to automatically
  244. // disable this dependency, so don't include any that haven't been selected (added)
  245. constexpr bool addedOnly = true;
  246. QVector<QModelIndex> dependents = gemModel->GatherDependentGems(index, addedOnly);
  247. QString nameList = GetGemNameList(dependents);
  248. if (!nameList.isEmpty())
  249. {
  250. QToolTip::showText(event->globalPos(), tr("This gem is a dependency of %1.\nTo disable this gem, first disable %1.").arg(nameList));
  251. }
  252. }
  253. }
  254. return true;
  255. }
  256. else if (QToolTip::isVisible())
  257. {
  258. QToolTip::hideText();
  259. event->ignore();
  260. return true;
  261. }
  262. }
  263. return QStyledItemDelegate::helpEvent(event, view, option, index);
  264. }
  265. void GemItemDelegate::CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const
  266. {
  267. outFullRect = QRect(option.rect);
  268. outItemRect = QRect(outFullRect.adjusted(s_itemMargins.left(), s_itemMargins.top(), -s_itemMargins.right(), -s_itemMargins.bottom()));
  269. outContentRect = QRect(outItemRect.adjusted(s_contentMargins.left(), s_contentMargins.top(), -s_contentMargins.right(), -s_contentMargins.bottom()));
  270. }
  271. QRect GemItemDelegate::GetTextRect(QFont& font, const QString& text, qreal fontSize) const
  272. {
  273. font.setPixelSize(static_cast<int>(fontSize));
  274. return QFontMetrics(font).boundingRect(text);
  275. }
  276. QPair<int, int> GemItemDelegate::CalcColumnXBounds(HeaderOrder header) const
  277. {
  278. return m_headerWidget->CalcColumnXBounds(static_cast<int>(header));
  279. }
  280. QRect GemItemDelegate::CalcButtonRect(const QRect& contentRect) const
  281. {
  282. const QPoint topLeft = QPoint(
  283. s_itemMargins.left() + CalcColumnXBounds(HeaderOrder::Status).first + AdjustableHeaderWidget::s_headerTextIndent + s_statusIconSize +
  284. s_statusButtonSpacing,
  285. contentRect.center().y() - s_buttonHeight / 2);
  286. const QSize size = QSize(s_buttonWidth, s_buttonHeight);
  287. return QRect(topLeft, size);
  288. }
  289. void GemItemDelegate::DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const
  290. {
  291. const GemInfo::Platforms platforms = GemModel::GetPlatforms(modelIndex);
  292. int startX = s_itemMargins.left() + CalcColumnXBounds(HeaderOrder::Name).first + AdjustableHeaderWidget::s_headerTextIndent;
  293. // Iterate and draw the platforms in the order they are defined in the enum.
  294. for (int i = 0; i < GemInfo::NumPlatforms; ++i)
  295. {
  296. // Check if the platform is supported by the given gem.
  297. const GemInfo::Platform platform = static_cast<GemInfo::Platform>(1 << i);
  298. if (platforms & platform)
  299. {
  300. // Get the icon for the platform and draw it.
  301. const auto iterator = m_platformIcons.find(platform);
  302. if (iterator != m_platformIcons.end())
  303. {
  304. const QPixmap& pixmap = iterator.value();
  305. painter->drawPixmap(contentRect.left() + startX, contentRect.bottom() - s_platformIconSize, pixmap);
  306. qreal aspectRatio = static_cast<qreal>(pixmap.width()) / pixmap.height();
  307. startX += static_cast<int>(s_platformIconSize * aspectRatio + s_platformIconSize / 2.5);
  308. }
  309. }
  310. }
  311. }
  312. void GemItemDelegate::DrawFeatureTags(
  313. QPainter* painter,
  314. const QRect& contentRect,
  315. const QStringList& featureTags,
  316. const QFont& standardFont,
  317. const QRect& summaryRect) const
  318. {
  319. QFont gemFeatureTagFont(standardFont);
  320. gemFeatureTagFont.setPixelSize(s_featureTagFontSize);
  321. gemFeatureTagFont.setBold(false);
  322. painter->setFont(gemFeatureTagFont);
  323. int x = CalcColumnXBounds(HeaderOrder::Summary).first + AdjustableHeaderWidget::s_headerTextIndent;
  324. for (const QString& featureTag : featureTags)
  325. {
  326. QRect featureTagRect = GetTextRect(gemFeatureTagFont, featureTag, s_featureTagFontSize);
  327. featureTagRect.moveTo(s_itemMargins.left() + x + s_featureTagBorderMarginX,
  328. contentRect.top() + 47);
  329. featureTagRect = painter->boundingRect(featureTagRect, Qt::TextSingleLine, featureTag);
  330. QRect backgroundRect = featureTagRect;
  331. backgroundRect = backgroundRect.adjusted(/*left=*/-s_featureTagBorderMarginX,
  332. /*top=*/-s_featureTagBorderMarginY,
  333. /*right=*/s_featureTagBorderMarginX,
  334. /*bottom=*/s_featureTagBorderMarginY);
  335. // Skip drawing all following feature tags as there is no more space available.
  336. if (backgroundRect.right() > summaryRect.right())
  337. {
  338. break;
  339. }
  340. // Draw border.
  341. painter->setPen(m_textColor);
  342. painter->setBrush(Qt::NoBrush);
  343. painter->drawRect(backgroundRect);
  344. // Draw text within the border.
  345. painter->setPen(m_textColor);
  346. painter->drawText(featureTagRect, Qt::TextSingleLine, featureTag);
  347. x += backgroundRect.width() + s_featureTagSpacing;
  348. }
  349. }
  350. AZStd::unique_ptr<QTextDocument> GetTextDocument(const QString& text, int width)
  351. {
  352. // using unique_ptr as a workaround for QTextDocument having a private copy constructor
  353. auto doc = AZStd::make_unique<QTextDocument>();
  354. QTextOption textOption(doc->defaultTextOption());
  355. textOption.setWrapMode(QTextOption::WordWrap);
  356. doc->setDefaultTextOption(textOption);
  357. doc->setHtml(text);
  358. doc->setTextWidth(width);
  359. return doc;
  360. }
  361. void GemItemDelegate::DrawText(const QString& text, QPainter* painter, const QRect& rect, const QFont& standardFont) const
  362. {
  363. painter->save();
  364. if (text.contains('<'))
  365. {
  366. painter->translate(rect.topLeft());
  367. // use QTextDocument because drawText does not support rich text or html
  368. QAbstractTextDocumentLayout::PaintContext paintContext;
  369. paintContext.clip = QRect(0, 0, rect.width(), rect.height());
  370. paintContext.palette.setColor(QPalette::Text, painter->pen().color());
  371. AZStd::unique_ptr<QTextDocument> textDocument = GetTextDocument(text, rect.width());
  372. textDocument->documentLayout()->draw(painter, paintContext);
  373. }
  374. else
  375. {
  376. painter->setFont(standardFont);
  377. painter->setPen(m_textColor);
  378. painter->drawText(rect, Qt::AlignLeft | Qt::TextWordWrap, text);
  379. }
  380. painter->restore();
  381. }
  382. void GemItemDelegate::DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const
  383. {
  384. painter->save();
  385. QPoint circleCenter;
  386. if (GemModel::IsAdded(modelIndex))
  387. {
  388. painter->setBrush(m_buttonEnabledColor);
  389. painter->setPen(m_buttonEnabledColor);
  390. circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1);
  391. }
  392. else if (GemModel::IsAddedDependency(modelIndex))
  393. {
  394. painter->setBrush(m_buttonImplicitlyEnabledColor);
  395. painter->setPen(m_buttonImplicitlyEnabledColor);
  396. circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1);
  397. }
  398. else
  399. {
  400. circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius + 1, 1);
  401. }
  402. // Rounded rect
  403. painter->drawRoundedRect(buttonRect, s_buttonBorderRadius, s_buttonBorderRadius);
  404. // Circle
  405. painter->setBrush(m_textColor);
  406. painter->drawEllipse(circleCenter, s_buttonCircleRadius, s_buttonCircleRadius);
  407. painter->restore();
  408. }
  409. QString GemItemDelegate::anchorAt(const QString& html, const QPoint& position, const QRect& rect)
  410. {
  411. if (!html.isEmpty())
  412. {
  413. AZStd::unique_ptr<QTextDocument> doc = GetTextDocument(html, rect.width());
  414. QAbstractTextDocumentLayout* layout = doc->documentLayout();
  415. if (layout)
  416. {
  417. return layout->anchorAt(position - rect.topLeft());
  418. }
  419. }
  420. return QString();
  421. }
  422. void GemItemDelegate::DrawDownloadStatusIcon(QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const
  423. {
  424. const GemInfo::DownloadStatus downloadStatus = GemModel::GetDownloadStatus(modelIndex);
  425. // Show no icon if gem is already downloaded
  426. if (downloadStatus == GemInfo::DownloadStatus::Downloaded)
  427. {
  428. return;
  429. }
  430. QPixmap currentFrame;
  431. const QPixmap* statusPixmap;
  432. if (downloadStatus == GemInfo::DownloadStatus::Downloading)
  433. {
  434. if (m_downloadingMovie->state() != QMovie::Running)
  435. {
  436. m_downloadingMovie->start();
  437. emit MovieStartedPlaying(m_downloadingMovie);
  438. }
  439. currentFrame = m_downloadingMovie->currentPixmap();
  440. currentFrame = currentFrame.scaled(s_statusIconSize, s_statusIconSize);
  441. statusPixmap = &currentFrame;
  442. }
  443. else if (downloadStatus == GemInfo::DownloadStatus::DownloadSuccessful)
  444. {
  445. statusPixmap = &m_downloadSuccessfulPixmap;
  446. }
  447. else if (downloadStatus == GemInfo::DownloadStatus::DownloadFailed)
  448. {
  449. statusPixmap = &m_downloadFailedPixmap;
  450. }
  451. else if (downloadStatus == GemInfo::DownloadStatus::NotDownloaded)
  452. {
  453. statusPixmap = &m_notDownloadedPixmap;
  454. }
  455. else
  456. {
  457. statusPixmap = &m_unknownStatusPixmap;
  458. }
  459. QSize statusSize = statusPixmap->size();
  460. painter->drawPixmap(
  461. buttonRect.left() - s_statusButtonSpacing - statusSize.width(),
  462. contentRect.center().y() - statusSize.height() / 2,
  463. *statusPixmap);
  464. }
  465. } // namespace O3DE::ProjectManager