[development] updated editor event handling with respect to imgui (#6114)

Fixes #4366

- Added support for sending text events from the Qt event mapper
- Fixed several issues found while running the Profiler gem in debug
- Added ability to track AZ-consumed events in the Qt event mapper
- Minor clean up to input handling in ImGuiManager
- Select ImGui mouse events can now be properly consumed during AZ-input processing in the editor viewport
- Added tests for the Qt event mapper

Signed-off-by: AMZN-ScottR 24445312+AMZN-ScottR@users.noreply.github.com
This commit is contained in:
Scott Romero
2021-12-09 13:58:27 -08:00
committed by GitHub
parent d1c184bafd
commit e9379c1746
9 changed files with 707 additions and 51 deletions
@@ -11,6 +11,7 @@
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Input/Buses/Notifications/InputChannelNotificationBus.h>
#include <AzFramework/Input/Buses/Notifications/InputTextNotificationBus.h>
#include <AzFramework/Input/Buses/Requests/InputChannelRequestBus.h>
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
@@ -24,6 +25,30 @@
namespace AzToolsFramework
{
static bool HandleTextEvent(QEvent::Type eventType, Qt::Key key, QString keyText, bool isAutoRepeat)
{
bool textConsumed = false;
if (key == Qt::Key_Backspace)
{
keyText = "\b";
}
if (!keyText.isEmpty())
{
// key events are first sent as shortcuts, if accepted they are then re-sent as traditional key
// down events. dispatching the key event as text during a shortcut (and auto-repeat press)
// ensures all printable keys a fair chance at being consumed before processing elsewhere
if (eventType == QEvent::Type::ShortcutOverride || (eventType == QEvent::Type::KeyPress && isAutoRepeat))
{
AzFramework::InputTextNotificationBus::Broadcast(
&AzFramework::InputTextNotifications::OnInputTextEvent, AZStd::string(keyText.toUtf8().data()), textConsumed);
}
}
return textConsumed;
}
void QtEventToAzInputMapper::InitializeKeyMappings()
{
// This assumes modifier keys (ctrl/shift/alt) map to the left control/shift/alt keys as Qt provides no way to disambiguate
@@ -194,6 +219,7 @@ namespace AzToolsFramework
// Install a global event filter to ensure we don't miss mouse and key release events.
QApplication::instance()->installEventFilter(this);
AzFramework::InputChannelNotificationBus::Handler::BusConnect();
}
bool QtEventToAzInputMapper::HandlesInputEvent(const AzFramework::InputChannel& channel) const
@@ -317,6 +343,19 @@ namespace AzToolsFramework
return false;
}
AZ::s32 QtEventToAzInputMapper::GetPriority() const
{
return AzFramework::InputChannelEventListener::GetPriorityLast();
}
void QtEventToAzInputMapper::OnInputChannelEvent(const AzFramework::InputChannel& inputChannel, bool& hasBeenConsumed)
{
if (m_enabled && hasBeenConsumed)
{
m_lastConsumedInputChannelIdCrc32 = inputChannel.GetInputChannelId().GetNameCrc32();
}
}
void QtEventToAzInputMapper::NotifyUpdateChannelIfNotIdle(const AzFramework::InputChannel* channel, QEvent* event)
{
if (channel->GetState() != AzFramework::InputChannel::State::Idle)
@@ -357,6 +396,9 @@ namespace AzToolsFramework
if (buttonChannel)
{
// reset the consumed event cache so the chain of calls from UpdateState below can properly update it, if necessary
m_lastConsumedInputChannelIdCrc32 = 0;
if (mouseEvent->type() != QEvent::Type::MouseButtonRelease)
{
buttonChannel->UpdateState(true);
@@ -366,7 +408,16 @@ namespace AzToolsFramework
buttonChannel->UpdateState(false);
}
NotifyUpdateChannelIfNotIdle(buttonChannel, mouseEvent);
if (m_lastConsumedInputChannelIdCrc32 == buttonChannel->GetInputChannelId().GetNameCrc32())
{
// a standard az-input handler consumed the event so mark it as such
mouseEvent->accept();
}
else
{
// only notify if not consumed elsewhere
NotifyUpdateChannelIfNotIdle(buttonChannel, mouseEvent);
}
}
}
}
@@ -408,16 +459,24 @@ namespace AzToolsFramework
void QtEventToAzInputMapper::HandleKeyEvent(QKeyEvent* keyEvent)
{
// Ignore key repeat events, they're unrelated to actual physical button presses.
const Qt::Key key = static_cast<Qt::Key>(keyEvent->key());
const QEvent::Type eventType = keyEvent->type();
// special handling for text events in edit mode
if (HandleTextEvent(eventType, key, keyEvent->text(), keyEvent->isAutoRepeat()))
{
keyEvent->accept();
return;
}
// Ignore key repeat events for non-text, they're unrelated to actual physical button presses.
if (keyEvent->isAutoRepeat())
{
return;
}
const Qt::Key key = static_cast<Qt::Key>(keyEvent->key());
// For ShortcutEvent, only continue processing if we're in the HighPriorityKeys set.
if (keyEvent->type() != QEvent::Type::ShortcutOverride || m_highPriorityKeys.find(key) != m_highPriorityKeys.end())
if (eventType != QEvent::Type::ShortcutOverride || m_highPriorityKeys.find(key) != m_highPriorityKeys.end())
{
if (auto keyIt = m_keyMappings.find(key); keyIt != m_keyMappings.end())
{
@@ -425,7 +484,7 @@ namespace AzToolsFramework
if (keyChannel)
{
if (keyEvent->type() == QEvent::Type::KeyPress || keyEvent->type() == QEvent::Type::ShortcutOverride)
if (eventType == QEvent::Type::KeyPress || eventType == QEvent::Type::ShortcutOverride)
{
keyChannel->UpdateState(true);
}
@@ -451,8 +510,22 @@ namespace AzToolsFramework
{
wheelAngle = angleDelta.y();
}
// reset the consumed event cache so the chain of calls from ProcessRawInputEvent below can properly update it, if necessary
m_lastConsumedInputChannelIdCrc32 = 0;
cursorZChannel->ProcessRawInputEvent(aznumeric_cast<float>(wheelAngle));
NotifyUpdateChannelIfNotIdle(cursorZChannel, wheelEvent);
if (m_lastConsumedInputChannelIdCrc32 == cursorZChannel->GetInputChannelId().GetNameCrc32())
{
// a standard az-input handler consumed the event so mark it as such
wheelEvent->accept();
}
else
{
// only notify if not consumed elsewhere
NotifyUpdateChannelIfNotIdle(cursorZChannel, wheelEvent);
}
}
void QtEventToAzInputMapper::ClearInputChannels(QEvent* event)
@@ -17,7 +17,7 @@
#include <AzFramework/Input/Channels/InputChannelDigitalWithSharedPosition2D.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Input/Events/InputChannelEventListener.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <QEvent>
@@ -34,7 +34,9 @@ namespace AzToolsFramework
{
//! Maps events from the Qt input system to synthetic InputChannels in AzFramework
//! that can be used by AzFramework::ViewportControllers.
class QtEventToAzInputMapper final : public QObject
class QtEventToAzInputMapper final
: public QObject
, public AzFramework::InputChannelNotificationBus::Handler
{
Q_OBJECT
@@ -69,6 +71,11 @@ namespace AzToolsFramework
//! \param event The underlying Qt event that triggered this change, if applicable.
void InputChannelUpdated(const AzFramework::InputChannel* channel, QEvent* event);
protected:
// AzFramework::InputChannelNotificationBus overrides ...
AZ::s32 GetPriority() const override;
void OnInputChannelEvent(const AzFramework::InputChannel& inputChannel, bool& hasBeenConsumed) override;
private:
// Gets an input channel of the specified type by ID.
template<class TInputChannel>
@@ -161,6 +168,8 @@ namespace AzToolsFramework
AZStd::unordered_set<Qt::Key> m_highPriorityKeys;
// A lookup table for AZ input channel ID -> physical input channel on our mouse or keyboard device.
AZStd::unordered_map<AzFramework::InputChannelId, AzFramework::InputChannel*> m_channels;
// The crc32 of the last consumed input event's channel id.
AZ::Crc32 m_lastConsumedInputChannelIdCrc32 = 0;
// Where the mouse cursor was at the last cursor event.
QPoint m_previousGlobalCursorPosition;
// The source widget to map events from, used to calculate the relative mouse position within the widget bounds.
@@ -56,6 +56,40 @@ namespace UnitTest
QApplication::sendEvent(widget, &mouseMoveEvent);
}
void MouseScroll(QWidget* widget, QPoint localEventPosition, QPoint wheelDelta,
Qt::MouseButtons mouseButtons, Qt::KeyboardModifiers keyboardModifiers)
{
const QPoint globalEventPos = widget->mapToGlobal(localEventPosition);
const QPoint zero = QPoint();
QWheelEvent wheelEventBegin(globalEventPos, zero, zero, wheelDelta, mouseButtons, keyboardModifiers, Qt::ScrollBegin, false);
QApplication::sendEvent(widget, &wheelEventBegin);
QWheelEvent wheelEventUpdate(globalEventPos, zero, zero, wheelDelta, mouseButtons, keyboardModifiers, Qt::ScrollUpdate, false);
QApplication::sendEvent(widget, &wheelEventUpdate);
QWheelEvent wheelEventEnd(globalEventPos, zero, zero, zero, mouseButtons, keyboardModifiers, Qt::ScrollEnd, false);
QApplication::sendEvent(widget, &wheelEventEnd);
}
AZStd::string QtKeyToAzString(Qt::Key key, Qt::KeyboardModifiers modifiers)
{
QKeySequence keySequence = QKeySequence(key);
QString keyText = keySequence.toString();
// QKeySequence seems to uppercase alpha keys regardless of shift-modifier
if (modifiers == Qt::NoModifier && keyText.isUpper())
{
keyText = keyText.toLower();
}
else if (modifiers != Qt::ShiftModifier)
{
keyText = QString();
}
return AZStd::string(keyText.toUtf8().data());
}
bool TestWidget::eventFilter(QObject* watched, QEvent* event)
{
AZ_UNUSED(watched);
@@ -77,6 +77,20 @@ namespace UnitTest
/// @param mouseButton The button to be held during the move.
void MouseMove(QWidget* widget, const QPoint& initialPosition, const QPoint& mouseDelta, Qt::MouseButton mouseButton = Qt::NoButton);
/// Performs a full series (begin, update, end) of mouse wheel events on the provided widget.
/// @param widget The widget to perform the mouse wheel events on.
/// @param localEventPosition The position of the mouse relative to the widget (will be remapped to a global position internally).
/// @param wheelDelta How far to move the mouse (note: mouseDelta may be zero and the mouse will only be moved to initialPosition).
/// @param mouseButtons Optional mouse buttons to include during the wheel events, defaults to Qt::NoButton
/// @param keyboardModifiers Optional keyboard modifiers to include during the wheel events, defaults to Qt::NoModifier
void MouseScroll(QWidget* widget, QPoint localEventPosition, QPoint wheelDelta,
Qt::MouseButtons mouseButtons = Qt::NoButton, Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier);
/// Convert a Qt::Key + optional modifiers to the printable text of the key sequence
/// @param key The widget to perform the mouse wheel event on.
/// @param modifiers Optional keyboard modifiers to include during the wheel events, defaults to Qt::NoModifier
AZStd::string QtKeyToAzString(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
/// Test widget to store QActions generated by EditorTransformComponentSelection.
class TestWidget : public QWidget
{