From 125bcc1b473f98ffa87d858985486d25e1ac5e59 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 14 Sep 2021 12:13:19 +0100 Subject: [PATCH] Provisional impl and testing of central state tracker. Signed-off-by: John --- .../ViewportEditorModeStateTrackerInterface.h | 42 ++ ...ortEditorModeStateTrackerNotificationBus.h | 65 +++ .../EditorInteractionInterface.h | 33 ++ .../ViewportEditorModeStateTracker.cpp | 121 +++++ .../ViewportEditorModeStateTracker.h | 62 +++ .../aztoolsframework_files.cmake | 4 + .../Viewport/ViewportEditorModeTests.cpp | 492 ++++++++++++++++++ .../Tests/aztoolsframeworktests_files.cmake | 1 + 8 files changed, 820 insertions(+) create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeStateTrackerInterface.h create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeStateTrackerNotificationBus.h create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionInterface.h create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeStateTracker.cpp create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeStateTracker.h create mode 100644 Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeStateTrackerInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeStateTrackerInterface.h new file mode 100644 index 0000000000..4f8b800215 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeStateTrackerInterface.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AzToolsFramework +{ + //! The AZ::Interface of the central editor mode state tracker for all viewports. + class ViewportEditorModeStateTrackerInterface + { + public: + AZ_RTTI(ViewportEditorModeStateTrackerInterface, "{7D72A4F7-2147-4ED9-A315-E456A3BE3CF6}"); + + virtual ~ViewportEditorModeStateTrackerInterface() = default; + + //! Enters the specified editor mode for the specified viewport. + virtual void EnterMode(const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) = 0; + + //! Exits the specified editor mode for the specified viewport. + virtual void ExitMode(const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) = 0; + + //! Attempts to retrieve the editor mode state for the specified viewport, otherwise returns nullptr. + virtual const ViewportEditorModeStateInterface* GetEditorModeState(const ViewportEditorModeInfo& viewportEditorModeInfo) const = 0; + + //! Returns the number of viewports currently being tracked. + virtual size_t GetNumTrackedViewports() const = 0; + + //! Returns true if the specified viewport is being tracked, otherwise false. + virtual bool IsViewportStateBeingTracked(const ViewportEditorModeInfo& viewportEditorModeInfo) const = 0; + + private: + }; +} // namespace AzToolsFramework + diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeStateTrackerNotificationBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeStateTrackerNotificationBus.h new file mode 100644 index 0000000000..7b95ebc6cc --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeStateTrackerNotificationBus.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include + +namespace AzToolsFramework +{ + //! Enumeration of each viewport editor state. + enum class ViewportEditorMode : AZ::u8 + { + Default, + Component, + Focus, + Pick + }; + + //! Viewport identifier and other relevant viewport data. + struct ViewportEditorModeInfo + { + using IdType = AzFramework::ViewportId; + IdType m_id = ViewportUi::DefaultViewportId; //!< The unique identifier for a given viewport. + }; + + //! Interface for the editor mode state of a given viewport. + class ViewportEditorModeStateInterface + { + public: + virtual ~ViewportEditorModeStateInterface() = default; + + //! Returns true if the specified editor mode is active, otherwise false. + virtual bool IsModeActive(ViewportEditorMode mode) const = 0; + }; + + //! Provides a bus to notify when the different editor modes are entered/exit. + class ViewportEditorModeNotifications + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = ViewportEditorModeInfo::IdType; + ////////////////////////////////////////////////////////////////////////// + + //! Notifies subscribers of the a given viewport to the entering of the specified editor mode. + virtual void OnEditorModeEnter([[maybe_unused]] const ViewportEditorModeStateInterface& editorModeState, [[maybe_unused]] ViewportEditorMode mode) + { + } + + //! Notifies subscribers of the a given viewport to the exiting of the specified editor mode. + virtual void OnEditorModeExit([[maybe_unused]] const ViewportEditorModeStateInterface& editorModeState, [[maybe_unused]] ViewportEditorMode mode) + { + } + }; + using ViewportEditorModeNotificationsBus = AZ::EBus; +} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionInterface.h new file mode 100644 index 0000000000..70cbfd09ec --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionInterface.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AzToolsFramework +{ + /*! + * EditorInteractionInterface + * Allows systems to alter the behavior of viewport selection. + */ + class EditorInteractionInterface + { + public: + AZ_RTTI(EditorInteractionInterface, "{09276E3C-9AA6-40FF-A0B5-3D33A33F0E5A}"); + + /*! + * Allows the entity system to redirect the selection of an entity to another entity. + * It can be used to select a container when clicking on its content. + */ + virtual AZ::EntityId RedirectEntitySelection(AZ::EntityId entityId) = 0; + }; + +} // namespace AzToolsFramework + diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeStateTracker.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeStateTracker.cpp new file mode 100644 index 0000000000..64e8c2bbd1 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeStateTracker.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace AzToolsFramework +{ + static constexpr const char* ViewportEditorModeLogWindow = "ViewportEditorMode"; + + void ViewportEditorModeState::SetModeActive(ViewportEditorMode mode) + { + if (const AZ::u32 modeIndex = static_cast(mode); + modeIndex < NumEditorModes) + { + m_editorModes[modeIndex] = true; + } + else + { + AZ_Error(ViewportEditorModeLogWindow, false, "Cannot activate mode %u, mode is not recognized", modeIndex) + } + } + + void ViewportEditorModeState::SetModeInactive(ViewportEditorMode mode) + { + if (const AZ::u32 modeIndex = static_cast(mode); modeIndex < NumEditorModes) + { + m_editorModes[modeIndex] = false; + } + else + { + AZ_Error(ViewportEditorModeLogWindow, false, "Cannot deactivate mode %u, mode is not recognized", modeIndex) + } + } + + bool ViewportEditorModeState::IsModeActive(ViewportEditorMode mode) const + { + return m_editorModes[static_cast(mode)]; + } + + void ViewportEditorModeStateTracker::RegisterInterface() + { + if (AZ::Interface::Get() == nullptr) + { + AZ::Interface::Register(this); + } + } + + void ViewportEditorModeStateTracker::UnregisterInterface() + { + if (AZ::Interface::Get() != nullptr) + { + AZ::Interface::Unregister(this); + } + } + + void ViewportEditorModeStateTracker::EnterMode(const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) + { + auto& editorModeStates = m_viewportEditorModeStates[viewportEditorModeInfo.m_id]; + AZ_Warning( + ViewportEditorModeLogWindow, !editorModeStates.IsModeActive(mode), + AZStd::string::format( + "Duplicate call to EnterMode for mode '%u' on id '%i'", static_cast(mode), viewportEditorModeInfo.m_id).c_str()); + editorModeStates.SetModeActive(mode); + ViewportEditorModeNotificationsBus::Event( + viewportEditorModeInfo.m_id, &ViewportEditorModeNotificationsBus::Events::OnEditorModeEnter, editorModeStates, mode); + } + + void ViewportEditorModeStateTracker::ExitMode(const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) + { + ViewportEditorModeState* editorModeStates = nullptr; + if (m_viewportEditorModeStates.count(viewportEditorModeInfo.m_id)) + { + editorModeStates = &m_viewportEditorModeStates.at(viewportEditorModeInfo.m_id); + AZ_Warning( + ViewportEditorModeLogWindow, editorModeStates->IsModeActive(mode), + AZStd::string::format( + "Duplicate call to ExitMode for mode '%u' on id '%i'", static_cast(mode), viewportEditorModeInfo.m_id).c_str()); + } + else + { + AZ_Warning( + ViewportEditorModeLogWindow, false, "Call to ExitMode for mode '%u' on id '%i' without precursor call to EnterMode", + static_cast(mode), viewportEditorModeInfo.m_id); + + editorModeStates = &m_viewportEditorModeStates[viewportEditorModeInfo.m_id]; + } + + editorModeStates->SetModeInactive(mode); + ViewportEditorModeNotificationsBus::Event( + viewportEditorModeInfo.m_id, &ViewportEditorModeNotificationsBus::Events::OnEditorModeExit, *editorModeStates, mode); + } + + const ViewportEditorModeStateInterface* ViewportEditorModeStateTracker::GetEditorModeState(const ViewportEditorModeInfo& viewportEditorModeInfo) const + { + if (auto editorModeStates = m_viewportEditorModeStates.find(viewportEditorModeInfo.m_id); + editorModeStates != m_viewportEditorModeStates.end()) + { + return &editorModeStates->second; + } + else + { + return nullptr; + } + } + + size_t ViewportEditorModeStateTracker::GetNumTrackedViewports() const + { + return m_viewportEditorModeStates.size(); + } + + bool ViewportEditorModeStateTracker::IsViewportStateBeingTracked(const ViewportEditorModeInfo& viewportEditorModeInfo) const + { + return m_viewportEditorModeStates.count(viewportEditorModeInfo.m_id) > 0; + } +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeStateTracker.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeStateTracker.h new file mode 100644 index 0000000000..30eabdf16e --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeStateTracker.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace AzToolsFramework +{ + //! The encapsulation of the editor modes for a given viewport. + class ViewportEditorModeState + : public ViewportEditorModeStateInterface + { + public: + + //! The number of currently supported viewport editor modes. + static constexpr AZ::u8 NumEditorModes = 4; + + //! Sets the specified mode as active. + void SetModeActive(ViewportEditorMode mode); + + // Sets the specified mode as inactive. + void SetModeInactive(ViewportEditorMode mode); + + // ViewportEditorModeStateInterface ... + bool IsModeActive(ViewportEditorMode mode) const override; + private: + AZStd::array m_editorModes{}; //!< State flags to track active/inactive status of viewport editor modes. + }; + + //! The implementation of the central editor mode state tracker for all viewports. + class ViewportEditorModeStateTracker + : public ViewportEditorModeStateTrackerInterface + { + public: + //! Registers this object with the AZ::Interface. + void RegisterInterface(); + + //! Unregisters this object with the AZ::Interface. + void UnregisterInterface(); + + // ViewportEditorModeStateTrackerInterface ... + void EnterMode(const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) override; + void ExitMode(const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) override; + const ViewportEditorModeStateInterface* GetEditorModeState(const ViewportEditorModeInfo& viewportEditorModeInfo) const override; + size_t GetNumTrackedViewports() const override; + bool IsViewportStateBeingTracked(const ViewportEditorModeInfo& viewportEditorModeInfo) const override; + + private: + using ViewportEditorModeStates = AZStd::unordered_map; + ViewportEditorModeStates m_viewportEditorModeStates; //!< Editor mode state per viewport. + }; +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index 2d9e75a115..9b7394a6e7 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -34,6 +34,7 @@ set(FILES API/EditorAnimationSystemRequestBus.h API/EditorEntityAPI.h API/EditorLevelNotificationBus.h + API/ViewportEditorModeStateTrackerNotificationBus.h API/EditorVegetationRequestsBus.h API/EditorPythonConsoleBus.h API/EditorPythonRunnerRequestsBus.h @@ -44,6 +45,7 @@ set(FILES API/EntityCompositionNotificationBus.h API/EditorViewportIconDisplayInterface.h API/ViewPaneOptions.h + API/ViewportEditorModeStateTrackerInterface.h Application/Ticker.h Application/Ticker.cpp Application/EditorEntityManager.cpp @@ -538,6 +540,8 @@ set(FILES ViewportSelection/EditorTransformComponentSelectionRequestBus.cpp ViewportSelection/EditorVisibleEntityDataCache.h ViewportSelection/EditorVisibleEntityDataCache.cpp + ViewportSelection/ViewportEditorModeStateTracker.cpp + ViewportSelection/ViewportEditorModeStateTracker.h ToolsFileUtils/ToolsFileUtils.h AssetBrowser/AssetBrowserBus.h AssetBrowser/AssetBrowserSourceDropBus.h diff --git a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp new file mode 100644 index 0000000000..7718c596d5 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp @@ -0,0 +1,492 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace UnitTest +{ + using ViewportEditorMode = AzToolsFramework::ViewportEditorMode; + using ViewportEditorModeState = AzToolsFramework::ViewportEditorModeState; + using ViewportEditorModeStateTracker = AzToolsFramework::ViewportEditorModeStateTracker; + using ViewportEditorModeInfo = AzToolsFramework::ViewportEditorModeInfo; + using ViewportId = ViewportEditorModeInfo::IdType; + using ViewportEditorModeStateInterface = AzToolsFramework::ViewportEditorModeStateInterface; + + void SetAllModesActive(ViewportEditorModeState& editorModeState) + { + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + editorModeState.SetModeActive(static_cast(mode)); + } + } + + void SetAllModesInactive(ViewportEditorModeState& editorModeState) + { + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + editorModeState.SetModeInactive(static_cast(mode)); + } + } + + // Fixture for testing editor mode states + class ViewportEditorModeStateTestsFixture + : public ::testing::Test + { + public: + ViewportEditorModeState m_editorModeState; + }; + + // Fixture for testing editor mode states with parameterized test arguments + class ViewportEditorModeStateTestsFixtureWithParams + : public ViewportEditorModeStateTestsFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + m_selectedEditorMode = GetParam(); + } + + ViewportEditorMode m_selectedEditorMode; + }; + + // Fixture for testing the viewport editor mode state tracker + class ViewportEditorModeStateTrackerTestFixture + : public ToolsApplicationFixture + { + public: + ViewportEditorModeStateTracker m_viewportEditorModeStteTracker; + }; + + // Subscriber of viewport editor mode notifications for a single viewport that expects a single mode to be activated/deactivated + class ViewportEditorModeNotificationsBusHandler + : private AzToolsFramework::ViewportEditorModeNotificationsBus::Handler + { + public: + struct ReceivedEvents + { + bool m_onEnter = false; + bool m_onLeave = false; + }; + + using EditModeTracker = AZStd::unordered_map; + + ViewportEditorModeNotificationsBusHandler(ViewportId viewportId) + : m_viewportSubscription(viewportId) + { + AzToolsFramework::ViewportEditorModeNotificationsBus::Handler::BusConnect(m_viewportSubscription); + } + + ~ViewportEditorModeNotificationsBusHandler() + { + AzToolsFramework::ViewportEditorModeNotificationsBus::Handler::BusDisconnect(); + } + + ViewportId GetViewportSubscription() const + { + return m_viewportSubscription; + } + + const EditModeTracker& GetEditorModes() const + { + return m_editorModes; + } + + void OnEditorModeEnter([[maybe_unused]]const ViewportEditorModeStateInterface& editorModeState, ViewportEditorMode mode) override + { + m_editorModes[mode].m_onEnter = true; + } + + virtual void OnEditorModeExit([[maybe_unused]] const ViewportEditorModeStateInterface& editorModeState, ViewportEditorMode mode) override + { + m_editorModes[mode].m_onLeave = true; + } + + private: + ViewportId m_viewportSubscription; + EditModeTracker m_editorModes; + + }; + + // Fixture for testing viewport editor mode notifications publishing + class ViewportEditorModePublisherTestFixture + : public ViewportEditorModeStateTrackerTestFixture + { + public: + + void SetUpEditorFixtureImpl() override + { + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + m_editorModeHandlers[mode] = AZStd::make_unique(mode); + } + } + + void TearDownEditorFixtureImpl() override + { + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + m_editorModeHandlers[mode].reset(); + } + } + + AZStd::array, ViewportEditorModeState::NumEditorModes> m_editorModeHandlers; + }; + + TEST_F(ViewportEditorModeStateTestsFixture, NumberOfEditorModesIsEqualTo4) + { + EXPECT_EQ(ViewportEditorModeState::NumEditorModes, 4); + } + + TEST_F(ViewportEditorModeStateTestsFixture, InitialEditorModeStateHasAllInactiveModes) + { + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + EXPECT_FALSE(m_editorModeState.IsModeActive(static_cast(mode))); + } + } + + TEST_P(ViewportEditorModeStateTestsFixtureWithParams, SettingModeActiveActivatesOnlyThatMode) + { + m_editorModeState.SetModeActive(m_selectedEditorMode); + + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + const auto editorMode = static_cast(mode); + if (editorMode == m_selectedEditorMode) + { + EXPECT_TRUE(m_editorModeState.IsModeActive(static_cast(editorMode))); + } + else + { + EXPECT_FALSE(m_editorModeState.IsModeActive(static_cast(editorMode))); + } + } + } + + TEST_P(ViewportEditorModeStateTestsFixtureWithParams, SettingModeInactiveInactivatesOnlyThatMode) + { + SetAllModesActive(m_editorModeState); + m_editorModeState.SetModeInactive(m_selectedEditorMode); + + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + const auto editorMode = static_cast(mode); + if (editorMode == m_selectedEditorMode) + { + EXPECT_FALSE(m_editorModeState.IsModeActive(editorMode)); + } + else + { + EXPECT_TRUE(m_editorModeState.IsModeActive(editorMode)); + } + } + } + + TEST_P(ViewportEditorModeStateTestsFixtureWithParams, SettingMultipleModesActiveActivatesAllThoseModesNonMutuallyExclusively) + { + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes - 1; mode++) + { + // Given only the selected mode active + SetAllModesInactive(m_editorModeState); + m_editorModeState.SetModeActive(m_selectedEditorMode); + + const auto editorMode = static_cast(mode); + if (editorMode == m_selectedEditorMode) + { + continue; + } + + // When other modes are activated + m_editorModeState.SetModeActive(editorMode); + + for (auto expectedMode = 0; expectedMode < ViewportEditorModeState::NumEditorModes; expectedMode++) + { + const auto expectedEditorMode = static_cast(expectedMode); + if (expectedEditorMode == editorMode || expectedEditorMode == m_selectedEditorMode) + { + // Expect the activated modes to be active + EXPECT_TRUE(m_editorModeState.IsModeActive(expectedEditorMode)); + } + else + { + // Expect the modes not active to be inactive + EXPECT_FALSE(m_editorModeState.IsModeActive(expectedEditorMode)); + } + } + } + } + + TEST_P(ViewportEditorModeStateTestsFixtureWithParams, SettingMultipleModesInactiveInactivatesAllThoseModesNonMutuallyExclusively) + { + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes - 1; mode++) + { + // Given only the selected mode inactive + SetAllModesActive(m_editorModeState); + m_editorModeState.SetModeInactive(m_selectedEditorMode); + + const auto editorMode = static_cast(mode); + if (editorMode == m_selectedEditorMode) + { + continue; + } + + // When other modes are deactivated + m_editorModeState.SetModeInactive(editorMode); + + for (auto expectedMode = 0; expectedMode < ViewportEditorModeState::NumEditorModes; expectedMode++) + { + const auto expectedEditorMode = static_cast(expectedMode); + if (expectedEditorMode == editorMode || expectedEditorMode == m_selectedEditorMode) + { + // Expect the deactivated modes to be inactive + EXPECT_FALSE(m_editorModeState.IsModeActive(expectedEditorMode)); + } + else + { + // Expects the modes not deactivated to still be active + EXPECT_TRUE(m_editorModeState.IsModeActive(expectedEditorMode)); + } + } + } + } + + INSTANTIATE_TEST_CASE_P( + AllEditorModes, + ViewportEditorModeStateTestsFixtureWithParams, + ::testing::Values( + AzToolsFramework::ViewportEditorMode::Default, + AzToolsFramework::ViewportEditorMode::Component, + AzToolsFramework::ViewportEditorMode::Focus, + AzToolsFramework::ViewportEditorMode::Pick)); + + TEST_F(ViewportEditorModeStateTestsFixture, SettingOutOfBoundsModeActiveIssuesErrorMsg) + { + UnitTest::TestRunner::Instance().StartAssertTests(); + m_editorModeState.SetModeActive(static_cast(ViewportEditorModeState::NumEditorModes)); + EXPECT_EQ(1, UnitTest::TestRunner::Instance().StopAssertTests()); + } + + TEST_F(ViewportEditorModeStateTestsFixture, SettingOutOfBoundsModeInactiveIssuesErrorMsg) + { + UnitTest::TestRunner::Instance().StartAssertTests(); + m_editorModeState.SetModeInactive(static_cast(ViewportEditorModeState::NumEditorModes)); + EXPECT_EQ(1, UnitTest::TestRunner::Instance().StopAssertTests()); + } + + TEST_F(ViewportEditorModeStateTrackerTestFixture, InitialCentralStateTrackerHasNoViewportEditorModeStates) + { + EXPECT_EQ(m_viewportEditorModeStteTracker.GetNumTrackedViewports(), 0); + } + + TEST_F(ViewportEditorModeStateTrackerTestFixture, EnteringViewportEditorModeForNonExistentIdCreatesViewportEditorModeStateForThatId) + { + // Given a viewport not currently being tracked + const ViewportId viewportid = 0; + EXPECT_FALSE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + EXPECT_EQ(m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }), nullptr); + + // When a mode is activated for that viewport + const auto editorMode = ViewportEditorMode::Default; + m_viewportEditorModeStteTracker.EnterMode({ viewportid }, editorMode); + const auto* viewportEditorModeState = m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }); + + // Expect that viewport to now be tracked + EXPECT_TRUE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + EXPECT_NE(viewportEditorModeState, nullptr); + + // Expect the mode for that viewport to be active + EXPECT_TRUE(viewportEditorModeState->IsModeActive(editorMode)); + } + + TEST_F(ViewportEditorModeStateTrackerTestFixture, ExitingViewportEditorModeForNonExistentIdCreatesViewportEditorModeStateForThatIdButIssuesErrorMsg) + { + // Given a viewport not currently being tracked + const ViewportId viewportid = 0; + EXPECT_FALSE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + EXPECT_EQ(m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }), nullptr); + + // When a mode is deactivated for that viewport + const auto editorMode = ViewportEditorMode::Default; + UnitTest::ErrorHandler errorHandler(AZStd::string::format( + "Call to ExitMode for mode '%u' on id '%i' without precursor call to EnterMode", static_cast(editorMode), viewportid).c_str()); + m_viewportEditorModeStteTracker.ExitMode({ viewportid }, editorMode); + + // Expect a warning to be issued due to no precursor activation of that mode + EXPECT_EQ(errorHandler.GetExpectedWarningCount(), 1); + + // Expect that viewport to now be tracked + const auto* viewportEditorModeState = m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }); + EXPECT_TRUE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + + // Expect the mode for that viewport to be inactive + EXPECT_NE(viewportEditorModeState, nullptr); + EXPECT_FALSE(viewportEditorModeState->IsModeActive(editorMode)); + } + + TEST_F(ViewportEditorModeStateTrackerTestFixture, GettingNonExistentViewportEditorModeStateForIdReturnsNull) + { + const ViewportId viewportid = 0; + EXPECT_FALSE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + EXPECT_EQ(m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }), nullptr); + } + + TEST_F(ViewportEditorModeStateTrackerTestFixture, EnteringViewportEditorModeStateForExistingIdInThatStateIssuesWarningMsg) + { + // Given a viewport not currently tracked + const ViewportId viewportid = 0; + EXPECT_FALSE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + EXPECT_EQ(m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }), nullptr); + + const auto editorMode = ViewportEditorMode::Default; + const auto expectedWarning = AZStd::string::format( + "Duplicate call to EnterMode for mode '%u' on id '%i'", static_cast(editorMode), viewportid); + + { + UnitTest::ErrorHandler errorHandler(expectedWarning.c_str()); + + // When the mode is activated for the viewport + m_viewportEditorModeStteTracker.EnterMode({ viewportid }, editorMode); + + // Expect no warning to be issued as there is no duplicate activation + EXPECT_EQ(errorHandler.GetExpectedWarningCount(), 0); + + // Expect the mode to be active for the viewport + const auto* viewportEditorModeState = m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }); + EXPECT_TRUE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + EXPECT_NE(viewportEditorModeState, nullptr); + EXPECT_TRUE(viewportEditorModeState->IsModeActive(editorMode)); + } + { + // When the mode is activated again for the viewport + UnitTest::ErrorHandler errorHandler(expectedWarning.c_str()); + m_viewportEditorModeStteTracker.EnterMode({ viewportid }, editorMode); + + // Expect a warning to be issued for the duplicate activation + EXPECT_EQ(errorHandler.GetExpectedWarningCount(), 1); + + // Expect the mode to still be active for the viewport + const auto* viewportEditorModeState = m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }); + EXPECT_TRUE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + EXPECT_NE(viewportEditorModeState, nullptr); + EXPECT_TRUE(viewportEditorModeState->IsModeActive(editorMode)); + } + } + + TEST_F(ViewportEditorModeStateTrackerTestFixture, ExitingViewportEditorModeStateForExistingIdNotInThatStateIssuesWarningMsg) + { + // Given a viewport not currently tracked + const ViewportId viewportid = 0; + EXPECT_FALSE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + EXPECT_EQ(m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }), nullptr); + + const auto editorMode = ViewportEditorMode::Default; + const auto expectedWarning = + AZStd::string::format("Duplicate call to ExitMode for mode '%u' on id '%i'", static_cast(editorMode), viewportid); + + { + UnitTest::ErrorHandler errorHandler(expectedWarning.c_str()); + + // When the mode is activated and then deactivated for the viewport + m_viewportEditorModeStteTracker.EnterMode({ viewportid }, editorMode); + m_viewportEditorModeStteTracker.ExitMode({ viewportid }, editorMode); + + // Expect no warning to be issued as there is no duplicate deactivation + EXPECT_EQ(errorHandler.GetExpectedWarningCount(), 0); + + // Expect the mode to be inctive for the viewport + const auto* viewportEditorModeState = m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }); + EXPECT_TRUE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + EXPECT_NE(viewportEditorModeState, nullptr); + EXPECT_FALSE(viewportEditorModeState->IsModeActive(editorMode)); + } + { + UnitTest::ErrorHandler errorHandler(expectedWarning.c_str()); + + // When the mode is deactivated again for the viewport + m_viewportEditorModeStteTracker.ExitMode({ viewportid }, editorMode); + + // Expect a warning to be issued for the duplicate deactivation + EXPECT_EQ(errorHandler.GetExpectedWarningCount(), 1); + + // Expect the mode to still be inactive for the viewport + const auto* viewportEditorModeState = m_viewportEditorModeStteTracker.GetEditorModeState({ viewportid }); + EXPECT_TRUE(m_viewportEditorModeStteTracker.IsViewportStateBeingTracked({ viewportid })); + EXPECT_NE(viewportEditorModeState, nullptr); + EXPECT_FALSE(viewportEditorModeState->IsModeActive(editorMode)); + } + } + + TEST_F( + ViewportEditorModePublisherTestFixture, + EnteringViewportEditorModeStateForExistingIdPublishesOnViewportEditorModeEnterEventForAllSubscribers) + { + // Given a set of subscribers tracking the editor modes for their exclusive viewport + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + // Expect each subscriber to have received no editor mode state changes + EXPECT_EQ(m_editorModeHandlers[mode]->GetEditorModes().size(), 0); + } + + // When each editor mode is activated by the state tracker for a specific viewport + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + const ViewportId viewportId = mode; + const ViewportEditorMode editorMode = static_cast(mode); + m_viewportEditorModeStteTracker.EnterMode({ mode }, editorMode); + } + + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + // Expect only the subscribers of each viewport to have received the editor mode activated event + const ViewportEditorMode editorMode = static_cast(mode); + const auto& editorModes = m_editorModeHandlers[mode]->GetEditorModes(); + EXPECT_EQ(editorModes.size(), 1); + EXPECT_EQ(editorModes.count(editorMode), 1); + const auto& expectedEditorModeSet = editorModes.find(editorMode); + EXPECT_NE(expectedEditorModeSet, editorModes.end()); + EXPECT_TRUE(expectedEditorModeSet->second.m_onEnter); + EXPECT_FALSE(expectedEditorModeSet->second.m_onLeave); + } + } + + TEST_F( + ViewportEditorModePublisherTestFixture, + ExitingViewportEditorModeStateForExistingIdPublishesOnViewportEditorModeExitEventForAllSubscribers) + { + // Given a set of subscribers tracking the editor modes for their exclusive viewport + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + EXPECT_EQ(m_editorModeHandlers[mode]->GetEditorModes().size(), 0); + } + + // When each editor mode is activated deactivated by the state tracker for a specific viewport + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + const ViewportId viewportId = mode; + const ViewportEditorMode editorMode = static_cast(mode); + m_viewportEditorModeStteTracker.EnterMode({ mode }, editorMode); + m_viewportEditorModeStteTracker.ExitMode({ mode }, editorMode); + } + + for (auto mode = 0; mode < ViewportEditorModeState::NumEditorModes; mode++) + { + // Expect only the subscribers of each viewport to have received the editor mode activated and deactivated event + const ViewportEditorMode editorMode = static_cast(mode); + const auto& editorModes = m_editorModeHandlers[mode]->GetEditorModes(); + EXPECT_EQ(editorModes.size(), 1); + EXPECT_EQ(editorModes.count(editorMode), 1); + const auto& expectedEditorModeSet = editorModes.find(editorMode); + EXPECT_NE(expectedEditorModeSet, editorModes.end()); + EXPECT_TRUE(expectedEditorModeSet->second.m_onEnter); + EXPECT_TRUE(expectedEditorModeSet->second.m_onLeave); + } + } +} diff --git a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake index 5ff41d9e6c..764afce266 100644 --- a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake +++ b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake @@ -110,6 +110,7 @@ set(FILES UI/EntityPropertyEditorTests.cpp UndoStack.cpp Viewport/ClusterTests.cpp + Viewport/ViewportEditorModeTests.cpp Viewport/ViewportScreenTests.cpp Viewport/ViewportUiClusterTests.cpp Viewport/ViewportUiDisplayTests.cpp