diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 9ac28cfd91..d0318ee181 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -893,37 +892,34 @@ namespace AzToolsFramework prevModifiers = action.m_modifiers; } - static void HandleAccents( - const bool hasSelectedEntities, - const AZ::EntityId entityIdUnderCursor, - const bool ctrlHeld, - AZ::EntityId& hoveredEntityId, + void HandleAccents( + const AZ::EntityId currentEntityIdUnderCursor, + AZ::EntityId& hoveredEntityIdUnderCursor, + const HandleAccentsContext& handleAccentsContext, const ViewportInteraction::MouseButtons mouseButtons, - const bool usingBoxSelect) + const AZStd::function& setEntityAccentedFn) { - AZ_PROFILE_FUNCTION(AzToolsFramework); - const bool invalidMouseButtonHeld = mouseButtons.Middle() || mouseButtons.Right(); + const bool hasSelectedEntities = handleAccentsContext.m_hasSelectedEntities; + const bool ctrlHeld = handleAccentsContext.m_ctrlHeld; + const bool boxSelect = handleAccentsContext.m_usingBoxSelect; + const bool stickySelect = handleAccentsContext.m_usingStickySelect; + const bool canSelect = stickySelect ? !hasSelectedEntities || ctrlHeld : true; - if ((hoveredEntityId.IsValid() && hoveredEntityId != entityIdUnderCursor) || - (hasSelectedEntities && !ctrlHeld && hoveredEntityId.IsValid()) || invalidMouseButtonHeld) + const bool removePreviousAccent = + (currentEntityIdUnderCursor != hoveredEntityIdUnderCursor && hoveredEntityIdUnderCursor.IsValid()) || invalidMouseButtonHeld; + const bool addNextAccent = currentEntityIdUnderCursor.IsValid() && canSelect && !invalidMouseButtonHeld && !boxSelect; + + if (removePreviousAccent) { - if (hoveredEntityId.IsValid()) - { - ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetEntityHighlighted, hoveredEntityId, false); - - hoveredEntityId.SetInvalid(); - } + setEntityAccentedFn(hoveredEntityIdUnderCursor, false); + hoveredEntityIdUnderCursor.SetInvalid(); } - if (!invalidMouseButtonHeld && !usingBoxSelect && (!hasSelectedEntities || ctrlHeld)) + if (addNextAccent) { - if (entityIdUnderCursor.IsValid()) - { - ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetEntityHighlighted, entityIdUnderCursor, true); - - hoveredEntityId = entityIdUnderCursor; - } + setEntityAccentedFn(currentEntityIdUnderCursor, true); + hoveredEntityIdUnderCursor = currentEntityIdUnderCursor; } } @@ -1781,7 +1777,7 @@ namespace AzToolsFramework const AzFramework::CameraState cameraState = GetCameraState(viewportId); const auto cursorEntityIdQuery = m_editorHelpers->FindEntityIdUnderCursor(cameraState, mouseInteraction); - m_cachedEntityIdUnderCursor = cursorEntityIdQuery.ContainerAncestorEntityId(); + m_currentEntityIdUnderCursor = cursorEntityIdQuery.ContainerAncestorEntityId(); const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction); m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates); @@ -1802,7 +1798,7 @@ namespace AzToolsFramework mouseInteraction.m_mouseInteraction, AZ::Aabb::CreateFromMinMax(boxPosition - scaledSize, boxPosition + scaledSize))) { - m_cachedEntityIdUnderCursor = entityId; + m_currentEntityIdUnderCursor = entityId; } } } @@ -1822,7 +1818,7 @@ namespace AzToolsFramework return true; } - const AZ::EntityId entityIdUnderCursor = m_cachedEntityIdUnderCursor; + const AZ::EntityId entityIdUnderCursor = m_currentEntityIdUnderCursor; if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick && mouseInteraction.m_mouseInteraction.m_mouseButtons.Left()) @@ -3341,9 +3337,23 @@ namespace AzToolsFramework m_cursorState.Update(); + bool stickySelect = false; + ViewportInteraction::ViewportSettingsRequestBus::EventResult( + stickySelect, viewportInfo.m_viewportId, &ViewportInteraction::ViewportSettingsRequestBus::Events::StickySelectEnabled); + + HandleAccentsContext handleAccentsContext; + handleAccentsContext.m_ctrlHeld = keyboardModifiers.Ctrl(); + handleAccentsContext.m_hasSelectedEntities = !m_selectedEntityIds.empty(); + handleAccentsContext.m_usingBoxSelect = m_boxSelect.Active(); + handleAccentsContext.m_usingStickySelect = stickySelect; + HandleAccents( - !m_selectedEntityIds.empty(), m_cachedEntityIdUnderCursor, keyboardModifiers.Ctrl(), m_hoveredEntityId, - ViewportInteraction::BuildMouseButtons(QGuiApplication::mouseButtons()), m_boxSelect.Active()); + m_currentEntityIdUnderCursor, m_hoveredEntityId, handleAccentsContext, + ViewportInteraction::BuildMouseButtons(QGuiApplication::mouseButtons()), + [](const AZ::EntityId entityId, bool highlighted) + { + ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetEntityHighlighted, entityId, highlighted); + }); const ReferenceFrame referenceFrame = m_spaceCluster.m_spaceLock.value_or(ReferenceFrameFromModifiers(keyboardModifiers)); @@ -3589,7 +3599,8 @@ namespace AzToolsFramework if (auto prefabFocusPublicInterface = AZ::Interface::Get()) { AzFramework::EntityContextId editorEntityContextId = GetEntityContextId(); - if (AZ::EntityId focusRoot = prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId); focusRoot.IsValid()) + if (AZ::EntityId focusRoot = prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId); + focusRoot.IsValid()) { m_selectedEntityIds.erase(focusRoot); } @@ -3721,7 +3732,6 @@ namespace AzToolsFramework break; case ViewportEditorMode::Focus: { - ViewportUi::ViewportUiRequestBus::Event( ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::RemoveViewportBorder); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 1e1cc6d2e1..10f83db739 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -317,7 +317,7 @@ namespace AzToolsFramework void SetAllViewportUiVisible(bool visible); AZ::EntityId m_hoveredEntityId; //!< What EntityId is the mouse currently hovering over (if any). - AZ::EntityId m_cachedEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display. + AZ::EntityId m_currentEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display. AZ::EntityId m_editorCameraComponentEntityId; //!< The EditorCameraComponent EntityId if it is set. EntityIdSet m_selectedEntityIds; //!< Represents the current entities in the selection. @@ -357,6 +357,23 @@ namespace AzToolsFramework bool m_viewportUiVisible = true; //!< Used to hide/show the viewport ui elements. }; + //! Bundles viewport state that impacts how accents are added/removed in HandleAccents. + struct HandleAccentsContext + { + bool m_hasSelectedEntities; + bool m_ctrlHeld; + bool m_usingBoxSelect; + bool m_usingStickySelect; + }; + + //! Updates whether accents (icon highlights) are added/removed for a given entity based on the cursor position. + void HandleAccents( + AZ::EntityId currentEntityIdUnderCursor, + AZ::EntityId& hoveredEntityIdUnderCursor, + const HandleAccentsContext& handleAccentsContext, + ViewportInteraction::MouseButtons mouseButtons, + const AZStd::function& setEntityAccentedFn); + //! The ETCS (EntityTransformComponentSelection) namespace contains functions and data used exclusively by //! the EditorTransformComponentSelection type. Functions in this namespace are exposed to facilitate testing //! and should not be used outside of EditorTransformComponentSelection or EditorTransformComponentSelectionTests. diff --git a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp index 4f4c3eb456..f4077e992b 100644 --- a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp @@ -2781,4 +2781,196 @@ namespace UnitTest /////////////////////////////////////////////////////////////////////////////////////////////////////////////// } + TEST(HandleAccents, CurrentValidEntityIdBecomesHoveredWithNoSelectionAndUnstickySelect) + { + namespace azvi = AzToolsFramework::ViewportInteraction; + + const AZ::EntityId currentEntityId = AZ::EntityId(12345); + AZ::EntityId hoveredEntityEntityId; + + AzToolsFramework::HandleAccentsContext handleAccentsContext; + handleAccentsContext.m_ctrlHeld = false; + handleAccentsContext.m_hasSelectedEntities = false; + handleAccentsContext.m_usingBoxSelect = false; + handleAccentsContext.m_usingStickySelect = false; + + bool currentEntityIdAccentAdded = false; + AzToolsFramework::HandleAccents( + currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None), + [¤tEntityIdAccentAdded, currentEntityId](const AZ::EntityId entityId, const bool accent) + { + if (entityId == currentEntityId && accent) + { + currentEntityIdAccentAdded = true; + } + }); + + using ::testing::Eq; + using ::testing::IsTrue; + EXPECT_THAT(currentEntityId, Eq(hoveredEntityEntityId)); + EXPECT_THAT(currentEntityIdAccentAdded, IsTrue()); + } + + TEST(HandleAccents, CurrentValidEntityIdBecomesHoveredWithSelectionAndUnstickySelect) + { + namespace azvi = AzToolsFramework::ViewportInteraction; + + const AZ::EntityId currentEntityId = AZ::EntityId(12345); + AZ::EntityId hoveredEntityEntityId; + + AzToolsFramework::HandleAccentsContext handleAccentsContext; + handleAccentsContext.m_ctrlHeld = false; + handleAccentsContext.m_hasSelectedEntities = true; + handleAccentsContext.m_usingBoxSelect = false; + handleAccentsContext.m_usingStickySelect = false; + + bool currentEntityIdAccentAdded = false; + AzToolsFramework::HandleAccents( + currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None), + [¤tEntityIdAccentAdded, currentEntityId](const AZ::EntityId entityId, const bool accent) + { + if (entityId == currentEntityId && accent) + { + currentEntityIdAccentAdded = true; + } + }); + + using ::testing::Eq; + using ::testing::IsTrue; + EXPECT_THAT(currentEntityId, Eq(hoveredEntityEntityId)); + EXPECT_THAT(currentEntityIdAccentAdded, IsTrue()); + } + + TEST(HandleAccents, CurrentValidEntityIdDoesNotBecomeHoveredWithSelectionUnstickySelectAndInvalidButton) + { + namespace azvi = AzToolsFramework::ViewportInteraction; + + const AZ::EntityId currentEntityId = AZ::EntityId(12345); + AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321); + + AzToolsFramework::HandleAccentsContext handleAccentsContext; + handleAccentsContext.m_ctrlHeld = false; + handleAccentsContext.m_hasSelectedEntities = false; + handleAccentsContext.m_usingBoxSelect = false; + handleAccentsContext.m_usingStickySelect = false; + + bool hoveredEntityIdAccentRemoved = false; + AzToolsFramework::HandleAccents( + currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::Middle), + [&hoveredEntityIdAccentRemoved, hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent) + { + if (entityId == hoveredEntityEntityId && !accent) + { + hoveredEntityIdAccentRemoved = true; + } + }); + + using ::testing::Eq; + using ::testing::IsFalse; + using ::testing::IsTrue; + EXPECT_THAT(hoveredEntityEntityId.IsValid(), IsFalse()); + EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue()); + } + + TEST(HandleAccents, CurrentValidEntityIdDoesNotBecomeHoveredWithSelectionUnstickySelectAndDoingBoxSelect) + { + namespace azvi = AzToolsFramework::ViewportInteraction; + + const AZ::EntityId currentEntityId = AZ::EntityId(12345); + AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321); + + AzToolsFramework::HandleAccentsContext handleAccentsContext; + handleAccentsContext.m_ctrlHeld = false; + handleAccentsContext.m_hasSelectedEntities = false; + handleAccentsContext.m_usingBoxSelect = true; + handleAccentsContext.m_usingStickySelect = false; + + bool hoveredEntityIdAccentRemoved = false; + AzToolsFramework::HandleAccents( + currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None), + [&hoveredEntityIdAccentRemoved, hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent) + { + if (entityId == hoveredEntityEntityId && !accent) + { + hoveredEntityIdAccentRemoved = true; + } + }); + + using ::testing::Eq; + using ::testing::IsFalse; + using ::testing::IsTrue; + EXPECT_THAT(hoveredEntityEntityId.IsValid(), IsFalse()); + EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue()); + } + + // mimics the mouse moving off of hovered entity onto a new entity with sticky select enabled + TEST(HandleAccents, CurrentValidEntityIdDoesNotBecomeHoveredWithSelectionAndStickySelect) + { + namespace azvi = AzToolsFramework::ViewportInteraction; + + const AZ::EntityId currentEntityId = AZ::EntityId(12345); + AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321); + + AzToolsFramework::HandleAccentsContext handleAccentsContext; + handleAccentsContext.m_ctrlHeld = false; + handleAccentsContext.m_hasSelectedEntities = true; + handleAccentsContext.m_usingBoxSelect = false; + handleAccentsContext.m_usingStickySelect = true; + + bool hoveredEntityIdAccentRemoved = false; + AzToolsFramework::HandleAccents( + currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None), + [&hoveredEntityIdAccentRemoved, hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent) + { + if (entityId == hoveredEntityEntityId && !accent) + { + hoveredEntityIdAccentRemoved = true; + } + }); + + using ::testing::Eq; + using ::testing::IsFalse; + using ::testing::IsTrue; + EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue()); + EXPECT_THAT(hoveredEntityEntityId.IsValid(), IsFalse()); + } + + TEST(HandleAccents, CurrentValidEntityIdDoesBecomeHoveredWithSelectionAndStickySelectAndCtrl) + { + namespace azvi = AzToolsFramework::ViewportInteraction; + + const AZ::EntityId currentEntityId = AZ::EntityId(12345); + AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321); + + AzToolsFramework::HandleAccentsContext handleAccentsContext; + handleAccentsContext.m_ctrlHeld = true; + handleAccentsContext.m_hasSelectedEntities = true; + handleAccentsContext.m_usingBoxSelect = false; + handleAccentsContext.m_usingStickySelect = true; + + bool currentEntityIdAccentAdded = false; + bool hoveredEntityIdAccentRemoved = false; + AzToolsFramework::HandleAccents( + currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None), + [&hoveredEntityIdAccentRemoved, ¤tEntityIdAccentAdded, currentEntityId, + hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent) + { + if (entityId == currentEntityId && accent) + { + currentEntityIdAccentAdded = true; + } + + if (entityId == hoveredEntityEntityId && !accent) + { + hoveredEntityIdAccentRemoved = true; + } + }); + + using ::testing::Eq; + using ::testing::IsFalse; + using ::testing::IsTrue; + EXPECT_THAT(currentEntityIdAccentAdded, IsTrue()); + EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue()); + EXPECT_THAT(hoveredEntityEntityId, Eq(AZ::EntityId(12345))); + } } // namespace UnitTest