diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeTrackerInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeTrackerInterface.h new file mode 100644 index 0000000000..bcc8afbe6a --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeTrackerInterface.h @@ -0,0 +1,43 @@ +/* + * 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 +{ + //! The AZ::Interface of the central editor mode tracker for all viewports. + class ViewportEditorModeTrackerInterface + { + public: + AZ_RTTI(ViewportEditorModeTrackerInterface, "{7D72A4F7-2147-4ED9-A315-E456A3BE3CF6}"); + + virtual ~ViewportEditorModeTrackerInterface() = default; + + //! Activates the specified editor mode for the specified viewport. + virtual AZ::Outcome ActivateMode( + const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) = 0; + + //! Deactivates the specified editor mode for the specified viewport. + virtual AZ::Outcome DeactivateMode( + const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) = 0; + + //! Attempts to retrieve the editor mode state for the specified viewport, otherwise returns nullptr. + virtual const ViewportEditorModesInterface* GetViewportEditorModes(const ViewportEditorModeInfo& viewportEditorModeInfo) const = 0; + + //! Returns the number of viewports currently being tracked. + virtual size_t GetTrackedViewportCount() const = 0; + + //! Returns true if the specified viewport is being tracked, otherwise false. + virtual bool IsViewportModeTracked(const ViewportEditorModeInfo& viewportEditorModeInfo) const = 0; + }; +} // namespace AzToolsFramework + diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h new file mode 100644 index 0000000000..42a1cb0113 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h @@ -0,0 +1,66 @@ +/* + * 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 mode. + 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 modes of a given viewport. + class ViewportEditorModesInterface + { + public: + virtual ~ViewportEditorModesInterface() = 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 activation of the specified editor mode. + virtual void OnEditorModeActivated([[maybe_unused]] const ViewportEditorModesInterface& editorModeState, [[maybe_unused]] ViewportEditorMode mode) + { + } + + //! Notifies subscribers of the a given viewport to the deactivation of the specified editor mode. + virtual void OnEditorModeDeactivated([[maybe_unused]] const ViewportEditorModesInterface& editorModeState, [[maybe_unused]] ViewportEditorMode mode) + { + } + }; + using ViewportEditorModeNotificationsBus = AZ::EBus; +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeTracker.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeTracker.cpp new file mode 100644 index 0000000000..4adddb02e1 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeTracker.cpp @@ -0,0 +1,149 @@ +/* + * 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 +{ + AZ::Outcome ViewportEditorModes::ActivateMode(ViewportEditorMode mode) + { + if (const AZ::u32 modeIndex = static_cast(mode); + modeIndex < NumEditorModes) + { + m_editorModes[modeIndex] = true; + return AZ::Success(); + } + else + { + return AZ::Failure( + AZStd::string::format("Cannot activate mode %u, mode is not recognized", modeIndex)); + } + } + + AZ::Outcome ViewportEditorModes::DeactivateMode(ViewportEditorMode mode) + { + if (const AZ::u32 modeIndex = static_cast(mode); modeIndex < NumEditorModes) + { + m_editorModes[modeIndex] = false; + return AZ::Success(); + } + else + { + return AZ::Failure( + AZStd::string::format("Cannot deactivate mode %u, mode is not recognized", modeIndex)); + } + } + + bool ViewportEditorModes::IsModeActive(ViewportEditorMode mode) const + { + return m_editorModes[static_cast(mode)]; + } + + void ViewportEditorModeTracker::RegisterInterface() + { + if (AZ::Interface::Get() == nullptr) + { + AZ::Interface::Register(this); + } + } + + void ViewportEditorModeTracker::UnregisterInterface() + { + if (AZ::Interface::Get() != nullptr) + { + AZ::Interface::Unregister(this); + } + } + + AZ::Outcome ViewportEditorModeTracker::ActivateMode( + const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) + { + auto& editorModes = m_viewportEditorModesMap[viewportEditorModeInfo.m_id]; + if (editorModes.IsModeActive(mode)) + { + return AZ::Failure(AZStd::string::format( + "Duplicate call to ActivateMode for mode '%u' on id '%i'", static_cast(mode), viewportEditorModeInfo.m_id)); + } + + if (const auto result = editorModes.ActivateMode(mode); + !result.IsSuccess()) + { + return result; + } + + ViewportEditorModeNotificationsBus::Event( + viewportEditorModeInfo.m_id, &ViewportEditorModeNotificationsBus::Events::OnEditorModeActivated, editorModes, mode); + + return AZ::Success(); + } + + AZ::Outcome ViewportEditorModeTracker::DeactivateMode( + const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) + { + ViewportEditorModes* editorModes = nullptr; + bool modeWasActive = true; + if (m_viewportEditorModesMap.count(viewportEditorModeInfo.m_id)) + { + editorModes = &m_viewportEditorModesMap.at(viewportEditorModeInfo.m_id); + if (!editorModes->IsModeActive(mode)) + { + return AZ::Failure(AZStd::string::format( + "Duplicate call to DeactivateMode for mode '%u' on id '%i'", static_cast(mode), viewportEditorModeInfo.m_id)); + } + } + else + { + modeWasActive = false; + editorModes = &m_viewportEditorModesMap[viewportEditorModeInfo.m_id]; + } + + if(const auto result = editorModes->DeactivateMode(mode); + !result.IsSuccess()) + { + return result; + } + + ViewportEditorModeNotificationsBus::Event( + viewportEditorModeInfo.m_id, &ViewportEditorModeNotificationsBus::Events::OnEditorModeDeactivated, *editorModes, mode); + + if (modeWasActive) + { + return AZ::Success(); + } + else + { + return AZ::Failure(AZStd::string::format( + "Call to DeactivateMode for mode '%u' on id '%i' without precursor call to ActivateMode", static_cast(mode), + viewportEditorModeInfo.m_id)); + } + } + + const ViewportEditorModesInterface* ViewportEditorModeTracker::GetViewportEditorModes(const ViewportEditorModeInfo& viewportEditorModeInfo) const + { + if (auto editorModes = m_viewportEditorModesMap.find(viewportEditorModeInfo.m_id); + editorModes != m_viewportEditorModesMap.end()) + { + return &editorModes->second; + } + else + { + return nullptr; + } + } + + size_t ViewportEditorModeTracker::GetTrackedViewportCount() const + { + return m_viewportEditorModesMap.size(); + } + + bool ViewportEditorModeTracker::IsViewportModeTracked(const ViewportEditorModeInfo& viewportEditorModeInfo) const + { + return m_viewportEditorModesMap.count(viewportEditorModeInfo.m_id) > 0; + } +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeTracker.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeTracker.h new file mode 100644 index 0000000000..6ae68b39b2 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/ViewportEditorModeTracker.h @@ -0,0 +1,61 @@ +/* + * 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 ViewportEditorModes + : public ViewportEditorModesInterface + { + public: + //! The number of currently supported viewport editor modes. + static constexpr AZ::u8 NumEditorModes = 4; + + //! Sets the specified mode as active. + AZ::Outcome ActivateMode(ViewportEditorMode mode); + + // Sets the specified mode as inactive. + AZ::Outcome DeactivateMode(ViewportEditorMode mode); + + // ViewportEditorModesInterface ... + 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 ViewportEditorModeTracker + : public ViewportEditorModeTrackerInterface + { + public: + //! Registers this object with the AZ::Interface. + void RegisterInterface(); + + //! Unregisters this object with the AZ::Interface. + void UnregisterInterface(); + + // ViewportEditorModeTrackerInterface overrides ... + AZ::Outcome ActivateMode(const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) override; + AZ::Outcome DeactivateMode(const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) override; + const ViewportEditorModesInterface* GetViewportEditorModes(const ViewportEditorModeInfo& viewportEditorModeInfo) const override; + size_t GetTrackedViewportCount() const override; + bool IsViewportModeTracked(const ViewportEditorModeInfo& viewportEditorModeInfo) const override; + + private: + using ViewportEditorModesMap = AZStd::unordered_map; + ViewportEditorModesMap m_viewportEditorModesMap; //!< 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..3932190d8e 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/ViewportEditorModeTrackerNotificationBus.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/ViewportEditorModeTrackerInterface.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/ViewportEditorModeTracker.cpp + ViewportSelection/ViewportEditorModeTracker.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..3954ef6dc6 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp @@ -0,0 +1,498 @@ +/* + * 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 ViewportEditorModes = AzToolsFramework::ViewportEditorModes; + using ViewportEditorModeTracker = AzToolsFramework::ViewportEditorModeTracker; + using ViewportEditorModeInfo = AzToolsFramework::ViewportEditorModeInfo; + using ViewportId = ViewportEditorModeInfo::IdType; + using ViewportEditorModesInterface = AzToolsFramework::ViewportEditorModesInterface; + + void ActivateModeAndExpectSuccess(ViewportEditorModes& editorModeState, ViewportEditorMode mode) + { + const auto result = editorModeState.ActivateMode(mode); + EXPECT_TRUE(result.IsSuccess()); + } + + void DeactivateModeAndExpectSuccess(ViewportEditorModes& editorModeState, ViewportEditorMode mode) + { + const auto result = editorModeState.DeactivateMode(mode); + EXPECT_TRUE(result.IsSuccess()); + } + + void SetAllModesActive(ViewportEditorModes& editorModeState) + { + for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++) + { + ActivateModeAndExpectSuccess(editorModeState, static_cast(mode)); + } + } + + void SetAllModesInactive(ViewportEditorModes& editorModeState) + { + for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++) + { + DeactivateModeAndExpectSuccess(editorModeState, static_cast(mode)); + } + } + + // Fixture for testing editor mode states + class ViewportEditorModesTestsFixture + : public ::testing::Test + { + public: + ViewportEditorModes m_editorModes; + }; + + // Fixture for testing editor mode states with parameterized test arguments + class ViewportEditorModesTestsFixtureWithParams + : public ViewportEditorModesTestsFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + m_selectedEditorMode = GetParam(); + } + + ViewportEditorMode m_selectedEditorMode; + }; + + // Fixture for testing the viewport editor mode state tracker + class ViewportEditorModeTrackerTestFixture + : public ToolsApplicationFixture + { + public: + ViewportEditorModeTracker m_viewportEditorModeTracker; + }; + + // 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_onExit = 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 OnEditorModeActivated([[maybe_unused]]const ViewportEditorModesInterface& editorModeState, ViewportEditorMode mode) override + { + m_editorModes[mode].m_onEnter = true; + } + + virtual void OnEditorModeDeactivated([[maybe_unused]] const ViewportEditorModesInterface& editorModeState, ViewportEditorMode mode) override + { + m_editorModes[mode].m_onExit = true; + } + + private: + ViewportId m_viewportSubscription; + EditModeTracker m_editorModes; + + }; + + // Fixture for testing viewport editor mode notifications publishing + class ViewportEditorModePublisherTestFixture + : public ViewportEditorModeTrackerTestFixture + { + public: + + void SetUpEditorFixtureImpl() override + { + for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++) + { + m_editorModeHandlers[mode] = AZStd::make_unique(mode); + } + } + + void TearDownEditorFixtureImpl() override + { + for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++) + { + m_editorModeHandlers[mode].reset(); + } + } + + AZStd::array, ViewportEditorModes::NumEditorModes> m_editorModeHandlers; + }; + + TEST_F(ViewportEditorModesTestsFixture, NumberOfEditorModesIsEqualTo4) + { + EXPECT_EQ(ViewportEditorModes::NumEditorModes, 4); + } + + TEST_F(ViewportEditorModesTestsFixture, InitialEditorModeStateHasAllInactiveModes) + { + for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++) + { + EXPECT_FALSE(m_editorModes.IsModeActive(static_cast(mode))); + } + } + + TEST_P(ViewportEditorModesTestsFixtureWithParams, SettingModeActiveActivatesOnlyThatMode) + { + ActivateModeAndExpectSuccess(m_editorModes, m_selectedEditorMode); + + for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++) + { + const auto editorMode = static_cast(mode); + if (editorMode == m_selectedEditorMode) + { + EXPECT_TRUE(m_editorModes.IsModeActive(static_cast(editorMode))); + } + else + { + EXPECT_FALSE(m_editorModes.IsModeActive(static_cast(editorMode))); + } + } + } + + TEST_P(ViewportEditorModesTestsFixtureWithParams, SettingModeInactiveInactivatesOnlyThatMode) + { + SetAllModesActive(m_editorModes); + DeactivateModeAndExpectSuccess(m_editorModes, m_selectedEditorMode); + + for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++) + { + const auto editorMode = static_cast(mode); + if (editorMode == m_selectedEditorMode) + { + EXPECT_FALSE(m_editorModes.IsModeActive(editorMode)); + } + else + { + EXPECT_TRUE(m_editorModes.IsModeActive(editorMode)); + } + } + } + + TEST_P(ViewportEditorModesTestsFixtureWithParams, SettingMultipleModesActiveActivatesAllThoseModesNonMutuallyExclusively) + { + for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes - 1; mode++) + { + // Given only the selected mode active + SetAllModesInactive(m_editorModes); + { + ActivateModeAndExpectSuccess(m_editorModes, m_selectedEditorMode); + } + + const auto editorMode = static_cast(mode); + if (editorMode == m_selectedEditorMode) + { + continue; + } + + // When other modes are activated + ActivateModeAndExpectSuccess(m_editorModes, editorMode); + + for (auto expectedMode = 0; expectedMode < ViewportEditorModes::NumEditorModes; expectedMode++) + { + const auto expectedEditorMode = static_cast(expectedMode); + if (expectedEditorMode == editorMode || expectedEditorMode == m_selectedEditorMode) + { + // Expect the activated modes to be active + EXPECT_TRUE(m_editorModes.IsModeActive(expectedEditorMode)); + } + else + { + // Expect the modes not active to be inactive + EXPECT_FALSE(m_editorModes.IsModeActive(expectedEditorMode)); + } + } + } + } + + TEST_P(ViewportEditorModesTestsFixtureWithParams, SettingMultipleModesInactiveInactivatesAllThoseModesNonMutuallyExclusively) + { + for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes - 1; mode++) + { + // Given only the selected mode inactive + SetAllModesActive(m_editorModes); + DeactivateModeAndExpectSuccess(m_editorModes, m_selectedEditorMode); + + const auto editorMode = static_cast(mode); + if (editorMode == m_selectedEditorMode) + { + continue; + } + + // When other modes are deactivated + DeactivateModeAndExpectSuccess(m_editorModes, editorMode); + + for (auto expectedMode = 0; expectedMode < ViewportEditorModes::NumEditorModes; expectedMode++) + { + const auto expectedEditorMode = static_cast(expectedMode); + if (expectedEditorMode == editorMode || expectedEditorMode == m_selectedEditorMode) + { + // Expect the deactivated modes to be inactive + EXPECT_FALSE(m_editorModes.IsModeActive(expectedEditorMode)); + } + else + { + // Expects the modes not deactivated to still be active + EXPECT_TRUE(m_editorModes.IsModeActive(expectedEditorMode)); + } + } + } + } + + INSTANTIATE_TEST_CASE_P( + AllEditorModes, + ViewportEditorModesTestsFixtureWithParams, + ::testing::Values( + AzToolsFramework::ViewportEditorMode::Default, + AzToolsFramework::ViewportEditorMode::Component, + AzToolsFramework::ViewportEditorMode::Focus, + AzToolsFramework::ViewportEditorMode::Pick)); + + TEST_F(ViewportEditorModesTestsFixture, SettingOutOfBoundsModeActiveReturnsError) + { + const auto result = m_editorModes.ActivateMode(static_cast(ViewportEditorModes::NumEditorModes)); + EXPECT_FALSE(result.IsSuccess()); + } + + TEST_F(ViewportEditorModesTestsFixture, SettingOutOfBoundsModeInactiveReturnsError) + { + const auto result = m_editorModes.DeactivateMode(static_cast(ViewportEditorModes::NumEditorModes)); + EXPECT_FALSE(result.IsSuccess()); + } + + TEST_F(ViewportEditorModeTrackerTestFixture, InitialCentralStateTrackerHasNoViewportEditorModess) + { + EXPECT_EQ(m_viewportEditorModeTracker.GetTrackedViewportCount(), 0); + } + + TEST_F(ViewportEditorModeTrackerTestFixture, RegisteringViewportEditorModeForNonExistentIdCreatesViewportEditorModesForThatId) + { + // Given a viewport not currently being tracked + const ViewportId viewportid = 0; + EXPECT_FALSE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + EXPECT_EQ(m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }), nullptr); + + // When a mode is activated for that viewport + const auto editorMode = ViewportEditorMode::Default; + m_viewportEditorModeTracker.ActivateMode({ viewportid }, editorMode); + const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }); + + // Expect that viewport to now be tracked + EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + EXPECT_NE(viewportEditorModeState, nullptr); + + // Expect the mode for that viewport to be active + EXPECT_TRUE(viewportEditorModeState->IsModeActive(editorMode)); + } + + TEST_F(ViewportEditorModeTrackerTestFixture, UnregisteringViewportEditorModeForNonExistentIdCreatesViewportEditorModesForThatIdButReturnsError) + { + // Given a viewport not currently being tracked + const ViewportId viewportid = 0; + EXPECT_FALSE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + EXPECT_EQ(m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }), nullptr); + + // When a mode is deactivated for that viewport + const auto editorMode = ViewportEditorMode::Default; + const auto expectedErrorMsg = AZStd::string::format( + "Call to DeactivateMode for mode '%u' on id '%i' without precursor call to ActivateMode", static_cast(editorMode), viewportid); + const auto result = m_viewportEditorModeTracker.DeactivateMode({ viewportid }, editorMode); + + // Expect an error due to no precursor activation of that mode + EXPECT_FALSE(result.IsSuccess()); + EXPECT_EQ(result.GetError(), expectedErrorMsg); + + // Expect that viewport to now be tracked + const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }); + EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + + // Expect the mode for that viewport to be inactive + EXPECT_NE(viewportEditorModeState, nullptr); + EXPECT_FALSE(viewportEditorModeState->IsModeActive(editorMode)); + } + + TEST_F(ViewportEditorModeTrackerTestFixture, GettingNonExistentViewportEditorModesForIdReturnsNull) + { + const ViewportId viewportid = 0; + EXPECT_FALSE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + EXPECT_EQ(m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }), nullptr); + } + + TEST_F(ViewportEditorModeTrackerTestFixture, RegisteringViewportEditorModesForExistingIdInThatStateReturnsError) + { + // Given a viewport not currently tracked + const ViewportId viewportid = 0; + EXPECT_FALSE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + EXPECT_EQ(m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }), nullptr); + + const auto editorMode = ViewportEditorMode::Default; + { + // When the mode is activated for the viewport + const auto result = m_viewportEditorModeTracker.ActivateMode({ viewportid }, editorMode); + + // Expect no error as there is no duplicate activation + EXPECT_TRUE(result.IsSuccess()); + + // Expect the mode to be active for the viewport + const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }); + EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + EXPECT_NE(viewportEditorModeState, nullptr); + EXPECT_TRUE(viewportEditorModeState->IsModeActive(editorMode)); + } + { + // When the mode is activated again for the viewport + const auto result = m_viewportEditorModeTracker.ActivateMode({ viewportid }, editorMode); + + // Expect an error for the duplicate activation + const auto expectedErrorMsg = AZStd::string::format( + "Duplicate call to ActivateMode for mode '%u' on id '%i'", static_cast(editorMode), viewportid); + EXPECT_FALSE(result.IsSuccess()); + EXPECT_EQ(result.GetError(), expectedErrorMsg); + + // Expect the mode to still be active for the viewport + const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }); + EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + EXPECT_NE(viewportEditorModeState, nullptr); + EXPECT_TRUE(viewportEditorModeState->IsModeActive(editorMode)); + } + } + + TEST_F(ViewportEditorModeTrackerTestFixture, UnregisteringViewportEditorModesForExistingIdNotInThatStateReturnssError) + { + // Given a viewport not currently tracked + const ViewportId viewportid = 0; + EXPECT_FALSE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + EXPECT_EQ(m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }), nullptr); + + const auto editorMode = ViewportEditorMode::Default; + { + // When the mode is activated and then deactivated for the viewport + m_viewportEditorModeTracker.ActivateMode({ viewportid }, editorMode); + const auto result = m_viewportEditorModeTracker.DeactivateMode({ viewportid }, editorMode); + + // Expect no error as there is no duplicate deactivation + EXPECT_TRUE(result.IsSuccess()); + + // Expect the mode to be inctive for the viewport + const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }); + EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + EXPECT_NE(viewportEditorModeState, nullptr); + EXPECT_FALSE(viewportEditorModeState->IsModeActive(editorMode)); + } + { + // When the mode is deactivated again for the viewport + const auto result = m_viewportEditorModeTracker.DeactivateMode({ viewportid }, editorMode); + + // Expect an error for the duplicate deactivation + const auto expectedErrorMsg = AZStd::string::format( + "Duplicate call to DeactivateMode for mode '%u' on id '%i'", static_cast(editorMode), viewportid); + EXPECT_FALSE(result.IsSuccess()); + EXPECT_EQ(result.GetError(), expectedErrorMsg); + + // Expect the mode to still be inactive for the viewport + const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }); + EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid })); + EXPECT_NE(viewportEditorModeState, nullptr); + EXPECT_FALSE(viewportEditorModeState->IsModeActive(editorMode)); + } + } + + TEST_F( + ViewportEditorModePublisherTestFixture, + RegisteringViewportEditorModesForExistingIdPublishesOnViewportEditorModeRegisterEventForAllSubscribers) + { + // Given a set of subscribers tracking the editor modes for their exclusive viewport + for (auto mode = 0; mode < ViewportEditorModes::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 < ViewportEditorModes::NumEditorModes; mode++) + { + const ViewportId viewportId = mode; + const ViewportEditorMode editorMode = static_cast(mode); + m_viewportEditorModeTracker.ActivateMode({ viewportId }, editorMode); + } + + for (auto mode = 0; mode < ViewportEditorModes::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_onExit); + } + } + + TEST_F( + ViewportEditorModePublisherTestFixture, + UnregisteringViewportEditorModesForExistingIdPublishesOnViewportEditorModeUnregisterEventForAllSubscribers) + { + // Given a set of subscribers tracking the editor modes for their exclusive viewport + for (auto mode = 0; mode < ViewportEditorModes::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 < ViewportEditorModes::NumEditorModes; mode++) + { + const ViewportId viewportId = mode; + const ViewportEditorMode editorMode = static_cast(mode); + m_viewportEditorModeTracker.ActivateMode({ viewportId }, editorMode); + m_viewportEditorModeTracker.DeactivateMode({ viewportId }, editorMode); + } + + for (auto mode = 0; mode < ViewportEditorModes::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_onExit); + } + } +} // namespace UnitTest 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