[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:
@@ -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.
|
||||
|
||||
+34
@@ -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);
|
||||
|
||||
+14
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user