diff --git a/Code/Editor/LegacyViewportCameraController.cpp b/Code/Editor/LegacyViewportCameraController.cpp index f437196548..b18e807942 100644 --- a/Code/Editor/LegacyViewportCameraController.cpp +++ b/Code/Editor/LegacyViewportCameraController.cpp @@ -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(event.m_inputChannel.GetValue()); + } + else + { + dy = -aznumeric_cast(event.m_inputChannel.GetValue()); + } + return HandleMouseMove(dx, dy); } else if (id == MouseButton::Left) { diff --git a/Code/Editor/LegacyViewportCameraController.h b/Code/Editor/LegacyViewportCameraController.h index d3ff439b16..ff752e27ab 100644 --- a/Code/Editor/LegacyViewportCameraController.h +++ b/Code/Editor/LegacyViewportCameraController.h @@ -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); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp index eb5034fdd5..754c04e12f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -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::InputDeviceMouse::SystemCursorPosition); + auto movementXChannel = + GetInputChannel(AzFramework::InputDeviceMouse::Movement::X); + auto movementYChannel = + GetInputChannel(AzFramework::InputDeviceMouse::Movement::Y); auto mouseWheelChannel = GetInputChannel(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(m_sourceWidget->width()) / m_sourceWidget->devicePixelRatioF()); + movementYChannel->ProcessRawInputEvent( + m_cursorPosition->m_normalizedPositionDelta.GetY() * aznumeric_cast(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(position.x()) / aznumeric_cast(m_sourceWidget->width()); + const float normalizedY = aznumeric_cast(position.y()) / aznumeric_cast(m_sourceWidget->height()); + return AZ::Vector2{normalizedX, normalizedY}; + } + + QPoint QtEventToAzInputMapper::NormalizedPositionToWidgetPosition(AZ::Vector2 normalizedPosition) + { + const int denormalizedX = aznumeric_cast(normalizedPosition.GetX() * m_sourceWidget->width()); + const int denormalizedY = aznumeric_cast(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(mousePos.x()) / aznumeric_cast(m_sourceWidget->width()); - const float normalizedY = aznumeric_cast(mousePos.y()) / aznumeric_cast(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) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h index 6d95f3a4b0..6946a2c8e7 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h @@ -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 m_mouseDevice; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h index 4d3af0e878..5b25034de8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -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 PreviousViewportCursorScreenPosition() = 0; //! Is mouse over viewport. virtual bool IsMouseOver() const = 0; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h index d8171a83a1..99a3ea4840 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h @@ -109,7 +109,6 @@ namespace AtomToolsFramework void BeginCursorCapture() override; void EndCursorCapture() override; AzFramework::ScreenPoint ViewportCursorScreenPosition() override; - AZStd::optional 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 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. diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp index 6ab96bbebb..ff10b49483 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -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 RenderViewportWidget::PreviousViewportCursorScreenPosition() - { - using AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint; - return m_lastCursorPosition.has_value() ? ScreenPointFromQPoint(mapFromGlobal(m_lastCursorPosition.value())) - : AZStd::optional{}; - } - 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)