[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
{
@@ -0,0 +1,515 @@
/*
* 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/Input/QtEventToAzInputMapper.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzFramework/Input/Events/InputTextEventListener.h>
namespace UnitTest
{
static bool IsMouseButton(const AzFramework::InputChannelId& inputChannelId)
{
const auto& buttons = AzFramework::InputDeviceMouse::Button::All;
const auto& it = AZStd::find(buttons.cbegin(), buttons.cend(), inputChannelId);
return it != buttons.cend();
}
class QtEventToAzInputMapperFixture
: public AllocatorsTestFixture
, public AzFramework::InputChannelNotificationBus::Handler
, public AzFramework::InputTextNotificationBus::Handler
{
public:
static inline constexpr QSize WidgetSize = QSize(1920, 1080);
static inline constexpr int TestDeviceIdSeed = 4321;
void SetUp() override
{
AllocatorsTestFixture::SetUp();
m_rootWidget = AZStd::make_unique<QWidget>();
m_rootWidget->setFixedSize(WidgetSize);
m_rootWidget->move(0, 0);
m_inputChannelMapper = AZStd::make_unique<AzToolsFramework::QtEventToAzInputMapper>(m_rootWidget.get(), TestDeviceIdSeed);
// listen for events signaled from QtEventToAzInputMapper and forward to the controller list
QObject::connect(m_inputChannelMapper.get(), &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, m_rootWidget.get(),
[this]([[maybe_unused]] const AzFramework::InputChannel* inputChannel, QEvent* event)
{
const QEvent::Type eventType = event->type();
if (eventType == QEvent::Type::MouseButtonPress ||
eventType == QEvent::Type::MouseButtonRelease ||
eventType == QEvent::Type::MouseButtonDblClick)
{
m_signalEvents.push_back(QtEventInfo(static_cast<QMouseEvent*>(event)));
event->accept();
}
else if (eventType == QEvent::Type::Wheel)
{
m_signalEvents.push_back(QtEventInfo(static_cast<QWheelEvent*>(event)));
event->accept();
}
else if (eventType == QEvent::Type::KeyPress ||
eventType == QEvent::Type::KeyRelease ||
eventType == QEvent::Type::ShortcutOverride)
{
m_signalEvents.push_back(QtEventInfo(static_cast<QKeyEvent*>(event)));
event->accept();
}
});
}
void TearDown() override
{
m_inputChannelMapper.reset();
m_rootWidget.reset();
AllocatorsTestFixture::TearDown();
}
void OnInputChannelEvent(const AzFramework::InputChannel& inputChannel, bool& hasBeenConsumed) override
{
AZ_Assert(hasBeenConsumed == false, "Unexpected input event consumed elsewhere during QtEventToAzInputMapper tests");
const AzFramework::InputChannelId& inputChannelId = inputChannel.GetInputChannelId();
const AzFramework::InputDeviceId& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId();
if (AzFramework::InputDeviceMouse::IsMouseDevice(inputDeviceId))
{
if (IsMouseButton(inputChannelId))
{
m_azChannelEvents.push_back(AzEventInfo(inputChannel));
hasBeenConsumed = m_captureAzEvents;
}
else if (inputChannelId == AzFramework::InputDeviceMouse::Movement::Z)
{
m_azChannelEvents.push_back(AzEventInfo(inputChannel));
hasBeenConsumed = m_captureAzEvents;
}
}
else if (AzFramework::InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId))
{
m_azChannelEvents.push_back(AzEventInfo(inputChannel));
hasBeenConsumed = m_captureAzEvents;
}
}
void OnInputTextEvent(const AZStd::string& textUtf8, bool& hasBeenConsumed) override
{
AZ_Assert(hasBeenConsumed == false, "Unexpected text event consumed elsewhere during QtEventToAzInputMapper tests");
m_azTextEvents.push_back(textUtf8);
hasBeenConsumed = m_captureTextEvents;
}
// simple structure for caching minimal QtEvent data necessary for testing
struct QtEventInfo
{
explicit QtEventInfo(QMouseEvent* mouseEvent)
: m_eventType(mouseEvent->type())
, m_button(mouseEvent->button())
{
}
explicit QtEventInfo(QWheelEvent* mouseWheelEvent)
: m_eventType(mouseWheelEvent->type())
, m_scrollPhase(mouseWheelEvent->phase())
{
}
explicit QtEventInfo(QKeyEvent* keyEvent)
: m_eventType(keyEvent->type())
, m_key(keyEvent->key())
{
}
QEvent::Type m_eventType{ QEvent::None };
Qt::MouseButton m_button{ Qt::NoButton };
Qt::ScrollPhase m_scrollPhase{ Qt::NoScrollPhase };
int m_key{ 0 };
};
// simple structure for caching minimal AzInput event data necessary for testing
struct AzEventInfo
{
AzEventInfo() = delete;
explicit AzEventInfo(const AzFramework::InputChannel& inputChannel)
: m_inputChannelId(inputChannel.GetInputChannelId())
, m_isActive(inputChannel.IsActive())
{
}
AzFramework::InputChannelId m_inputChannelId;
bool m_isActive;
};
AZStd::unique_ptr<QWidget> m_rootWidget;
AZStd::unique_ptr<AzToolsFramework::QtEventToAzInputMapper> m_inputChannelMapper;
AZStd::vector<QtEventInfo> m_signalEvents;
AZStd::vector<AzEventInfo> m_azChannelEvents;
AZStd::vector<AZStd::string> m_azTextEvents;
bool m_captureAzEvents{ false };
bool m_captureTextEvents{ false };
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Qt event forwarding through the internal signal handler test
TEST_F(QtEventToAzInputMapperFixture, MouseWheel_NoAzHandlers_ReceivedThreeSignalAndZeroAzChannelEvents)
{
// setup
const QPoint mouseEventPos = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2);
const QPoint scrollDelta = QPoint(10, 10);
MouseScroll(m_rootWidget.get(), mouseEventPos, scrollDelta);
// qt validation
ASSERT_EQ(m_signalEvents.size(), 3);
EXPECT_EQ(m_signalEvents[0].m_eventType, QEvent::Type::Wheel);
EXPECT_EQ(m_signalEvents[0].m_scrollPhase, Qt::ScrollBegin);
EXPECT_EQ(m_signalEvents[1].m_eventType, QEvent::Type::Wheel);
EXPECT_EQ(m_signalEvents[1].m_scrollPhase, Qt::ScrollUpdate);
EXPECT_EQ(m_signalEvents[2].m_eventType, QEvent::Type::Wheel);
EXPECT_EQ(m_signalEvents[2].m_scrollPhase, Qt::ScrollEnd);
// az validation
EXPECT_EQ(m_azChannelEvents.size(), 0);
}
// Qt event to AzInput event conversion test
TEST_F(QtEventToAzInputMapperFixture, MouseWheel_AzHandlerNotCaptured_ReceivedThreeSignalAndThreeAzChannelEvents)
{
// setup
const AzFramework::InputChannelId mouseWheelId = AzFramework::InputDeviceMouse::Movement::Z;
const char* mouseWheelChannelName = mouseWheelId.GetName();
AzFramework::InputChannelNotificationBus::Handler::BusConnect();
m_captureAzEvents = false;
const QPoint mouseEventPos = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2);
const QPoint scrollDelta = QPoint(10, 10);
MouseScroll(m_rootWidget.get(), mouseEventPos, scrollDelta);
// qt validation
ASSERT_EQ(m_signalEvents.size(), 3);
EXPECT_EQ(m_signalEvents[0].m_eventType, QEvent::Type::Wheel);
EXPECT_EQ(m_signalEvents[0].m_scrollPhase, Qt::ScrollBegin);
EXPECT_EQ(m_signalEvents[1].m_eventType, QEvent::Type::Wheel);
EXPECT_EQ(m_signalEvents[1].m_scrollPhase, Qt::ScrollUpdate);
EXPECT_EQ(m_signalEvents[2].m_eventType, QEvent::Type::Wheel);
EXPECT_EQ(m_signalEvents[2].m_scrollPhase, Qt::ScrollEnd);
// az validation
ASSERT_EQ(m_azChannelEvents.size(), 3);
EXPECT_STREQ(m_azChannelEvents[0].m_inputChannelId.GetName(), mouseWheelChannelName);
EXPECT_STREQ(m_azChannelEvents[1].m_inputChannelId.GetName(), mouseWheelChannelName);
EXPECT_STREQ(m_azChannelEvents[2].m_inputChannelId.GetName(), mouseWheelChannelName);
// cleanup
AzFramework::InputChannelNotificationBus::Handler::BusDisconnect();
}
// AzInput event handler consumption test
TEST_F(QtEventToAzInputMapperFixture, MouseWheel_AzHandlerCaptured_ReceivedZeroSignalAndThreeAzChannelEvents)
{
// setup
const AzFramework::InputChannelId mouseWheelId = AzFramework::InputDeviceMouse::Movement::Z;
const char* mouseWheelChannelName = mouseWheelId.GetName();
AzFramework::InputChannelNotificationBus::Handler::BusConnect();
m_captureAzEvents = true;
const QPoint mouseEventPos = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2);
const QPoint scrollDelta = QPoint(10, 10);
MouseScroll(m_rootWidget.get(), mouseEventPos, scrollDelta);
// qt validation
EXPECT_EQ(m_signalEvents.size(), 0);
// az validation
ASSERT_EQ(m_azChannelEvents.size(), 3);
EXPECT_STREQ(m_azChannelEvents[0].m_inputChannelId.GetName(), mouseWheelChannelName);
EXPECT_STREQ(m_azChannelEvents[1].m_inputChannelId.GetName(), mouseWheelChannelName);
EXPECT_STREQ(m_azChannelEvents[2].m_inputChannelId.GetName(), mouseWheelChannelName);
// cleanup
AzFramework::InputChannelNotificationBus::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct MouseButtonIdsParam
{
Qt::MouseButton m_qt;
AzFramework::InputChannelId m_az;
};
class MouseButtonParamQtEventToAzInputMapperFixture
: public QtEventToAzInputMapperFixture
, public ::testing::WithParamInterface<MouseButtonIdsParam>
{
};
// Qt event forwarding through the internal signal handler test
TEST_P(MouseButtonParamQtEventToAzInputMapperFixture, MouseClick_NoAzHandlers_ReceivedTwoSignalAndZeroAzChannelEvents)
{
// setup
const MouseButtonIdsParam mouseButtonIds = GetParam();
const QPoint mouseEventPos = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2);
QTest::mouseClick(m_rootWidget.get(), mouseButtonIds.m_qt, Qt::NoModifier, mouseEventPos);
// qt validation
ASSERT_EQ(m_signalEvents.size(), 2);
EXPECT_EQ(m_signalEvents[0].m_eventType, QEvent::Type::MouseButtonPress);
EXPECT_EQ(m_signalEvents[0].m_button, mouseButtonIds.m_qt);
EXPECT_EQ(m_signalEvents[1].m_eventType, QEvent::Type::MouseButtonRelease);
EXPECT_EQ(m_signalEvents[1].m_button, mouseButtonIds.m_qt);
// az validation
EXPECT_EQ(m_azChannelEvents.size(), 0);
}
// Qt event to AzInput event conversion test
TEST_P(MouseButtonParamQtEventToAzInputMapperFixture, MouseClick_AzHandlerNotCaptured_ReceivedTwoSignalAndTwoAzChannelEvents)
{
// setup
const MouseButtonIdsParam mouseButtonIds = GetParam();
AzFramework::InputChannelNotificationBus::Handler::BusConnect();
m_captureAzEvents = false;
const QPoint mouseEventPos = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2);
QTest::mouseClick(m_rootWidget.get(), mouseButtonIds.m_qt, Qt::NoModifier, mouseEventPos);
// qt validation
ASSERT_EQ(m_signalEvents.size(), 2);
EXPECT_EQ(m_signalEvents[0].m_eventType, QEvent::Type::MouseButtonPress);
EXPECT_EQ(m_signalEvents[0].m_button, mouseButtonIds.m_qt);
EXPECT_EQ(m_signalEvents[1].m_eventType, QEvent::Type::MouseButtonRelease);
EXPECT_EQ(m_signalEvents[1].m_button, mouseButtonIds.m_qt);
// az validation
ASSERT_EQ(m_azChannelEvents.size(), 2);
EXPECT_STREQ(m_azChannelEvents[0].m_inputChannelId.GetName(), mouseButtonIds.m_az.GetName());
EXPECT_TRUE(m_azChannelEvents[0].m_isActive);
EXPECT_STREQ(m_azChannelEvents[1].m_inputChannelId.GetName(), mouseButtonIds.m_az.GetName());
EXPECT_FALSE(m_azChannelEvents[1].m_isActive);
// cleanup
AzFramework::InputChannelNotificationBus::Handler::BusDisconnect();
}
// AzInput event handler consumption test
TEST_P(MouseButtonParamQtEventToAzInputMapperFixture, MouseClick_AzHandlerCaptured_ReceivedZeroSignalAndTwoAzChannelEvents)
{
// setup
const MouseButtonIdsParam mouseButtonIds = GetParam();
AzFramework::InputChannelNotificationBus::Handler::BusConnect();
m_captureAzEvents = true;
const QPoint mouseEventPos = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2);
QTest::mouseClick(m_rootWidget.get(), mouseButtonIds.m_qt, Qt::NoModifier, mouseEventPos);
// qt validation
EXPECT_EQ(m_signalEvents.size(), 0);
// az validation
ASSERT_EQ(m_azChannelEvents.size(), 2);
EXPECT_STREQ(m_azChannelEvents[0].m_inputChannelId.GetName(), mouseButtonIds.m_az.GetName());
EXPECT_TRUE(m_azChannelEvents[0].m_isActive);
EXPECT_STREQ(m_azChannelEvents[1].m_inputChannelId.GetName(), mouseButtonIds.m_az.GetName());
EXPECT_FALSE(m_azChannelEvents[1].m_isActive);
// cleanup
AzFramework::InputChannelNotificationBus::Handler::BusDisconnect();
}
INSTANTIATE_TEST_CASE_P(All, MouseButtonParamQtEventToAzInputMapperFixture,
testing::Values(
MouseButtonIdsParam{ Qt::MouseButton::LeftButton, AzFramework::InputDeviceMouse::Button::Left },
MouseButtonIdsParam{ Qt::MouseButton::RightButton, AzFramework::InputDeviceMouse::Button::Right },
MouseButtonIdsParam{ Qt::MouseButton::MiddleButton, AzFramework::InputDeviceMouse::Button::Middle }
),
[](const ::testing::TestParamInfo<MouseButtonIdsParam>& info)
{
return info.param.m_az.GetName();
}
);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct KeyEventIdsParam
{
Qt::Key m_qt;
AzFramework::InputChannelId m_az;
};
class PrintableKeyEventParamQtEventToAzInputMapperFixture
: public QtEventToAzInputMapperFixture
, public ::testing::WithParamInterface<KeyEventIdsParam>
{
};
// Qt event forwarding through the internal signal handler test
TEST_P(PrintableKeyEventParamQtEventToAzInputMapperFixture, KeyClick_NoAzHandlers_ReceivedTwoSignalAndZeroAzEvents)
{
// setup
const KeyEventIdsParam keyEventIds = GetParam();
const Qt::KeyboardModifiers modifiers = Qt::NoModifier;
QTest::keyClick(m_rootWidget.get(), keyEventIds.m_qt, modifiers);
// qt validation
ASSERT_EQ(m_signalEvents.size(), 2);
EXPECT_EQ(m_signalEvents[0].m_eventType, QEvent::Type::KeyPress);
EXPECT_EQ(m_signalEvents[0].m_key, keyEventIds.m_qt);
EXPECT_EQ(m_signalEvents[1].m_eventType, QEvent::Type::KeyRelease);
EXPECT_EQ(m_signalEvents[1].m_key, keyEventIds.m_qt);
// az validation
EXPECT_EQ(m_azChannelEvents.size(), 0);
EXPECT_EQ(m_azTextEvents.size(), 0);
}
// Qt event to AzInput event conversion test
TEST_P(PrintableKeyEventParamQtEventToAzInputMapperFixture, KeyClick_AzHandlersNotCaptured_ReceivedTwoSignalAndThreeAzEvents)
{
// setup
const KeyEventIdsParam keyEventIds = GetParam();
const Qt::KeyboardModifiers modifiers = Qt::NoModifier;
AZStd::string keyAsText = QtKeyToAzString(keyEventIds.m_qt, modifiers);
AzFramework::InputChannelNotificationBus::Handler::BusConnect();
m_captureAzEvents = false;
AzFramework::InputTextNotificationBus::Handler::BusConnect();
m_captureAzEvents = false;
QTest::keyClick(m_rootWidget.get(), keyEventIds.m_qt, modifiers);
// qt validation
ASSERT_EQ(m_signalEvents.size(), 2);
EXPECT_EQ(m_signalEvents[0].m_eventType, QEvent::Type::KeyPress);
EXPECT_EQ(m_signalEvents[0].m_key, keyEventIds.m_qt);
EXPECT_EQ(m_signalEvents[1].m_eventType, QEvent::Type::KeyRelease);
EXPECT_EQ(m_signalEvents[1].m_key, keyEventIds.m_qt);
// az validation
ASSERT_EQ(m_azTextEvents.size(), 1);
EXPECT_STREQ(m_azTextEvents[0].c_str(), keyAsText.c_str());
ASSERT_EQ(m_azChannelEvents.size(), 2);
EXPECT_STREQ(m_azChannelEvents[0].m_inputChannelId.GetName(), keyEventIds.m_az.GetName());
EXPECT_TRUE(m_azChannelEvents[0].m_isActive);
EXPECT_STREQ(m_azChannelEvents[1].m_inputChannelId.GetName(), keyEventIds.m_az.GetName());
EXPECT_FALSE(m_azChannelEvents[1].m_isActive);
// cleanup
AzFramework::InputTextNotificationBus::Handler::BusDisconnect();
AzFramework::InputChannelNotificationBus::Handler::BusDisconnect();
}
INSTANTIATE_TEST_CASE_P(All, PrintableKeyEventParamQtEventToAzInputMapperFixture,
testing::Values(
KeyEventIdsParam{ Qt::Key_0, AzFramework::InputDeviceKeyboard::Key::Alphanumeric0 },
KeyEventIdsParam{ Qt::Key_1, AzFramework::InputDeviceKeyboard::Key::Alphanumeric1 },
KeyEventIdsParam{ Qt::Key_2, AzFramework::InputDeviceKeyboard::Key::Alphanumeric2 },
KeyEventIdsParam{ Qt::Key_3, AzFramework::InputDeviceKeyboard::Key::Alphanumeric3 },
KeyEventIdsParam{ Qt::Key_4, AzFramework::InputDeviceKeyboard::Key::Alphanumeric4 },
KeyEventIdsParam{ Qt::Key_5, AzFramework::InputDeviceKeyboard::Key::Alphanumeric5 },
KeyEventIdsParam{ Qt::Key_6, AzFramework::InputDeviceKeyboard::Key::Alphanumeric6 },
KeyEventIdsParam{ Qt::Key_7, AzFramework::InputDeviceKeyboard::Key::Alphanumeric7 },
KeyEventIdsParam{ Qt::Key_8, AzFramework::InputDeviceKeyboard::Key::Alphanumeric8 },
KeyEventIdsParam{ Qt::Key_9, AzFramework::InputDeviceKeyboard::Key::Alphanumeric9 },
KeyEventIdsParam{ Qt::Key_A, AzFramework::InputDeviceKeyboard::Key::AlphanumericA },
KeyEventIdsParam{ Qt::Key_B, AzFramework::InputDeviceKeyboard::Key::AlphanumericB },
KeyEventIdsParam{ Qt::Key_C, AzFramework::InputDeviceKeyboard::Key::AlphanumericC },
KeyEventIdsParam{ Qt::Key_D, AzFramework::InputDeviceKeyboard::Key::AlphanumericD },
KeyEventIdsParam{ Qt::Key_E, AzFramework::InputDeviceKeyboard::Key::AlphanumericE },
KeyEventIdsParam{ Qt::Key_F, AzFramework::InputDeviceKeyboard::Key::AlphanumericF },
KeyEventIdsParam{ Qt::Key_G, AzFramework::InputDeviceKeyboard::Key::AlphanumericG },
KeyEventIdsParam{ Qt::Key_H, AzFramework::InputDeviceKeyboard::Key::AlphanumericH },
KeyEventIdsParam{ Qt::Key_I, AzFramework::InputDeviceKeyboard::Key::AlphanumericI },
KeyEventIdsParam{ Qt::Key_J, AzFramework::InputDeviceKeyboard::Key::AlphanumericJ },
KeyEventIdsParam{ Qt::Key_K, AzFramework::InputDeviceKeyboard::Key::AlphanumericK },
KeyEventIdsParam{ Qt::Key_L, AzFramework::InputDeviceKeyboard::Key::AlphanumericL },
KeyEventIdsParam{ Qt::Key_M, AzFramework::InputDeviceKeyboard::Key::AlphanumericM },
KeyEventIdsParam{ Qt::Key_N, AzFramework::InputDeviceKeyboard::Key::AlphanumericN },
KeyEventIdsParam{ Qt::Key_O, AzFramework::InputDeviceKeyboard::Key::AlphanumericO },
KeyEventIdsParam{ Qt::Key_P, AzFramework::InputDeviceKeyboard::Key::AlphanumericP },
KeyEventIdsParam{ Qt::Key_Q, AzFramework::InputDeviceKeyboard::Key::AlphanumericQ },
KeyEventIdsParam{ Qt::Key_R, AzFramework::InputDeviceKeyboard::Key::AlphanumericR },
KeyEventIdsParam{ Qt::Key_S, AzFramework::InputDeviceKeyboard::Key::AlphanumericS },
KeyEventIdsParam{ Qt::Key_T, AzFramework::InputDeviceKeyboard::Key::AlphanumericT },
KeyEventIdsParam{ Qt::Key_U, AzFramework::InputDeviceKeyboard::Key::AlphanumericU },
KeyEventIdsParam{ Qt::Key_V, AzFramework::InputDeviceKeyboard::Key::AlphanumericV },
KeyEventIdsParam{ Qt::Key_W, AzFramework::InputDeviceKeyboard::Key::AlphanumericW },
KeyEventIdsParam{ Qt::Key_X, AzFramework::InputDeviceKeyboard::Key::AlphanumericX },
KeyEventIdsParam{ Qt::Key_Y, AzFramework::InputDeviceKeyboard::Key::AlphanumericY },
KeyEventIdsParam{ Qt::Key_Z, AzFramework::InputDeviceKeyboard::Key::AlphanumericZ },
// these may need to be special cased due to the printable text conversion
//KeyEventIdsParam{ Qt::Key_Space, AzFramework::InputDeviceKeyboard::Key::EditSpace },
//KeyEventIdsParam{ Qt::Key_Tab, AzFramework::InputDeviceKeyboard::Key::EditTab },
KeyEventIdsParam{ Qt::Key_Apostrophe, AzFramework::InputDeviceKeyboard::Key::PunctuationApostrophe },
KeyEventIdsParam{ Qt::Key_Backslash, AzFramework::InputDeviceKeyboard::Key::PunctuationBackslash },
KeyEventIdsParam{ Qt::Key_BracketLeft, AzFramework::InputDeviceKeyboard::Key::PunctuationBracketL },
KeyEventIdsParam{ Qt::Key_BracketRight, AzFramework::InputDeviceKeyboard::Key::PunctuationBracketR },
KeyEventIdsParam{ Qt::Key_Comma, AzFramework::InputDeviceKeyboard::Key::PunctuationComma },
KeyEventIdsParam{ Qt::Key_Equal, AzFramework::InputDeviceKeyboard::Key::PunctuationEquals },
KeyEventIdsParam{ Qt::Key_hyphen, AzFramework::InputDeviceKeyboard::Key::PunctuationHyphen },
KeyEventIdsParam{ Qt::Key_Period, AzFramework::InputDeviceKeyboard::Key::PunctuationPeriod },
KeyEventIdsParam{ Qt::Key_Semicolon, AzFramework::InputDeviceKeyboard::Key::PunctuationSemicolon },
KeyEventIdsParam{ Qt::Key_Slash, AzFramework::InputDeviceKeyboard::Key::PunctuationSlash },
KeyEventIdsParam{ Qt::Key_QuoteLeft, AzFramework::InputDeviceKeyboard::Key::PunctuationTilde }
),
[](const ::testing::TestParamInfo<KeyEventIdsParam>& info)
{
return info.param.m_az.GetName();
}
);
} // namespace UnitTest
@@ -48,6 +48,7 @@ set(FILES
FocusMode/EditorFocusModeSelectionTests.cpp
FocusMode/EditorFocusModeTests.cpp
GenericComponentWrapperTest.cpp
Input/QtEventToAzInputMapperTests.cpp
InstanceDataHierarchy.cpp
IntegerPrimtitiveTestConfig.h
LogLines.cpp
+42 -38
View File
@@ -401,34 +401,37 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
const InputChannelId& inputChannelId = inputChannel.GetInputChannelId();
const InputDeviceId& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId();
// Handle Keyboard Hotkeys
if (InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId) && inputChannel.IsStateBegan())
{
// Cycle through ImGui Menu Bar States on Home button press
if (inputChannelId == InputDeviceKeyboard::Key::NavigationHome)
{
ToggleThroughImGuiVisibleState();
}
bool consumeEvent = false;
// Cycle through Standalone Editor Window States
if (inputChannel.GetInputChannelId() == InputDeviceKeyboard::Key::NavigationEnd)
{
if (gEnv->IsEditor() && m_editorWindowState == DisplayState::Hidden)
{
ImGuiUpdateListenerBus::Broadcast(&IImGuiUpdateListener::OnOpenEditorWindow);
}
else
{
m_editorWindowState = m_editorWindowState == DisplayState::Visible
? DisplayState::VisibleNoMouse
: DisplayState::Visible;
}
}
}
// Handle Keyboard Modifier Keys
// Handle Keyboard Inputs
if (InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId))
{
// Handle Keyboard Hotkeys
if (inputChannel.IsStateBegan())
{
// Cycle through ImGui Menu Bar States on Home button press
if (inputChannelId == InputDeviceKeyboard::Key::NavigationHome)
{
ToggleThroughImGuiVisibleState();
}
// Cycle through Standalone Editor Window States
if (inputChannel.GetInputChannelId() == InputDeviceKeyboard::Key::NavigationEnd)
{
if (gEnv->IsEditor() && m_editorWindowState == DisplayState::Hidden)
{
ImGuiUpdateListenerBus::Broadcast(&IImGuiUpdateListener::OnOpenEditorWindow);
}
else
{
m_editorWindowState = m_editorWindowState == DisplayState::Visible
? DisplayState::VisibleNoMouse
: DisplayState::Visible;
}
}
}
// Handle Keyboard Modifier Keys
if (inputChannelId == InputDeviceKeyboard::Key::ModifierShiftL
|| inputChannelId == InputDeviceKeyboard::Key::ModifierShiftR)
{
@@ -454,7 +457,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
}
// Handle Controller Inputs
if (InputDeviceGamepad::IsGamepadDevice(inputDeviceId))
else if (InputDeviceGamepad::IsGamepadDevice(inputDeviceId))
{
// Only pipe in Controller Nav Inputs when at least 1 of the two controller modes are enabled.
if (m_controllerModeFlags)
@@ -496,32 +499,30 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
{
ToggleThroughImGuiVisibleState();
}
// If we have the Discrete Input Mode Enabled.. and we are in the Visible State, then consume input here
if (m_enableDiscreteInputMode && m_clientMenuBarState == DisplayState::Visible)
{
return true;
}
return false;
}
// Handle Mouse Inputs
if (InputDeviceMouse::IsMouseDevice(inputDeviceId))
else if (InputDeviceMouse::IsMouseDevice(inputDeviceId))
{
const int mouseButtonIndex = GetAzMouseButtonIndex(inputChannelId);
if (0 <= mouseButtonIndex && mouseButtonIndex < AZ_ARRAY_SIZE(io.MouseDown))
{
io.MouseDown[mouseButtonIndex] = inputChannel.IsActive();
// only consume the event during edit mode in the editor so the viewport doesn't also respond to it
consumeEvent = gEnv->IsEditing() && io.WantCaptureMouse;
}
else if (inputChannelId == InputDeviceMouse::Movement::Z)
{
io.MouseWheel = inputChannel.GetValue() / static_cast<float>(IMGUI_WHEEL_DELTA);
// only consume the event during edit mode in the editor so the viewport doesn't also respond to it
consumeEvent = gEnv->IsEditing() && io.WantCaptureMouse;
}
}
// Handle Touch Inputs
if (InputDeviceTouch::IsTouchDevice(inputDeviceId))
else if (InputDeviceTouch::IsTouchDevice(inputDeviceId))
{
const int touchIndex = GetAzTouchIndex(inputChannelId);
if (0 <= touchIndex && touchIndex < AZ_ARRAY_SIZE(io.MouseDown))
@@ -542,7 +543,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
}
// Handle Virtual Keyboard Inputs
if (InputDeviceVirtualKeyboard::IsVirtualKeyboardDevice(inputDeviceId))
else if (InputDeviceVirtualKeyboard::IsVirtualKeyboardDevice(inputDeviceId))
{
if (inputChannelId == AzFramework::InputDeviceVirtualKeyboard::Command::EditEnter)
{
@@ -554,13 +555,16 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
if (m_clientMenuBarState == DisplayState::Visible
|| m_editorWindowState == DisplayState::Visible)
{
// If we have the Discrete Input Mode Enabled.. then consume the input here.
// If we have the Discrete Input Mode Enabled.. then consume the input here.
if (m_enableDiscreteInputMode)
{
return true;
}
return consumeEvent;
}
// don't allow event capturing when ImGui isn't active
return false;
}
@@ -324,7 +324,8 @@ namespace Profiler
// Gets called when region ends and all data is set
void CpuTimingLocalStorage::AddCachedRegion(const CachedTimeRegion& timeRegionCached)
{
if (m_hitSizeLimitMap[timeRegionCached.m_groupRegionName.m_regionName])
if (auto iter = m_hitSizeLimitMap.find(timeRegionCached.m_groupRegionName.m_regionName);
iter != m_hitSizeLimitMap.end() && iter->second)
{
return;
}
@@ -15,6 +15,7 @@
#include <AzCore/Debug/ProfilerBus.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/JSON/filereadstream.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/JsonSerializationResult.h>
@@ -375,6 +376,7 @@ namespace Profiler
DrawTable();
}
ImGui::EndChild();
}
void ImGuiCpuProfiler::DrawFilePicker()
@@ -597,8 +599,9 @@ namespace Profiler
DrawFrameBoundaries();
// Draw an invisible button to capture inputs
ImGui::InvisibleButton("Timeline Input", { ImGui::GetWindowContentRegionWidth(), baseRow * RowHeight });
// Draw an invisible button to capture inputs and make sure it has a non-zero height
ImGui::InvisibleButton("Timeline Input",
{ ImGui::GetWindowContentRegionWidth(), AZ::GetMax(baseRow, decltype(baseRow){1}) * RowHeight });
// Controls
ImGuiIO& io = ImGui::GetIO();
@@ -643,7 +646,9 @@ namespace Profiler
}
}
}
ImGui::EndChild();
ImGui::EndChild(); // "Timeline"
ImGui::EndChild(); // "Options and Statistics"
}
void ImGuiCpuProfiler::CacheCpuTimingStatistics()