Move Qt Toast Notifications from GraphCanvas into Framework

Move the existing Qt Toast Notification QWidgets, EBuses and logic from the GraphCanvas gem into AzQtComponents and AzToolsFramework so they can be re-used.

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>
This commit is contained in:
Alex Peterson
2021-10-20 11:15:24 -07:00
committed by GitHub
parent 67532ed18f
commit 7ddcdffed7
25 changed files with 514 additions and 381 deletions
@@ -0,0 +1,196 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Component/Entity.h>
#include <AzQtComponents/Components/ToastNotification.h>
#include <AzQtComponents/Components/ui_ToastNotification.h>
#include <QCursor>
#include <QIcon>
#include <QToolButton>
#include <QPropertyAnimation>
namespace AzQtComponents
{
ToastNotification::ToastNotification(QWidget* parent, const ToastConfiguration& toastConfiguration)
: QDialog(parent, Qt::FramelessWindowHint)
, m_closeOnClick(true)
, m_ui(new Ui::ToastNotification())
, m_fadeAnimation(nullptr)
{
setProperty("HasNoWindowDecorations", true);
setAttribute(Qt::WA_ShowWithoutActivating);
setAttribute(Qt::WA_DeleteOnClose);
m_ui->setupUi(this);
QIcon toastIcon;
switch (toastConfiguration.m_toastType)
{
case ToastType::Error:
toastIcon = QIcon(":/stylesheet/img/logging/error.svg");
break;
case ToastType::Warning:
toastIcon = QIcon(":/stylesheet/img/logging/warning-yellow.svg");
break;
case ToastType::Information:
toastIcon = QIcon(":/stylesheet/img/logging/information.svg");
break;
case ToastType::Custom:
toastIcon = QIcon(toastConfiguration.m_customIconImage);
default:
break;
}
m_ui->iconLabel->setPixmap(toastIcon.pixmap(64, 64));
m_ui->titleLabel->setText(toastConfiguration.m_title);
m_ui->mainLabel->setText(toastConfiguration.m_description);
m_lifeSpan.setInterval(aznumeric_cast<int>(toastConfiguration.m_duration.count()));
m_closeOnClick = toastConfiguration.m_closeOnClick;
m_ui->closeButton->setVisible(m_closeOnClick);
QObject::connect(m_ui->closeButton, &QToolButton::clicked, this, &ToastNotification::accept);
m_fadeDuration = toastConfiguration.m_fadeDuration;
QObject::connect(&m_lifeSpan, &QTimer::timeout, this, &ToastNotification::FadeOut);
}
ToastNotification::~ToastNotification()
{
}
void ToastNotification::ShowToastAtCursor()
{
QPoint globalCursorPos = QCursor::pos();
// Left/middle align it relative to the cursor.
QPointF anchorPoint(0, 0.5);
// Magic offset to try to get it to not hide under the cursor.
// No way to get this programatically from what I can tell.
globalCursorPos.setX(globalCursorPos.x() + 16);
ShowToastAtPoint(globalCursorPos, anchorPoint);
}
void ToastNotification::ShowToastAtPoint(const QPoint& screenPosition, const QPointF& anchorPoint)
{
show();
updateGeometry();
UpdatePosition(screenPosition, anchorPoint);
}
void ToastNotification::UpdatePosition(const QPoint& screenPosition, const QPointF& anchorPoint)
{
QRect dialogGeometry = geometry();
QPoint finalPosition;
finalPosition.setX(aznumeric_cast<int>(screenPosition.x() - dialogGeometry.width() * anchorPoint.x()));
finalPosition.setY(aznumeric_cast<int>(screenPosition.y() - dialogGeometry.height() * anchorPoint.y()));
move(finalPosition);
}
void ToastNotification::showEvent(QShowEvent* showEvent)
{
QDialog::showEvent(showEvent);
if (m_fadeDuration.count() > 0)
{
m_fadeAnimation = new QPropertyAnimation(this, "windowOpacity", this);
m_fadeAnimation->setKeyValueAt(0, 0);
m_fadeAnimation->setKeyValueAt(1, 1);
m_fadeAnimation->setDuration(static_cast<int>(m_fadeDuration.count()));
m_fadeAnimation->start();
QObject::connect(m_fadeAnimation, &QPropertyAnimation::finished, this, &ToastNotification::StartTimer);
}
else
{
StartTimer();
}
}
void ToastNotification::hideEvent(QHideEvent* hideEvent)
{
QDialog::hideEvent(hideEvent);
m_lifeSpan.stop();
if (m_fadeAnimation)
{
m_fadeAnimation->stop();
delete m_fadeAnimation;
}
emit ToastNotificationHidden();
}
void ToastNotification::mousePressEvent(QMouseEvent*)
{
if (m_closeOnClick)
{
emit ToastNotificationInteraction();
accept();
}
}
bool ToastNotification::eventFilter(QObject*, QEvent* event)
{
if (event->type() == QEvent::MouseButtonPress)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent && mouseEvent->button() == Qt::MouseButton::LeftButton)
{
accept();
}
}
return false;
}
void ToastNotification::StartTimer()
{
delete m_fadeAnimation;
m_fadeAnimation = nullptr;
if (m_lifeSpan.interval() != 0)
{
m_lifeSpan.start();
}
}
void ToastNotification::FadeOut()
{
if (m_fadeDuration.count() > 0)
{
m_fadeAnimation = new QPropertyAnimation(this, "windowOpacity", this);
m_fadeAnimation->setKeyValueAt(0, windowOpacity());
m_fadeAnimation->setKeyValueAt(1, 0);
m_fadeAnimation->setDuration(static_cast<int>(m_fadeDuration.count()));
m_fadeAnimation->start();
QObject::connect(m_fadeAnimation, &QPropertyAnimation::finished, this, &ToastNotification::accept);
}
else
{
accept();
}
}
#include "Components/moc_ToastNotification.cpp"
}
@@ -0,0 +1,74 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/ToastNotificationConfiguration.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <QEvent>
#include <QDialog>
#include <QMouseEvent>
#include <QTimer>
#endif
namespace Ui
{
class ToastNotification;
}
QT_FORWARD_DECLARE_CLASS(QPropertyAnimation)
namespace AzQtComponents
{
class AZ_QT_COMPONENTS_API ToastNotification
: public QDialog
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(ToastNotification, AZ::SystemAllocator, 0);
ToastNotification(QWidget* parent, const ToastConfiguration& toastConfiguration);
virtual ~ToastNotification();
// Shows the toast notification relative to the current cursor.
void ShowToastAtCursor();
// Aligns the toast notification so that the specified anchor point on the notification lies on the specified screen position.
// i.e. anchor point of 0,0 will align the top left position of the dialog with the screen position
// anchor point of 1,1 will align the bottom right position of the dialog with the screen position
void ShowToastAtPoint(const QPoint& screenPosition, const QPointF& anchorPoint);
void UpdatePosition(const QPoint& screenPosition, const QPointF& anchorPoint);
// QDialog
void showEvent(QShowEvent* showEvent) override;
void hideEvent(QHideEvent* hideEvent) override;
void mousePressEvent(QMouseEvent* mouseEvent) override;
bool eventFilter(QObject* object, QEvent* event) override;
public slots:
void StartTimer();
void FadeOut();
signals:
void ToastNotificationHidden();
void ToastNotificationInteraction();
private:
QPropertyAnimation* m_fadeAnimation;
bool m_closeOnClick;
QTimer m_lifeSpan;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::chrono::milliseconds m_fadeDuration;
AZStd::unique_ptr<Ui::ToastNotification> m_ui;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
} // namespace AzQtComponents
@@ -0,0 +1,236 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ToastNotification</class>
<widget class="QDialog" name="ToastNotification">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>225</width>
<height>48</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>225</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="icon_frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgba(255, 255, 255, 20);</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="iconLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgba(255, 255, 255, 0);</string>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="resources.qrc">:/stylesheet/img/logging/information.svg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="text_frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QFrame" name="titleFrame">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="titleLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Invalid Connection</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="closeButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/stylesheet/img/close_x.svg</normaloff>:/stylesheet/img/close_x.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="mainLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Types are not a match.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="resources.qrc"/>
</resources>
<connections/>
</ui>
@@ -0,0 +1,18 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzQtComponents/Components/ToastNotificationConfiguration.h>
namespace AzQtComponents
{
ToastConfiguration::ToastConfiguration(ToastType toastType, const QString& title, const QString& description)
: m_toastType(toastType)
, m_title(title)
, m_description(description)
{
}
} // namespace AzQtComponents
@@ -0,0 +1,46 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/chrono/chrono.h>
#include <QString>
#endif
namespace AzQtComponents
{
enum class ToastType
{
Information,
Warning,
Error,
Custom
};
class AZ_QT_COMPONENTS_API ToastConfiguration
{
public:
AZ_CLASS_ALLOCATOR(ToastConfiguration, AZ::SystemAllocator, 0);
ToastConfiguration(ToastType toastType, const QString& title, const QString& description);
bool m_closeOnClick = true;
ToastType m_toastType = ToastType::Information;
QString m_title;
QString m_description;
QString m_customIconImage;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds(5000);
AZStd::chrono::milliseconds m_fadeDuration = AZStd::chrono::milliseconds(250);
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
} // namespace AzQtComponents
@@ -49,6 +49,11 @@ set(FILES
Components/Titlebar.h
Components/TitleBarOverdrawHandler.cpp
Components/TitleBarOverdrawHandler.h
Components/ToastNotification.cpp
Components/ToastNotification.h
Components/ToastNotificationConfiguration.h
Components/ToastNotificationConfiguration.cpp
Components/ToastNotification.ui
Components/ToolButtonComboBox.cpp
Components/ToolButtonComboBox.h
Components/ToolButtonLineEdit.cpp
@@ -0,0 +1,91 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/EntityId.h>
#include <AzQtComponents/Components/ToastNotificationConfiguration.h>
#include <QPoint>
#endif
namespace AzToolsFramework
{
typedef AZ::EntityId ToastId;
/**
* An EBus for receiving notifications when a user interacts with or dismisses
* a toast notification.
*/
class ToastNotifications
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = ToastId;
virtual void OnToastInteraction() {}
virtual void OnToastDismissed() {}
};
using ToastNotificationBus = AZ::EBus<ToastNotifications>;
typedef AZ::u32 ToastRequestBusId;
/**
* An EBus used to hide or show toast notifications. Generally, these request are handled by a
* ToastNotificationsView that has been created with a specific ToastRequestBusId
* e.g. AZ_CRC("ExampleToastNotificationView")
*/
class ToastRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = ToastRequestBusId; // bus is addressed by CRC of the view name
/**
* Hide a toast notification widget.
*
* @param toastId The toast notification's ToastId
*/
virtual void HideToastNotification(const ToastId& toastId) = 0;
/**
* Show a toast notification with the specified toast configuration. When handled by a ToastNotificationsView,
* notifications are queued and presented to the user in sequence.
*
* @param toastConfiguration The toast configuration
* @return a ToastId
*/
virtual ToastId ShowToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration) = 0;
/**
* Show a toast notification with the specified toast configuration at the current moust cursor location.
*
* @param toastConfiguration The toast configuration
* @return a ToastId
*/
virtual ToastId ShowToastAtCursor(const AzQtComponents::ToastConfiguration& toastConfiguration) = 0;
/**
* Show a toast notification with the specified toast configuration at the specified location.
*
* @param screenPosition The screen position
* @param anchorPoint The anchorPoint for the toast notification widget
* @param toastConfiguration The toast configuration
* @return a ToastId
*/
virtual ToastId ShowToastAtPoint(const QPoint& screenPosition, const QPointF& anchorPoint, const AzQtComponents::ToastConfiguration&) = 0;
};
using ToastRequestBus = AZ::EBus<ToastRequests>;
}
@@ -0,0 +1,180 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzToolsFramework/UI/Notifications/ToastNotificationsView.h>
#include <AzQtComponents/Components/ToastNotification.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/functional.h>
namespace AzToolsFramework
{
ToastNotificationsView::ToastNotificationsView(QWidget* parent, ToastRequestBusId busId)
: QWidget(parent)
{
ToastRequestBus::Handler::BusConnect(busId);
}
ToastNotificationsView::~ToastNotificationsView()
{
ToastRequestBus::Handler::BusDisconnect();
}
void ToastNotificationsView::OnHide()
{
QWidget::hide();
if (m_activeNotification.IsValid())
{
auto notificationIter = m_notifications.find(m_activeNotification);
if (notificationIter != m_notifications.end())
{
notificationIter->second->hide();
}
}
}
void ToastNotificationsView::UpdateToastPosition()
{
if (m_activeNotification.IsValid())
{
auto notificationIter = m_notifications.find(m_activeNotification);
if (notificationIter != m_notifications.end())
{
notificationIter->second->UpdatePosition(GetGlobalPoint(), m_anchorPoint);
}
}
}
void ToastNotificationsView::OnShow()
{
QWidget::show();
if (m_activeNotification.IsValid() || !m_queuedNotifications.empty())
{
DisplayQueuedNotification();
}
}
ToastId ToastNotificationsView::ShowToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration)
{
ToastId toastId = CreateToastNotification(toastConfiguration);
m_queuedNotifications.emplace_back(toastId);
if (!m_activeNotification.IsValid())
{
DisplayQueuedNotification();
}
return toastId;
}
ToastId ToastNotificationsView::ShowToastAtCursor(const AzQtComponents::ToastConfiguration& toastConfiguration)
{
ToastId toastId = CreateToastNotification(toastConfiguration);
m_notifications[toastId]->ShowToastAtCursor();
return toastId;
}
ToastId ToastNotificationsView::ShowToastAtPoint(const QPoint& screenPosition, const QPointF& anchorPoint, const AzQtComponents::ToastConfiguration& toastConfiguration)
{
ToastId toastId = CreateToastNotification(toastConfiguration);
m_notifications[toastId]->ShowToastAtPoint(screenPosition, anchorPoint);
return toastId;
}
void ToastNotificationsView::HideToastNotification(const ToastId& toastId)
{
auto notificationIter = m_notifications.find(toastId);
if (notificationIter != m_notifications.end())
{
auto queuedIter = AZStd::find(m_queuedNotifications.begin(), m_queuedNotifications.end(), toastId);
if (queuedIter != m_queuedNotifications.end())
{
m_queuedNotifications.erase(queuedIter);
}
notificationIter->second->reject();
}
}
ToastId ToastNotificationsView::CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration)
{
AzQtComponents::ToastNotification* notification = aznew AzQtComponents::ToastNotification(parentWidget(), toastConfiguration);
ToastId toastId = AZ::Entity::MakeId();
m_notifications[toastId] = notification;
QObject::connect(
m_notifications[toastId], &AzQtComponents::ToastNotification::ToastNotificationHidden,
[toastId]()
{
ToastNotificationBus::Event(toastId, &ToastNotificationBus::Events::OnToastDismissed);
});
QObject::connect(
m_notifications[toastId], &AzQtComponents::ToastNotification::ToastNotificationInteraction,
[toastId]()
{
ToastNotificationBus::Event(toastId, &ToastNotificationBus::Events::OnToastInteraction);
});
return toastId;
}
QPoint ToastNotificationsView::GetGlobalPoint()
{
QPoint relativePoint = m_offset;
AZ_Assert(parentWidget(), "ToastNotificationsView has invalid parent QWidget");
if (m_anchorPoint.x() == 1.0)
{
relativePoint.setX(parentWidget()->width() - m_offset.x());
}
if (m_anchorPoint.y() == 1.0)
{
relativePoint.setY(parentWidget()->height() - m_offset.y());
}
return parentWidget()->mapToGlobal(relativePoint);
}
void ToastNotificationsView::DisplayQueuedNotification()
{
AZ_Assert(parentWidget(), "ToastNotificationsView has invalid parent QWidget");
if (m_queuedNotifications.empty() || !parentWidget()->isVisible() || !isVisible())
{
return;
}
ToastId toastId = m_queuedNotifications.front();
m_queuedNotifications.erase(m_queuedNotifications.begin());
auto notificationIter = m_notifications.find(toastId);
if (notificationIter != m_notifications.end())
{
m_activeNotification = toastId;
notificationIter->second->ShowToastAtPoint(GetGlobalPoint(), m_anchorPoint);
QObject::connect(
notificationIter->second, &AzQtComponents::ToastNotification::ToastNotificationHidden,
[&]()
{
m_activeNotification.SetInvalid();
DisplayQueuedNotification();
}
);
}
// If we didn't actually show something, recurse to avoid things getting stuck in the queue.
if (!m_activeNotification.IsValid())
{
DisplayQueuedNotification();
}
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QWidget>
#include <QPoint>
#include <QPointF>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzToolsFramework/UI/Notifications/ToastBus.h>
#endif
namespace AzQtComponents
{
class ToastNotification;
}
namespace AzToolsFramework
{
/**
* \brief A QWidget that displays and manages a queue of toast notifications.
*
* This view must be updated by its parent when the parent widget is show, hidden, moved
* or resized because toast notifications are displayed on top of the parent and are not part
* of the layout, so they must be manually moved.
*/
class ToastNotificationsView final
: public QWidget
, protected ToastRequestBus::Handler
{
Q_OBJECT
public:
ToastNotificationsView(QWidget* parent, ToastRequestBusId busId);
~ToastNotificationsView() override;
void HideToastNotification(const ToastId& toastId) override;
ToastId ShowToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration) override;
ToastId ShowToastAtCursor(const AzQtComponents::ToastConfiguration& toastConfiguration) override;
ToastId ShowToastAtPoint(const QPoint& screenPosition, const QPointF& anchorPoint, const AzQtComponents::ToastConfiguration&) override;
void OnHide();
void OnShow();
void UpdateToastPosition();
private:
ToastId CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration);
void DisplayQueuedNotification();
QPoint GetGlobalPoint();
ToastId m_activeNotification;
AZStd::unordered_map<ToastId, AzQtComponents::ToastNotification*> m_notifications;
AZStd::vector<ToastId> m_queuedNotifications;
QPoint m_offset = QPoint(10, 10);
QPointF m_anchorPoint = QPointF(1, 0);
};
} // AzToolsFramework
@@ -759,6 +759,9 @@ set(FILES
UI/Prefab/PrefabUiHandler.cpp
UI/Prefab/PrefabViewportFocusPathHandler.h
UI/Prefab/PrefabViewportFocusPathHandler.cpp
UI/Notifications/ToastNotificationsView.cpp
UI/Notifications/ToastNotificationsView.h
UI/Notifications/ToastBus.h
PythonTerminal/ScriptHelpDialog.cpp
PythonTerminal/ScriptHelpDialog.h
PythonTerminal/ScriptHelpDialog.ui