Make Viewport input events fully Qt based (#2142)

* Make Viewport input events fully Qt based.

This should fix an issue with Qt touch event -> mouse event translation in the Editor, and may also fix issues with the Mac Editor and remote desktop (though, lacking the requisite hardware, I can test precisely none of these things personally).

See https://github.com/o3de/o3de/issues/1889

- Adapted LegacyViewportCameraController to use Movement::X & Y (mostly for testing purposes, it's on the slate for being removed soon)
- Moved cursor capture logic from RenderViewportWidget into QtEventToAzInputManager so that it can make sure it generates correct movement deltas
- Removed ViewportMouseCursorRequests::PreviousViewportCursorScreenPosition to have our viewport controllers use our dedicated Movement::X and Y channels instead, which will work in the launcher

Signed-off-by: nvsickle <nvsickle@amazon.com>

* Address review feedback

Signed-off-by: nvsickle <nvsickle@amazon.com>

* Fix Linux build

Signed-off-by: nvsickle <nvsickle@amazon.com>
This commit is contained in:
Nicholas Van Sickle
2021-07-13 20:58:48 -07:00
committed by GitHub
parent 11327b59ea
commit 867ae23664
7 changed files with 93 additions and 72 deletions
+19 -20
View File
@@ -83,9 +83,9 @@ AZ::RPI::ViewportContextPtr LegacyViewportCameraControllerInstance::GetViewportC
}
bool LegacyViewportCameraControllerInstance::HandleMouseMove(
const AzFramework::ScreenPoint& currentMousePos, const AzFramework::ScreenPoint& previousMousePos)
int dx, int dy)
{
if (previousMousePos == currentMousePos)
if (dx == 0 && dy == 0)
{
return false;
}
@@ -105,7 +105,7 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove(
if (m_inMoveMode || m_inOrbitMode || m_inRotateMode || m_inZoomMode)
{
m_totalMouseMoveDelta += (QPoint(currentMousePos.m_x, currentMousePos.m_y)-QPoint(previousMousePos.m_x, previousMousePos.m_y)).manhattanLength();
m_totalMouseMoveDelta += AZStd::abs(dx) + AZStd::abs(dy);
}
if ((m_inRotateMode && m_inMoveMode) || m_inZoomMode)
@@ -115,7 +115,7 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove(
Vec3 ydir = m.GetColumn1().GetNormalized();
Vec3 pos = m.GetTranslation();
const float posDelta = 0.2f * (previousMousePos.m_y - currentMousePos.m_y) * speedScale;
const float posDelta = 0.2f * dy * speedScale;
pos = pos - ydir * posDelta;
m_orbitDistance = m_orbitDistance + posDelta;
m_orbitDistance = fabs(m_orbitDistance);
@@ -126,7 +126,7 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove(
}
else if (m_inRotateMode)
{
Ang3 angles(-currentMousePos.m_y + previousMousePos.m_y, 0, -currentMousePos.m_x + previousMousePos.m_x);
Ang3 angles(dy, 0, dx);
angles = angles * 0.002f * gSettings.cameraRotateSpeed;
if (gSettings.invertYRotation)
{
@@ -158,7 +158,7 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove(
}
Vec3 pos = m.GetTranslation();
pos += 0.1f * xdir * (currentMousePos.m_x - previousMousePos.m_x) * speedScale + 0.1f * zdir * (previousMousePos.m_y - currentMousePos.m_y) * speedScale;
pos += 0.1f * xdir * dx * speedScale + 0.1f * zdir * dy * speedScale;
m.SetTranslation(pos);
AZ::Transform transform = viewportContext->GetCameraTransform();
@@ -168,7 +168,7 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove(
}
else if (m_inOrbitMode)
{
Ang3 angles(-currentMousePos.m_y + previousMousePos.m_y, 0, -currentMousePos.m_x + previousMousePos.m_x);
Ang3 angles(dy, 0, dx);
angles = angles * 0.002f * gSettings.cameraRotateSpeed;
if (gSettings.invertPan)
@@ -302,20 +302,19 @@ bool LegacyViewportCameraControllerInstance::HandleInputChannelEvent(const AzFra
bool shouldCaptureCursor = m_capturingCursor;
bool shouldConsumeEvent = false;
if (id == AzFramework::InputDeviceMouse::SystemCursorPosition)
if (id == AzFramework::InputDeviceMouse::Movement::X || id == AzFramework::InputDeviceMouse::Movement::Y)
{
bool result = false;
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Event(
GetViewportId(),
[this, &result](AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequests* mouseRequests)
{
if (auto previousMousePosition = mouseRequests->PreviousViewportCursorScreenPosition();
previousMousePosition.has_value())
{
result = HandleMouseMove(mouseRequests->ViewportCursorScreenPosition(), previousMousePosition.value());
}
});
return result;
int dx = 0;
int dy = 0;
if (id == AzFramework::InputDeviceMouse::Movement::X)
{
dx = -aznumeric_cast<int>(event.m_inputChannel.GetValue());
}
else
{
dy = -aznumeric_cast<int>(event.m_inputChannel.GetValue());
}
return HandleMouseMove(dx, dy);
}
else if (id == MouseButton::Left)
{
+1 -1
View File
@@ -69,7 +69,7 @@ namespace SandboxEditor
AZ::RPI::ViewportContextPtr GetViewportContext();
bool HandleMouseMove(const AzFramework::ScreenPoint& currentMousePos, const AzFramework::ScreenPoint& previousMousePos);
bool HandleMouseMove(int dx, int dy);
bool HandleMouseWheel(float zDelta);
bool IsKeyDown(Qt::Key key) const;
void UpdateCursorCapture(bool shouldCaptureCursor);
@@ -11,6 +11,7 @@
#include <AzFramework/Input/Buses/Notifications/InputChannelNotificationBus.h>
#include <AzFramework/Input/Buses/Requests/InputChannelRequestBus.h>
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
#include <QApplication>
#include <QCursor>
@@ -187,12 +188,6 @@ namespace AzToolsFramework
bool QtEventToAzInputMapper::HandlesInputEvent(const AzFramework::InputChannel& channel) const
{
const AzFramework::InputChannelId& channelId = channel.GetInputChannelId();
if (channelId == AzFramework::InputDeviceMouse::Movement::X || channelId == AzFramework::InputDeviceMouse::Movement::Y)
{
return false;
}
// We map keyboard and mouse events from Qt, so flag all events coming from those devices
// as handled by our synthetic event system.
const AzFramework::InputDeviceId& deviceId = channel.GetInputDevice().GetInputDeviceId();
@@ -210,6 +205,22 @@ namespace AzToolsFramework
}
}
void QtEventToAzInputMapper::SetCursorCaptureEnabled(bool enabled)
{
if (m_capturingCursor != enabled)
{
m_capturingCursor = enabled;
if (m_capturingCursor)
{
qApp->setOverrideCursor(Qt::BlankCursor);
}
else
{
qApp->restoreOverrideCursor();
}
}
}
bool QtEventToAzInputMapper::eventFilter(QObject* object, QEvent* event)
{
// Abort if processing isn't enabled.
@@ -284,13 +295,25 @@ namespace AzToolsFramework
{
auto systemCursorChannel =
GetInputChannel<AzFramework::InputChannelDeltaWithSharedPosition2D>(AzFramework::InputDeviceMouse::SystemCursorPosition);
auto movementXChannel =
GetInputChannel<AzFramework::InputChannelDeltaWithSharedPosition2D>(AzFramework::InputDeviceMouse::Movement::X);
auto movementYChannel =
GetInputChannel<AzFramework::InputChannelDeltaWithSharedPosition2D>(AzFramework::InputDeviceMouse::Movement::Y);
auto mouseWheelChannel =
GetInputChannel<AzFramework::InputChannelDeltaWithSharedPosition2D>(AzFramework::InputDeviceMouse::Movement::Z);
systemCursorChannel->ProcessRawInputEvent(m_cursorPosition->m_normalizedPositionDelta.GetLength());
// Generate movement events based on the pixel delta divided by the DPI scaling factor, to calculate a rough approximation
// of cursor movement velocity.
movementXChannel->ProcessRawInputEvent(
m_cursorPosition->m_normalizedPositionDelta.GetX() * aznumeric_cast<float>(m_sourceWidget->width()) / m_sourceWidget->devicePixelRatioF());
movementYChannel->ProcessRawInputEvent(
m_cursorPosition->m_normalizedPositionDelta.GetY() * aznumeric_cast<float>(m_sourceWidget->height()) / m_sourceWidget->devicePixelRatioF());
mouseWheelChannel->ProcessRawInputEvent(0.f);
NotifyUpdateChannelIfNotIdle(systemCursorChannel, nullptr);
NotifyUpdateChannelIfNotIdle(movementXChannel, nullptr);
NotifyUpdateChannelIfNotIdle(movementYChannel, nullptr);
NotifyUpdateChannelIfNotIdle(mouseWheelChannel, nullptr);
}
@@ -318,16 +341,42 @@ namespace AzToolsFramework
}
}
AZ::Vector2 QtEventToAzInputMapper::WidgetPositionToNormalizedPosition(QPoint position)
{
const float normalizedX = aznumeric_cast<float>(position.x()) / aznumeric_cast<float>(m_sourceWidget->width());
const float normalizedY = aznumeric_cast<float>(position.y()) / aznumeric_cast<float>(m_sourceWidget->height());
return AZ::Vector2{normalizedX, normalizedY};
}
QPoint QtEventToAzInputMapper::NormalizedPositionToWidgetPosition(AZ::Vector2 normalizedPosition)
{
const int denormalizedX = aznumeric_cast<int>(normalizedPosition.GetX() * m_sourceWidget->width());
const int denormalizedY = aznumeric_cast<int>(normalizedPosition.GetY() * m_sourceWidget->height());
return QPoint{denormalizedX, denormalizedY};
}
void QtEventToAzInputMapper::HandleMouseMoveEvent(QMouseEvent* mouseEvent)
{
AZ::Vector2 lastCursorPosition = m_cursorPosition->m_normalizedPosition;
const QPoint mousePos = mouseEvent->pos();
const float normalizedX = aznumeric_cast<float>(mousePos.x()) / aznumeric_cast<float>(m_sourceWidget->width());
const float normalizedY = aznumeric_cast<float>(mousePos.y()) / aznumeric_cast<float>(m_sourceWidget->height());
const AZ::Vector2 normalizedPosition(normalizedX, normalizedY);
const AZ::Vector2 normalizedPosition = WidgetPositionToNormalizedPosition(mousePos);
m_cursorPosition->m_normalizedPositionDelta = normalizedPosition - m_cursorPosition->m_normalizedPosition;
m_cursorPosition->m_normalizedPosition = normalizedPosition;
ProcessPendingMouseEvents();
m_mouseChannelsNeedUpdate = true;
if (m_capturingCursor)
{
// Reset our cursor position to the previous point.
QPoint targetScreenPosition = m_sourceWidget->mapToGlobal(NormalizedPositionToWidgetPosition(lastCursorPosition));
AzQtComponents::SetCursorPos(targetScreenPosition);
// Even though we just set the cursor position, there are edge cases such as remote desktop that will leave
// the cursor position unchanged. For safety, we re-cache our last cursor position for delta generation.
QPoint actualWidgetPosition = m_sourceWidget->mapFromGlobal(QCursor::pos());
m_cursorPosition->m_normalizedPosition = WidgetPositionToNormalizedPosition(actualWidgetPosition);
}
}
void QtEventToAzInputMapper::HandleKeyEvent(QKeyEvent* keyEvent)
@@ -47,6 +47,12 @@ namespace AzToolsFramework
//! Sets whether or not this input mapper should be updating its input channels from Qt events.
void SetEnabled(bool enabled);
//! Sets whether or not the cursor should be constrained to the source widget and invisible.
//! Internally, this will reset the cursor position after each move event to ensure movement
//! events don't allow the cursor to escape. This can be used for typical camera controls
//! like a dolly or rotation, where mouse movement is important but cursor location is not.
void SetCursorCaptureEnabled(bool enabled);
// QObject overrides...
bool eventFilter(QObject* object, QEvent* event) override;
@@ -106,6 +112,11 @@ namespace AzToolsFramework
// Processes any pending mouse movement events, this allows mouse movement channels to close themselves.
void ProcessPendingMouseEvents();
// Converts a point in logical source widget space [0..m_sourceWidget->size()] to normalized [0..1] space.
AZ::Vector2 WidgetPositionToNormalizedPosition(QPoint position);
// Converts a point in normalized [0..1] space to logical source widget space [0..m_sourceWidget->size()].
QPoint NormalizedPositionToWidgetPosition(AZ::Vector2 normalizedPosition);
// Handle mouse click events.
void HandleMouseButtonEvent(QMouseEvent* mouseEvent);
// Handle mouse move events.
@@ -144,6 +155,8 @@ namespace AzToolsFramework
bool m_mouseChannelsNeedUpdate = false;
// Flags whether or not Qt events should currently be processed.
bool m_enabled = true;
// Flags whether or not the cursor is being constrained to the source widget (for invisible mouse movement).
bool m_capturingCursor = false;
// Our viewport-specific AZ devices. We control their internal input channel states.
AZStd::unique_ptr<EditorQtMouseDevice> m_mouseDevice;
@@ -276,11 +276,6 @@ namespace AzToolsFramework
virtual void EndCursorCapture() = 0;
//! Gets the most recent recorded cursor position in the viewport in screen space coordinates.
virtual AzFramework::ScreenPoint ViewportCursorScreenPosition() = 0;
//! Gets the cursor position recorded prior to the most recent cursor position.
//! Note: The cursor may be captured by the viewport, in which case this may not correspond to the last result
//! from ViewportCursorScreenPosition. This method will always return the correct position to generate a mouse
//! position delta.
virtual AZStd::optional<AzFramework::ScreenPoint> PreviousViewportCursorScreenPosition() = 0;
//! Is mouse over viewport.
virtual bool IsMouseOver() const = 0;
@@ -109,7 +109,6 @@ namespace AtomToolsFramework
void BeginCursorCapture() override;
void EndCursorCapture() override;
AzFramework::ScreenPoint ViewportCursorScreenPosition() override;
AZStd::optional<AzFramework::ScreenPoint> PreviousViewportCursorScreenPosition() override;
bool IsMouseOver() const override;
// AzFramework::WindowRequestBus::Handler ...
@@ -160,8 +159,6 @@ namespace AtomToolsFramework
AZ::ScriptTimePoint m_time;
// Whether the Viewport is currently hiding and capturing the cursor position.
bool m_capturingCursor = false;
// The last known position of the mouse cursor, if one is available.
AZStd::optional<QPoint> m_lastCursorPosition;
// The viewport settings (e.g. grid snapping, grid size) for this viewport.
const AzToolsFramework::ViewportInteraction::ViewportSettings* m_viewportSettings = nullptr;
// Maps our internal Qt events into AzFramework InputChannels for our ViewportControllerList.
@@ -16,7 +16,6 @@
#include <AzCore/Math/MathUtils.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/Bootstrap/BootstrapRequestBus.h>
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
#include <QApplication>
#include <QCursor>
@@ -234,18 +233,6 @@ namespace AtomToolsFramework
void RenderViewportWidget::mouseMoveEvent(QMouseEvent* event)
{
m_mousePosition = event->localPos();
if (m_capturingCursor && m_lastCursorPosition.has_value())
{
AzQtComponents::SetCursorPos(m_lastCursorPosition.value());
// Even though we just set the cursor position, there are edge cases such as remote desktop that will leave
// the cursor position unchanged. For safety, we re-cache our last cursor position for delta generation.
m_lastCursorPosition = QCursor::pos();
}
else
{
m_lastCursorPosition = event->globalPos();
}
}
void RenderViewportWidget::SendWindowResizeEvent()
@@ -420,13 +407,6 @@ namespace AtomToolsFramework
return AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(m_mousePosition.toPoint());
}
AZStd::optional<AzFramework::ScreenPoint> RenderViewportWidget::PreviousViewportCursorScreenPosition()
{
using AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint;
return m_lastCursorPosition.has_value() ? ScreenPointFromQPoint(mapFromGlobal(m_lastCursorPosition.value()))
: AZStd::optional<AzFramework::ScreenPoint>{};
}
bool RenderViewportWidget::IsMouseOver() const
{
return m_mouseOver;
@@ -434,24 +414,12 @@ namespace AtomToolsFramework
void RenderViewportWidget::BeginCursorCapture()
{
if (m_capturingCursor)
{
return;
}
qApp->setOverrideCursor(Qt::BlankCursor);
m_capturingCursor = true;
m_inputChannelMapper->SetCursorCaptureEnabled(true);
}
void RenderViewportWidget::EndCursorCapture()
{
if (!m_capturingCursor)
{
return;
}
qApp->restoreOverrideCursor();
m_capturingCursor = false;
m_inputChannelMapper->SetCursorCaptureEnabled(false);
}
void RenderViewportWidget::SetWindowTitle(const AZStd::string& title)