Fix mouse capture behavior for Editor Viewport (#3417)

* first pass of fixes for cursor capture and context menu

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* restore previous behavior of HandleMouseMoveEvent

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* tidy-up from previous cursor/input changes

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* add missing casts

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* small updates to support tests

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* additional tests and some tidy-up

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* small updates before publishing PR (comment/naming updates)

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* add missing parameter to MouseInteractionEvent constructor

Signed-off-by: hultonha <hultonha@amazon.co.uk>
monroegm-disable-blank-issue-2
hultonha 4 years ago committed by GitHub
parent bb90c4ddfe
commit 7fbfda0371
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -33,6 +33,7 @@ namespace SandboxEditor
constexpr AZStd::string_view CameraTranslateSmoothnessSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothness"; constexpr AZStd::string_view CameraTranslateSmoothnessSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothness";
constexpr AZStd::string_view CameraTranslateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothing"; constexpr AZStd::string_view CameraTranslateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothing";
constexpr AZStd::string_view CameraRotateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothing"; constexpr AZStd::string_view CameraRotateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothing";
constexpr AZStd::string_view CameraCaptureCursorLookSetting = "/Amazon/Preferences/Editor/Camera/CaptureCursorLook";
constexpr AZStd::string_view CameraTranslateForwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateForwardId"; constexpr AZStd::string_view CameraTranslateForwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateForwardId";
constexpr AZStd::string_view CameraTranslateBackwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateBackwardId"; constexpr AZStd::string_view CameraTranslateBackwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateBackwardId";
constexpr AZStd::string_view CameraTranslateLeftIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateLeftId"; constexpr AZStd::string_view CameraTranslateLeftIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateLeftId";
@ -60,7 +61,7 @@ namespace SandboxEditor
AZStd::remove_cvref_t<T> GetRegistry(const AZStd::string_view setting, T&& defaultValue) AZStd::remove_cvref_t<T> GetRegistry(const AZStd::string_view setting, T&& defaultValue)
{ {
AZStd::remove_cvref_t<T> value = AZStd::forward<T>(defaultValue); AZStd::remove_cvref_t<T> value = AZStd::forward<T>(defaultValue);
if (auto* registry = AZ::SettingsRegistry::Get()) if (const auto* registry = AZ::SettingsRegistry::Get())
{ {
registry->Get(value, setting); registry->Get(value, setting);
} }
@ -281,6 +282,16 @@ namespace SandboxEditor
SetRegistry(CameraTranslateSmoothingSetting, enabled); SetRegistry(CameraTranslateSmoothingSetting, enabled);
} }
bool CameraCaptureCursorForLook()
{
return GetRegistry(CameraCaptureCursorLookSetting, true);
}
void SetCameraCaptureCursorForLook(const bool capture)
{
SetRegistry(CameraCaptureCursorLookSetting, capture);
}
AzFramework::InputChannelId CameraTranslateForwardChannelId() AzFramework::InputChannelId CameraTranslateForwardChannelId()
{ {
return AzFramework::InputChannelId( return AzFramework::InputChannelId(

@ -86,6 +86,9 @@ namespace SandboxEditor
SANDBOX_API bool CameraTranslateSmoothingEnabled(); SANDBOX_API bool CameraTranslateSmoothingEnabled();
SANDBOX_API void SetCameraTranslateSmoothingEnabled(bool enabled); SANDBOX_API void SetCameraTranslateSmoothingEnabled(bool enabled);
SANDBOX_API bool CameraCaptureCursorForLook();
SANDBOX_API void SetCameraCaptureCursorForLook(bool capture);
SANDBOX_API AzFramework::InputChannelId CameraTranslateForwardChannelId(); SANDBOX_API AzFramework::InputChannelId CameraTranslateForwardChannelId();
SANDBOX_API void SetCameraTranslateForwardChannelId(AZStd::string_view cameraTranslateForwardId); SANDBOX_API void SetCameraTranslateForwardChannelId(AZStd::string_view cameraTranslateForwardId);

@ -104,7 +104,6 @@
AZ_CVAR( AZ_CVAR(
bool, ed_visibility_logTiming, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Output the timing of the new IVisibilitySystem query"); bool, ed_visibility_logTiming, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Output the timing of the new IVisibilitySystem query");
AZ_CVAR(bool, ed_showCursorCameraLook, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Show the cursor when using free look with the new camera system");
EditorViewportWidget* EditorViewportWidget::m_pPrimaryViewport = nullptr; EditorViewportWidget* EditorViewportWidget::m_pPrimaryViewport = nullptr;
@ -1079,13 +1078,19 @@ AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateMod
{ {
const auto hideCursor = [viewportId] const auto hideCursor = [viewportId]
{ {
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Event( if (SandboxEditor::CameraCaptureCursorForLook())
viewportId, &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::BeginCursorCapture); {
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Event(
viewportId, &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::BeginCursorCapture);
}
}; };
const auto showCursor = [viewportId] const auto showCursor = [viewportId]
{ {
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Event( if (SandboxEditor::CameraCaptureCursorForLook())
viewportId, &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::EndCursorCapture); {
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Event(
viewportId, &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::EndCursorCapture);
}
}; };
auto firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(SandboxEditor::CameraFreeLookChannelId()); auto firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(SandboxEditor::CameraFreeLookChannelId());
@ -1094,12 +1099,10 @@ AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateMod
return SandboxEditor::CameraRotateSpeed(); return SandboxEditor::CameraRotateSpeed();
}; };
if (!ed_showCursorCameraLook) // default behavior is to hide the cursor but this can be disabled (useful for remote desktop)
{ // note: See CaptureCursorLook in the Settings Registry
// default behavior is to hide the cursor but this can be disabled (useful for remote desktop) firstPersonRotateCamera->SetActivationBeganFn(hideCursor);
firstPersonRotateCamera->SetActivationBeganFn(hideCursor); firstPersonRotateCamera->SetActivationEndedFn(showCursor);
firstPersonRotateCamera->SetActivationEndedFn(showCursor);
}
auto firstPersonPanCamera = auto firstPersonPanCamera =
AZStd::make_shared<AzFramework::PanCameraInput>(SandboxEditor::CameraFreePanChannelId(), AzFramework::LookPan); AZStd::make_shared<AzFramework::PanCameraInput>(SandboxEditor::CameraFreePanChannelId(), AzFramework::LookPan);

@ -7,6 +7,7 @@
*/ */
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h> #include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <AzFramework/Viewport/ViewportControllerList.h> #include <AzFramework/Viewport/ViewportControllerList.h>
#include <AzToolsFramework/Input/QtEventToAzInputManager.h> #include <AzToolsFramework/Input/QtEventToAzInputManager.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h> #include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
@ -95,10 +96,16 @@ namespace UnitTest
m_controllerList->RegisterViewportContext(TestViewportId); m_controllerList->RegisterViewportContext(TestViewportId);
m_inputChannelMapper = AZStd::make_unique<AzToolsFramework::QtEventToAzInputMapper>(m_rootWidget.get(), TestViewportId); m_inputChannelMapper = AZStd::make_unique<AzToolsFramework::QtEventToAzInputMapper>(m_rootWidget.get(), TestViewportId);
m_settingsRegistry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
AZ::SettingsRegistry::Register(m_settingsRegistry.get());
} }
void TearDown() override void TearDown() override
{ {
AZ::SettingsRegistry::Unregister(m_settingsRegistry.get());
m_settingsRegistry.reset();
m_inputChannelMapper.reset(); m_inputChannelMapper.reset();
m_controllerList->UnregisterViewportContext(TestViewportId); m_controllerList->UnregisterViewportContext(TestViewportId);
@ -170,7 +177,7 @@ namespace UnitTest
void RepeatDiagonalMouseMovements(const AZStd::function<float()>& deltaTimeFn) void RepeatDiagonalMouseMovements(const AZStd::function<float()>& deltaTimeFn)
{ {
// move to the center of the screen // move to the center of the screen
auto start = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2); const auto start = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2);
MouseMove(m_rootWidget.get(), start, QPoint(0, 0)); MouseMove(m_rootWidget.get(), start, QPoint(0, 0));
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTimeFn()), AZ::ScriptTimePoint() }); m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTimeFn()), AZ::ScriptTimePoint() });
@ -204,12 +211,15 @@ namespace UnitTest
::testing::NiceMock<MockWindowRequests> m_mockWindowRequests; ::testing::NiceMock<MockWindowRequests> m_mockWindowRequests;
ViewportMouseCursorRequestImpl m_viewportMouseCursorRequests; ViewportMouseCursorRequestImpl m_viewportMouseCursorRequests;
AtomToolsFramework::ModularCameraViewportContext* m_cameraViewportContextView = nullptr; AtomToolsFramework::ModularCameraViewportContext* m_cameraViewportContextView = nullptr;
AZStd::unique_ptr<AZ::SettingsRegistryInterface> m_settingsRegistry;
}; };
const AzFramework::ViewportId ModularViewportCameraControllerFixture::TestViewportId = AzFramework::ViewportId(0); const AzFramework::ViewportId ModularViewportCameraControllerFixture::TestViewportId = AzFramework::ViewportId(0);
TEST_F(ModularViewportCameraControllerFixture, MouseMovementDoesNotAccumulateExcessiveDriftInModularViewportCameraWithVaryingDeltaTime) TEST_F(ModularViewportCameraControllerFixture, MouseMovementDoesNotAccumulateExcessiveDriftInModularViewportCameraWithVaryingDeltaTime)
{ {
SandboxEditor::SetCameraCaptureCursorForLook(false);
// Given // Given
PrepareCollaborators(); PrepareCollaborators();
@ -242,6 +252,8 @@ namespace UnitTest
ModularViewportCameraControllerDeltaTimeParamFixture, ModularViewportCameraControllerDeltaTimeParamFixture,
MouseMovementDoesNotAccumulateExcessiveDriftInModularViewportCameraWithFixedDeltaTime) MouseMovementDoesNotAccumulateExcessiveDriftInModularViewportCameraWithFixedDeltaTime)
{ {
SandboxEditor::SetCameraCaptureCursorForLook(false);
// Given // Given
PrepareCollaborators(); PrepareCollaborators();
@ -263,4 +275,92 @@ namespace UnitTest
INSTANTIATE_TEST_CASE_P( INSTANTIATE_TEST_CASE_P(
All, ModularViewportCameraControllerDeltaTimeParamFixture, testing::Values(1.0f / 60.0f, 1.0f / 50.0f, 1.0f / 30.0f)); All, ModularViewportCameraControllerDeltaTimeParamFixture, testing::Values(1.0f / 60.0f, 1.0f / 50.0f, 1.0f / 30.0f));
TEST_F(ModularViewportCameraControllerFixture, MouseMovementOrientatesCameraWhenCursorIsCaptured)
{
// Given
PrepareCollaborators();
// ensure cursor is captured
SandboxEditor::SetCameraCaptureCursorForLook(true);
const float deltaTime = 1.0f / 60.0f;
// When
// move to the center of the screen
auto start = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2);
MouseMove(m_rootWidget.get(), start, QPoint(0, 0));
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
const auto mouseDelta = QPoint(5, 0);
// initial movement to begin the camera behavior
MousePressAndMove(m_rootWidget.get(), start, mouseDelta, Qt::MouseButton::RightButton);
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
// move the cursor right
for (int i = 0; i < 50; ++i)
{
MousePressAndMove(m_rootWidget.get(), start + mouseDelta, mouseDelta, Qt::MouseButton::RightButton);
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
}
// move the cursor left (do an extra iteration moving left to account for the initial dead-zone)
for (int i = 0; i < 51; ++i)
{
MousePressAndMove(m_rootWidget.get(), start + mouseDelta, -mouseDelta, Qt::MouseButton::RightButton);
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
}
QTest::mouseRelease(m_rootWidget.get(), Qt::MouseButton::RightButton, Qt::KeyboardModifier::NoModifier, start + mouseDelta);
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
// Then
// retrieve the amount of yaw rotation
const AZ::Quaternion cameraRotation = m_cameraViewportContextView->GetCameraTransform().GetRotation();
const auto eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromQuaternion(cameraRotation));
// camera should be back at the center (no yaw)
using ::testing::FloatNear;
EXPECT_THAT(eulerAngles.GetZ(), FloatNear(0.0f, 0.001f));
// Clean-up
HaltCollaborators();
}
TEST_F(ModularViewportCameraControllerFixture, CameraDoesNotContinueToRotateGivenNoInputWhenCaptured)
{
// Given
PrepareCollaborators();
SandboxEditor::SetCameraCaptureCursorForLook(true);
const float deltaTime = 1.0f / 60.0f;
// When
// move to the center of the screen
auto start = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2);
MouseMove(m_rootWidget.get(), start, QPoint(0, 0));
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
// will move a small amount initially
const auto mouseDelta = QPoint(5, 0);
MousePressAndMove(m_rootWidget.get(), start, mouseDelta, Qt::MouseButton::RightButton);
// ensure further updates to not continue to rotate
for (int i = 0; i < 50; ++i)
{
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
}
// Then
// ensure the camera rotation is no longer the identity
const AZ::Quaternion cameraRotation = m_cameraViewportContextView->GetCameraTransform().GetRotation();
const auto eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromQuaternion(cameraRotation));
// initial amount of rotation after first mouse move
using ::testing::FloatNear;
EXPECT_THAT(eulerAngles.GetZ(), FloatNear(-0.025f, 0.001f));
// Clean-up
HaltCollaborators();
}
} // namespace UnitTest } // namespace UnitTest

@ -12,6 +12,7 @@
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h> #include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h> #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h> #include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
#include <AzFramework/Viewport/ScreenGeometry.h> #include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzCore/Script/ScriptTimePoint.h> #include <AzCore/Script/ScriptTimePoint.h>
@ -112,9 +113,9 @@ namespace SandboxEditor
AzFramework::WindowRequestBus::EventResult( AzFramework::WindowRequestBus::EventResult(
windowSize, event.m_windowHandle, &AzFramework::WindowRequestBus::Events::GetClientAreaSize); windowSize, event.m_windowHandle, &AzFramework::WindowRequestBus::Events::GetClientAreaSize);
auto screenPoint = AzFramework::ScreenPoint( const auto screenPoint = AzFramework::ScreenPoint(
static_cast<int>(position->m_normalizedPosition.GetX() * windowSize.m_width), aznumeric_cast<int>(position->m_normalizedPosition.GetX() * windowSize.m_width),
static_cast<int>(position->m_normalizedPosition.GetY() * windowSize.m_height)); aznumeric_cast<int>(position->m_normalizedPosition.GetY() * windowSize.m_height));
m_mouseInteraction.m_mousePick.m_screenCoordinates = screenPoint; m_mouseInteraction.m_mousePick.m_screenCoordinates = screenPoint;
AZStd::optional<ProjectedViewportRay> ray; AZStd::optional<ProjectedViewportRay> ray;
@ -207,20 +208,27 @@ namespace SandboxEditor
? &InteractionBus::Events::InternalHandleMouseManipulatorInteraction ? &InteractionBus::Events::InternalHandleMouseManipulatorInteraction
: &InteractionBus::Events::InternalHandleMouseViewportInteraction; : &InteractionBus::Events::InternalHandleMouseViewportInteraction;
const auto mouseInteractionEvent = [mouseInteraction, event = eventType.value(), wheelDelta] { auto currentCursorState = AzFramework::SystemCursorState::Unknown;
AzFramework::InputSystemCursorRequestBus::EventResult(
currentCursorState, event.m_inputChannel.GetInputDevice().GetInputDeviceId(),
&AzFramework::InputSystemCursorRequestBus::Events::GetSystemCursorState);
const auto mouseInteractionEvent = [mouseInteraction, event = eventType.value(), wheelDelta,
cursorCaptured = currentCursorState == AzFramework::SystemCursorState::ConstrainedAndHidden]
{
switch (event) switch (event)
{ {
case MouseEvent::Up: case MouseEvent::Up:
case MouseEvent::Down: case MouseEvent::Down:
case MouseEvent::Move: case MouseEvent::Move:
case MouseEvent::DoubleClick: case MouseEvent::DoubleClick:
return MouseInteractionEvent(AZStd::move(mouseInteraction), event); return MouseInteractionEvent(AZStd::move(mouseInteraction), event, cursorCaptured);
case MouseEvent::Wheel: case MouseEvent::Wheel:
return MouseInteractionEvent(AZStd::move(mouseInteraction), wheelDelta); return MouseInteractionEvent(AZStd::move(mouseInteraction), wheelDelta);
} }
AZ_Assert(false, "Unhandled MouseEvent"); AZ_Assert(false, "Unhandled MouseEvent");
return MouseInteractionEvent(MouseInteraction{}, MouseEvent::Up); return MouseInteractionEvent(MouseInteraction{}, MouseEvent::Up, false);
}(); }();
InteractionBus::EventResult( InteractionBus::EventResult(

@ -22,6 +22,8 @@ namespace AZStd
using std::exp2; using std::exp2;
using std::floor; using std::floor;
using std::fmod; using std::fmod;
using std::llround;
using std::lround;
using std::pow; using std::pow;
using std::round; using std::round;
using std::sin; using std::sin;

@ -144,6 +144,7 @@ namespace AzFramework
if (const auto& cursor = AZStd::get_if<CursorEvent>(&event)) if (const auto& cursor = AZStd::get_if<CursorEvent>(&event))
{ {
m_cursorState.SetCurrentPosition(cursor->m_position); m_cursorState.SetCurrentPosition(cursor->m_position);
m_cursorState.SetCaptured(cursor->m_captured);
} }
else if (const auto& horizontalMotion = AZStd::get_if<HorizontalMotionEvent>(&event)) else if (const auto& horizontalMotion = AZStd::get_if<HorizontalMotionEvent>(&event))
{ {
@ -790,17 +791,24 @@ namespace AzFramework
const auto* position = inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>(); const auto* position = inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>();
AZ_Assert(position, "Expected PositionData2D but found nullptr"); AZ_Assert(position, "Expected PositionData2D but found nullptr");
return CursorEvent{ ScreenPoint( auto currentCursorState = AzFramework::SystemCursorState::Unknown;
static_cast<int>(position->m_normalizedPosition.GetX() * windowSize.m_width), AzFramework::InputSystemCursorRequestBus::EventResult(
static_cast<int>(position->m_normalizedPosition.GetY() * windowSize.m_height)) }; currentCursorState, inputDeviceId, &AzFramework::InputSystemCursorRequestBus::Events::GetSystemCursorState);
const auto x = position->m_normalizedPosition.GetX() * aznumeric_cast<float>(windowSize.m_width);
const auto y = position->m_normalizedPosition.GetY() * aznumeric_cast<float>(windowSize.m_height);
return CursorEvent{ ScreenPoint(aznumeric_cast<int>(AZStd::lround(x)), aznumeric_cast<int>(AZStd::lround(y))),
currentCursorState == AzFramework::SystemCursorState::ConstrainedAndHidden };
} }
else if (inputChannelId == InputDeviceMouse::Movement::X) else if (inputChannelId == InputDeviceMouse::Movement::X)
{ {
return HorizontalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) }; const auto x = inputChannel.GetValue();
return HorizontalMotionEvent{ aznumeric_cast<int>(AZStd::lround(x)) };
} }
else if (inputChannelId == InputDeviceMouse::Movement::Y) else if (inputChannelId == InputDeviceMouse::Movement::Y)
{ {
return VerticalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) }; const auto y = inputChannel.GetValue();
return VerticalMotionEvent{ aznumeric_cast<int>(AZStd::lround(y)) };
} }
else if (inputChannelId == InputDeviceMouse::Movement::Z) else if (inputChannelId == InputDeviceMouse::Movement::Z)
{ {

@ -88,6 +88,7 @@ namespace AzFramework
struct CursorEvent struct CursorEvent
{ {
ScreenPoint m_position; ScreenPoint m_position;
bool m_captured = false;
}; };
struct ScrollEvent struct ScrollEvent

@ -21,6 +21,8 @@ namespace AzFramework
[[nodiscard]] ScreenVector CursorDelta() const; [[nodiscard]] ScreenVector CursorDelta() const;
//! Call this in a 'handle event' call to update the most recent cursor position. //! Call this in a 'handle event' call to update the most recent cursor position.
void SetCurrentPosition(const ScreenPoint& currentPosition); void SetCurrentPosition(const ScreenPoint& currentPosition);
//! Set whether the cursor is currently being constrained (and hidden).
void SetCaptured(bool captured);
//! Call this in an 'update' call to copy the current cursor position to the last //! Call this in an 'update' call to copy the current cursor position to the last
//! cursor position. //! cursor position.
void Update(); void Update();
@ -28,8 +30,14 @@ namespace AzFramework
private: private:
AZStd::optional<ScreenPoint> m_lastCursorPosition; AZStd::optional<ScreenPoint> m_lastCursorPosition;
AZStd::optional<ScreenPoint> m_currentCursorPosition; AZStd::optional<ScreenPoint> m_currentCursorPosition;
bool m_captured = false;
}; };
inline void CursorState::SetCaptured(const bool captured)
{
m_captured = captured;
}
inline void CursorState::SetCurrentPosition(const ScreenPoint& currentPosition) inline void CursorState::SetCurrentPosition(const ScreenPoint& currentPosition)
{ {
m_currentCursorPosition = currentPosition; m_currentCursorPosition = currentPosition;
@ -44,9 +52,16 @@ namespace AzFramework
inline void CursorState::Update() inline void CursorState::Update()
{ {
if (m_currentCursorPosition.has_value()) if (!m_captured)
{
if (m_currentCursorPosition.has_value())
{
m_lastCursorPosition = m_currentCursorPosition;
}
}
else
{ {
m_lastCursorPosition = m_currentCursorPosition; m_currentCursorPosition = m_lastCursorPosition;
} }
} }
} // namespace AzFramework } // namespace AzFramework

@ -122,7 +122,7 @@ namespace AzManipulatorTestFramework
MouseInteractionEvent CreateMouseInteractionEvent(const MouseInteraction& mouseInteraction, MouseEvent event) MouseInteractionEvent CreateMouseInteractionEvent(const MouseInteraction& mouseInteraction, MouseEvent event)
{ {
return MouseInteractionEvent(mouseInteraction, event); return MouseInteractionEvent(mouseInteraction, event, /*captured=*/false);
} }
void DispatchMouseInteractionEvent(const MouseInteractionEvent& event) void DispatchMouseInteractionEvent(const MouseInteractionEvent& event)

@ -158,6 +158,16 @@ namespace AzToolsFramework
SetImplementation(nullptr); SetImplementation(nullptr);
} }
void QtEventToAzInputMapper::EditorQtMouseDevice::SetSystemCursorState(const AzFramework::SystemCursorState systemCursorState)
{
m_systemCursorState = systemCursorState;
}
AzFramework::SystemCursorState QtEventToAzInputMapper::EditorQtMouseDevice::GetSystemCursorState() const
{
return m_systemCursorState;
}
QtEventToAzInputMapper::QtEventToAzInputMapper(QWidget* sourceWidget, int syntheticDeviceId) QtEventToAzInputMapper::QtEventToAzInputMapper(QWidget* sourceWidget, int syntheticDeviceId)
: QObject(sourceWidget) : QObject(sourceWidget)
, m_sourceWidget(sourceWidget) , m_sourceWidget(sourceWidget)
@ -210,12 +220,15 @@ namespace AzToolsFramework
if (m_capturingCursor != enabled) if (m_capturingCursor != enabled)
{ {
m_capturingCursor = enabled; m_capturingCursor = enabled;
if (m_capturingCursor) if (m_capturingCursor)
{ {
m_mouseDevice->SetSystemCursorState(AzFramework::SystemCursorState::ConstrainedAndHidden);
qApp->setOverrideCursor(Qt::BlankCursor); qApp->setOverrideCursor(Qt::BlankCursor);
} }
else else
{ {
m_mouseDevice->SetSystemCursorState(AzFramework::SystemCursorState::UnconstrainedAndVisible);
qApp->restoreOverrideCursor(); qApp->restoreOverrideCursor();
} }
} }
@ -238,10 +251,22 @@ namespace AzToolsFramework
return false; return false;
} }
// If our focus changes, go ahead and reset all input devices.
if (eventType == QEvent::FocusIn || eventType == QEvent::FocusOut) if (eventType == QEvent::FocusIn || eventType == QEvent::FocusOut)
{ {
// If our focus changes, go ahead and reset all input devices.
HandleFocusChange(event); HandleFocusChange(event);
// If we focus in on the source widget and the mouse is contained in its
// bounds, refresh the cached cursor position to ensure it is up to date (this
// ensures cursor positions are refreshed correctly with context menu focus changes)
if (eventType == QEvent::FocusIn)
{
const auto widgetCursorPosition = m_sourceWidget->mapFromGlobal(QCursor::pos());
if (m_sourceWidget->geometry().contains(widgetCursorPosition))
{
HandleMouseMoveEvent(widgetCursorPosition);
}
}
} }
// Map key events to input channels. // Map key events to input channels.
// ShortcutOverride is used in lieu of KeyPress for high priority input channels like Alt // ShortcutOverride is used in lieu of KeyPress for high priority input channels like Alt
@ -249,7 +274,7 @@ namespace AzToolsFramework
else if ( else if (
eventType == QEvent::Type::KeyPress || eventType == QEvent::Type::KeyRelease || eventType == QEvent::Type::ShortcutOverride) eventType == QEvent::Type::KeyPress || eventType == QEvent::Type::KeyRelease || eventType == QEvent::Type::ShortcutOverride)
{ {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); auto keyEvent = static_cast<QKeyEvent*>(event);
HandleKeyEvent(keyEvent); HandleKeyEvent(keyEvent);
} }
// Map mouse events to input channels. // Map mouse events to input channels.
@ -257,20 +282,20 @@ namespace AzToolsFramework
eventType == QEvent::Type::MouseButtonPress || eventType == QEvent::Type::MouseButtonRelease || eventType == QEvent::Type::MouseButtonPress || eventType == QEvent::Type::MouseButtonRelease ||
eventType == QEvent::Type::MouseButtonDblClick) eventType == QEvent::Type::MouseButtonDblClick)
{ {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event); auto mouseEvent = static_cast<QMouseEvent*>(event);
HandleMouseButtonEvent(mouseEvent); HandleMouseButtonEvent(mouseEvent);
} }
// Map mouse movement to the movement input channels. // Map mouse movement to the movement input channels.
// This includes SystemCursorPosition alongside Movement::X and Movement::Y. // This includes SystemCursorPosition alongside Movement::X and Movement::Y.
else if (eventType == QEvent::Type::MouseMove) else if (eventType == QEvent::Type::MouseMove)
{ {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event); auto mouseEvent = static_cast<QMouseEvent*>(event);
HandleMouseMoveEvent(mouseEvent); HandleMouseMoveEvent(mouseEvent->pos());
} }
// Map wheel events to the mouse Z movement channel. // Map wheel events to the mouse Z movement channel.
else if (eventType == QEvent::Type::Wheel) else if (eventType == QEvent::Type::Wheel)
{ {
QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event); auto wheelEvent = static_cast<QWheelEvent*>(event);
HandleWheelEvent(wheelEvent); HandleWheelEvent(wheelEvent);
} }
@ -345,9 +370,8 @@ namespace AzToolsFramework
return QPoint{ denormalizedX, denormalizedY }; return QPoint{ denormalizedX, denormalizedY };
} }
void QtEventToAzInputMapper::HandleMouseMoveEvent(QMouseEvent* mouseEvent) void QtEventToAzInputMapper::HandleMouseMoveEvent(const QPoint& cursorPosition)
{ {
const QPoint cursorPosition = mouseEvent->pos();
const QPoint cursorDelta = cursorPosition - m_previousCursorPosition; const QPoint cursorDelta = cursorPosition - m_previousCursorPosition;
m_mouseDevice->m_cursorPositionData2D->m_normalizedPosition = WidgetPositionToNormalizedPosition(cursorPosition); m_mouseDevice->m_cursorPositionData2D->m_normalizedPosition = WidgetPositionToNormalizedPosition(cursorPosition);
@ -357,17 +381,14 @@ namespace AzToolsFramework
if (m_capturingCursor) if (m_capturingCursor)
{ {
// Reset our cursor position to the previous point. // Reset our cursor position to the previous point
const QPoint targetScreenPosition = m_sourceWidget->mapToGlobal(m_previousCursorPosition); const QPoint screenCursorPosition = m_sourceWidget->mapToGlobal(m_previousCursorPosition);
AzQtComponents::SetCursorPos(targetScreenPosition); AzQtComponents::SetCursorPos(screenCursorPosition);
}
// Even though we just set the cursor position, there are edge cases such as remote desktop that will leave else
// the cursor position unchanged. For safety, we re-cache our last cursor position for delta generation. {
const QPoint actualWidgetPosition = m_sourceWidget->mapFromGlobal(QCursor::pos()); m_previousCursorPosition = cursorPosition;
m_mouseDevice->m_cursorPositionData2D->m_normalizedPosition = WidgetPositionToNormalizedPosition(actualWidgetPosition);
} }
m_previousCursorPosition = cursorPosition;
} }
void QtEventToAzInputMapper::HandleKeyEvent(QKeyEvent* keyEvent) void QtEventToAzInputMapper::HandleKeyEvent(QKeyEvent* keyEvent)

@ -105,7 +105,14 @@ namespace AzToolsFramework
public: public:
EditorQtMouseDevice(AzFramework::InputDeviceId id); EditorQtMouseDevice(AzFramework::InputDeviceId id);
// AzFramework::InputDeviceMouse overrides ...
void SetSystemCursorState(AzFramework::SystemCursorState systemCursorState) override;
AzFramework::SystemCursorState GetSystemCursorState() const override;
friend class QtEventToAzInputMapper; friend class QtEventToAzInputMapper;
private:
AzFramework::SystemCursorState m_systemCursorState = AzFramework::SystemCursorState::UnconstrainedAndVisible;
}; };
// Emits InputChannelUpdated if channel has transitioned in state (i.e. has gone from active to inactive or vice versa). // Emits InputChannelUpdated if channel has transitioned in state (i.e. has gone from active to inactive or vice versa).
@ -122,7 +129,7 @@ namespace AzToolsFramework
// Handle mouse click events. // Handle mouse click events.
void HandleMouseButtonEvent(QMouseEvent* mouseEvent); void HandleMouseButtonEvent(QMouseEvent* mouseEvent);
// Handle mouse move events. // Handle mouse move events.
void HandleMouseMoveEvent(QMouseEvent* mouseEvent); void HandleMouseMoveEvent(const QPoint& cursorPosition);
// Handles key press / release events (or ShortcutOverride events for keys listed in m_highPriorityKeys). // Handles key press / release events (or ShortcutOverride events for keys listed in m_highPriorityKeys).
void HandleKeyEvent(QKeyEvent* keyEvent); void HandleKeyEvent(QKeyEvent* keyEvent);
// Handles mouse wheel events. // Handles mouse wheel events.

@ -115,7 +115,7 @@ namespace UnitTest
handled, AzToolsFramework::GetEntityContextId(), handled, AzToolsFramework::GetEntityContextId(),
&EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleMouseViewportInteraction, &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleMouseViewportInteraction,
AzToolsFramework::ViewportInteraction::MouseInteractionEvent( AzToolsFramework::ViewportInteraction::MouseInteractionEvent(
mouseInteraction, AzToolsFramework::ViewportInteraction::MouseEvent::Down)); mouseInteraction, AzToolsFramework::ViewportInteraction::MouseEvent::Down, /*captured=*/false));
return handled; return handled;
} }
} }

@ -41,7 +41,8 @@ namespace AzToolsFramework
ViewportInteraction::QPointFromScreenPoint(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates); ViewportInteraction::QPointFromScreenPoint(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
// if the mouse hasn't moved, open the pop-up menu // if the mouse hasn't moved, open the pop-up menu
if ((currentScreenCoords - contextMenu.m_clickPoint).manhattanLength() < ed_contextMenuDisplayThreshold) if ((currentScreenCoords - contextMenu.m_clickPoint).manhattanLength() < ed_contextMenuDisplayThreshold &&
!mouseInteraction.m_captured)
{ {
QWidget* parent = nullptr; QWidget* parent = nullptr;
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult( ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(

@ -212,9 +212,10 @@ namespace AzToolsFramework
static void Reflect(AZ::SerializeContext& context); static void Reflect(AZ::SerializeContext& context);
//! Constructor to create a default MouseInteractionEvent //! Constructor to create a default MouseInteractionEvent
MouseInteractionEvent(MouseInteraction mouseInteraction, const MouseEvent mouseEvent) MouseInteractionEvent(MouseInteraction mouseInteraction, const MouseEvent mouseEvent, const bool captured)
: m_mouseInteraction(std::move(mouseInteraction)) : m_mouseInteraction(std::move(mouseInteraction))
, m_mouseEvent(mouseEvent) , m_mouseEvent(mouseEvent)
, m_captured(captured)
{ {
} }
@ -228,6 +229,7 @@ namespace AzToolsFramework
MouseInteraction m_mouseInteraction; //!< Mouse state. MouseInteraction m_mouseInteraction; //!< Mouse state.
MouseEvent m_mouseEvent; //!< Mouse event. MouseEvent m_mouseEvent; //!< Mouse event.
bool m_captured = false; //!< Is the mouse cursor being captured during the event.
//! Special friend function to return the mouse wheel delta (scroll amount) //! Special friend function to return the mouse wheel delta (scroll amount)
//! if the event was of type MouseEvent::Wheel. //! if the event was of type MouseEvent::Wheel.

Loading…
Cancel
Save