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,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