ViewportManipulatorController.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "ViewportManipulatorController.h"
  9. #include <AzCore/Script/ScriptTimePoint.h>
  10. #include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
  11. #include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
  12. #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
  13. #include <AzFramework/Viewport/ScreenGeometry.h>
  14. #include <AzFramework/Viewport/ViewportScreen.h>
  15. #include <AzToolsFramework/Manipulators/ManipulatorManager.h>
  16. #include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
  17. #include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
  18. #include <AzToolsFramework/Viewport/ViewportInteractionHelpers.h>
  19. #include <AzToolsFramework/Input/QtEventToAzInputMapper.h>
  20. #include <Editor/EditorViewportSettings.h>
  21. #include <QApplication>
  22. static const auto ManipulatorPriority = AzFramework::ViewportControllerPriority::Highest;
  23. static const auto InteractionPriority = AzFramework::ViewportControllerPriority::High;
  24. namespace SandboxEditor
  25. {
  26. ViewportManipulatorControllerInstance::ViewportManipulatorControllerInstance(
  27. AzFramework::ViewportId viewport, ViewportManipulatorController* controller)
  28. : AzFramework::MultiViewportControllerInstanceInterface<ViewportManipulatorController>(viewport, controller)
  29. {
  30. }
  31. ViewportManipulatorControllerInstance::~ViewportManipulatorControllerInstance() = default;
  32. bool ViewportManipulatorControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event)
  33. {
  34. // We only care about manipulator and viewport interaction events
  35. if (event.m_priority != ManipulatorPriority && event.m_priority != InteractionPriority)
  36. {
  37. return false;
  38. }
  39. using InteractionBus = AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
  40. using AzFramework::InputChannel;
  41. using AzToolsFramework::ViewportInteraction::KeyboardModifier;
  42. using AzToolsFramework::ViewportInteraction::MouseButton;
  43. using AzToolsFramework::ViewportInteraction::MouseEvent;
  44. using AzToolsFramework::ViewportInteraction::MouseInteraction;
  45. using AzToolsFramework::ViewportInteraction::MouseInteractionEvent;
  46. using AzToolsFramework::ViewportInteraction::ProjectedViewportRay;
  47. using AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus;
  48. using AzToolsFramework::ViewportInteraction::Helpers;
  49. bool interactionHandled = false;
  50. float wheelDelta = 0.0f;
  51. AZStd::optional<MouseButton> overrideButton;
  52. AZStd::optional<MouseEvent> eventType;
  53. // Because we receive events multiple times at separate priorities for manipulator events and
  54. // viewport interaction events, we want to avoid updating our "last tick state" until we're on our last event,
  55. // which currently is the low priority Interaction processor.
  56. const bool finishedProcessingEvents = event.m_priority == InteractionPriority;
  57. const auto state = event.m_inputChannel.GetState();
  58. if (Helpers::IsMouseMove(event.m_inputChannel))
  59. {
  60. // Cache the ray trace results when doing manipulator interaction checks, no need to recalculate after
  61. if (event.m_priority == ManipulatorPriority)
  62. {
  63. const auto* position = event.m_inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>();
  64. AZ_Assert(position, "Expected PositionData2D but found nullptr");
  65. AzFramework::WindowSize windowSize;
  66. AzFramework::WindowRequestBus::EventResult(
  67. windowSize, event.m_windowHandle, &AzFramework::WindowRequestBus::Events::GetRenderResolution);
  68. const auto screenPoint = AzFramework::ScreenPoint(
  69. aznumeric_cast<int>(position->m_normalizedPosition.GetX() * windowSize.m_width),
  70. aznumeric_cast<int>(position->m_normalizedPosition.GetY() * windowSize.m_height));
  71. ProjectedViewportRay ray{};
  72. ViewportInteractionRequestBus::EventResult(
  73. ray, GetViewportId(), &ViewportInteractionRequestBus::Events::ViewportScreenToWorldRay, screenPoint);
  74. m_mouseInteraction.m_mousePick.m_rayOrigin = ray.m_origin;
  75. m_mouseInteraction.m_mousePick.m_rayDirection = ray.m_direction;
  76. m_mouseInteraction.m_mousePick.m_screenCoordinates = screenPoint;
  77. }
  78. eventType = MouseEvent::Move;
  79. }
  80. else if (auto mouseButton = Helpers::GetMouseButton(event.m_inputChannel); mouseButton != MouseButton::None)
  81. {
  82. const AZ::u32 mouseButtonValue = static_cast<AZ::u32>(mouseButton);
  83. overrideButton = mouseButton;
  84. if (state == InputChannel::State::Began)
  85. {
  86. m_mouseInteraction.m_mouseButtons.m_mouseButtons |= mouseButtonValue;
  87. if (IsDoubleClick(mouseButton))
  88. {
  89. // Only remove the double click flag once we're done processing both Manipulator and Interaction events
  90. if (event.m_priority == InteractionPriority)
  91. {
  92. m_pendingDoubleClicks.erase(mouseButton);
  93. }
  94. eventType = MouseEvent::DoubleClick;
  95. }
  96. else
  97. {
  98. // Only insert the double click timing once we're done processing events, to avoid a false IsDoubleClick positive
  99. if (finishedProcessingEvents)
  100. {
  101. m_pendingDoubleClicks[mouseButton] = { m_currentTime, m_mouseInteraction.m_mousePick.m_screenCoordinates };
  102. }
  103. eventType = MouseEvent::Down;
  104. }
  105. }
  106. else if (state == InputChannel::State::Ended)
  107. {
  108. // If we've actually logged a mouse down event, forward a mouse up event.
  109. // This prevents corner cases like the context menu thinking it should be opened even though no one clicked in this
  110. // viewport, due to RenderViewportWidget ensuring all controllers get InputChannel::State::Ended events.
  111. if (m_mouseInteraction.m_mouseButtons.m_mouseButtons & mouseButtonValue)
  112. {
  113. // Erase the button from our state if we're done processing events.
  114. if (event.m_priority == InteractionPriority)
  115. {
  116. m_mouseInteraction.m_mouseButtons.m_mouseButtons &= ~mouseButtonValue;
  117. }
  118. eventType = MouseEvent::Up;
  119. }
  120. }
  121. }
  122. else if (auto keyboardModifier = Helpers::GetKeyboardModifier(event.m_inputChannel); keyboardModifier != KeyboardModifier::None)
  123. {
  124. if (state == InputChannel::State::Began || state == InputChannel::State::Updated)
  125. {
  126. m_mouseInteraction.m_keyboardModifiers.m_keyModifiers |= static_cast<AZ::u32>(keyboardModifier);
  127. }
  128. else if (state == InputChannel::State::Ended)
  129. {
  130. m_mouseInteraction.m_keyboardModifiers.m_keyModifiers &= ~static_cast<AZ::u32>(keyboardModifier);
  131. }
  132. }
  133. else if (event.m_inputChannel.GetInputChannelId() == AzFramework::InputDeviceMouse::Movement::Z)
  134. {
  135. if (state == InputChannel::State::Began || state == InputChannel::State::Updated)
  136. {
  137. eventType = MouseEvent::Wheel;
  138. wheelDelta = event.m_inputChannel.GetValue();
  139. }
  140. }
  141. if (eventType)
  142. {
  143. MouseInteraction mouseInteraction = m_mouseInteraction;
  144. if (overrideButton)
  145. {
  146. mouseInteraction.m_mouseButtons.m_mouseButtons = static_cast<AZ::u32>(overrideButton.value());
  147. }
  148. mouseInteraction.m_interactionId.m_viewportId = GetViewportId();
  149. // Depending on priority, we dispatch to either the manipulator or viewport interaction event
  150. const auto& targetInteractionEvent = event.m_priority == ManipulatorPriority
  151. ? &InteractionBus::Events::InternalHandleMouseManipulatorInteraction
  152. : &InteractionBus::Events::InternalHandleMouseViewportInteraction;
  153. auto currentCursorState = AzFramework::SystemCursorState::Unknown;
  154. AzFramework::InputSystemCursorRequestBus::EventResult(
  155. currentCursorState, event.m_inputChannel.GetInputDevice().GetInputDeviceId(),
  156. &AzFramework::InputSystemCursorRequestBus::Events::GetSystemCursorState);
  157. const auto mouseInteractionEvent = [mouseInteraction, event = eventType.value(), wheelDelta,
  158. cursorCaptured = currentCursorState == AzFramework::SystemCursorState::ConstrainedAndHidden]
  159. {
  160. switch (event)
  161. {
  162. case MouseEvent::Up:
  163. case MouseEvent::Down:
  164. case MouseEvent::Move:
  165. case MouseEvent::DoubleClick:
  166. return MouseInteractionEvent(AZStd::move(mouseInteraction), event, cursorCaptured);
  167. case MouseEvent::Wheel:
  168. return MouseInteractionEvent(AZStd::move(mouseInteraction), wheelDelta);
  169. }
  170. AZ_Assert(false, "Unhandled MouseEvent");
  171. return MouseInteractionEvent(MouseInteraction{}, MouseEvent::Up, false);
  172. }();
  173. InteractionBus::EventResult(
  174. interactionHandled, AzToolsFramework::GetEntityContextId(), targetInteractionEvent, mouseInteractionEvent);
  175. }
  176. return interactionHandled;
  177. }
  178. void ViewportManipulatorControllerInstance::ResetInputChannels()
  179. {
  180. m_pendingDoubleClicks.clear();
  181. m_mouseInteraction = AzToolsFramework::ViewportInteraction::MouseInteraction();
  182. }
  183. void ViewportManipulatorControllerInstance::UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event)
  184. {
  185. m_currentTime = event.m_time;
  186. }
  187. bool ViewportManipulatorControllerInstance::IsDoubleClick(AzToolsFramework::ViewportInteraction::MouseButton button) const
  188. {
  189. if (auto clickIt = m_pendingDoubleClicks.find(button); clickIt != m_pendingDoubleClicks.end())
  190. {
  191. const double doubleClickThresholdMilliseconds = qApp->doubleClickInterval();
  192. const bool insideTimeThreshold =
  193. (m_currentTime.GetMilliseconds() - clickIt->second.m_time.GetMilliseconds()) < doubleClickThresholdMilliseconds;
  194. const bool insideDistanceThreshold =
  195. AzFramework::ScreenVectorLength(clickIt->second.m_position - m_mouseInteraction.m_mousePick.m_screenCoordinates) <
  196. AzFramework::DefaultMouseMoveDeadZone;
  197. return insideTimeThreshold && insideDistanceThreshold;
  198. }
  199. return false;
  200. }
  201. } // namespace SandboxEditor