Added toast notifications to the gem catalog

monroegm-disable-blank-issue-2
Alex Peterson 4 years ago committed by GitHub
parent 136788bccf
commit 1989316cac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,6 +13,7 @@
#include <QIcon> #include <QIcon>
#include <QToolButton> #include <QToolButton>
#include <QPropertyAnimation> #include <QPropertyAnimation>
#include <QPainter>
namespace AzQtComponents namespace AzQtComponents
{ {
@ -27,6 +28,13 @@ namespace AzQtComponents
setAttribute(Qt::WA_ShowWithoutActivating); setAttribute(Qt::WA_ShowWithoutActivating);
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
m_borderRadius = toastConfiguration.m_borderRadius;
if (m_borderRadius > 0)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
setAttribute(Qt::WA_TranslucentBackground);
}
m_ui->setupUi(this); m_ui->setupUi(this);
QIcon toastIcon; QIcon toastIcon;
@ -53,6 +61,13 @@ namespace AzQtComponents
m_ui->titleLabel->setText(toastConfiguration.m_title); m_ui->titleLabel->setText(toastConfiguration.m_title);
m_ui->mainLabel->setText(toastConfiguration.m_description); m_ui->mainLabel->setText(toastConfiguration.m_description);
// hide the optional description if none is provided so the title is centered vertically
if (toastConfiguration.m_description.isEmpty())
{
m_ui->mainLabel->setVisible(false);
m_ui->verticalLayout->removeWidget(m_ui->mainLabel);
}
m_lifeSpan.setInterval(aznumeric_cast<int>(toastConfiguration.m_duration.count())); m_lifeSpan.setInterval(aznumeric_cast<int>(toastConfiguration.m_duration.count()));
m_closeOnClick = toastConfiguration.m_closeOnClick; m_closeOnClick = toastConfiguration.m_closeOnClick;
@ -68,6 +83,24 @@ namespace AzQtComponents
{ {
} }
void ToastNotification::paintEvent(QPaintEvent* event)
{
if (m_borderRadius > 0)
{
QPainter p(this);
p.setPen(Qt::transparent);
QColor painterColor;
painterColor.setRgbF(0, 0, 0, 255);
p.setBrush(painterColor);
p.setRenderHint(QPainter::Antialiasing);
p.drawRoundedRect(rect(), m_borderRadius, m_borderRadius);
}
else
{
QDialog::paintEvent(event);
}
}
void ToastNotification::ShowToastAtCursor() void ToastNotification::ShowToastAtCursor()
{ {
QPoint globalCursorPos = QCursor::pos(); QPoint globalCursorPos = QCursor::pos();

@ -52,6 +52,8 @@ namespace AzQtComponents
void mousePressEvent(QMouseEvent* mouseEvent) override; void mousePressEvent(QMouseEvent* mouseEvent) override;
bool eventFilter(QObject* object, QEvent* event) override; bool eventFilter(QObject* object, QEvent* event) override;
void paintEvent(QPaintEvent* event) override;
public slots: public slots:
void StartTimer(); void StartTimer();
void FadeOut(); void FadeOut();
@ -65,6 +67,7 @@ namespace AzQtComponents
bool m_closeOnClick; bool m_closeOnClick;
QTimer m_lifeSpan; QTimer m_lifeSpan;
uint32_t m_borderRadius = 0;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::chrono::milliseconds m_fadeDuration; AZStd::chrono::milliseconds m_fadeDuration;

@ -191,7 +191,7 @@
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>40</width> <width>20</width>
<height>20</height> <height>20</height>
</size> </size>
</property> </property>

@ -37,6 +37,7 @@ namespace AzQtComponents
QString m_title; QString m_title;
QString m_description; QString m_description;
QString m_customIconImage; QString m_customIconImage;
uint32_t m_borderRadius = 0;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds(5000); AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds(5000);

@ -177,4 +177,14 @@ namespace AzToolsFramework
DisplayQueuedNotification(); DisplayQueuedNotification();
} }
} }
void ToastNotificationsView::SetOffset(const QPoint& offset)
{
m_offset = offset;
}
void ToastNotificationsView::SetAnchorPoint(const QPointF& anchorPoint)
{
m_anchorPoint = anchorPoint;
}
} }

@ -50,6 +50,9 @@ namespace AzToolsFramework
void OnShow(); void OnShow();
void UpdateToastPosition(); void UpdateToastPosition();
void SetOffset(const QPoint& offset);
void SetAnchorPoint(const QPointF& anchorPoint);
private: private:
ToastId CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration); ToastId CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration);
void DisplayQueuedNotification(); void DisplayQueuedNotification();

@ -43,7 +43,7 @@ ly_add_target(
3rdParty::pybind11 3rdParty::pybind11
AZ::AzCore AZ::AzCore
AZ::AzFramework AZ::AzFramework
AZ::AzQtComponents AZ::AzToolsFramework
) )
ly_add_target( ly_add_target(

@ -40,5 +40,6 @@
<file>Delete.svg</file> <file>Delete.svg</file>
<file>Download.svg</file> <file>Download.svg</file>
<file>in_progress.gif</file> <file>in_progress.gif</file>
<file>gem.svg</file>
</qresource> </qresource>
</RCC> </RCC>

@ -61,6 +61,24 @@ QTabBar::tab:focus {
color: #4082eb; color: #4082eb;
} }
#ToastNotification {
background-color: black;
border-radius: 20px;
border:1px solid #dddddd;
qproperty-minimumSize: 100px 50px;
}
#ToastNotification #icon_frame {
border-radius: 4px;
qproperty-minimumSize: 44px 20px;
}
#ToastNotification #iconLabel {
qproperty-minimumSize: 30px 20px;
qproperty-maximumSize: 30px 20px;
margin-left: 6px;
}
/************** General (Forms) **************/ /************** General (Forms) **************/
#formLineEditWidget, #formLineEditWidget,

@ -0,0 +1,3 @@
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.3771 6.26808L18.0075 0.389195C17.9407 0.271396 17.844 0.173353 17.7271 0.105004C17.6101 0.0366557 17.4772 0.000430184 17.3418 0H4.15017C4.01474 0.000430184 3.88184 0.0366557 3.76492 0.105004C3.64801 0.173353 3.55125 0.271396 3.48444 0.389195L0.10459 6.26808C0.0213476 6.41083 -0.0136462 6.57661 0.00480427 6.74082C0.0232547 6.90502 0.0941655 7.05891 0.20701 7.17962L10.1724 17.7596C10.2442 17.8355 10.3308 17.896 10.4267 17.9373C10.5227 17.9787 10.6261 18 10.7306 18C10.8351 18 10.9385 17.9787 11.0345 17.9373C11.1305 17.896 11.217 17.8355 11.2888 17.7596L21.2542 7.17962C21.3703 7.06129 21.4451 6.90857 21.4672 6.74428C21.4894 6.57999 21.4578 6.41294 21.3771 6.26808ZM12.5998 7.17962L10.7562 13.7345L8.88195 7.17962H12.5998ZM8.42107 5.64332L7.25348 1.54654H14.218L13.0504 5.64332H8.42107ZM9.44526 14.687L2.31685 7.17962H7.24324L9.44526 14.687ZM14.2385 7.17962H19.1546L11.9853 14.7382L14.2385 7.17962ZM19.2878 5.64332H14.6789L15.8465 1.54654H16.9014L19.2878 5.64332ZM4.64178 1.54654H5.66598L6.83356 5.64332H2.23492L4.64178 1.54654Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -66,6 +66,9 @@ namespace O3DE::ProjectManager
hLayout->addWidget(filterWidget); hLayout->addWidget(filterWidget);
hLayout->addLayout(middleVLayout); hLayout->addLayout(middleVLayout);
hLayout->addWidget(m_gemInspector); hLayout->addWidget(m_gemInspector);
m_notificationsView = AZStd::make_unique<AzToolsFramework::ToastNotificationsView>(this, AZ_CRC("GemCatalogNotificationsView"));
m_notificationsView->SetOffset(QPoint(10, 70));
} }
void GemCatalogScreen::ReinitForProject(const QString& projectPath) void GemCatalogScreen::ReinitForProject(const QString& projectPath)
@ -86,6 +89,7 @@ namespace O3DE::ProjectManager
m_headerWidget->ReinitForProject(); m_headerWidget->ReinitForProject();
connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter); connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter);
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
// Select the first entry after everything got correctly sized // Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{ QTimer::singleShot(200, [=]{
@ -94,6 +98,72 @@ namespace O3DE::ProjectManager
}); });
} }
void GemCatalogScreen::OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies)
{
if (m_notificationsEnabled)
{
bool added = GemModel::IsAdded(modelIndex);
bool dependency = GemModel::IsAddedDependency(modelIndex);
bool gemStateChanged = (added && !dependency) || (!added && !dependency);
if (!gemStateChanged && !numChangedDependencies)
{
// no actual changes made
return;
}
QString notification;
if (gemStateChanged)
{
notification = GemModel::GetDisplayName(modelIndex);
if (numChangedDependencies > 0)
{
notification += " " + tr("and") + " ";
}
}
if (numChangedDependencies == 1 )
{
notification += "1 Gem " + tr("dependency");
}
else if (numChangedDependencies > 1)
{
notification += QString("%d Gem ").arg(numChangedDependencies) + tr("dependencies");
}
notification += " " + (added ? tr("activated") : tr("deactivated"));
AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, "");
toastConfiguration.m_customIconImage = ":/gem.svg";
toastConfiguration.m_borderRadius = 4;
toastConfiguration.m_duration = AZStd::chrono::milliseconds(3000);
m_notificationsView->ShowToastNotification(toastConfiguration);
}
}
void GemCatalogScreen::hideEvent(QHideEvent* event)
{
ScreenWidget::hideEvent(event);
m_notificationsView->OnHide();
}
void GemCatalogScreen::showEvent(QShowEvent* event)
{
ScreenWidget::showEvent(event);
m_notificationsView->OnShow();
}
void GemCatalogScreen::resizeEvent(QResizeEvent* event)
{
ScreenWidget::resizeEvent(event);
m_notificationsView->UpdateToastPosition();
}
void GemCatalogScreen::moveEvent(QMoveEvent* event)
{
ScreenWidget::moveEvent(event);
m_notificationsView->UpdateToastPosition();
}
void GemCatalogScreen::FillModel(const QString& projectPath) void GemCatalogScreen::FillModel(const QString& projectPath)
{ {
AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath); AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath);
@ -107,6 +177,7 @@ namespace O3DE::ProjectManager
} }
m_gemModel->UpdateGemDependencies(); m_gemModel->UpdateGemDependencies();
m_notificationsEnabled = false;
// Gather enabled gems for the given project. // Gather enabled gems for the given project.
auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath); auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath);
@ -133,6 +204,8 @@ namespace O3DE::ProjectManager
{ {
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str())); QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
} }
m_notificationsEnabled = true;
} }
else else
{ {

@ -10,6 +10,8 @@
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)
#include <ScreenWidget.h> #include <ScreenWidget.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzToolsFramework/UI/Notifications/ToastNotificationsView.h>
#include <GemCatalog/GemCatalogHeaderWidget.h> #include <GemCatalog/GemCatalogHeaderWidget.h>
#include <GemCatalog/GemFilterWidget.h> #include <GemCatalog/GemFilterWidget.h>
#include <GemCatalog/GemListView.h> #include <GemCatalog/GemListView.h>
@ -41,13 +43,24 @@ namespace O3DE::ProjectManager
GemModel* GetGemModel() const { return m_gemModel; } GemModel* GetGemModel() const { return m_gemModel; }
DownloadController* GetDownloadController() const { return m_downloadController; } DownloadController* GetDownloadController() const { return m_downloadController; }
public slots:
void OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies);
protected:
void hideEvent(QHideEvent* event) override;
void showEvent(QShowEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void moveEvent(QMoveEvent* event) override;
private slots: private slots:
void HandleOpenGemRepo(); void HandleOpenGemRepo();
private:
private:
void FillModel(const QString& projectPath); void FillModel(const QString& projectPath);
AZStd::unique_ptr<AzToolsFramework::ToastNotificationsView> m_notificationsView;
GemListView* m_gemListView = nullptr; GemListView* m_gemListView = nullptr;
GemInspector* m_gemInspector = nullptr; GemInspector* m_gemInspector = nullptr;
GemModel* m_gemModel = nullptr; GemModel* m_gemModel = nullptr;
@ -56,5 +69,6 @@ namespace O3DE::ProjectManager
QVBoxLayout* m_filterWidgetLayout = nullptr; QVBoxLayout* m_filterWidgetLayout = nullptr;
GemFilterWidget* m_filterWidget = nullptr; GemFilterWidget* m_filterWidget = nullptr;
DownloadController* m_downloadController = nullptr; DownloadController* m_downloadController = nullptr;
bool m_notificationsEnabled = true;
}; };
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -10,6 +10,7 @@
#include <GemCatalog/GemModel.h> #include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h> #include <GemCatalog/GemSortFilterProxyModel.h>
#include <AzCore/Casting/numeric_cast.h> #include <AzCore/Casting/numeric_cast.h>
#include <AzToolsFramework/UI/Notifications/ToastBus.h>
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
{ {
@ -299,23 +300,50 @@ namespace O3DE::ProjectManager
AZ_Assert(gemModel, "Failed to obtain GemModel"); AZ_Assert(gemModel, "Failed to obtain GemModel");
QVector<QModelIndex> dependencies = gemModel->GatherGemDependencies(modelIndex); QVector<QModelIndex> dependencies = gemModel->GatherGemDependencies(modelIndex);
uint32_t numChangedDependencies = 0;
if (IsAdded(modelIndex)) if (IsAdded(modelIndex))
{ {
for (const QModelIndex& dependency : dependencies) for (const QModelIndex& dependency : dependencies)
{ {
SetIsAddedDependency(*gemModel, dependency, true); if (!IsAddedDependency(dependency))
{
SetIsAddedDependency(*gemModel, dependency, true);
// if the gem was already added then the state didn't really change
if (!IsAdded(dependency))
{
numChangedDependencies++;
}
}
} }
} }
else else
{ {
// still a dependency if some added gem depends on this one // still a dependency if some added gem depends on this one
SetIsAddedDependency(model, modelIndex, gemModel->HasDependentGems(modelIndex)); bool hasDependentGems = gemModel->HasDependentGems(modelIndex);
if (IsAddedDependency(modelIndex) != hasDependentGems)
{
SetIsAddedDependency(model, modelIndex, hasDependentGems);
}
for (const QModelIndex& dependency : dependencies) for (const QModelIndex& dependency : dependencies)
{ {
SetIsAddedDependency(*gemModel, dependency, gemModel->HasDependentGems(dependency)); hasDependentGems = gemModel->HasDependentGems(dependency);
if (IsAddedDependency(dependency) != hasDependentGems)
{
SetIsAddedDependency(*gemModel, dependency, hasDependentGems);
// if the gem was already added then the state didn't really change
if (!IsAdded(dependency))
{
numChangedDependencies++;
}
}
} }
} }
gemModel->emit gemStatusChanged(modelIndex, numChangedDependencies);
} }
void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded)
@ -488,5 +516,4 @@ namespace O3DE::ProjectManager
} }
return result; return result;
} }
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -77,6 +77,9 @@ namespace O3DE::ProjectManager
int TotalAddedGems(bool includeDependencies = false) const; int TotalAddedGems(bool includeDependencies = false) const;
signals:
void gemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies);
private: private:
void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames); void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames);
void GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& inOutGems); void GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& inOutGems);

Loading…
Cancel
Save