diff --git a/Assets/Editor/Icons/AssetBrowser/TreeBranch_First.svg b/Assets/Editor/Icons/AssetBrowser/TreeBranch_First.svg
new file mode 100644
index 0000000000..f1d36d3e41
--- /dev/null
+++ b/Assets/Editor/Icons/AssetBrowser/TreeBranch_First.svg
@@ -0,0 +1,7 @@
+
diff --git a/Assets/Editor/Icons/AssetBrowser/TreeBranch_Last.svg b/Assets/Editor/Icons/AssetBrowser/TreeBranch_Last.svg
new file mode 100644
index 0000000000..9fc9fe52c2
--- /dev/null
+++ b/Assets/Editor/Icons/AssetBrowser/TreeBranch_Last.svg
@@ -0,0 +1,5 @@
+
diff --git a/Assets/Editor/Icons/AssetBrowser/TreeBranch_Middle.svg b/Assets/Editor/Icons/AssetBrowser/TreeBranch_Middle.svg
new file mode 100644
index 0000000000..7a61db38e0
--- /dev/null
+++ b/Assets/Editor/Icons/AssetBrowser/TreeBranch_Middle.svg
@@ -0,0 +1,6 @@
+
diff --git a/Assets/Editor/Icons/AssetBrowser/TreeBranch_OneChild.svg b/Assets/Editor/Icons/AssetBrowser/TreeBranch_OneChild.svg
new file mode 100644
index 0000000000..c6fb977c52
--- /dev/null
+++ b/Assets/Editor/Icons/AssetBrowser/TreeBranch_OneChild.svg
@@ -0,0 +1,6 @@
+
diff --git a/Assets/Editor/Translation/scriptcanvas_en_us.ts b/Assets/Editor/Translation/scriptcanvas_en_us.ts
index 937f6a4d96..7b8361c879 100644
--- a/Assets/Editor/Translation/scriptcanvas_en_us.ts
+++ b/Assets/Editor/Translation/scriptcanvas_en_us.ts
@@ -8098,7 +8098,7 @@
COLOR_FROMVALUES_PARAM0_TOOLTIP
- The Red value of hte Color [0, 255]
+ The Red value of the Color [0.0-1.0]
COLOR_FROMVALUES_PARAM1_NAME
@@ -8107,7 +8107,7 @@
COLOR_FROMVALUES_PARAM1_TOOLTIP
- The Green value of the Color [0, 255]
+ The Green value of the Color [0.0-1.0]
COLOR_FROMVALUES_PARAM2_NAME
@@ -8116,7 +8116,7 @@
COLOR_FROMVALUES_PARAM2_TOOLTIP
- The Blue value of the Color [0, 255]
+ The Blue value of the Color [0.0-1.0]
COLOR_FROMVALUES_PARAM3_NAME
@@ -8125,7 +8125,7 @@
COLOR_FROMVALUES_PARAM3_TOOLTIP
- The Alpha value of the Color [0, 255]
+ The Alpha value of the Color [0.0-1.0]
diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py
index 0bc66f2614..4c5716d365 100644
--- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py
+++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py
@@ -38,24 +38,24 @@ class TestAtomEditorComponentsMain(object):
7. Exposure Control
8. Directional Light
9. DepthOfField
- 10. Decal (Atom)
+ 10. Decal
"""
cfg_args = [level]
expected_lines = [
- # Decal (Atom) Component
- "Decal (Atom) Entity successfully created",
- "Decal (Atom)_test: Component added to the entity: True",
- "Decal (Atom)_test: Component removed after UNDO: True",
- "Decal (Atom)_test: Component added after REDO: True",
- "Decal (Atom)_test: Entered game mode: True",
- "Decal (Atom)_test: Exit game mode: True",
- "Decal (Atom) Controller|Configuration|Material: SUCCESS",
- "Decal (Atom)_test: Entity is hidden: True",
- "Decal (Atom)_test: Entity is shown: True",
- "Decal (Atom)_test: Entity deleted: True",
- "Decal (Atom)_test: UNDO entity deletion works: True",
- "Decal (Atom)_test: REDO entity deletion works: True",
+ # Decal Component
+ "Decal Entity successfully created",
+ "Decal_test: Component added to the entity: True",
+ "Decal_test: Component removed after UNDO: True",
+ "Decal_test: Component added after REDO: True",
+ "Decal_test: Entered game mode: True",
+ "Decal_test: Exit game mode: True",
+ "Decal Controller|Configuration|Material: SUCCESS",
+ "Decal_test: Entity is hidden: True",
+ "Decal_test: Entity is shown: True",
+ "Decal_test: Entity deleted: True",
+ "Decal_test: UNDO entity deletion works: True",
+ "Decal_test: REDO entity deletion works: True",
# DepthOfField Component
"DepthOfField Entity successfully created",
"DepthOfField_test: Component added to the entity: True",
diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_AddedToEntity.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_AddedToEntity.py
index a2e950e1dc..602e7564b3 100644
--- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_AddedToEntity.py
+++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_AddedToEntity.py
@@ -184,7 +184,7 @@ def run():
material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", material_asset_path, math.Uuid(), False)
ComponentTests(
- "Decal (Atom)", lambda entity_obj: verify_set_property(
+ "Decal", lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Material", material_asset))
# Directional Light Component
diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py
index d68e1f5844..fa02b75fe2 100644
--- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py
+++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py
@@ -73,7 +73,7 @@ def AtomEditorComponents_Decal_AddedToEntity():
# Test steps begin.
# 1. Create a Decal entity with no components.
- decal_name = "Decal (Atom)"
+ decal_name = "Decal"
decal_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), decal_name)
Report.critical_result(Tests.decal_creation, decal_entity.exists())
diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/CMakeLists.txt
index 4a26500ee2..52682e70bb 100644
--- a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/CMakeLists.txt
+++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/CMakeLists.txt
@@ -17,5 +17,14 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
AZ::AssetProcessorBatch
AZ::AssetProcessor
)
+
+ ly_add_pytest(
+ NAME AssetPipelineTests.Fbx_Tests
+ PATH ${CMAKE_CURRENT_LIST_DIR}/fbx_test/fbx_test.py
+ TEST_SUITE sandbox
+ RUNTIME_DEPENDENCIES
+ AZ::AssetProcessorBatch
+ AZ::AssetProcessor
+ )
endif()
diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py
index 2b90f53fc3..5cb61da68e 100755
--- a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py
+++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py
@@ -34,11 +34,13 @@ logger = logging.getLogger(__name__)
targetProjects = ["AutomatedTesting"]
@pytest.fixture
+@pytest.mark.SUITE_sandbox
def local_resources(request, workspace, ap_setup_fixture):
ap_setup_fixture["tests_dir"] = os.path.dirname(os.path.realpath(__file__))
@dataclass
+@pytest.mark.SUITE_sandbox
class BlackboxAssetTest:
test_name: str
asset_folder: str
@@ -338,9 +340,11 @@ blackbox_fbx_special_tests = [
@pytest.mark.usefixtures("local_resources")
@pytest.mark.parametrize("project", targetProjects)
@pytest.mark.assetpipeline
+@pytest.mark.SUITE_sandbox
class TestsFBX_AllPlatforms(object):
@pytest.mark.BAT
+ @pytest.mark.SUITE_sandbox
@pytest.mark.parametrize("blackbox_param", blackbox_fbx_tests)
def test_FBXBlackboxTest_SourceFiles_Processed_ResultInExpectedProducts(self, workspace,
ap_setup_fixture, asset_processor, project,
@@ -359,6 +363,7 @@ class TestsFBX_AllPlatforms(object):
asset_processor, project, blackbox_param)
@pytest.mark.BAT
+ @pytest.mark.SUITE_sandbox
@pytest.mark.parametrize("blackbox_param", blackbox_fbx_special_tests)
def test_FBXBlackboxTest_AssetInfoModified_AssetReprocessed_ResultInExpectedProducts(self,
workspace, ap_setup_fixture,
diff --git a/Code/Editor/CMakeLists.txt b/Code/Editor/CMakeLists.txt
index 5158ebc6d5..5884795413 100644
--- a/Code/Editor/CMakeLists.txt
+++ b/Code/Editor/CMakeLists.txt
@@ -254,4 +254,35 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_googletest(
NAME Legacy::EditorLib.Tests
)
+
+ ly_add_target(
+ NAME EditorLib.Camera.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
+ NAMESPACE Legacy
+ FILES_CMAKE
+ Lib/Tests/Camera/editor_lib_camera_test_files.cmake
+ INCLUDE_DIRECTORIES
+ PRIVATE
+ .
+ BUILD_DEPENDENCIES
+ PRIVATE
+ AZ::AzCore
+ AZ::AzTest
+ AZ::AzToolsFramework
+ AZ::AzTestShared
+ Legacy::EditorLib
+ Gem::Camera.Editor
+ Gem::AtomToolsFramework.Static
+ RUNTIME_DEPENDENCIES
+ Legacy::EditorLib
+ )
+
+ ly_add_source_properties(
+ SOURCES Lib/Tests/Camera/test_EditorCamera.cpp
+ PROPERTY COMPILE_DEFINITIONS
+ VALUES CAMERA_EDITOR_MODULE="$"
+ )
+
+ ly_add_googletest(
+ NAME Legacy::EditorLib.Camera.Tests
+ )
endif()
diff --git a/Code/Editor/EditorModularViewportCameraComposer.cpp b/Code/Editor/EditorModularViewportCameraComposer.cpp
index 498d6f3353..e375e98fb6 100644
--- a/Code/Editor/EditorModularViewportCameraComposer.cpp
+++ b/Code/Editor/EditorModularViewportCameraComposer.cpp
@@ -9,6 +9,7 @@
#include
#include
+#include
#include
#include
#include
@@ -34,10 +35,12 @@ namespace SandboxEditor
: m_viewportId(viewportId)
{
EditorModularViewportCameraComposerNotificationBus::Handler::BusConnect(viewportId);
+ Camera::EditorCameraNotificationBus::Handler::BusConnect();
}
EditorModularViewportCameraComposer::~EditorModularViewportCameraComposer()
{
+ Camera::EditorCameraNotificationBus::Handler::BusDisconnect();
EditorModularViewportCameraComposerNotificationBus::Handler::BusDisconnect();
}
@@ -283,4 +286,22 @@ namespace SandboxEditor
m_orbitCamera->SetOrbitInputChannelId(SandboxEditor::CameraOrbitChannelId());
m_orbitDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraOrbitDollyChannelId());
}
+
+ void EditorModularViewportCameraComposer::OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId)
+ {
+ if (viewEntityId.IsValid())
+ {
+ AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
+ AZ::TransformBus::EventResult(worldFromLocal, viewEntityId, &AZ::TransformBus::Events::GetWorldTM);
+
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
+ m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame,
+ worldFromLocal);
+ }
+ else
+ {
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
+ m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame);
+ }
+ }
} // namespace SandboxEditor
diff --git a/Code/Editor/EditorModularViewportCameraComposer.h b/Code/Editor/EditorModularViewportCameraComposer.h
index cb223d39e6..e6e71c976c 100644
--- a/Code/Editor/EditorModularViewportCameraComposer.h
+++ b/Code/Editor/EditorModularViewportCameraComposer.h
@@ -10,13 +10,16 @@
#include
#include
+#include
#include
#include
namespace SandboxEditor
{
//! Type responsible for building the editor's modular viewport camera controller.
- class EditorModularViewportCameraComposer : private EditorModularViewportCameraComposerNotificationBus::Handler
+ class EditorModularViewportCameraComposer
+ : private EditorModularViewportCameraComposerNotificationBus::Handler
+ , private Camera::EditorCameraNotificationBus::Handler
{
public:
SANDBOX_API explicit EditorModularViewportCameraComposer(AzFramework::ViewportId viewportId);
@@ -32,6 +35,9 @@ namespace SandboxEditor
// EditorModularViewportCameraComposerNotificationBus overrides ...
void OnEditorModularViewportCameraComposerSettingsChanged() override;
+ // EditorCameraNotificationBus overrides ...
+ void OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) override;
+
AZStd::shared_ptr m_firstPersonRotateCamera;
AZStd::shared_ptr m_firstPersonPanCamera;
AZStd::shared_ptr m_firstPersonTranslateCamera;
diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Android/tool_dependencies_android.cmake b/Code/Editor/Lib/Tests/Camera/editor_lib_camera_test_files.cmake
similarity index 85%
rename from Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Android/tool_dependencies_android.cmake
rename to Code/Editor/Lib/Tests/Camera/editor_lib_camera_test_files.cmake
index 5bf4d7cb7e..69d3e37f2d 100644
--- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Android/tool_dependencies_android.cmake
+++ b/Code/Editor/Lib/Tests/Camera/editor_lib_camera_test_files.cmake
@@ -6,5 +6,6 @@
#
#
-set(GEM_DEPENDENCIES
+set(FILES
+ test_EditorCamera.cpp
)
diff --git a/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp b/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp
new file mode 100644
index 0000000000..affe5794cc
--- /dev/null
+++ b/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp
@@ -0,0 +1,225 @@
+/*
+ * 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
+#include
+#include
+#include
+#include
+#include
+
+namespace UnitTest
+{
+ class EditorCameraTestEnvironment : public AZ::Test::GemTestEnvironment
+ {
+ // AZ::Test::GemTestEnvironment overrides ...
+ void AddGemsAndComponents() override;
+ };
+
+ void EditorCameraTestEnvironment::AddGemsAndComponents()
+ {
+ AddDynamicModulePaths({ CAMERA_EDITOR_MODULE });
+ AddComponentDescriptors({ AzToolsFramework::Components::TransformComponent::CreateDescriptor() });
+ }
+
+ class EditorCameraFixture : public ::testing::Test
+ {
+ public:
+ AtomToolsFramework::ModularCameraViewportContext* m_cameraViewportContextView = nullptr;
+ AZStd::unique_ptr m_editorModularViewportCameraComposer;
+ AZStd::unique_ptr m_editorLibHandle;
+ AzFramework::ViewportControllerListPtr m_controllerList;
+ AZStd::unique_ptr m_entity;
+
+ static const AzFramework::ViewportId TestViewportId;
+
+ void SetUp() override
+ {
+ m_editorLibHandle = AZ::DynamicModuleHandle::Create("EditorLib");
+ [[maybe_unused]] const bool loaded = m_editorLibHandle->Load(true);
+ AZ_Assert(loaded, "EditorLib could not be loaded");
+
+ m_controllerList = AZStd::make_shared();
+ m_controllerList->RegisterViewportContext(TestViewportId);
+
+ m_entity = AZStd::make_unique();
+ m_entity->Init();
+ m_entity->CreateComponent();
+ m_entity->Activate();
+
+ m_editorModularViewportCameraComposer = AZStd::make_unique(TestViewportId);
+
+ auto controller = m_editorModularViewportCameraComposer->CreateModularViewportCameraController();
+ // set some overrides for the test
+ controller->SetCameraViewportContextBuilderCallback(
+ [this](AZStd::unique_ptr& cameraViewportContext) mutable
+ {
+ cameraViewportContext = AZStd::make_unique();
+ m_cameraViewportContextView = cameraViewportContext.get();
+ });
+
+ m_controllerList->Add(controller);
+ }
+
+ void TearDown() override
+ {
+ m_editorModularViewportCameraComposer.reset();
+ m_cameraViewportContextView = nullptr;
+ m_entity.reset();
+ m_editorLibHandle = {};
+ }
+ };
+
+ const AzFramework::ViewportId EditorCameraFixture::TestViewportId = AzFramework::ViewportId(1337);
+
+ TEST_F(EditorCameraFixture, ModularViewportCameraControllerReferenceFrameUpdatedWhenViewportEntityisChanged)
+ {
+ // Given
+ const auto entityTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
+ AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(10.0f, 5.0f, -2.0f));
+ AZ::TransformBus::Event(m_entity->GetId(), &AZ::TransformBus::Events::SetWorldTM, entityTransform);
+
+ // When
+ // imitate viewport entity changing
+ Camera::EditorCameraNotificationBus::Broadcast(
+ &Camera::EditorCameraNotificationBus::Events::OnViewportViewEntityChanged, m_entity->GetId());
+
+ // ensure the viewport updates after the viewport view entity change
+ const float deltaTime = 1.0f / 60.0f;
+ m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
+
+ // retrieve updated camera transform
+ const AZ::Transform cameraTransform = m_cameraViewportContextView->GetCameraTransform();
+
+ // Then
+ // camera transform matches that of the entity
+ EXPECT_THAT(cameraTransform, IsClose(entityTransform));
+ }
+
+ TEST_F(EditorCameraFixture, ReferenceFrameRemainsIdentityAfterExternalCameraTransformChangeWhenNotSet)
+ {
+ // Given
+ m_cameraViewportContextView->SetCameraTransform(AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 20.0f, 30.0f)));
+
+ // When
+ AZ::Transform referenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f));
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
+ referenceFrame, TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame);
+
+ // Then
+ // reference frame is still the identity
+ EXPECT_THAT(referenceFrame, IsClose(AZ::Transform::CreateIdentity()));
+ }
+
+ TEST_F(EditorCameraFixture, ExternalCameraTransformChangeWhenReferenceFrameIsSetUpdatesReferenceFrame)
+ {
+ // Given
+ const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation(
+ AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f));
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
+ TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame);
+
+ const AZ::Transform nextTransform = AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 20.0f, 30.0f));
+ m_cameraViewportContextView->SetCameraTransform(nextTransform);
+
+ // When
+ AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f));
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
+ currentReferenceFrame, TestViewportId,
+ &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame);
+
+ // Then
+ EXPECT_THAT(currentReferenceFrame, IsClose(nextTransform));
+ }
+
+ TEST_F(EditorCameraFixture, ReferenceFrameReturnedToIdentityAfterClear)
+ {
+ // Given
+ const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation(
+ AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f));
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
+ TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame);
+
+ // When
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
+ TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame);
+
+ AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f));
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
+ currentReferenceFrame, TestViewportId,
+ &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame);
+
+ // Then
+ EXPECT_THAT(currentReferenceFrame, IsClose(AZ::Transform::CreateIdentity()));
+ }
+
+ TEST_F(EditorCameraFixture, InterpolateToTransform)
+ {
+ // When
+ AZ::Transform transformToInterpolateTo = AZ::Transform::CreateFromQuaternionAndTranslation(
+ AZ::Quaternion::CreateRotationZ(AZ::DegToRad(90.0f)), AZ::Vector3(20.0f, 40.0f, 60.0f));
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
+ TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform,
+ transformToInterpolateTo, 0.0f);
+
+ // simulate interpolation
+ m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() });
+ m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() });
+
+ const auto finalTransform = m_cameraViewportContextView->GetCameraTransform();
+
+ // Then
+ EXPECT_THAT(finalTransform, IsClose(transformToInterpolateTo));
+ }
+
+ TEST_F(EditorCameraFixture, InterpolateToTransformWithReferenceSpaceSet)
+ {
+ // Given
+ const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation(
+ AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f));
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
+ TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame);
+
+ AZ::Transform transformToInterpolateTo = AZ::Transform::CreateFromQuaternionAndTranslation(
+ AZ::Quaternion::CreateRotationZ(AZ::DegToRad(90.0f)), AZ::Vector3(20.0f, 40.0f, 60.0f));
+
+ // When
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
+ TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform,
+ transformToInterpolateTo, 0.0f);
+
+ // simulate interpolation
+ m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() });
+ m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() });
+
+ AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f));
+ AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
+ currentReferenceFrame, TestViewportId,
+ &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame);
+
+ const auto finalTransform = m_cameraViewportContextView->GetCameraTransform();
+
+ // Then
+ EXPECT_THAT(finalTransform, IsClose(transformToInterpolateTo));
+ EXPECT_THAT(currentReferenceFrame, IsClose(AZ::Transform::CreateIdentity()));
+ }
+} // namespace UnitTest
+
+// required to support running integration tests with the Camera Gem
+AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv)
+{
+ ::testing::InitGoogleMock(&argc, argv);
+ AZ::Test::printUnusedParametersWarning(argc, argv);
+ AZ::Test::addTestEnvironments({ new UnitTest::EditorCameraTestEnvironment() });
+ int result = RUN_ALL_TESTS();
+ return result;
+}
+
+IMPLEMENT_TEST_EXECUTABLE_MAIN();
diff --git a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp
index ce5564c7f6..157291cfc2 100644
--- a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp
+++ b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp
@@ -58,28 +58,6 @@ namespace UnitTest
return true;
}
- class TestModularCameraViewportContextImpl : public AtomToolsFramework::ModularCameraViewportContext
- {
- public:
- AZ::Transform GetCameraTransform() const override
- {
- return m_cameraTransform;
- }
-
- void SetCameraTransform(const AZ::Transform& transform) override
- {
- m_cameraTransform = transform;
- }
-
- void ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler&) override
- {
- // noop
- }
-
- private:
- AZ::Transform m_cameraTransform = AZ::Transform::CreateIdentity();
- };
-
class ModularViewportCameraControllerFixture : public AllocatorsTestFixture
{
public:
@@ -146,7 +124,7 @@ namespace UnitTest
controller->SetCameraViewportContextBuilderCallback(
[this](AZStd::unique_ptr& cameraViewportContext)
{
- cameraViewportContext = AZStd::make_unique();
+ cameraViewportContext = AZStd::make_unique();
m_cameraViewportContextView = cameraViewportContext.get();
});
diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.cpp b/Code/Framework/AzCore/AzCore/Component/Entity.cpp
index 00c1895261..2e92873238 100644
--- a/Code/Framework/AzCore/AzCore/Component/Entity.cpp
+++ b/Code/Framework/AzCore/AzCore/Component/Entity.cpp
@@ -207,12 +207,6 @@ namespace AZ
ActivateComponent(**it);
}
- // Cache the transform interface to the transform interface
- // Generally this pattern is not recommended unless for component event buses
- // As we have a guarantee (by design) that components can't change during active state)
- // Even though technically they can connect disconnect from the bus.
- m_transform = TransformBus::FindFirstHandler(m_id);
-
SetState(State::Active);
EBUS_EVENT_ID(m_id, EntityBus, OnEntityActivated, m_id);
@@ -1320,6 +1314,19 @@ namespace AZ
return *processSignature;
}
+ AZ::TransformInterface* Entity::GetTransform() const
+ {
+ // Lazy evaluation of the cached entity transform.
+ if(!m_transform)
+ {
+ // Generally this pattern is not recommended unless for component event buses
+ // As we have a guarantee (by design) that components can't change during active state)
+ // Even though technically they can connect disconnect from the bus.
+ m_transform = TransformBus::FindFirstHandler(m_id);
+ }
+ return m_transform;
+ }
+
//=========================================================================
// MakeId
// Ids must be unique across a project at authoring time. Runtime doesn't matter
diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.h b/Code/Framework/AzCore/AzCore/Component/Entity.h
index 7ec63a56ac..356533f268 100644
--- a/Code/Framework/AzCore/AzCore/Component/Entity.h
+++ b/Code/Framework/AzCore/AzCore/Component/Entity.h
@@ -354,10 +354,9 @@ namespace AZ
//! @return The Process Signature of the local machine.
static AZ::u32 GetProcessSignature();
- /// @cond EXCLUDE_DOCS
- //! @deprecated Use the TransformBus to communicate with the TransformInterface.
- inline TransformInterface* GetTransform() const { return m_transform; }
- /// @endcond
+ //! Gets the TransformInterface for the entity.
+ //! @return The TransformInterface for the entity.
+ TransformInterface* GetTransform() const;
//! Sorts an entity's components based on the dependencies between components.
//! If all dependencies are met, the required services can be activated
@@ -406,7 +405,7 @@ namespace AZ
//! A cached pointer to the transform interface.
//! We recommend using AZ::TransformBus and caching locally instead of accessing
//! the transform interface directly through this pointer.
- TransformInterface* m_transform;
+ mutable TransformInterface* m_transform;
//! A user-friendly name for the entity. This makes error messages easier to read.
AZStd::string m_name;
diff --git a/Code/Framework/AzCore/AzCore/IO/FileReader.cpp b/Code/Framework/AzCore/AzCore/IO/FileReader.cpp
new file mode 100644
index 0000000000..94118cdfe4
--- /dev/null
+++ b/Code/Framework/AzCore/AzCore/IO/FileReader.cpp
@@ -0,0 +1,200 @@
+/*
+ * 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 AZ::IO
+{
+ FileReader::FileReader() = default;
+
+ FileReader::FileReader(AZ::IO::FileIOBase* fileIoBase, const char* filePath)
+ {
+ Open(fileIoBase, filePath);
+ }
+
+ FileReader::~FileReader()
+ {
+ Close();
+ }
+
+ FileReader::FileReader(FileReader&& other)
+ {
+ AZStd::swap(m_file, other.m_file);
+ AZStd::swap(m_fileIoBase, other.m_fileIoBase);
+ }
+
+ FileReader& FileReader::operator=(FileReader&& other)
+ {
+ // Close the current file and take over other file
+ Close();
+ m_file = AZStd::move(other.m_file);
+ m_fileIoBase = AZStd::move(other.m_fileIoBase);
+ other.m_file = AZStd::monostate{};
+ other.m_fileIoBase = {};
+
+ return *this;
+ }
+
+ bool FileReader::Open(AZ::IO::FileIOBase* fileIoBase, const char* filePath)
+ {
+ // Close file if the FileReader has an instance open
+ Close();
+
+ if (fileIoBase != nullptr)
+ {
+ AZ::IO::HandleType fileHandle;
+ if (fileIoBase->Open(filePath, IO::OpenMode::ModeRead, fileHandle))
+ {
+ m_file = fileHandle;
+ m_fileIoBase = fileIoBase;
+ return true;
+ }
+ }
+ else
+ {
+ AZ::IO::SystemFile file;
+ if (file.Open(filePath, IO::SystemFile::OpenMode::SF_OPEN_READ_ONLY))
+ {
+ m_file = AZStd::move(file);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool FileReader::IsOpen() const
+ {
+ if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
+ {
+ return *fileHandle != AZ::IO::InvalidHandle;
+ }
+ else if (auto systemFile = AZStd::get_if(&m_file); systemFile != nullptr)
+ {
+ return systemFile->IsOpen();
+ }
+
+ return false;
+ }
+
+ void FileReader::Close()
+ {
+ if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
+ {
+ if (AZ::IO::FileIOBase* fileIo = m_fileIoBase; fileIo != nullptr)
+ {
+ fileIo->Close(*fileHandle);
+ }
+ }
+
+ m_file = AZStd::monostate{};
+ m_fileIoBase = {};
+ }
+
+ auto FileReader::Length() const -> SizeType
+ {
+ if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
+ {
+ if (SizeType fileSize{}; m_fileIoBase->Size(*fileHandle, fileSize))
+ {
+ return fileSize;
+ }
+ }
+ else if (auto systemFile = AZStd::get_if(&m_file); systemFile != nullptr)
+ {
+ return systemFile->Length();
+ }
+
+ return 0;
+ }
+
+ auto FileReader::Read(SizeType byteSize, void* buffer) -> SizeType
+ {
+ if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
+ {
+ if (SizeType bytesRead{}; m_fileIoBase->Read(*fileHandle, buffer, byteSize, false, &bytesRead))
+ {
+ return bytesRead;
+ }
+ }
+ else if (auto systemFile = AZStd::get_if(&m_file); systemFile != nullptr)
+ {
+ return systemFile->Read(byteSize, buffer);
+ }
+
+ return 0;
+ }
+
+ auto FileReader::Tell() const -> SizeType
+ {
+ if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
+ {
+ if (SizeType fileOffset{}; m_fileIoBase->Tell(*fileHandle, fileOffset))
+ {
+ return fileOffset;
+ }
+ }
+ else if (auto systemFile = AZStd::get_if(&m_file); systemFile != nullptr)
+ {
+ return systemFile->Tell();
+ }
+
+ return 0;
+ }
+
+ bool FileReader::Seek(AZ::s64 offset, SeekType type)
+ {
+ if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
+ {
+ return m_fileIoBase->Seek(*fileHandle, offset, type);
+ }
+ else if (auto systemFile = AZStd::get_if(&m_file); systemFile != nullptr)
+ {
+ systemFile->Seek(offset, static_cast(type));
+ return true;
+ }
+
+ return false;
+ }
+
+ bool FileReader::Eof() const
+ {
+ if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
+ {
+ return m_fileIoBase->Eof(*fileHandle);
+ }
+ else if (auto systemFile = AZStd::get_if(&m_file); systemFile != nullptr)
+ {
+ return systemFile->Eof();
+ }
+
+ return false;
+ }
+
+ bool FileReader::GetFilePath(AZ::IO::FixedMaxPath& filePath) const
+ {
+ if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
+ {
+ AZ::IO::FixedMaxPathString& pathStringRef = filePath.Native();
+ if (m_fileIoBase->GetFilename(*fileHandle, pathStringRef.data(), pathStringRef.capacity()))
+ {
+ pathStringRef.resize_no_construct(AZStd::char_traits::length(pathStringRef.data()));
+ return true;
+ }
+ }
+ else if (auto systemFile = AZStd::get_if(&m_file); systemFile != nullptr)
+ {
+ filePath = systemFile->Name();
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/Code/Framework/AzCore/AzCore/IO/FileReader.h b/Code/Framework/AzCore/AzCore/IO/FileReader.h
new file mode 100644
index 0000000000..4fdb18b2b2
--- /dev/null
+++ b/Code/Framework/AzCore/AzCore/IO/FileReader.h
@@ -0,0 +1,92 @@
+/*
+ * 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 AZ::IO
+{
+ class FileIOBase;
+ enum class SeekType : AZ::u32;
+
+ //! Structure which encapsulates delegates File Read operations
+ //! to either the FileIOBase or SystemFile classes based if a FileIOBase* instance has been supplied
+ //! to the FileSystemReader class
+ //! the SettingsRegistry option to use FileIO
+ class FileReader
+ {
+ using HandleType = AZ::u32;
+ using FileHandleType = AZStd::variant;
+ public:
+ using SizeType = AZ::u64;
+
+ //! Creates FileReader instance in the default state with no file opend
+ FileReader();
+ ~FileReader();
+
+ //! Creates a new FileReader instance and attempts to open the file at the supplied path
+ //! Uses the FileIOBase instance if supplied
+ //! @param fileIOBase pointer to fileIOBase instance
+ //! @param null-terminated filePath to open
+ FileReader(AZ::IO::FileIOBase* fileIoBase, const char* filePath);
+
+ //! Takes ownership of the supplied FileReader handle
+ FileReader(FileReader&& other);
+
+ //! Moves ownership of FileReader handle to this instance
+ FileReader& operator=(FileReader&& other);
+
+ //! Opens a File using the FileIOBase instance if non-nullptr
+ //! Otherwise fall back to use SystemFile
+ //! @param fileIOBase pointer to fileIOBase instance
+ //! @param null-terminated filePath to open
+ //! @return true if the File is opened successfully
+ bool Open(AZ::IO::FileIOBase* fileIoBase, const char* filePath);
+
+ //! Returns true if a file is currently open
+ //! @return true if the file is open
+ bool IsOpen() const;
+
+ //! Closes the File
+ void Close();
+
+ //! Retrieve the length of the OpenFile
+ SizeType Length() const;
+
+ //! Attempts to read up to byte size bytes into the supplied buffer
+ //! @param byteSize - Maximum number of bytes to read
+ //! @param buffer - Buffer to read bytes into
+ //! @returns the number of bytes read if the file is open, otherwise 0
+ SizeType Read(SizeType byteSize, void* buffer);
+
+ //! Returns the current file offset
+ //! @returns file offset if the file is open, otherwise 0
+ SizeType Tell() const;
+
+ //! Seeks within the open file to the offset supplied
+ //! @param offset File offset to seek to
+ //! @param type parameter to indicate the reference point to start the seek from
+ //! @returns true if the file is open and the seek succeeded
+ bool Seek(AZ::s64 offset, SeekType type);
+
+ //! Returns true if the file is open and in the EOF state
+ bool Eof() const;
+
+ //! Store the file path of the open file into the output file path parameter
+ //! The filePath reference is left unmodified, if the path was not stored
+ //! @return true if the filePath was stored
+ bool GetFilePath(AZ::IO::FixedMaxPath& filePath) const;
+
+ private:
+
+ FileHandleType m_file;
+ AZ::IO::FileIOBase* m_fileIoBase{};
+ };
+}
diff --git a/Code/Framework/AzCore/AzCore/IO/SystemFile.cpp b/Code/Framework/AzCore/AzCore/IO/SystemFile.cpp
index 5bff79b422..651abb89fe 100644
--- a/Code/Framework/AzCore/AzCore/IO/SystemFile.cpp
+++ b/Code/Framework/AzCore/AzCore/IO/SystemFile.cpp
@@ -160,12 +160,12 @@ void SystemFile::Seek(SeekSizeType offset, SeekMode mode)
Platform::Seek(m_handle, this, offset, mode);
}
-SystemFile::SizeType SystemFile::Tell()
+SystemFile::SizeType SystemFile::Tell() const
{
return Platform::Tell(m_handle, this);
}
-bool SystemFile::Eof()
+bool SystemFile::Eof() const
{
return Platform::Eof(m_handle, this);
}
diff --git a/Code/Framework/AzCore/AzCore/IO/SystemFile.h b/Code/Framework/AzCore/AzCore/IO/SystemFile.h
index 8a5b2b2521..551ce89ce7 100644
--- a/Code/Framework/AzCore/AzCore/IO/SystemFile.h
+++ b/Code/Framework/AzCore/AzCore/IO/SystemFile.h
@@ -72,9 +72,9 @@ namespace AZ
/// Seek in current file.
void Seek(SeekSizeType offset, SeekMode mode);
/// Get the cursor position in the current file.
- SizeType Tell();
+ SizeType Tell() const;
/// Is the cursor at the end of the file?
- bool Eof();
+ bool Eof() const;
/// Get the time the file was last modified.
AZ::u64 ModificationTime();
/// Read data from a file synchronous. Return number of bytes actually read in the buffer.
diff --git a/Code/Framework/AzCore/AzCore/Math/Transform.cpp b/Code/Framework/AzCore/AzCore/Math/Transform.cpp
index 5cb09fe9a8..1701820bae 100644
--- a/Code/Framework/AzCore/AzCore/Math/Transform.cpp
+++ b/Code/Framework/AzCore/AzCore/Math/Transform.cpp
@@ -353,6 +353,7 @@ namespace AZ
Method("CreateFromMatrix3x3AndTranslation", &Transform::CreateFromMatrix3x3AndTranslation)->
Method("CreateUniformScale", &Transform::CreateUniformScale)->
Method("CreateTranslation", &Transform::CreateTranslation)->
+ Method("CreateLookAt", &Transform::CreateLookAt)->
Method("ConstructFromValuesNumeric", &Internal::ConstructTransformFromValues);
}
}
diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp
index 7ef1fa661d..92f546815e 100644
--- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp
+++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -1116,118 +1117,6 @@ namespace AZ
}
}
- //! Structure which encapsulates Commands to either the FileIOBase or SystemFile classes based on
- //! the SettingsRegistry option to use FileIO
- struct SettingsRegistryFileReader
- {
- using FileHandleType = AZStd::variant;
-
- SettingsRegistryFileReader() = default;
- SettingsRegistryFileReader(bool useFileIo, const char* filePath)
- {
- Open(useFileIo, filePath);
- }
-
- ~SettingsRegistryFileReader()
- {
- if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
- {
- if (AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance(); fileIo != nullptr)
- {
- fileIo->Close(*fileHandle);
- }
- }
- }
-
- bool Open(bool useFileIo, const char* filePath)
- {
- Close();
- if (AZ::IO::FileIOBase* fileIo = useFileIo ? AZ::IO::FileIOBase::GetInstance() : nullptr; fileIo != nullptr)
- {
- AZ::IO::HandleType fileHandle;
- if (fileIo->Open(filePath, IO::OpenMode::ModeRead, fileHandle))
- {
- m_file = fileHandle;
- return true;
- }
- }
- else
- {
- AZ::IO::SystemFile file;
- if (file.Open(filePath, IO::SystemFile::OpenMode::SF_OPEN_READ_ONLY))
- {
- m_file = AZStd::move(file);
- return true;
- }
- }
-
- return false;
- }
-
- bool IsOpen() const
- {
- if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
- {
- return *fileHandle != AZ::IO::InvalidHandle;
- }
- else if (auto systemFile = AZStd::get_if(&m_file); systemFile != nullptr)
- {
- return systemFile->IsOpen();
- }
-
- return false;
- }
-
- void Close()
- {
- if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
- {
- if (AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance(); fileIo != nullptr)
- {
- fileIo->Close(*fileHandle);
- }
- }
-
- m_file = AZStd::monostate{};
- }
-
- u64 Length() const
- {
- if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
- {
- if (u64 fileSize{}; AZ::IO::FileIOBase::GetInstance()->Size(*fileHandle, fileSize))
- {
- return fileSize;
- }
- }
- else if (auto systemFile = AZStd::get_if(&m_file); systemFile != nullptr)
- {
- return systemFile->Length();
- }
-
- return 0;
- }
-
- AZ::IO::SizeType Read(AZ::IO::SizeType byteSize, void* buffer)
- {
- if (auto fileHandle = AZStd::get_if(&m_file); fileHandle != nullptr)
- {
- if (AZ::u64 bytesRead{}; AZ::IO::FileIOBase::GetInstance()->Read(*fileHandle, buffer, byteSize, false, &bytesRead))
- {
- return bytesRead;
- }
- }
- else if (auto systemFile = AZStd::get_if(&m_file); systemFile != nullptr)
- {
- return systemFile->Read(byteSize, buffer);
- }
-
- return 0;
- }
-
- FileHandleType m_file;
- };
-
bool SettingsRegistryImpl::MergeSettingsFileInternal(const char* path, Format format, AZStd::string_view rootKey,
AZStd::vector& scratchBuffer)
{
@@ -1236,7 +1125,7 @@ namespace AZ
Pointer pointer(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/-");
- SettingsRegistryFileReader fileReader(m_useFileIo, path);
+ FileReader fileReader(m_useFileIo ? AZ::IO::FileIOBase::GetInstance(): nullptr, path);
if (!fileReader.IsOpen())
{
AZ_Error("Settings Registry", false, R"(Unable to open registry file "%s".)", path);
diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp
index 5290c9b02a..da7110e36e 100644
--- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp
+++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp
@@ -6,6 +6,8 @@
*
*/
+#include
+#include
#include
#include
#include
@@ -388,8 +390,36 @@ namespace AZ::SettingsRegistryMergeUtils
const ConfigParserSettings& configParserSettings)
{
auto configPath = FindEngineRoot(registry) / filePath;
- IO::SystemFile configFile;
- if (!configFile.Open(configPath.c_str(), IO::SystemFile::OpenMode::SF_OPEN_READ_ONLY))
+ IO::FileReader configFile;
+ bool configFileOpened{};
+ switch (configParserSettings.m_fileReaderClass)
+ {
+ case ConfigParserSettings::FileReaderClass::UseFileIOIfAvailableFallbackToSystemFile:
+ {
+ auto fileIo = AZ::IO::FileIOBase::GetInstance();
+ configFileOpened = configFile.Open(fileIo, configPath.c_str());
+ break;
+ }
+ case ConfigParserSettings::FileReaderClass::UseSystemFileOnly:
+ {
+ configFileOpened = configFile.Open(nullptr, configPath.c_str());
+ break;
+ }
+ case ConfigParserSettings::FileReaderClass::UseFileIOOnly:
+ {
+ auto fileIo = AZ::IO::FileIOBase::GetInstance();
+ if (fileIo == nullptr)
+ {
+ return false;
+ }
+ configFileOpened = configFile.Open(fileIo, configPath.c_str());
+ break;
+ }
+ default:
+ AZ_Error("SettingsRegistryMergeUtils", false, "An Invalid FileReaderClass enum value has been supplied");
+ return false;
+ }
+ if (!configFileOpened)
{
AZ_Warning("SettingsRegistryMergeUtils", false, R"(Unable to open file "%s")", configPath.c_str());
return false;
@@ -480,7 +510,7 @@ namespace AZ::SettingsRegistryMergeUtils
AZ_Error("SettingsRegistryMergeUtils", false,
R"(The config file "%s" contains a line which is longer than the max line length of %zu.)" "\n"
R"(Parsing will halt. The line content so far is:)" "\n"
- R"("%.*s")" "\n", configFile.Name(), configBuffer.max_size(),
+ R"("%.*s")" "\n", configPath.c_str(), configBuffer.max_size(),
aznumeric_cast(configBuffer.size()), configBuffer.data());
configFileParsed = false;
break;
diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h
index 02346c2ba1..daa64c0343 100644
--- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h
+++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h
@@ -155,6 +155,15 @@ namespace AZ::SettingsRegistryMergeUtils
//! structure which is forwarded to the SettingsRegistryInterface MergeCommandLineArgument function
//! The structure contains a functor which returns true if a character is a valid delimiter
SettingsRegistryInterface::CommandLineArgumentSettings m_commandLineSettings;
+
+ //! enumeration to indicate if AZ::IO::FileIOBase should be used to open the config file over AZ::IO::SystemFile
+ enum class FileReaderClass
+ {
+ UseFileIOIfAvailableFallbackToSystemFile,
+ UseSystemFileOnly,
+ UseFileIOOnly
+ };
+ FileReaderClass m_fileReaderClass = FileReaderClass::UseFileIOIfAvailableFallbackToSystemFile;
};
//! Loads basic configuration files which have structures similar to Windows INI files
//! It is inspired by the Python configparser module: https://docs.python.org/3.10/library/configparser.html
diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake
index 6c498c3335..aa07959997 100644
--- a/Code/Framework/AzCore/AzCore/azcore_files.cmake
+++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake
@@ -166,6 +166,8 @@ set(FILES
IO/FileIO.cpp
IO/FileIO.h
IO/FileIOEventBus.h
+ IO/FileReader.cpp
+ IO/FileReader.h
IO/IOUtils.h
IO/IOUtils.cpp
IO/IStreamer.h
diff --git a/Code/Framework/AzCore/Tests/IO/FileReaderTests.cpp b/Code/Framework/AzCore/Tests/IO/FileReaderTests.cpp
new file mode 100644
index 0000000000..691b3f2821
--- /dev/null
+++ b/Code/Framework/AzCore/Tests/IO/FileReaderTests.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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
+{
+ template
+ class FileReaderTestFixture
+ : public ScopedAllocatorSetupFixture
+ {
+ public:
+ void SetUp() override
+ {
+ if constexpr (AZStd::is_same_v)
+ {
+ m_fileIo = AZStd::make_unique();
+ }
+ }
+
+ void TearDown() override
+ {
+ m_fileIo.reset();
+ }
+
+ protected:
+ AZStd::unique_ptr m_fileIo{};
+ };
+
+ using FileIOTypes = ::testing::Types;
+
+ TYPED_TEST_CASE(FileReaderTestFixture, FileIOTypes);
+
+ TYPED_TEST(FileReaderTestFixture, ConstructorWithFilePath_OpensFileSuccessfully)
+ {
+ AZ::IO::FileReader fileReader(this->m_fileIo.get(), AZ::IO::SystemFile::GetNullFilename());
+ EXPECT_TRUE(fileReader.IsOpen());
+ }
+
+ TYPED_TEST(FileReaderTestFixture, Open_OpensFileSucessfully)
+ {
+ AZ::IO::FileReader fileReader;
+ fileReader.Open(this->m_fileIo.get(), AZ::IO::SystemFile::GetNullFilename());
+ EXPECT_TRUE(fileReader.IsOpen());
+ }
+
+ TYPED_TEST(FileReaderTestFixture, Eof_OnNULDeviceFile_Succeeds)
+ {
+ AZ::IO::FileReader fileReader(this->m_fileIo.get(), AZ::IO::SystemFile::GetNullFilename());
+ EXPECT_TRUE(fileReader.Eof());
+ }
+
+ TYPED_TEST(FileReaderTestFixture, GetFilePath_ReturnsNULDeviceFilename_Succeeds)
+ {
+ AZ::IO::FileReader fileReader(this->m_fileIo.get(), AZ::IO::SystemFile::GetNullFilename());
+ AZ::IO::FixedMaxPath filePath;
+ EXPECT_TRUE(fileReader.GetFilePath(filePath));
+ AZ::IO::FixedMaxPath nulFilename{ AZ::IO::SystemFile::GetNullFilename() };
+ if (this->m_fileIo)
+ {
+ EXPECT_TRUE(this->m_fileIo->ResolvePath(nulFilename, nulFilename));
+ }
+ EXPECT_EQ(nulFilename, filePath);
+ }
+
+} // namespace UnitTest
diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake
index d4d107f094..c36d37d874 100644
--- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake
+++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake
@@ -37,6 +37,7 @@ set(FILES
FileIOBaseTestTypes.h
Geometry2DUtils.cpp
Interface.cpp
+ IO/FileReaderTests.cpp
IO/Path/PathTests.cpp
IPC.cpp
Jobs.cpp
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp
index 75d31321c2..91a916ae70 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -248,6 +249,7 @@ namespace AzToolsFramework
components.insert(components.end(), {
azrtti_typeid(),
azrtti_typeid(),
+ azrtti_typeid(),
azrtti_typeid(),
azrtti_typeid(),
azrtti_typeid(),
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.cpp
index 469a235b9f..ec709aef8a 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.cpp
@@ -138,7 +138,13 @@ namespace AzToolsFramework
m_indexMap[row] = index;
m_rowMap[index] = row;
++row;
- ++m_displayedItemsCounter;
+
+ // We only want to increase the displayed counter if it is a parent (Source)
+ // so we don't cut children entries.
+ if (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Source)
+ {
+ ++m_displayedItemsCounter;
+ }
}
if (model->hasChildren(index))
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.cpp
index 37091c11e2..217e282a37 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.cpp
@@ -29,20 +29,20 @@ namespace AzToolsFramework
{
AssetBrowserTableView::AssetBrowserTableView(QWidget* parent)
: AzQtComponents::TableView(parent)
- , m_delegate(new EntryDelegate(this))
+ , m_delegate(new SearchEntryDelegate(this))
{
- setSortingEnabled(true);
+ setSortingEnabled(false);
setItemDelegate(m_delegate);
setRootIsDecorated(false);
//Styling the header aligning text to the left and using a bold font.
header()->setDefaultAlignment(Qt::AlignLeft);
- header()->setStyleSheet("QHeaderView { font-weight: bold; }");
+ header()->setStyleSheet("QHeaderView { font-weight: bold; };");
+
setContextMenuPolicy(Qt::CustomContextMenu);
setMouseTracking(true);
- setSortingEnabled(false);
setSelectionMode(QAbstractItemView::SingleSelection);
connect(this, &AzQtComponents::TableView::customContextMenuRequested, this, &AssetBrowserTableView::OnContextMenu);
@@ -67,6 +67,8 @@ namespace AzToolsFramework
header()->setSectionResizeMode(0, QHeaderView::ResizeMode::Stretch);
header()->setSectionResizeMode(1, QHeaderView::ResizeMode::Stretch);
+ header()->setSortIndicatorShown(false);
+ header()->setSectionsClickable(false);
}
void AssetBrowserTableView::SetName(const QString& name)
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h
index 94e9bdc4e4..b4eca59cef 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h
@@ -26,7 +26,7 @@ namespace AzToolsFramework
class AssetBrowserEntry;
class AssetBrowserTableModel;
class AssetBrowserFilterModel;
- class EntryDelegate;
+ class SearchEntryDelegate;
class AssetBrowserTableView //! Table view that displays the asset browser entries in a list.
: public AzQtComponents::TableView
@@ -67,9 +67,9 @@ namespace AzToolsFramework
private:
QString m_name;
- QPointer m_tableModel = nullptr;
- QPointer m_sourceFilterModel = nullptr;
- EntryDelegate* m_delegate = nullptr;
+ QPointer m_tableModel;
+ QPointer m_sourceFilterModel;
+ SearchEntryDelegate* m_delegate = nullptr;
private Q_SLOTS:
void OnContextMenu(const QPoint& point);
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.cpp
index 29324c612c..755c59b55b 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.cpp
@@ -11,12 +11,13 @@
#include
#include
#include
-
+#include
#include
#include
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // 4251: class 'QScopedPointer' needs to have dll-interface to be used by clients of class 'QBrush'
// 4800: 'uint': forcing value to bool 'true' or 'false' (performance warning)
+#include
#include
AZ_POP_DISABLE_WARNING
@@ -24,8 +25,13 @@ namespace AzToolsFramework
{
namespace AssetBrowser
{
- const int ENTRY_SPACING_LEFT_PIXELS = 8;
- const int ENTRY_ICON_MARGIN_LEFT_PIXELS = 2;
+ static constexpr const char* TreeIconPathFirst = "Assets/Editor/Icons/AssetBrowser/TreeBranch_First.svg";
+ static constexpr const char* TreeIconPathMiddle = "Assets/Editor/Icons/AssetBrowser/TreeBranch_Middle.svg";
+ static constexpr const char* TreeIconPathLast = "Assets/Editor/Icons/AssetBrowser/TreeBranch_Last.svg";
+ static constexpr const char* TreeIconPathOneChild = "Assets/Editor/Icons/AssetBrowser/TreeBranch_OneChild.svg";
+
+ const int EntrySpacingLeftPixels = 8;
+ const int EntryIconMarginLeftPixels = 2;
EntryDelegate::EntryDelegate(QWidget* parent)
: QStyledItemDelegate(parent)
@@ -62,7 +68,7 @@ namespace AzToolsFramework
// Draw main entry thumbnail.
QRect remainingRect(option.rect);
- remainingRect.adjust(ENTRY_ICON_MARGIN_LEFT_PIXELS, 0, 0, 0); // bump it rightwards to give some margin to the icon.
+ remainingRect.adjust(EntryIconMarginLeftPixels, 0, 0, 0); // bump it rightwards to give some margin to the icon.
QSize iconSize(m_iconSize, m_iconSize);
// Note that the thumbnail might actually be smaller than the row if theres a lot of padding or font size
@@ -89,7 +95,7 @@ namespace AzToolsFramework
}
remainingRect.adjust(thumbX, 0, 0, 0); // bump it to the right by the size of the thumbnail
- remainingRect.adjust(ENTRY_SPACING_LEFT_PIXELS, 0, 0, 0); // bump it to the right by the spacing.
+ remainingRect.adjust(EntrySpacingLeftPixels, 0, 0, 0); // bump it to the right by the spacing.
}
QString displayString = index.column() == aznumeric_cast(AssetBrowserEntry::Column::Name)
? qvariant_cast(entry->data(aznumeric_cast(AssetBrowserEntry::Column::Name)))
@@ -148,7 +154,162 @@ namespace AzToolsFramework
return m_iconSize;
}
- } // namespace Thumbnailer
+ SearchEntryDelegate::SearchEntryDelegate(QWidget* parent)
+ : EntryDelegate(parent)
+ {
+ LoadBranchPixMaps();
+ }
+
+ void SearchEntryDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
+ {
+ auto data = index.data(AssetBrowserModel::Roles::EntryRole);
+ if (data.canConvert())
+ {
+ bool isEnabled = (option.state & QStyle::State_Enabled) != 0;
+ bool isSelected = (option.state & QStyle::State_Selected) != 0;
+
+ QStyle* style = option.widget ? option.widget->style() : QApplication::style();
+
+ // draw the background
+ style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget);
+
+ // Draw main entry thumbnail.
+ QRect remainingRect(option.rect);
+
+ QSize iconSize(m_iconSize, m_iconSize);
+ // Note that the thumbnail might actually be smaller than the row if theres a lot of padding or font size
+ // so it needs to center vertically with padding in that case:
+ QPoint iconTopLeft;
+ QPoint branchIconTopLeft = QPoint();
+
+ auto entry = qvariant_cast(data);
+ auto sourceEntry = azrtti_cast(entry);
+
+ //If it is a SourceEntry or it is not the column name we don't want to add space for the branch Icon
+ if (sourceEntry || index.column() != aznumeric_cast(AssetBrowserEntry::Column::Name))
+ {
+ remainingRect.adjust(EntryIconMarginLeftPixels, 0, 0, 0); // bump it rightwards to give some margin to the icon.
+ iconTopLeft = QPoint(remainingRect.x(), remainingRect.y() + (remainingRect.height() / 2) - (m_iconSize / 2));
+ }
+ else
+ {
+ remainingRect.adjust(EntryIconMarginLeftPixels + m_iconSize, 0, 0, 0); // bump it rightwards to give some margin to the icon.
+ iconTopLeft = QPoint(remainingRect.x() / 2 + m_iconSize, remainingRect.y() + (remainingRect.height() / 2) - (m_iconSize / 2));
+ branchIconTopLeft = QPoint((remainingRect.x() / 2) - 2, remainingRect.y() + (remainingRect.height() / 2) - (m_iconSize / 2));
+ }
+
+ QPalette actualPalette(option.palette);
+
+ if (index.column() == aznumeric_cast(AssetBrowserEntry::Column::Name))
+ {
+ int thumbX = DrawThumbnail(painter, iconTopLeft, iconSize, entry->GetThumbnailKey());
+ if (sourceEntry)
+ {
+ if (m_showSourceControl)
+ {
+ DrawThumbnail(painter, iconTopLeft, iconSize, sourceEntry->GetSourceControlThumbnailKey());
+ }
+ // sources with no children should be greyed out.
+ if (sourceEntry->GetChildCount() == 0)
+ {
+ isEnabled = false; // draw in disabled style.
+ actualPalette.setCurrentColorGroup(QPalette::Disabled);
+ }
+ }
+ else
+ {
+ //Get the indexes above and below our entry to see what type are they.
+ QAbstractItemView* view = qobject_cast(option.styleObject);
+ const QAbstractItemModel* viewModel = view->model();
+
+ const QModelIndex indexBelow = viewModel->index(index.row() + 1, index.column());
+ const QModelIndex indexAbove = viewModel->index(index.row() - 1, index.column());
+
+ auto aboveEntry = qvariant_cast(indexBelow.data(AssetBrowserModel::Roles::EntryRole));
+ auto belowEntry = qvariant_cast(indexAbove.data(AssetBrowserModel::Roles::EntryRole));
+
+ auto aboveSourceEntry = azrtti_cast(aboveEntry);
+ auto belowSourceEntry = azrtti_cast(belowEntry);
+
+ // if current index is the last entry in the view
+ // or the index above it is a Source Entry and
+ // the index below is invalid or is valid but it is also a source entry
+ // then the current index is the only child.
+ if (index.row() == viewModel->rowCount() - 1 ||
+ (indexBelow.isValid() && aboveSourceEntry &&
+ (!indexAbove.isValid() || (indexAbove.isValid() && belowSourceEntry))))
+ {
+ DrawBranchPixMap(EntryBranchType::OneChild, painter, branchIconTopLeft, iconSize); // Draw One Child Icon
+ }
+ else if (indexBelow.isValid() && aboveSourceEntry) // The index above is a source entry
+ {
+ DrawBranchPixMap(EntryBranchType::Last, painter, branchIconTopLeft, iconSize); // Draw First child Icon
+ }
+ else if (indexAbove.isValid() && belowSourceEntry) // The index below is a source entry
+ {
+ DrawBranchPixMap(EntryBranchType::First, painter, branchIconTopLeft, iconSize); // Draw Last Child Icon
+ }
+ else //the index above and below are also child entries
+ {
+ DrawBranchPixMap(EntryBranchType::Middle, painter, branchIconTopLeft, iconSize); // Draw Default child Icon.
+ }
+ }
+
+ remainingRect.adjust(thumbX, 0, 0, 0); // bump it to the right by the size of the thumbnail
+ remainingRect.adjust(EntrySpacingLeftPixels, 0, 0, 0); // bump it to the right by the spacing.
+ }
+ QString displayString = index.column() == aznumeric_cast(AssetBrowserEntry::Column::Name)
+ ? qvariant_cast(entry->data(aznumeric_cast(AssetBrowserEntry::Column::Name)))
+ : qvariant_cast(entry->data(aznumeric_cast(AssetBrowserEntry::Column::Path)));
+
+ style->drawItemText(
+ painter, remainingRect, option.displayAlignment, actualPalette, isEnabled, displayString,
+ isSelected ? QPalette::HighlightedText : QPalette::Text);
+ }
+ }
+
+ void SearchEntryDelegate::LoadBranchPixMaps()
+ {
+ AZ::IO::BasicPath absoluteIconPath;
+ for (int branchType = EntryBranchType::First; branchType != EntryBranchType::Count; ++branchType)
+ {
+ QPixmap pixmap;
+ switch (branchType)
+ {
+ case AzToolsFramework::AssetBrowser::EntryBranchType::First:
+ absoluteIconPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / TreeIconPathFirst;
+ break;
+ case AzToolsFramework::AssetBrowser::EntryBranchType::Middle:
+ absoluteIconPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / TreeIconPathMiddle;
+ break;
+ case AzToolsFramework::AssetBrowser::EntryBranchType::Last:
+ absoluteIconPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / TreeIconPathLast;
+ break;
+ case AzToolsFramework::AssetBrowser::EntryBranchType::OneChild:
+ default:
+ absoluteIconPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / TreeIconPathOneChild;
+ break;
+ }
+ bool pixmapLoadedSuccess = pixmap.load(absoluteIconPath.c_str());
+ AZ_Assert(pixmapLoadedSuccess, "Error loading Branch Icons in SearchEntryDelegate");
+
+ m_branchIcons[static_cast(branchType)] = pixmap;
+
+ }
+ }
+
+ void SearchEntryDelegate::DrawBranchPixMap(
+ EntryBranchType branchType, QPainter* painter, const QPoint& point, const QSize& size) const
+ {
+ const QPixmap& pixmap = m_branchIcons[branchType];
+
+ pixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ const QSize sizeDelta = size - pixmap.size();
+ const QPoint pointDelta = QPoint(sizeDelta.width() / 2, sizeDelta.height() / 2);
+ painter->drawPixmap(point + pointDelta, pixmap);
+ }
+
+ } // namespace AssetBrowser
} // namespace AzToolsFramework
#include "AssetBrowser/Views/moc_EntryDelegate.cpp"
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.h b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.h
index 643bedec7c..ac68c19248 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.h
@@ -27,9 +27,19 @@ namespace AzToolsFramework
{
namespace AssetBrowser
{
+ //! Type of branch icon the delegate should paint.
+ enum EntryBranchType
+ {
+ First,
+ Middle,
+ Last,
+ OneChild,
+ Count
+ };
+
class AssetBrowserFilterModel;
- //! EntryDelegate draws a single item in AssetBrowser
+ //! EntryDelegate draws a single item in AssetBrowser.
class EntryDelegate
: public QStyledItemDelegate
{
@@ -52,5 +62,23 @@ namespace AzToolsFramework
//! Draw a thumbnail and return its width
int DrawThumbnail(QPainter* painter, const QPoint& point, const QSize& size, Thumbnailer::SharedThumbnailKey thumbnailKey) const;
};
+
+ //! SearchEntryDelegate draws a single item in AssetBrowserTableView.
+ class SearchEntryDelegate
+ : public EntryDelegate
+ {
+ Q_OBJECT
+ public:
+ explicit SearchEntryDelegate(QWidget* parent = nullptr);
+
+ void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
+
+ private:
+ void LoadBranchPixMaps();
+ void DrawBranchPixMap(EntryBranchType branchType, QPainter* painter, const QPoint& point, const QSize& size) const;
+
+ private:
+ QMap m_branchIcons;
+ };
} // namespace AssetBrowser
} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp
index 97f52e253b..b68d086892 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -69,6 +70,7 @@ namespace AzToolsFramework
Components::EditorSelectionAccentSystemComponent::CreateDescriptor(),
EditorEntityContextComponent::CreateDescriptor(),
EditorEntityFixupComponent::CreateDescriptor(),
+ FocusModeSystemComponent::CreateDescriptor(),
SliceMetadataEntityContextComponent::CreateDescriptor(),
SliceRequestComponent::CreateDescriptor(),
Prefab::PrefabSystemComponent::CreateDescriptor(),
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h
new file mode 100644
index 0000000000..a4b90f95b7
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h
@@ -0,0 +1,39 @@
+/*
+ * 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
+{
+ //! FocusModeInterface
+ //! Interface to handle the Editor Focus Mode.
+ class FocusModeInterface
+ {
+ public:
+ AZ_RTTI(FocusModeInterface, "{437243B0-F86B-422F-B7B8-4A21CC000702}");
+
+ //! Sets the root entity the Editor should focus on.
+ //! The Editor will only allow the user to select entities that are descendants of the EntityId provided.
+ //! @param entityId The entityId that will become the new focus root.
+ virtual void SetFocusRoot(AZ::EntityId entityId) = 0;
+
+ //! Clears the Editor focus, allowing the user to select the whole level again.
+ virtual void ClearFocusRoot() = 0;
+
+ //! Returns the entity id of the root of the current Editor focus.
+ //! @return The entity id of the root of the Editor focus, or an invalid entity id if no focus is set.
+ virtual AZ::EntityId GetFocusRoot() = 0;
+
+ //! Returns whether the entity id provided is part of the focused sub-tree.
+ virtual bool IsInFocusSubTree(AZ::EntityId entityId) const = 0;
+ };
+
+} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp
new file mode 100644
index 0000000000..e1c04fcb93
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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 AzToolsFramework
+{
+ bool IsInFocusSubTree(AZ::EntityId entityId, AZ::EntityId focusRootId)
+ {
+ if (entityId == AZ::EntityId())
+ {
+ return false;
+ }
+
+ if (entityId == focusRootId)
+ {
+ return true;
+ }
+
+ AZ::EntityId parentId;
+ AZ::TransformBus::EventResult(parentId, entityId, &AZ::TransformInterface::GetParentId);
+
+ return IsInFocusSubTree(parentId, focusRootId);
+ }
+
+ void FocusModeSystemComponent::Init()
+ {
+ }
+
+ void FocusModeSystemComponent::Activate()
+ {
+ AZ::Interface::Register(this);
+ }
+
+ void FocusModeSystemComponent::Deactivate()
+ {
+ AZ::Interface::Unregister(this);
+ }
+
+ void FocusModeSystemComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context)
+ {
+ }
+
+ void FocusModeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+ {
+ provided.push_back(AZ_CRC_CE("EditorFocusMode"));
+ }
+
+ void FocusModeSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+ {
+ }
+
+ void FocusModeSystemComponent::GetIncompatibleServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+ {
+ }
+
+ void FocusModeSystemComponent::SetFocusRoot(AZ::EntityId entityId)
+ {
+ m_focusRoot = entityId;
+
+ // TODO - If m_focusRoot != AZ::EntityId(), activate focus mode via ViewportEditorModeTrackerInterface; else, deactivate focus mode
+ }
+
+ void FocusModeSystemComponent::ClearFocusRoot()
+ {
+ SetFocusRoot(AZ::EntityId());
+ }
+
+ AZ::EntityId FocusModeSystemComponent::GetFocusRoot()
+ {
+ return m_focusRoot;
+ }
+
+ bool FocusModeSystemComponent::IsInFocusSubTree(AZ::EntityId entityId) const
+ {
+ if (m_focusRoot == AZ::EntityId())
+ {
+ return true;
+ }
+
+ return AzToolsFramework::IsInFocusSubTree(entityId, m_focusRoot);
+ }
+
+} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h
new file mode 100644
index 0000000000..27ddebe1ed
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h
@@ -0,0 +1,51 @@
+/*
+ * 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
+{
+ bool IsInFocusSubTree(AZ::EntityId entityId, AZ::EntityId focusRootId);
+
+ //! System Component to handle the Editor Focus Mode system
+ class FocusModeSystemComponent final
+ : public AZ::Component
+ , private FocusModeInterface
+ {
+ public:
+ AZ_COMPONENT(FocusModeSystemComponent, "{6CE522FE-2057-4794-BD05-61E04BD8EA30}");
+
+ FocusModeSystemComponent() = default;
+ virtual ~FocusModeSystemComponent() = default;
+
+ // AZ::Component overrides ...
+ void Init() override;
+ void Activate() override;
+ void Deactivate() override;
+
+ static void Reflect(AZ::ReflectContext* context);
+ static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+ static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+ static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+
+ // FocusModeInterface overrides ...
+ void SetFocusRoot(AZ::EntityId entityId) override;
+ void ClearFocusRoot() override;
+ AZ::EntityId GetFocusRoot() override;
+ bool IsInFocusSubTree(AZ::EntityId entityId) const override;
+
+ private:
+ AZ::EntityId m_focusRoot;
+ };
+
+} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp
new file mode 100644
index 0000000000..d69d0004cc
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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
+#include
+
+namespace AzToolsFramework::Prefab
+{
+ PrefabFocusHandler::PrefabFocusHandler()
+ {
+ m_instanceEntityMapperInterface = AZ::Interface::Get();
+ AZ_Assert(
+ m_instanceEntityMapperInterface,
+ "Prefab - PrefabFocusHandler - "
+ "Instance Entity Mapper Interface could not be found. "
+ "Check that it is being correctly initialized.");
+
+ AZ::Interface::Register(this);
+ }
+
+ PrefabFocusHandler::~PrefabFocusHandler()
+ {
+ AZ::Interface::Unregister(this);
+ }
+
+ PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId)
+ {
+ InstanceOptionalReference focusedInstance;
+
+ if (!entityId.IsValid())
+ {
+ PrefabEditorEntityOwnershipInterface* prefabEditorEntityOwnershipInterface =
+ AZ::Interface::Get();
+
+ if(!prefabEditorEntityOwnershipInterface)
+ {
+ return AZ::Failure(AZStd::string("Could not focus on root prefab instance - internal error "
+ "(PrefabEditorEntityOwnershipInterface unavailable)."));
+ }
+
+ focusedInstance = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance();
+ }
+ else
+ {
+ focusedInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
+ }
+
+ if (!focusedInstance.has_value())
+ {
+ return AZ::Failure(AZStd::string(
+ "Prefab Focus Handler: Couldn't find owning instance of entityId provided."));
+ }
+
+ m_focusedInstance = focusedInstance;
+ m_focusedTemplateId = focusedInstance->get().GetTemplateId();
+
+ FocusModeInterface* focusModeInterface = AZ::Interface::Get();
+ if (focusModeInterface)
+ {
+ focusModeInterface->SetFocusRoot(focusedInstance->get().GetContainerEntityId());
+ }
+
+ return AZ::Success();
+ }
+
+ TemplateId PrefabFocusHandler::GetFocusedPrefabTemplateId()
+ {
+ return m_focusedTemplateId;
+ }
+
+ InstanceOptionalReference PrefabFocusHandler::GetFocusedPrefabInstance()
+ {
+ return m_focusedInstance;
+ }
+
+ bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId)
+ {
+ if (!m_focusedInstance.has_value())
+ {
+ // PrefabFocusHandler has not been initialized yet.
+ return false;
+ }
+
+ if (!entityId.IsValid())
+ {
+ return false;
+ }
+
+ InstanceOptionalReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
+
+ return instance.has_value() && (&instance->get() == &m_focusedInstance->get());
+ }
+
+} // namespace AzToolsFramework::Prefab
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h
new file mode 100644
index 0000000000..fa7727894d
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h
@@ -0,0 +1,44 @@
+/*
+ * 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
+
+namespace AzToolsFramework::Prefab
+{
+ class InstanceEntityMapperInterface;
+
+ //! Handles Prefab Focus mode, determining which prefab file entity changes will target.
+ class PrefabFocusHandler final
+ : private PrefabFocusInterface
+ {
+ public:
+ AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0);
+
+ PrefabFocusHandler();
+ ~PrefabFocusHandler();
+
+ // PrefabFocusInterface override ...
+ PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override;
+ TemplateId GetFocusedPrefabTemplateId() override;
+ InstanceOptionalReference GetFocusedPrefabInstance() override;
+ bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) override;
+
+ private:
+ InstanceOptionalReference m_focusedInstance;
+ TemplateId m_focusedTemplateId;
+
+ InstanceEntityMapperInterface* m_instanceEntityMapperInterface;
+ };
+
+} // namespace AzToolsFramework::Prefab
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h
new file mode 100644
index 0000000000..833a5ef7c8
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.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
+#include
+
+namespace AzToolsFramework::Prefab
+{
+ using PrefabFocusOperationResult = AZ::Outcome;
+
+ //! Interface to handle operations related to the Prefab Focus system.
+ class PrefabFocusInterface
+ {
+ public:
+ AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}");
+
+ //! Set the focused prefab instance to the owning instance of the entityId provided.
+ //! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
+ virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0;
+
+ //! Returns the template id of the instance the prefab system is focusing on.
+ virtual TemplateId GetFocusedPrefabTemplateId() = 0;
+
+ //! Returns a reference to the instance the prefab system is focusing on.
+ virtual InstanceOptionalReference GetFocusedPrefabInstance() = 0;
+
+ //! Returns whether the entity belongs to the instance that is being focused on, or one of its descendants.
+ //! @param entityId The entityId of the queried entity.
+ //! @return true if the entity belongs to the focused instance or one of its descendants, false otherwise.
+ virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) = 0;
+ };
+
+} // namespace AzToolsFramework::Prefab
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h
index 42b6bc1666..f3c0e67d46 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h
@@ -13,6 +13,7 @@
#include
#include
+#include
#include
#include
#include
@@ -189,6 +190,9 @@ namespace AzToolsFramework
PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
+ // Handles the Prefab Focus API that determines what prefab is being edited.
+ PrefabFocusHandler m_prefabFocusHandler;
+
// Caches entity states for undo/redo purposes
PrefabUndoCache m_prefabUndoCache;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h
index d6908922c9..746c306c2b 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h
@@ -369,7 +369,7 @@ namespace AzToolsFramework
// A counter for generating unique Link Ids.
AZStd::atomic m_linkIdCounter = 0u;
- // Used for finding the owning instance of an arbitrary entity
+ // Used for finding the owning instance of an arbitrary entity.
InstanceEntityMapper m_instanceEntityMapper;
// Used for finding the Instances owned by an arbitrary Template.
@@ -378,16 +378,16 @@ namespace AzToolsFramework
// Used for loading/saving Prefab Template files.
PrefabLoader m_prefabLoader;
- // Handler the public Prefab API used by UI and scripting
+ // Handles the public Prefab API used by UI and scripting.
PrefabPublicHandler m_prefabPublicHandler;
// Used for updating Instances of Prefab Template.
InstanceUpdateExecutor m_instanceUpdateExecutor;
- // Used for updating Templates when Instances are modified
+ // Used for updating Templates when Instances are modified.
InstanceToTemplatePropagator m_instanceToTemplatePropagator;
- // Handler of the public Prefab requests
+ // Handler of the public Prefab requests.
PrefabPublicRequestHandler m_prefabPublicRequestHandler;
};
} // namespace Prefab
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss
index 15ff8b1b67..b3c5882334 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss
@@ -18,18 +18,19 @@ AzToolsFramework--EntityOutlinerWidget QTreeView
selection-background-color: transparent;
}
+
+/*
+ * Entity Outliner handles hover and selected state of items via code,
+ * so we need to override the AzQtComponents::Treeview style.
+ */
AzToolsFramework--EntityOutlinerWidget QTreeView::branch:hover
, AzToolsFramework--EntityOutlinerWidget QTreeView::item:hover
-{
- background: rgba(255, 255, 255, 30);
-}
-
-AzToolsFramework--EntityOutlinerWidget QTreeView::branch:selected
+, AzToolsFramework--EntityOutlinerWidget QTreeView::branch:selected
, AzToolsFramework--EntityOutlinerWidget QTreeView::item:selected
, AzToolsFramework--EntityOutlinerWidget QTreeView::branch:selected:active
, AzToolsFramework--EntityOutlinerWidget QTreeView::item:selected:active
{
- background: rgba(255, 255, 255, 45);
+ background: transparent;
}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp
index d9f3ff8bb8..90f8e1d121 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp
@@ -44,6 +44,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -102,10 +103,14 @@ namespace AzToolsFramework
EntityCompositionNotificationBus::Handler::BusConnect();
AZ::EntitySystemBus::Handler::BusConnect();
- m_editorEntityFrameworkInterface = AZ::Interface::Get();
+ m_editorEntityUiInterface = AZ::Interface::Get();
+ AZ_Assert(m_editorEntityUiInterface != nullptr,
+ "EntityOutlinerListModel requires a EditorEntityUiInterface instance on Initialize.");
- AZ_Assert(m_editorEntityFrameworkInterface != nullptr,
- "EntityOutlinerListModel requires a EditorEntityFrameworkInterface instance on Initialize.");
+ m_focusModeInterface = AZ::Interface::Get();
+ AZ_Assert(
+ m_focusModeInterface != nullptr,
+ "EntityOutlinerListModel requires a FocusModeInterface instance on Initialize.");
}
int EntityOutlinerListModel::rowCount(const QModelIndex& parent) const
@@ -279,7 +284,7 @@ namespace AzToolsFramework
QVariant EntityOutlinerListModel::GetEntityIcon(const AZ::EntityId& id) const
{
- auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(id);
+ auto entityUiHandler = m_editorEntityUiInterface->GetHandler(id);
QIcon icon;
// Retrieve the icon from the handler
@@ -316,7 +321,7 @@ namespace AzToolsFramework
QVariant EntityOutlinerListModel::GetEntityTooltip(const AZ::EntityId& id) const
{
- auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(id);
+ auto entityUiHandler = m_editorEntityUiInterface->GetHandler(id);
QString tooltip;
// Retrieve the tooltip from the handler
@@ -349,7 +354,7 @@ namespace AzToolsFramework
QVariant EntityOutlinerListModel::dataForVisibility(const QModelIndex& index, int role) const
{
auto entityId = GetEntityFromIndex(index);
- auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(entityId);
+ auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId);
if (!entityUiHandler || entityUiHandler->CanToggleLockVisibility(entityId))
{
@@ -377,7 +382,7 @@ namespace AzToolsFramework
QVariant EntityOutlinerListModel::dataForLock(const QModelIndex& index, int role) const
{
auto entityId = GetEntityFromIndex(index);
- auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(entityId);
+ auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId);
if (!entityUiHandler || entityUiHandler->CanToggleLockVisibility(entityId))
{
@@ -436,7 +441,7 @@ namespace AzToolsFramework
if (value.canConvert())
{
const auto entityId = GetEntityFromIndex(index);
- auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(entityId);
+ auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId);
if (!entityUiHandler || entityUiHandler->CanToggleLockVisibility(entityId))
{
@@ -532,6 +537,11 @@ namespace AzToolsFramework
break;
}
+ if (AZ::EntityId entityId = GetEntityFromIndex(index); !m_focusModeInterface->IsInFocusSubTree(entityId))
+ {
+ itemFlags &= !Qt::ItemIsEnabled;
+ }
+
return itemFlags;
}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx
index 25e4276dd4..4b13aa6d27 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx
@@ -36,6 +36,7 @@
namespace AzToolsFramework
{
class EditorEntityUiInterface;
+ class FocusModeInterface;
namespace EntityOutliner
{
@@ -273,7 +274,8 @@ namespace AzToolsFramework
QVariant GetEntityIcon(const AZ::EntityId& id) const;
QVariant GetEntityTooltip(const AZ::EntityId& id) const;
- EditorEntityUiInterface* m_editorEntityFrameworkInterface = nullptr;
+ EditorEntityUiInterface* m_editorEntityUiInterface = nullptr;
+ FocusModeInterface* m_focusModeInterface = nullptr;
};
class EntityOutlinerCheckBox
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp
index 0549c600d3..2d20ac7364 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp
@@ -38,6 +38,8 @@ namespace AzToolsFramework
AZ_Assert((m_editorEntityFrameworkInterface != nullptr),
"EntityOutlinerTreeView requires a EditorEntityFrameworkInterface instance on Construction.");
+
+ viewport()->setMouseTracking(true);
}
EntityOutlinerTreeView::~EntityOutlinerTreeView()
@@ -59,6 +61,11 @@ namespace AzToolsFramework
}
}
+ void EntityOutlinerTreeView::leaveEvent([[maybe_unused]] QEvent* event)
+ {
+ m_mousePosition = QPoint();
+ }
+
void EntityOutlinerTreeView::mousePressEvent(QMouseEvent* event)
{
//postponing normal mouse pressed logic until mouse is released or dragged
@@ -112,6 +119,8 @@ namespace AzToolsFramework
setSelectionMode(selectionModeBefore);
}
+ m_mousePosition = event->pos();
+
//process mouse movement as normal, potentially triggering drag and drop
QTreeView::mouseMoveEvent(event);
}
@@ -172,12 +181,45 @@ namespace AzToolsFramework
void EntityOutlinerTreeView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const
{
+ const bool isEnabled = (this->model()->flags(index) & Qt::ItemIsEnabled);
+
+ const bool isSelected = selectionModel()->isSelected(index);
+ const bool isHovered = (index == indexAt(m_mousePosition)) && isEnabled;
+
+ // Paint the branch Selection/Hover Rect
+ PaintBranchSelectionHoverRect(painter, rect, isSelected, isHovered);
+
// Paint the branch background as defined by the entity's handler, or its closes ancestor's.
PaintBranchBackground(painter, rect, index);
QTreeView::drawBranches(painter, rect, index);
}
+ void EntityOutlinerTreeView::PaintBranchSelectionHoverRect(
+ QPainter* painter, const QRect& rect, bool isSelected, bool isHovered) const
+ {
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing, false);
+
+ if (isSelected || isHovered)
+ {
+ QPainterPath backgroundPath;
+ QRect backgroundRect(rect);
+
+ backgroundPath.addRect(backgroundRect);
+
+ QColor backgroundColor = m_hoverColor;
+ if (isSelected)
+ {
+ backgroundColor = m_selectedColor;
+ }
+
+ painter->fillPath(backgroundPath, backgroundColor);
+ }
+
+ painter->restore();
+ }
+
void EntityOutlinerTreeView::PaintBranchBackground(QPainter* painter, const QRect& rect, const QModelIndex& index) const
{
// Go through ancestors and add them to the stack
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx
index 66cd082407..5b0134366b 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx
@@ -59,6 +59,7 @@ namespace AzToolsFramework
void startDrag(Qt::DropActions supportedActions) override;
void dragMoveEvent(QDragMoveEvent* event) override;
void dropEvent(QDropEvent* event) override;
+ void leaveEvent(QEvent* event) override;
//! Renders the left side of the item: appropriate background, branch lines, icons.
void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override;
@@ -72,8 +73,10 @@ namespace AzToolsFramework
void StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions) override;
void PaintBranchBackground(QPainter* painter, const QRect& rect, const QModelIndex& index) const;
+ void PaintBranchSelectionHoverRect(QPainter* painter, const QRect& rect, bool isSelected, bool isHovered) const;
QMouseEvent* m_queuedMouseEvent;
+ QPoint m_mousePosition;
bool m_draggingUnselectedItem; // This is set when an item is dragged outside its bounding box.
int m_expandOnlyDelay = -1;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp
index c75f6aa86d..bb6bdf0ebd 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp
@@ -8,7 +8,6 @@
#include
-#include
#include
#include
@@ -24,14 +23,6 @@ namespace AzToolsFramework
LevelRootUiHandler::LevelRootUiHandler()
{
- m_prefabEditInterface = AZ::Interface::Get();
-
- if (m_prefabEditInterface == nullptr)
- {
- AZ_Assert(false, "LevelRootUiHandler - could not get PrefabEditInterface on LevelRootUiHandler construction.");
- return;
- }
-
m_prefabPublicInterface = AZ::Interface::Get();
if (m_prefabPublicInterface == nullptr)
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h
index 6eeccfe88d..1e485572b8 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h
@@ -14,7 +14,6 @@ namespace AzToolsFramework
{
namespace Prefab
{
- class PrefabEditInterface;
class PrefabPublicInterface;
};
@@ -36,7 +35,6 @@ namespace AzToolsFramework
void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
private:
- Prefab::PrefabEditInterface* m_prefabEditInterface = nullptr;
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
static constexpr int m_levelRootBorderThickness = 1;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditInterface.h
deleted file mode 100644
index 1bd4a164f8..0000000000
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditInterface.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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
-{
- namespace Prefab
- {
- /*!
- * PrefabEditInterface
- * Interface to expose the API to Edit Prefabs in the Editor.
- */
- class PrefabEditInterface
- {
- public:
- AZ_RTTI(PrefabEditInterface, "{DABB1D43-3760-420E-9F1E-5104F0AFF167}");
-
- /**
- * Sets the prefab for the instance owning the entity provided as the prefab being edited.
- * @param entityId The entity whose owning prefab should be edited.
- */
- virtual void EditOwningPrefab(AZ::EntityId entityId) = 0;
-
- /**
- * Queries the Edit Manager to know if the provided entity is part of the prefab currently being edited.
- * @param entityId The entity whose prefab editing state we want to query.
- * @return True if the prefab owning this entity is being edited, false otherwise.
- */
- virtual bool IsOwningPrefabBeingEdited(AZ::EntityId entityId) = 0;
- };
-
- } // namespace Prefab
-} // namespace AzToolsFramework
-
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.cpp
deleted file mode 100644
index 27a4c3bcaf..0000000000
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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
-{
- namespace Prefab
- {
- PrefabEditManager::PrefabEditManager()
- {
- m_prefabPublicInterface = AZ::Interface::Get();
-
- if (m_prefabPublicInterface == nullptr)
- {
- AZ_Assert(false, "Prefab - could not get PrefabPublicInterface on PrefabEditManager construction.");
- return;
- }
-
- AZ::Interface::Register(this);
- }
-
- PrefabEditManager::~PrefabEditManager()
- {
- AZ::Interface::Unregister(this);
- }
-
- void PrefabEditManager::EditOwningPrefab(AZ::EntityId entityId)
- {
- m_instanceBeingEdited = m_prefabPublicInterface->GetInstanceContainerEntityId(entityId);
- }
-
- bool PrefabEditManager::IsOwningPrefabBeingEdited(AZ::EntityId entityId)
- {
- AZ::EntityId containerEntity = m_prefabPublicInterface->GetInstanceContainerEntityId(entityId);
- return m_instanceBeingEdited == containerEntity;
- }
- }
-}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.h
deleted file mode 100644
index d155322e3c..0000000000
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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
-
-namespace AzToolsFramework
-{
- namespace Prefab
- {
- class PrefabEditManager final
- : private PrefabEditInterface
- {
- public:
- AZ_CLASS_ALLOCATOR(PrefabEditManager, AZ::SystemAllocator, 0);
-
- PrefabEditManager();
- ~PrefabEditManager();
-
- private:
- // PrefabEditInterface...
- void EditOwningPrefab(AZ::EntityId entityId) override;
- bool IsOwningPrefabBeingEdited(AZ::EntityId entityId) override;
-
- AZ::EntityId m_instanceBeingEdited;
-
- PrefabPublicInterface* m_prefabPublicInterface;
- };
- }
-}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp
index 0c61ccb501..9b7f2fff10 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -57,9 +58,9 @@ namespace AzToolsFramework
{
EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr;
- PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr;
- PrefabEditInterface* PrefabIntegrationManager::s_prefabEditInterface = nullptr;
+ PrefabFocusInterface* PrefabIntegrationManager::s_prefabFocusInterface = nullptr;
PrefabLoaderInterface* PrefabIntegrationManager::s_prefabLoaderInterface = nullptr;
+ PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr;
PrefabSystemComponentInterface* PrefabIntegrationManager::s_prefabSystemComponentInterface = nullptr;
const AZStd::string PrefabIntegrationManager::s_prefabFileExtension = ".prefab";
@@ -102,13 +103,6 @@ namespace AzToolsFramework
return;
}
- s_prefabEditInterface = AZ::Interface::Get();
- if (s_prefabEditInterface == nullptr)
- {
- AZ_Assert(false, "Prefab - could not get PrefabEditInterface on PrefabIntegrationManager construction.");
- return;
- }
-
s_prefabLoaderInterface = AZ::Interface::Get();
if (s_prefabLoaderInterface == nullptr)
{
@@ -123,6 +117,13 @@ namespace AzToolsFramework
return;
}
+ s_prefabFocusInterface = AZ::Interface::Get();
+ if (s_prefabFocusInterface == nullptr)
+ {
+ AZ_Assert(false, "Prefab - could not get PrefabFocusInterface on PrefabIntegrationManager construction.");
+ return;
+ }
+
EditorContextMenuBus::Handler::BusConnect();
PrefabInstanceContainerNotificationBus::Handler::BusConnect();
AZ::Interface::Register(this);
@@ -224,7 +225,7 @@ namespace AzToolsFramework
// Edit Prefab
if (prefabWipFeaturesEnabled)
{
- bool beingEdited = s_prefabEditInterface->IsOwningPrefabBeingEdited(selectedEntity);
+ bool beingEdited = s_prefabFocusInterface->IsOwningPrefabBeingFocused(selectedEntity);
if (!beingEdited)
{
@@ -428,7 +429,7 @@ namespace AzToolsFramework
void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity)
{
- s_prefabEditInterface->EditOwningPrefab(containerEntity);
+ s_prefabFocusInterface->FocusOnOwningPrefab(containerEntity);
}
void PrefabIntegrationManager::ContextMenu_SavePrefab(AZ::EntityId containerEntity)
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h
index 3e66350932..6f66b1f514 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h
@@ -17,7 +17,7 @@
#include
#include
#include
-#include
+
#include
#include
#include
@@ -28,7 +28,7 @@ namespace AzToolsFramework
{
namespace Prefab
{
-
+ class PrefabFocusInterface;
class PrefabLoaderInterface;
//! Structure for saving/retrieving user settings related to prefab workflows.
@@ -80,9 +80,6 @@ namespace AzToolsFramework
void ExecuteSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference) override;
private:
- // Manages the Edit Mode UI for prefabs
- PrefabEditManager m_prefabEditManager;
-
// Used to handle the UI for the level root
LevelRootUiHandler m_levelRootUiHandler;
@@ -135,13 +132,12 @@ namespace AzToolsFramework
AZStd::unique_ptr ConstructSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference);
void SavePrefabsInDialog(QDialog* unsavedPrefabsDialog);
-
static const AZStd::string s_prefabFileExtension;
static EditorEntityUiInterface* s_editorEntityUiInterface;
- static PrefabPublicInterface* s_prefabPublicInterface;
- static PrefabEditInterface* s_prefabEditInterface;
+ static PrefabFocusInterface* s_prefabFocusInterface;
static PrefabLoaderInterface* s_prefabLoaderInterface;
+ static PrefabPublicInterface* s_prefabPublicInterface;
static PrefabSystemComponentInterface* s_prefabSystemComponentInterface;
};
}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp
index fda56b79f1..c802850c4b 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp
@@ -8,7 +8,7 @@
#include
-#include
+#include
#include
#include
@@ -26,21 +26,19 @@ namespace AzToolsFramework
PrefabUiHandler::PrefabUiHandler()
{
- m_prefabEditInterface = AZ::Interface::Get();
-
- if (m_prefabEditInterface == nullptr)
- {
- AZ_Assert(false, "PrefabUiHandler - could not get PrefabEditInterface on PrefabUiHandler construction.");
- return;
- }
-
m_prefabPublicInterface = AZ::Interface::Get();
-
if (m_prefabPublicInterface == nullptr)
{
AZ_Assert(false, "PrefabUiHandler - could not get PrefabPublicInterface on PrefabUiHandler construction.");
return;
}
+
+ m_prefabFocusInterface = AZ::Interface::Get();
+ if (m_prefabFocusInterface == nullptr)
+ {
+ AZ_Assert(false, "PrefabUiHandler - could not get PrefabFocusInterface on PrefabUiHandler construction.");
+ return;
+ }
}
QString PrefabUiHandler::GenerateItemInfoString(AZ::EntityId entityId) const
@@ -83,7 +81,7 @@ namespace AzToolsFramework
QIcon PrefabUiHandler::GenerateItemIcon(AZ::EntityId entityId) const
{
- if (m_prefabEditInterface->IsOwningPrefabBeingEdited(entityId))
+ if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId))
{
return QIcon(m_prefabEditIconPath);
}
@@ -105,7 +103,7 @@ namespace AzToolsFramework
const bool hasVisibleChildren = index.data(EntityOutlinerListModel::ExpandedRole).value() && index.model()->hasChildren(index);
QColor backgroundColor = m_prefabCapsuleColor;
- if (m_prefabEditInterface->IsOwningPrefabBeingEdited(entityId))
+ if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId))
{
backgroundColor = m_prefabCapsuleEditColor;
}
@@ -191,7 +189,7 @@ namespace AzToolsFramework
const bool isLastColumn = descendantIndex.column() == EntityOutlinerListModel::ColumnLockToggle;
QColor borderColor = m_prefabCapsuleColor;
- if (m_prefabEditInterface->IsOwningPrefabBeingEdited(entityId))
+ if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId))
{
borderColor = m_prefabCapsuleEditColor;
}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h
index d900fae427..547c100eb1 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h
@@ -12,9 +12,10 @@
namespace AzToolsFramework
{
+
namespace Prefab
{
- class PrefabEditInterface;
+ class PrefabFocusInterface;
class PrefabPublicInterface;
};
@@ -37,7 +38,7 @@ namespace AzToolsFramework
const QModelIndex& descendantIndex) const override;
private:
- Prefab::PrefabEditInterface* m_prefabEditInterface = nullptr;
+ Prefab::PrefabFocusInterface* m_prefabFocusInterface = nullptr;
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
static bool IsLastVisibleChild(const QModelIndex& parent, const QModelIndex& child);
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp
index 25b10463dc..57e6c9ff7a 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp
@@ -13,6 +13,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -112,6 +113,17 @@ namespace AzToolsFramework
}
}
+ EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache)
+ : m_entityDataCache(entityDataCache)
+ {
+ m_focusModeInterface = AZ::Interface::Get();
+ AZ_Assert(
+ m_focusModeInterface,
+ "EditorHelpers - "
+ "Focus Mode Interface could not be found. "
+ "Check that it is being correctly initialized.");
+ }
+
AZ::EntityId EditorHelpers::HandleMouseInteraction(
const AzFramework::CameraState& cameraState, const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
@@ -173,6 +185,12 @@ namespace AzToolsFramework
}
}
+ // Verify if the entity Id corresponds to an entity that is focused; if not, halt selection.
+ if (!m_focusModeInterface->IsInFocusSubTree(entityIdUnderCursor))
+ {
+ return AZ::EntityId();
+ }
+
return entityIdUnderCursor;
}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h
index 0dbb1d0f6c..a6a78a4e61 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h
@@ -22,6 +22,7 @@ namespace AzFramework
namespace AzToolsFramework
{
class EditorVisibleEntityDataCache;
+ class FocusModeInterface;
namespace ViewportInteraction
{
@@ -38,10 +39,7 @@ namespace AzToolsFramework
//! An EditorVisibleEntityDataCache must be passed to EditorHelpers to allow it to
//! efficiently read entity data without resorting to EBus calls.
- explicit EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache)
- : m_entityDataCache(entityDataCache)
- {
- }
+ explicit EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache);
EditorHelpers(const EditorHelpers&) = delete;
EditorHelpers& operator=(const EditorHelpers&) = delete;
~EditorHelpers() = default;
@@ -62,5 +60,6 @@ namespace AzToolsFramework
private:
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers.
+ const FocusModeInterface* m_focusModeInterface = nullptr;
};
} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp
index 27a77f9ce7..8157d369d2 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp
@@ -3642,7 +3642,7 @@ namespace AzToolsFramework
}
}
- void EditorTransformComponentSelection::OnViewportViewEntityChanged(const AZ::EntityId& newViewId)
+ void EditorTransformComponentSelection::OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
@@ -3650,12 +3650,12 @@ namespace AzToolsFramework
// match the editor camera translation/orientation), record the entity id if we have
// a manipulator tracking it (entity id exists in m_entityIdManipulator lookups)
// and remove it when recreating manipulators (see InitializeManipulators)
- if (newViewId.IsValid())
+ if (viewEntityId.IsValid())
{
- const auto entityIdLookupIt = m_entityIdManipulators.m_lookups.find(newViewId);
+ const auto entityIdLookupIt = m_entityIdManipulators.m_lookups.find(viewEntityId);
if (entityIdLookupIt != m_entityIdManipulators.m_lookups.end())
{
- m_editorCameraComponentEntityId = newViewId;
+ m_editorCameraComponentEntityId = viewEntityId;
RegenerateManipulators();
}
}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h
index 935e80951a..6cc89b2d10 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h
@@ -270,7 +270,7 @@ namespace AzToolsFramework
void OnTransformChanged(const AZ::Transform& localTM, const AZ::Transform& worldTM) override;
// Camera::EditorCameraNotificationBus overrides ...
- void OnViewportViewEntityChanged(const AZ::EntityId& newViewId) override;
+ void OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) override;
// EditorContextVisibilityNotificationBus overrides ...
void OnEntityVisibilityChanged(bool visibility) override;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake
index 3932190d8e..88febef8c2 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake
@@ -149,6 +149,9 @@ set(FILES
Entity/SliceEditorEntityOwnershipServiceBus.h
Fingerprinting/TypeFingerprinter.h
Fingerprinting/TypeFingerprinter.cpp
+ FocusMode/FocusModeInterface.h
+ FocusMode/FocusModeSystemComponent.h
+ FocusMode/FocusModeSystemComponent.cpp
Logger/TraceLogger.cpp
Logger/TraceLogger.h
Manipulators/AngularManipulator.cpp
@@ -629,6 +632,9 @@ set(FILES
Prefab/PrefabDomTypes.h
Prefab/PrefabDomUtils.h
Prefab/PrefabDomUtils.cpp
+ Prefab/PrefabFocusHandler.h
+ Prefab/PrefabFocusHandler.cpp
+ Prefab/PrefabFocusInterface.h
Prefab/PrefabIdTypes.h
Prefab/PrefabLoader.h
Prefab/PrefabLoader.cpp
@@ -721,9 +727,6 @@ set(FILES
UI/Layer/LayerUiHandler.cpp
UI/Prefab/LevelRootUiHandler.h
UI/Prefab/LevelRootUiHandler.cpp
- UI/Prefab/PrefabEditInterface.h
- UI/Prefab/PrefabEditManager.h
- UI/Prefab/PrefabEditManager.cpp
UI/Prefab/PrefabIntegrationBus.h
UI/Prefab/PrefabIntegrationManager.h
UI/Prefab/PrefabIntegrationManager.cpp
diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp
new file mode 100644
index 0000000000..5972da58a7
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp
@@ -0,0 +1,128 @@
+/*
+ * 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
+#include
+#include
+
+namespace AzToolsFramework
+{
+ class EditorFocusModeTests
+ : public ::testing::Test
+ {
+ protected:
+ void SetUp() override
+ {
+ m_app.Start(m_descriptor);
+
+ // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
+ // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
+ // in the unit tests.
+ AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
+
+ GenerateTestHierarchy();
+ }
+
+ void GenerateTestHierarchy()
+ {
+ /*
+ * City
+ * |_ Street
+ * |_ Car
+ * | |_ Passenger
+ * |_ SportsCar
+ * |_ Passenger
+ */
+
+ m_entityMap["cityId"] = CreateEditorEntity("City", AZ::EntityId());
+ m_entityMap["streetId"] = CreateEditorEntity("Street", m_entityMap["cityId"]);
+ m_entityMap["carId"] = CreateEditorEntity("Car", m_entityMap["streetId"]);
+ m_entityMap["passengerId1"] = CreateEditorEntity("Passenger", m_entityMap["carId"]);
+ m_entityMap["sportsCarId"] = CreateEditorEntity("SportsCar", m_entityMap["streetId"]);
+ m_entityMap["passengerId2"] = CreateEditorEntity("Passenger", m_entityMap["sportsCarId"]);
+ }
+
+ AZ::EntityId CreateEditorEntity(const char* name, AZ::EntityId parentId)
+ {
+ AZ::Entity* entity = nullptr;
+ UnitTest::CreateDefaultEditorEntity(name, &entity);
+
+ // Parent
+ AZ::TransformBus::Event(entity->GetId(), &AZ::TransformInterface::SetParent, parentId);
+
+ return entity->GetId();
+ }
+
+ void TearDown() override
+ {
+ m_app.Stop();
+ }
+
+ UnitTest::ToolsTestApplication m_app{ "EditorFocusModeTests" };
+ AZ::ComponentApplication::Descriptor m_descriptor;
+ AZStd::unordered_map m_entityMap;
+ };
+
+ TEST_F(EditorFocusModeTests, EditorFocusModeTests_SetFocus)
+ {
+ FocusModeInterface* focusModeInterface = AZ::Interface::Get();
+ EXPECT_TRUE(focusModeInterface != nullptr);
+
+ focusModeInterface->SetFocusRoot(m_entityMap["carId"]);
+ EXPECT_EQ(focusModeInterface->GetFocusRoot(), m_entityMap["carId"]);
+
+ focusModeInterface->ClearFocusRoot();
+ EXPECT_EQ(focusModeInterface->GetFocusRoot(), AZ::EntityId());
+ }
+
+ TEST_F(EditorFocusModeTests, EditorFocusModeTests_IsInFocusSubTree)
+ {
+ FocusModeInterface* focusModeInterface = AZ::Interface::Get();
+ EXPECT_TRUE(focusModeInterface != nullptr);
+
+ focusModeInterface->ClearFocusRoot();
+
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["cityId"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["streetId"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["carId"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId1"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["sportsCarId"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId2"]), true);
+
+ focusModeInterface->SetFocusRoot(m_entityMap["streetId"]);
+
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["cityId"]), false);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["streetId"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["carId"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId1"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["sportsCarId"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId2"]), true);
+
+ focusModeInterface->SetFocusRoot(m_entityMap["carId"]);
+
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["cityId"]), false);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["streetId"]), false);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["carId"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId1"]), true);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["sportsCarId"]), false);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId2"]), false);
+
+ focusModeInterface->SetFocusRoot(m_entityMap["passengerId2"]);
+
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["cityId"]), false);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["streetId"]), false);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["carId"]), false);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId1"]), false);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["sportsCarId"]), false);
+ EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId2"]), true);
+
+ focusModeInterface->ClearFocusRoot();
+ }
+}
diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp
new file mode 100644
index 0000000000..ffd6cd2a44
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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