You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Sandbox/Editor/ViewportManipulatorControll...

228 lines
9.2 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ViewportManipulatorController.h"
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzCore/Script/ScriptTimePoint.h>
#include <QApplication>
static const auto ManipulatorPriority = AzFramework::ViewportControllerPriority::High;
static const auto InteractionPriority = AzFramework::ViewportControllerPriority::Low;
namespace SandboxEditor
{
ViewportManipulatorControllerInstance::ViewportManipulatorControllerInstance(AzFramework::ViewportId viewport)
: AzFramework::MultiViewportControllerInstanceInterface(viewport)
{
}
AzToolsFramework::ViewportInteraction::MouseButton ViewportManipulatorControllerInstance::GetMouseButton(
const AzFramework::InputChannel& inputChannel)
{
using AzToolsFramework::ViewportInteraction::MouseButton;
using InputButton = AzFramework::InputDeviceMouse::Button;
const auto& id = inputChannel.GetInputChannelId();
if (id == InputButton::Left)
{
return MouseButton::Left;
}
if (id == InputButton::Middle)
{
return MouseButton::Middle;
}
if (id == InputButton::Right)
{
return MouseButton::Right;
}
return MouseButton::None;
}
bool ViewportManipulatorControllerInstance::IsMouseMove(const AzFramework::InputChannel& inputChannel)
{
return inputChannel.GetInputChannelId() == AzFramework::InputDeviceMouse::SystemCursorPosition;
}
AzToolsFramework::ViewportInteraction::KeyboardModifier ViewportManipulatorControllerInstance::GetKeyboardModifier(
const AzFramework::InputChannel& inputChannel)
{
using AzToolsFramework::ViewportInteraction::KeyboardModifier;
using Key = AzFramework::InputDeviceKeyboard::Key;
const auto& id = inputChannel.GetInputChannelId();
if (id == Key::ModifierAltL || id == Key::ModifierAltR)
{
return KeyboardModifier::Alt;
}
if (id == Key::ModifierCtrlL || id == Key::ModifierCtrlR)
{
return KeyboardModifier::Ctrl;
}
if (id == Key::ModifierShiftL || id == Key::ModifierShiftR)
{
return KeyboardModifier::Shift;
}
return KeyboardModifier::None;
}
bool ViewportManipulatorControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event)
{
// We only care about manipulator and viewport interaction events
if (event.m_priority != ManipulatorPriority && event.m_priority != InteractionPriority)
{
return false;
}
using InteractionBus = AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
using namespace AzToolsFramework::ViewportInteraction;
using AzFramework::InputChannel;
bool interactionHandled = false;
AZStd::optional<MouseButton> overrideButton;
AZStd::optional<MouseEvent> eventType;
// Because we receive events multiple times at separate priorities for manipulator events and
// viewport interaction events, we want to avoid updating our "last tick state" until we're on our last event,
// which currently is the low priority Interaction processor.
const bool finishedProcessingEvents = event.m_priority == InteractionPriority;
if (IsMouseMove(event.m_inputChannel))
{
// Cache the ray trace results when doing manipulator interaction checks, no need to recalculate after
if (event.m_priority == ManipulatorPriority)
{
AzFramework::ScreenPoint screenPosition = AzFramework::ScreenPoint(0, 0);
ViewportMouseCursorRequestBus::EventResult(
screenPosition, GetViewportId(), &ViewportMouseCursorRequestBus::Events::ViewportCursorScreenPosition);
m_state.m_mousePick.m_screenCoordinates = screenPosition;
AZStd::optional<ProjectedViewportRay> ray;
ViewportInteractionRequestBus::EventResult(
ray, GetViewportId(), &ViewportInteractionRequestBus::Events::ViewportScreenToWorldRay,
QPoint(screenPosition.m_x, screenPosition.m_y));
if (ray.has_value())
{
m_state.m_mousePick.m_rayOrigin = ray.value().origin;
m_state.m_mousePick.m_rayDirection = ray.value().direction;
}
}
eventType = MouseEvent::Move;
}
else if (auto mouseButton = GetMouseButton(event.m_inputChannel); mouseButton != MouseButton::None)
{
const AZ::u32 mouseButtonValue = static_cast<AZ::u32>(mouseButton);
overrideButton = mouseButton;
if (event.m_inputChannel.GetState() == InputChannel::State::Began)
{
m_state.m_mouseButtons.m_mouseButtons |= mouseButtonValue;
if (IsDoubleClick(mouseButton))
{
// Only remove the double click flag once we're done processing both Manipulator and Interaction events
if (event.m_priority == InteractionPriority)
{
m_pendingDoubleClicks.erase(mouseButton);
}
eventType = MouseEvent::DoubleClick;
}
else
{
// Only insert the double click timing once we're done processing events, to avoid a false IsDoubleClick positive
if (finishedProcessingEvents)
{
m_pendingDoubleClicks[mouseButton] = m_curTime;
}
eventType = MouseEvent::Down;
}
}
else if (event.m_inputChannel.GetState() == InputChannel::State::Ended)
{
// If we've actually logged a mouse down event, forward a mouse up event.
// This prevents corner cases like the context menu thinking it should be opened even though no one clicked in this viewport,
// due to RenderViewportWidget ensuring all controllers get InputChannel::State::Ended events.
if (m_state.m_mouseButtons.m_mouseButtons & mouseButtonValue)
{
// Erase the button from our state if we're done processing events.
if (event.m_priority == InteractionPriority)
{
m_state.m_mouseButtons.m_mouseButtons &= ~mouseButtonValue;
}
eventType = MouseEvent::Up;
}
}
}
else if (auto keyboardModifier = GetKeyboardModifier(event.m_inputChannel); keyboardModifier != KeyboardModifier::None)
{
if (event.m_inputChannel.GetState() == InputChannel::State::Began || event.m_inputChannel.GetState() == InputChannel::State::Updated)
{
m_state.m_keyboardModifiers.m_keyModifiers |= static_cast<AZ::u32>(keyboardModifier);
}
else if (event.m_inputChannel.GetState() == InputChannel::State::Ended)
{
m_state.m_keyboardModifiers.m_keyModifiers &= ~static_cast<AZ::u32>(keyboardModifier);
}
}
if (eventType)
{
MouseInteraction mouseInteraction = m_state;
if (overrideButton)
{
mouseInteraction.m_mouseButtons.m_mouseButtons = static_cast<AZ::u32>(overrideButton.value());
}
mouseInteraction.m_interactionId.m_viewportId = GetViewportId();
// Depending on priority, we dispatch to either the manipulator or viewport interaction event
const auto& targetInteractionEvent =
event.m_priority == ManipulatorPriority
? &InteractionBus::Events::InternalHandleMouseManipulatorInteraction
: &InteractionBus::Events::InternalHandleMouseViewportInteraction;
InteractionBus::EventResult(
interactionHandled,
AzToolsFramework::GetEntityContextId(),
targetInteractionEvent,
MouseInteractionEvent(AZStd::move(mouseInteraction), eventType.value()));
}
return interactionHandled;
}
void ViewportManipulatorControllerInstance::ResetInputChannels()
{
m_pendingDoubleClicks.clear();
m_state = AzToolsFramework::ViewportInteraction::MouseInteraction();
}
void ViewportManipulatorControllerInstance::UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event)
{
m_curTime = event.m_time;
}
bool ViewportManipulatorControllerInstance::IsDoubleClick(AzToolsFramework::ViewportInteraction::MouseButton button) const
{
auto clickIt = m_pendingDoubleClicks.find(button);
if (clickIt == m_pendingDoubleClicks.end())
{
return false;
}
const double doubleClickThresholdMilliseconds = qApp->doubleClickInterval();
return (m_curTime.GetMilliseconds() - clickIt->second.GetMilliseconds()) < doubleClickThresholdMilliseconds;
}
} //namespace SandboxEditor