Additional test support for Manipulator Ditto commands (#4191)

* additional test support for manipulator ditto

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

* tidy-up to changes

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

* fix for non-unity build compile error

Signed-off-by: hultonha <hultonha@amazon.co.uk>
This commit is contained in:
hultonha
2021-09-20 09:49:01 +01:00
committed by GitHub
parent 22e43c9122
commit de9a27597b
11 changed files with 220 additions and 27 deletions
+14 -2
View File
@@ -34,10 +34,14 @@ namespace EditorInternal
: ToolsApplication(argc, argv)
{
EditorToolsApplicationRequests::Bus::Handler::BusConnect();
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect();
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusConnect();
}
EditorToolsApplication::~EditorToolsApplication()
{
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect();
EditorToolsApplicationRequests::Bus::Handler::BusDisconnect();
Stop();
}
@@ -48,7 +52,6 @@ namespace EditorInternal
return m_StartupAborted;
}
void EditorToolsApplication::RegisterCoreComponents()
{
AzToolsFramework::ToolsApplication::RegisterCoreComponents();
@@ -274,5 +277,14 @@ namespace EditorInternal
Exit();
}
}
AzToolsFramework::ViewportInteraction::KeyboardModifiers EditorToolsApplication::QueryKeyboardModifiers()
{
return AzToolsFramework::ViewportInteraction::BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers());
}
AZStd::chrono::milliseconds EditorToolsApplication::EditorViewportInputTimeNow()
{
const auto now = AZStd::chrono::high_resolution_clock::now();
return AZStd::chrono::time_point_cast<AZStd::chrono::milliseconds>(now).time_since_epoch();
}
} // namespace EditorInternal
+10
View File
@@ -7,7 +7,9 @@
*/
#pragma once
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include "Core/EditorMetricsPlainTextNameRegistration.h"
#include "EditorToolsApplicationAPI.h"
@@ -19,6 +21,8 @@ namespace EditorInternal
class EditorToolsApplication
: public AzToolsFramework::ToolsApplication
, public EditorToolsApplicationRequests::Bus::Handler
, public AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler
, public AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler
{
public:
EditorToolsApplication(int* argc, char*** argv);
@@ -44,6 +48,12 @@ namespace EditorInternal
void CreateReflectionManager() override;
void Reflect(AZ::ReflectContext* context) override;
// EditorModifierKeyRequestBus overrides ...
AzToolsFramework::ViewportInteraction::KeyboardModifiers QueryKeyboardModifiers() override;
// EditorViewportInputTimeNowRequestBus overrides ...
AZStd::chrono::milliseconds EditorViewportInputTimeNow() override;
protected:
// From EditorToolsApplicationRequests
bool OpenLevel(AZStd::string_view levelName) override;
+11 -8
View File
@@ -744,11 +744,15 @@ void EditorViewportWidget::RenderAll()
{
namespace AztfVi = AzToolsFramework::ViewportInteraction;
AztfVi::KeyboardModifiers keyboardModifiers;
AztfVi::EditorModifierKeyRequestBus::BroadcastResult(
keyboardModifiers, &AztfVi::EditorModifierKeyRequestBus::Events::QueryKeyboardModifiers);
m_debugDisplay->DepthTestOff();
m_manipulatorManager->DrawManipulators(
*m_debugDisplay, GetCameraState(),
BuildMouseInteractionInternal(
AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), QueryKeyboardModifiers(),
AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), keyboardModifiers,
BuildMousePick(WidgetToViewport(mapFromGlobal(QCursor::pos())))));
m_debugDisplay->DepthTestOn();
}
@@ -959,12 +963,13 @@ QWidget* EditorViewportWidget::GetWidgetForViewportContextMenu()
bool EditorViewportWidget::ShowingWorldSpace()
{
return QueryKeyboardModifiers().Shift();
}
namespace AztfVi = AzToolsFramework::ViewportInteraction;
AzToolsFramework::ViewportInteraction::KeyboardModifiers EditorViewportWidget::QueryKeyboardModifiers()
{
return AzToolsFramework::ViewportInteraction::BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers());
AztfVi::KeyboardModifiers keyboardModifiers;
AztfVi::EditorModifierKeyRequestBus::BroadcastResult(
keyboardModifiers, &AztfVi::EditorModifierKeyRequestBus::Events::QueryKeyboardModifiers);
return keyboardModifiers.Shift();
}
void EditorViewportWidget::SetViewportId(int id)
@@ -1039,7 +1044,6 @@ void EditorViewportWidget::ConnectViewportInteractionRequestBus()
{
AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusConnect(GetViewportId());
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusConnect(GetViewportId());
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect();
m_viewportUi.ConnectViewportUiBus(GetViewportId());
AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusConnect();
@@ -1050,7 +1054,6 @@ void EditorViewportWidget::DisconnectViewportInteractionRequestBus()
AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusDisconnect();
m_viewportUi.DisconnectViewportUiBus();
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusDisconnect();
}
-4
View File
@@ -92,7 +92,6 @@ class SANDBOX_API EditorViewportWidget final
, private AzFramework::InputSystemCursorConstraintRequestBus::Handler
, private AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler
, private AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler
, private AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler
, private AzFramework::AssetCatalogEventBus::Handler
, private AZ::RPI::SceneNotificationBus::Handler
{
@@ -212,9 +211,6 @@ private:
// EditorEntityViewportInteractionRequestBus overrides ...
void FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntities) override;
// EditorModifierKeyRequestBus overrides ...
AzToolsFramework::ViewportInteraction::KeyboardModifiers QueryKeyboardModifiers() override;
// Camera::EditorCameraRequestBus overrides ...
void SetViewFromEntityPerspective(const AZ::EntityId& entityId) override;
void SetViewAndMovementLockFromEntityPerspective(const AZ::EntityId& entityId, bool lockCameraMovement) override;
@@ -9,8 +9,19 @@
#include <AzFramework/Viewport/ClickDetector.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzCore/std/chrono/clocks.h>
namespace AzFramework
{
ClickDetector::ClickDetector()
{
m_timeNowFn = []
{
const auto now = AZStd::chrono::high_resolution_clock::now();
return AZStd::chrono::time_point_cast<AZStd::chrono::milliseconds>(now).time_since_epoch();
};
}
ClickDetector::ClickOutcome ClickDetector::DetectClick(const ClickEvent clickEvent, const ScreenVector& cursorDelta)
{
const auto previousDetectionState = m_detectionState;
@@ -26,11 +37,13 @@ namespace AzFramework
if (clickEvent == ClickEvent::Down)
{
const auto now = std::chrono::steady_clock::now();
const auto now = m_timeNowFn();
if (m_tryBeginTime)
{
const std::chrono::duration<float> diff = now - m_tryBeginTime.value();
if (diff.count() < m_doubleClickInterval)
using FloatingPointSeconds = AZStd::chrono::duration<float, AZStd::chrono::seconds::period>;
const auto diff = now - m_tryBeginTime.value();
if (FloatingPointSeconds(diff).count() < m_doubleClickInterval)
{
return ClickOutcome::Nil;
}
@@ -43,7 +56,8 @@ namespace AzFramework
}
else if (clickEvent == ClickEvent::Up)
{
const auto clickOutcome = [detectionState = m_detectionState] {
const auto clickOutcome = [detectionState = m_detectionState]
{
if (detectionState == DetectionState::WaitingForMove)
{
return ClickOutcome::Click;
@@ -66,4 +80,9 @@ namespace AzFramework
return ClickOutcome::Nil;
}
void ClickDetector::OverrideTimeNowFn(AZStd::function<AZStd::chrono::milliseconds()> timeNowFn)
{
m_timeNowFn = AZStd::move(timeNowFn);
}
} // namespace AzFramework
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/std/functional.h>
#include <AzCore/std/optional.h>
#include <chrono>
@@ -21,10 +22,9 @@ namespace AzFramework
//! (mouse down with movement and then mouse up).
class ClickDetector
{
//! Alias for recording time of mouse down events
using Time = std::chrono::time_point<std::chrono::steady_clock>;
public:
ClickDetector();
//! Internal representation of click event (map from external event for this when
//! calling DetectClick).
enum class ClickEvent
@@ -51,6 +51,10 @@ namespace AzFramework
void SetDoubleClickInterval(float doubleClickInterval);
//! Override the dead zone before a 'move' outcome will be triggered.
void SetDeadZone(float deadZone);
//! Override how the current time is retrieved.
//! This is helpful to override when it comes to simulating different passages of
//! time to avoid double click issues in tests for example.
void OverrideTimeNowFn(AZStd::function<AZStd::chrono::milliseconds()> timeNowFn);
private:
//! Internal state of ClickDetector based on incoming events.
@@ -65,7 +69,9 @@ namespace AzFramework
float m_deadZone = 2.0f; //!< How far to move before a click is cancelled (when Move will fire).
float m_doubleClickInterval = 0.4f; //!< Default double click interval, can be overridden.
DetectionState m_detectionState; //!< Internal state of ClickDetector.
AZStd::optional<Time> m_tryBeginTime; //!< Mouse down time (happens each mouse down, helps with double click handling).
//! Mouse down time (happens each mouse down, helps with double click handling).
AZStd::optional<AZStd::chrono::milliseconds> m_tryBeginTime;
AZStd::function<AZStd::chrono::milliseconds()> m_timeNowFn; //!< Interface to query the current time.
};
inline void ClickDetector::SetDoubleClickInterval(const float doubleClickInterval)
@@ -18,6 +18,7 @@ namespace AzManipulatorTestFramework
class ImmediateModeActionDispatcher
: public ActionDispatcher<ImmediateModeActionDispatcher>
, public AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler
, public AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler
{
using KeyboardModifier = AzToolsFramework::ViewportInteraction::KeyboardModifier;
using KeyboardModifiers = AzToolsFramework::ViewportInteraction::KeyboardModifiers;
@@ -50,6 +51,9 @@ namespace AzManipulatorTestFramework
// EditorModifierKeyRequestBus overrides ...
KeyboardModifiers QueryKeyboardModifiers() override;
// EditorViewportInputTimeNowRequestBus overrides ...
AZStd::chrono::milliseconds EditorViewportInputTimeNow() override;
protected:
// ActionDispatcher ...
void SetSnapToGridImpl(bool enabled) override;
@@ -79,6 +83,9 @@ namespace AzManipulatorTestFramework
mutable AZStd::unique_ptr<MouseInteractionEvent> m_event;
ManipulatorViewportInteraction& m_viewportManipulatorInteraction;
//! Current time that ticks up after each call to EditorViewportInputTimeNow.
AZStd::chrono::milliseconds m_timeNow = AZStd::chrono::milliseconds(0);
};
template<typename ActualT, typename ExpectedT>
@@ -106,4 +113,13 @@ namespace AzManipulatorTestFramework
{
return GetMouseInteractionEvent()->m_mouseInteraction.m_keyboardModifiers;
}
inline AZStd::chrono::milliseconds ImmediateModeActionDispatcher::EditorViewportInputTimeNow()
{
// step the time for each call to be greater than the minimum time required for a double click to register
// note: the time increment is very high to ensure any potential system changes to settings such as double
// click interval will not be impacted
m_timeNow += AZStd::chrono::milliseconds(10000);
return m_timeNow;
}
} // namespace AzManipulatorTestFramework
@@ -33,10 +33,12 @@ namespace AzManipulatorTestFramework
: m_viewportManipulatorInteraction(viewportManipulatorInteraction)
{
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect();
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusConnect();
}
ImmediateModeActionDispatcher::~ImmediateModeActionDispatcher()
{
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect();
}
@@ -282,6 +282,24 @@ namespace AzToolsFramework
return keyboardModifiers;
}
//! An interface to deal with time requests relating to viewports.
//! @note The bus is global and not per viewport.
class EditorViewportInputTimeNowRequests : public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Returns the current time in seconds.
//! This interface can be overridden for the purposes of testing to simplify viewport input requests.
virtual AZStd::chrono::milliseconds EditorViewportInputTimeNow() = 0;
protected:
~EditorViewportInputTimeNowRequests() = default;
};
using EditorViewportInputTimeNowRequestBus = AZ::EBus<EditorViewportInputTimeNowRequests>;
//! Viewport requests for managing the viewport cursor state.
class ViewportMouseCursorRequests
{
@@ -253,10 +253,10 @@ namespace AzToolsFramework
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl());
}
static bool ManipulatorDitto(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
static bool ManipulatorDitto(
const AzFramework::ClickDetector::ClickOutcome clickOutcome, const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
return mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down &&
mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
return clickOutcome == AzFramework::ClickDetector::ClickOutcome::Click &&
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl() &&
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt();
}
@@ -1054,6 +1054,17 @@ namespace AzToolsFramework
RegisterActions();
SetupBoxSelect();
RefreshSelectedEntityIdsAndRegenerateManipulators();
// ensure the click detector uses the EditorViewportInputTimeNowRequests interface to retrieve elapsed time
// note: this is to facilitate overriding this functionality for purposes such as testing
m_clickDetector.OverrideTimeNowFn(
[]
{
AZStd::chrono::milliseconds timeNow;
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::BroadcastResult(
timeNow, &AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Events::EditorViewportInputTimeNow);
return timeNow;
});
}
EditorTransformComponentSelection::~EditorTransformComponentSelection()
@@ -1883,7 +1894,7 @@ namespace AzToolsFramework
}
// set manipulator pivot override translation or orientation (update manipulators)
if (Input::ManipulatorDitto(mouseInteraction))
if (Input::ManipulatorDitto(clickOutcome, mouseInteraction))
{
PerformManipulatorDitto(entityIdUnderCursor);
return false;
@@ -783,7 +783,8 @@ namespace UnitTest
const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState);
// click the entity in the viewport
m_actionDispatcher->SetStickySelect(true)->CameraState(m_cameraState)
m_actionDispatcher->SetStickySelect(true)
->CameraState(m_cameraState)
->MousePosition(entity2ScreenPosition)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->MouseLButtonDown()
@@ -1018,6 +1019,105 @@ namespace UnitTest
EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1));
}
class EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam
: public EditorTransformComponentSelectionViewportPickingManipulatorTestFixture
, public ::testing::WithParamInterface<bool>
{
};
TEST_P(
EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam,
StickyAndUnstickyDittoManipulatorToOtherEntityChangesManipulatorAndDoesNotChangeSelection)
{
PositionEntities();
PositionCamera(m_cameraState);
AzToolsFramework::SelectEntity(m_entityId1);
// calculate the position in screen space of the second entity
const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState);
// single click select entity2
m_actionDispatcher->SetStickySelect(GetParam())
->CameraState(m_cameraState)
->MousePosition(entity2ScreenPosition)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt)
->MouseLButtonDown()
->MouseLButtonUp();
// entity1 is still selected
using ::testing::UnorderedElementsAre;
auto selectedEntitiesAfter = SelectedEntities();
EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1));
AZStd::optional<AZ::Transform> manipulatorTransform;
AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
manipulatorTransform, AzToolsFramework::GetEntityContextId(),
&AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity2WorldTranslation));
}
TEST_P(
EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam,
StickyAndUnstickyDittoManipulatorToOtherEntityChangesManipulatorAndClickOffResetsManipulator)
{
PositionEntities();
PositionCamera(m_cameraState);
AzToolsFramework::SelectEntity(m_entityId1);
// calculate the position in screen space of the second entity
const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState);
// position in space above the entities
const auto clickOffPositionWorld = AZ::Vector3(5.0f, 15.0f, 12.0f);
// calculate the screen space position of the click
const auto clickOffPositionScreen = AzFramework::WorldToScreen(clickOffPositionWorld, m_cameraState);
using ::testing::UnorderedElementsAre;
// single click select entity2, then click off
m_actionDispatcher->SetStickySelect(GetParam())
->CameraState(m_cameraState)
->MousePosition(entity2ScreenPosition)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt)
->MouseLButtonDown()
->MouseLButtonUp()
->ExecuteBlock(
[this]()
{
auto selectedEntitiesAfter = SelectedEntities();
EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1));
AZStd::optional<AZ::Transform> manipulatorTransform;
AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
manipulatorTransform, AzToolsFramework::GetEntityContextId(),
&AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity2WorldTranslation));
})
->MousePosition(clickOffPositionScreen)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt)
->MouseLButtonDown()
->MouseLButtonUp();
auto selectedEntitiesAfter = SelectedEntities();
EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1));
AZStd::optional<AZ::Transform> manipulatorTransform;
AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
manipulatorTransform, AzToolsFramework::GetEntityContextId(),
&AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
// manipulator transform is reset
EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity1WorldTranslation));
}
INSTANTIATE_TEST_CASE_P(All, EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam, testing::Values(true, false));
using EditorTransformComponentSelectionManipulatorTestFixture =
IndirectCallManipulatorViewportInteractionFixtureMixin<EditorTransformComponentSelectionFixture>;