diff --git a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp index ea3653f663..275df11784 100644 --- a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp +++ b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp @@ -38,6 +38,8 @@ namespace UnitTest void BeginCursorCapture() override; void EndCursorCapture() override; bool IsMouseOver() const override; + void SetOverrideCursor(AzToolsFramework::ViewportInteraction::CursorStyleOverride cursorStyleOverride) override; + void ClearOverrideCursor() override; private: AzToolsFramework::QtEventToAzInputMapper* m_inputChannelMapper = nullptr; @@ -58,6 +60,17 @@ namespace UnitTest return true; } + void ViewportMouseCursorRequestImpl::SetOverrideCursor( + [[maybe_unused]] AzToolsFramework::ViewportInteraction::CursorStyleOverride cursorStyleOverride) + { + // noop + } + + void ViewportMouseCursorRequestImpl::ClearOverrideCursor() + { + // noop + } + class ModularViewportCameraControllerFixture : public AllocatorsTestFixture { public: diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp index 18acccf049..574470ad6e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp @@ -244,6 +244,17 @@ namespace AzToolsFramework const auto eventType = event->type(); + if (eventType == QEvent::Type::MouseMove) + { + // clear override cursor when moving outside of the viewport + const auto* mouseEvent = static_cast(event); + if (m_overrideCursor && !m_sourceWidget->geometry().contains(m_sourceWidget->mapFromGlobal(mouseEvent->globalPos()))) + { + qApp->restoreOverrideCursor(); + m_overrideCursor = false; + } + } + // Only accept mouse & key release events that originate from an object that is not our target widget, // as we don't want to erroneously intercept user input meant for another component. if (object != m_sourceWidget && eventType != QEvent::Type::KeyRelease && eventType != QEvent::Type::MouseButtonRelease) @@ -262,7 +273,7 @@ namespace AzToolsFramework if (eventType == QEvent::FocusIn) { const auto globalCursorPosition = QCursor::pos(); - if (m_sourceWidget->geometry().contains(globalCursorPosition)) + if (m_sourceWidget->geometry().contains(m_sourceWidget->mapFromGlobal(globalCursorPosition))) { HandleMouseMoveEvent(globalCursorPosition); } @@ -452,4 +463,32 @@ namespace AzToolsFramework } } } + + static Qt::CursorShape QtCursorFromAzCursor(const ViewportInteraction::CursorStyleOverride cursorStyleOverride) + { + switch (cursorStyleOverride) + { + case ViewportInteraction::CursorStyleOverride::Forbidden: + return Qt::ForbiddenCursor; + default: + return Qt::ArrowCursor; + } + } + + void QtEventToAzInputMapper::SetOverrideCursor(ViewportInteraction::CursorStyleOverride cursorStyleOverride) + { + ClearOverrideCursor(); + + qApp->setOverrideCursor(QtCursorFromAzCursor(cursorStyleOverride)); + m_overrideCursor = true; + } + + void QtEventToAzInputMapper::ClearOverrideCursor() + { + if (m_overrideCursor) + { + qApp->restoreOverrideCursor(); + m_overrideCursor = false; + } + } } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h index 5add24ad84..4cb391e63b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h @@ -15,10 +15,11 @@ #include #include #include - #include #include +#include + #include #include #include @@ -55,6 +56,9 @@ namespace AzToolsFramework //! like a dolly or rotation, where mouse movement is important but cursor location is not. void SetCursorCaptureEnabled(bool enabled); + void SetOverrideCursor(ViewportInteraction::CursorStyleOverride cursorStyleOverride); + void ClearOverrideCursor(); + // QObject overrides... bool eventFilter(QObject* object, QEvent* event) override; @@ -164,6 +168,8 @@ namespace AzToolsFramework 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; + // Flags whether the cursor has been overridden. + bool m_overrideCursor = 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 9a8e0f1f5f..8ec772f0da 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -302,6 +302,12 @@ namespace AzToolsFramework using EditorViewportInputTimeNowRequestBus = AZ::EBus; + //! The style of cursor override. + enum class CursorStyleOverride + { + Forbidden + }; + //! Viewport requests for managing the viewport cursor state. class ViewportMouseCursorRequests { @@ -312,6 +318,10 @@ namespace AzToolsFramework virtual void EndCursorCapture() = 0; //! Is the mouse over the viewport. virtual bool IsMouseOver() const = 0; + //! Set the cursor style override. + virtual void SetOverrideCursor(CursorStyleOverride cursorStyleOverride) = 0; + //! Clear the cursor style override. + virtual void ClearOverrideCursor() = 0; protected: ~ViewportMouseCursorRequests() = default; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp index 94795c44a9..0f94b95e5f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp @@ -44,6 +44,13 @@ AZ_CVAR( nullptr, AZ::ConsoleFunctorFlags::Null, "Display the aggregate world bounds for a given entity (the union of all world component Aabbs)"); +AZ_CVAR( + bool, + ed_useCursorLockIconInFocusMode, + false, + nullptr, + AZ::ConsoleFunctorFlags::Null, + "Use a lock icon when the cursor is over entities that cannot be interacted with"); namespace AzToolsFramework { @@ -222,6 +229,13 @@ namespace AzToolsFramework // verify if the entity Id corresponds to an entity that is focused; if not, halt selection. if (entityIdUnderCursor.IsValid() && !IsSelectableAccordingToFocusMode(entityIdUnderCursor)) { + if (ed_useCursorLockIconInFocusMode) + { + ViewportInteraction::ViewportMouseCursorRequestBus::Event( + viewportId, &ViewportInteraction::ViewportMouseCursorRequestBus::Events::SetOverrideCursor, + ViewportInteraction::CursorStyleOverride::Forbidden); + } + if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() && mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down || mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick) @@ -232,6 +246,9 @@ namespace AzToolsFramework return CursorEntityIdQuery(AZ::EntityId(), AZ::EntityId()); } + ViewportInteraction::ViewportMouseCursorRequestBus::Event( + viewportId, &ViewportInteraction::ViewportMouseCursorRequestBus::Events::ClearOverrideCursor); + // container entity support - if the entity that is being selected is part of a closed container, // change the selection to the container instead. if (ContainerEntityInterface* containerEntityInterface = AZ::Interface::Get()) 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 268285adc0..8233658ded 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h @@ -102,6 +102,8 @@ namespace AtomToolsFramework void BeginCursorCapture() override; void EndCursorCapture() override; bool IsMouseOver() const override; + void SetOverrideCursor(AzToolsFramework::ViewportInteraction::CursorStyleOverride cursorStyleOverride) override; + void ClearOverrideCursor() override; // AzFramework::WindowRequestBus::Handler overrides ... void SetWindowTitle(const AZStd::string& title) override; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp index e8975c0b62..e43880827c 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp @@ -375,6 +375,16 @@ namespace AtomToolsFramework m_inputChannelMapper->SetCursorCaptureEnabled(false); } + void RenderViewportWidget::SetOverrideCursor(AzToolsFramework::ViewportInteraction::CursorStyleOverride cursorStyleOverride) + { + m_inputChannelMapper->SetOverrideCursor(cursorStyleOverride); + } + + void RenderViewportWidget::ClearOverrideCursor() + { + m_inputChannelMapper->ClearOverrideCursor(); + } + void RenderViewportWidget::SetWindowTitle(const AZStd::string& title) { setWindowTitle(QString::fromUtf8(title.c_str()));