From d207043f8f8be46de1c6f1d7ab210e3d0ddb2e08 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 4 Apr 2026 01:09:02 +0800 Subject: [PATCH] Formalize staged scene viewport gizmo frame API --- ...rlay_Gizmo_Rework_Checkpoint_2026-04-02.md | 17 ++ ...SceneViewportTransformGizmoCoordinator.cpp | 90 ++++++ .../SceneViewportTransformGizmoCoordinator.h | 62 ++++ editor/src/panels/SceneViewPanel.cpp | 75 ++--- ...e_viewport_transform_gizmo_coordinator.cpp | 286 ++++++++++++++++++ 5 files changed, 486 insertions(+), 44 deletions(-) diff --git a/docs/plan/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_2026-04-02.md b/docs/plan/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_2026-04-02.md index 87c62efb..0ddbb252 100644 --- a/docs/plan/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_2026-04-02.md +++ b/docs/plan/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_2026-04-02.md @@ -1,5 +1,22 @@ # SceneViewport Overlay/Gizmo Rework Checkpoint +## Update 2026-04-04 Phase 5D + +### Stage-Oriented Gizmo Frame API Completed + +- Extended `SceneViewportTransformGizmoCoordinator.{h,cpp}` with formal frame options, refresh requests, frame updates, and refresh-and-submit helpers. +- `SceneViewPanel` no longer directly assembles the large `RefreshSceneViewportTransformGizmos(...)` argument list for interaction and draw stages. +- The required dual-stage gizmo timing remains intact, but the stage orchestration now flows through `BuildSceneViewportTransformGizmoRefreshRequest(...)` and `RefreshAndSubmitSceneViewportTransformGizmoFrame(...)`. +- Added coordinator tests covering frame option/request construction and refresh-to-submit flow. + +### Verification + +- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` +- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportInteractionActionsTest.*:SceneViewportInteractionResolverTest.*:SceneViewportTransformGizmoCoordinatorTest.*:SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` +- `cmake --build build --config Debug --target XCEditor` + +All commands completed successfully in `Debug`. + ## Update 2026-04-04 Phase 5C ### Transform Gizmo Coordinator Completed diff --git a/editor/src/Viewport/SceneViewportTransformGizmoCoordinator.cpp b/editor/src/Viewport/SceneViewportTransformGizmoCoordinator.cpp index 6007bc9e..1b338b81 100644 --- a/editor/src/Viewport/SceneViewportTransformGizmoCoordinator.cpp +++ b/editor/src/Viewport/SceneViewportTransformGizmoCoordinator.cpp @@ -5,6 +5,96 @@ namespace XCEngine { namespace Editor { +SceneViewportTransformGizmoFrameOptions BuildSceneViewportTransformGizmoFrameOptions( + bool useCenterPivot, + bool localSpace, + bool usingTransformTool, + bool showingMoveGizmo, + bool showingRotateGizmo, + bool showingScaleGizmo) { + SceneViewportTransformGizmoFrameOptions options = {}; + options.useCenterPivot = useCenterPivot; + options.localSpace = localSpace; + options.usingTransformTool = usingTransformTool; + options.showingMoveGizmo = showingMoveGizmo; + options.showingRotateGizmo = showingRotateGizmo; + options.showingScaleGizmo = showingScaleGizmo; + return options; +} + +SceneViewportTransformGizmoRefreshRequest BuildSceneViewportTransformGizmoRefreshRequest( + IEditorContext& context, + const SceneViewportOverlayData& overlay, + const Math::Vector2& viewportSize, + const Math::Vector2& mousePosition, + const SceneViewportTransformGizmoFrameOptions& options) { + SceneViewportTransformGizmoRefreshRequest request = {}; + request.context = &context; + request.overlay = overlay; + request.viewportSize = viewportSize; + request.mousePosition = mousePosition; + request.options = options; + return request; +} + +void CancelSceneViewportTransformGizmoFrame( + IEditorContext& context, + SceneViewportMoveGizmo& moveGizmo, + SceneViewportRotateGizmo& rotateGizmo, + SceneViewportScaleGizmo& scaleGizmo) { + CancelSceneViewportTransformGizmoDrags(context, moveGizmo, rotateGizmo, scaleGizmo); +} + +SceneViewportTransformGizmoFrameUpdate RefreshSceneViewportTransformGizmoFrame( + const SceneViewportTransformGizmoRefreshRequest& request, + SceneViewportMoveGizmo& moveGizmo, + SceneViewportRotateGizmo& rotateGizmo, + SceneViewportScaleGizmo& scaleGizmo) { + SceneViewportTransformGizmoFrameUpdate update = {}; + if (!request.IsValid()) { + return update; + } + + update.frameState = RefreshSceneViewportTransformGizmos( + *request.context, + request.overlay, + request.viewportSize, + request.mousePosition, + request.options.useCenterPivot, + request.options.localSpace, + request.options.usingTransformTool, + request.options.showingMoveGizmo, + moveGizmo, + request.options.showingRotateGizmo, + rotateGizmo, + request.options.showingScaleGizmo, + scaleGizmo); + update.overlaySubmission = BuildSceneViewportTransformGizmoOverlaySubmission( + update.frameState, + request.options.showingMoveGizmo, + moveGizmo, + request.options.showingRotateGizmo, + rotateGizmo, + request.options.showingScaleGizmo, + scaleGizmo); + return update; +} + +SceneViewportTransformGizmoFrameUpdate RefreshAndSubmitSceneViewportTransformGizmoFrame( + IViewportHostService& viewportHostService, + const SceneViewportTransformGizmoRefreshRequest& request, + SceneViewportMoveGizmo& moveGizmo, + SceneViewportRotateGizmo& rotateGizmo, + SceneViewportScaleGizmo& scaleGizmo) { + SceneViewportTransformGizmoFrameUpdate update = RefreshSceneViewportTransformGizmoFrame( + request, + moveGizmo, + rotateGizmo, + scaleGizmo); + SubmitSceneViewportTransformGizmoOverlaySubmission(viewportHostService, update.overlaySubmission); + return update; +} + SceneViewportTransformGizmoOverlaySubmission BuildSceneViewportTransformGizmoOverlaySubmission( const SceneViewportTransformGizmoFrameState& frameState, bool showingMoveGizmo, diff --git a/editor/src/Viewport/SceneViewportTransformGizmoCoordinator.h b/editor/src/Viewport/SceneViewportTransformGizmoCoordinator.h index 5a58db58..0aab85e7 100644 --- a/editor/src/Viewport/SceneViewportTransformGizmoCoordinator.h +++ b/editor/src/Viewport/SceneViewportTransformGizmoCoordinator.h @@ -1,7 +1,9 @@ #pragma once +#include "Core/IEditorContext.h" #include "SceneViewportInteractionActions.h" #include "SceneViewportOverlayHandleBuilder.h" +#include "SceneViewportTransformGizmoFrameBuilder.h" #include @@ -10,6 +12,27 @@ namespace Editor { class IUndoManager; +struct SceneViewportTransformGizmoFrameOptions { + bool useCenterPivot = false; + bool localSpace = false; + bool usingTransformTool = false; + bool showingMoveGizmo = false; + bool showingRotateGizmo = false; + bool showingScaleGizmo = false; +}; + +struct SceneViewportTransformGizmoRefreshRequest { + IEditorContext* context = nullptr; + SceneViewportOverlayData overlay = {}; + Math::Vector2 viewportSize = Math::Vector2::Zero(); + Math::Vector2 mousePosition = Math::Vector2::Zero(); + SceneViewportTransformGizmoFrameOptions options = {}; + + bool IsValid() const { + return context != nullptr; + } +}; + struct SceneViewportTransformGizmoOverlaySubmission { SceneViewportTransformGizmoOverlayState overlayState = {}; SceneViewportActiveGizmoKind activeGizmoKind = SceneViewportActiveGizmoKind::None; @@ -36,6 +59,45 @@ struct SceneViewportTransformGizmoLifecycleCommand { } }; +struct SceneViewportTransformGizmoFrameUpdate { + SceneViewportTransformGizmoFrameState frameState = {}; + SceneViewportTransformGizmoOverlaySubmission overlaySubmission = {}; +}; + +SceneViewportTransformGizmoFrameOptions BuildSceneViewportTransformGizmoFrameOptions( + bool useCenterPivot, + bool localSpace, + bool usingTransformTool, + bool showingMoveGizmo, + bool showingRotateGizmo, + bool showingScaleGizmo); + +SceneViewportTransformGizmoRefreshRequest BuildSceneViewportTransformGizmoRefreshRequest( + IEditorContext& context, + const SceneViewportOverlayData& overlay, + const Math::Vector2& viewportSize, + const Math::Vector2& mousePosition, + const SceneViewportTransformGizmoFrameOptions& options); + +void CancelSceneViewportTransformGizmoFrame( + IEditorContext& context, + SceneViewportMoveGizmo& moveGizmo, + SceneViewportRotateGizmo& rotateGizmo, + SceneViewportScaleGizmo& scaleGizmo); + +SceneViewportTransformGizmoFrameUpdate RefreshSceneViewportTransformGizmoFrame( + const SceneViewportTransformGizmoRefreshRequest& request, + SceneViewportMoveGizmo& moveGizmo, + SceneViewportRotateGizmo& rotateGizmo, + SceneViewportScaleGizmo& scaleGizmo); + +SceneViewportTransformGizmoFrameUpdate RefreshAndSubmitSceneViewportTransformGizmoFrame( + IViewportHostService& viewportHostService, + const SceneViewportTransformGizmoRefreshRequest& request, + SceneViewportMoveGizmo& moveGizmo, + SceneViewportRotateGizmo& rotateGizmo, + SceneViewportScaleGizmo& scaleGizmo); + SceneViewportTransformGizmoOverlaySubmission BuildSceneViewportTransformGizmoOverlaySubmission( const SceneViewportTransformGizmoFrameState& frameState, bool showingMoveGizmo, diff --git a/editor/src/panels/SceneViewPanel.cpp b/editor/src/panels/SceneViewPanel.cpp index 387d3387..da175046 100644 --- a/editor/src/panels/SceneViewPanel.cpp +++ b/editor/src/panels/SceneViewPanel.cpp @@ -9,7 +9,6 @@ #include "Viewport/SceneViewportInteractionResolver.h" #include "Viewport/SceneViewportMath.h" #include "Viewport/SceneViewportTransformGizmoCoordinator.h" -#include "Viewport/SceneViewportTransformGizmoFrameBuilder.h" #include "ViewportPanelContent.h" #include "Platform/Win32Utf8.h" #include "UI/UI.h" @@ -339,46 +338,49 @@ void SceneViewPanel::Render() { const bool showingScaleGizmo = m_toolMode == SceneViewportToolMode::Scale || usingTransformTool; const bool useCenterPivot = m_pivotMode == SceneViewportPivotMode::Center; const bool localSpace = m_transformSpaceMode == SceneViewportTransformSpaceMode::Local; + const SceneViewportTransformGizmoFrameOptions gizmoFrameOptions = + BuildSceneViewportTransformGizmoFrameOptions( + useCenterPivot, + localSpace, + usingTransformTool, + showingMoveGizmo, + showingRotateGizmo, + showingScaleGizmo); const Math::Vector2 viewportSize(content.availableSize.x, content.availableSize.y); const Math::Vector2 localMousePosition( io.MousePos.x - content.itemMin.x, io.MousePos.y - content.itemMin.y); SceneViewportOverlayData overlay = {}; + SceneViewportTransformGizmoFrameUpdate interactionGizmoFrame = {}; SceneViewportTransformGizmoFrameState gizmoFrameState = {}; SceneViewportOverlayFrameData emptySceneOverlayFrameData = {}; if (hasInteractiveViewport) { overlay = viewportHostService->GetSceneViewOverlayData(); - gizmoFrameState = RefreshSceneViewportTransformGizmos( - *m_context, - overlay, - viewportSize, - localMousePosition, - useCenterPivot, - localSpace, - usingTransformTool, - showingMoveGizmo, + interactionGizmoFrame = RefreshAndSubmitSceneViewportTransformGizmoFrame( + *viewportHostService, + BuildSceneViewportTransformGizmoRefreshRequest( + *m_context, + overlay, + viewportSize, + localMousePosition, + gizmoFrameOptions), m_moveGizmo, - showingRotateGizmo, m_rotateGizmo, - showingScaleGizmo, m_scaleGizmo); + gizmoFrameState = interactionGizmoFrame.frameState; } else { - CancelSceneViewportTransformGizmoDrags(*m_context, m_moveGizmo, m_rotateGizmo, m_scaleGizmo); + CancelSceneViewportTransformGizmoFrame( + *m_context, + m_moveGizmo, + m_rotateGizmo, + m_scaleGizmo); } const SceneViewportTransformGizmoOverlaySubmission interactionGizmoSubmission = hasInteractiveViewport - ? BuildSceneViewportTransformGizmoOverlaySubmission( - gizmoFrameState, - showingMoveGizmo, - m_moveGizmo, - showingRotateGizmo, - m_rotateGizmo, - showingScaleGizmo, - m_scaleGizmo) + ? interactionGizmoFrame.overlaySubmission : SceneViewportTransformGizmoOverlaySubmission{}; - SubmitSceneViewportTransformGizmoOverlaySubmission(*viewportHostService, interactionGizmoSubmission); const SceneViewportOverlayFrameData& interactionOverlayFrameData = hasInteractiveViewport ? viewportHostService->GetSceneViewEditorOverlayFrameData(*m_context) @@ -559,32 +561,17 @@ void SceneViewPanel::Render() { if (content.hasViewportArea && content.frame.hasTexture) { overlay = viewportHostService->GetSceneViewOverlayData(); - const SceneViewportTransformGizmoFrameState drawGizmoFrameState = - RefreshSceneViewportTransformGizmos( + RefreshAndSubmitSceneViewportTransformGizmoFrame( + *viewportHostService, + BuildSceneViewportTransformGizmoRefreshRequest( *m_context, overlay, viewportSize, localMousePosition, - useCenterPivot, - localSpace, - usingTransformTool, - showingMoveGizmo, - m_moveGizmo, - showingRotateGizmo, - m_rotateGizmo, - showingScaleGizmo, - m_scaleGizmo); - - SubmitSceneViewportTransformGizmoOverlaySubmission( - *viewportHostService, - BuildSceneViewportTransformGizmoOverlaySubmission( - drawGizmoFrameState, - showingMoveGizmo, - m_moveGizmo, - showingRotateGizmo, - m_rotateGizmo, - showingScaleGizmo, - m_scaleGizmo)); + gizmoFrameOptions), + m_moveGizmo, + m_rotateGizmo, + m_scaleGizmo); DrawSceneViewportHudOverlay( ImGui::GetWindowDrawList(), diff --git a/tests/editor/test_scene_viewport_transform_gizmo_coordinator.cpp b/tests/editor/test_scene_viewport_transform_gizmo_coordinator.cpp index 92afae77..b3b702b9 100644 --- a/tests/editor/test_scene_viewport_transform_gizmo_coordinator.cpp +++ b/tests/editor/test_scene_viewport_transform_gizmo_coordinator.cpp @@ -1,21 +1,40 @@ #include +#include "Core/EventBus.h" +#include "Core/IEditorContext.h" +#include "Core/IProjectManager.h" +#include "Core/ISceneManager.h" +#include "Core/ISelectionManager.h" #include "Core/IUndoManager.h" #include "Viewport/IViewportHostService.h" #include "Viewport/SceneViewportTransformGizmoCoordinator.h" #include +#include + namespace { using XCEngine::Editor::BuildBeginSceneViewportTransformGizmoLifecycleCommand; using XCEngine::Editor::BuildFrameSceneViewportTransformGizmoLifecycleCommand; +using XCEngine::Editor::BuildSceneViewportTransformGizmoFrameOptions; +using XCEngine::Editor::BuildSceneViewportTransformGizmoRefreshRequest; using XCEngine::Editor::BuildSceneViewportTransformGizmoOverlaySubmission; +using XCEngine::Editor::CancelSceneViewportTransformGizmoFrame; using XCEngine::Editor::EditorViewportFrame; using XCEngine::Editor::EditorViewportKind; +using XCEngine::Editor::EditorActionRoute; +using XCEngine::Editor::EditorRuntimeMode; +using XCEngine::Editor::EventBus; using XCEngine::Editor::IEditorContext; +using XCEngine::Editor::IProjectManager; +using XCEngine::Editor::ISceneManager; +using XCEngine::Editor::ISelectionManager; using XCEngine::Editor::IViewportHostService; +using XCEngine::Editor::AssetItemPtr; using XCEngine::Editor::SceneViewportActiveGizmoKind; +using XCEngine::Editor::SceneViewportTransformGizmoFrameOptions; +using XCEngine::Editor::SceneViewportTransformGizmoFrameUpdate; using XCEngine::Editor::SceneViewportInput; using XCEngine::Editor::SceneViewportInteractionActions; using XCEngine::Editor::SceneViewportOrientationAxis; @@ -24,8 +43,185 @@ using XCEngine::Editor::SceneViewportOverlayFrameData; using XCEngine::Editor::SceneViewportTransformGizmoFrameState; using XCEngine::Editor::SceneViewportTransformGizmoLifecycleStage; using XCEngine::Editor::SceneViewportTransformGizmoOverlayState; +using XCEngine::Editor::RefreshAndSubmitSceneViewportTransformGizmoFrame; using XCEngine::Editor::SubmitSceneViewportTransformGizmoOverlaySubmission; +using XCEngine::Editor::SceneSnapshot; using XCEngine::Rendering::RenderContext; +using XCEngine::Math::Vector2; + +class StubSelectionManager : public ISelectionManager { +public: + void SetSelectedEntity(uint64_t entityId) override { + selectedEntity = entityId; + selectedEntities = entityId == 0 ? std::vector{} : std::vector{entityId}; + } + + void SetSelectedEntities(const std::vector& entityIds) override { + selectedEntities = entityIds; + selectedEntity = entityIds.empty() ? 0 : entityIds.front(); + } + + void AddToSelection(uint64_t entityId) override { + selectedEntities.push_back(entityId); + selectedEntity = entityId; + } + + void RemoveFromSelection(uint64_t entityId) override { + selectedEntities.erase( + std::remove(selectedEntities.begin(), selectedEntities.end(), entityId), + selectedEntities.end()); + if (selectedEntity == entityId) { + selectedEntity = selectedEntities.empty() ? 0 : selectedEntities.front(); + } + } + + void ClearSelection() override { + selectedEntity = 0; + selectedEntities.clear(); + } + + uint64_t GetSelectedEntity() const override { + return selectedEntity; + } + + const std::vector& GetSelectedEntities() const override { + return selectedEntities; + } + + bool HasSelection() const override { + return !selectedEntities.empty(); + } + + size_t GetSelectionCount() const override { + return selectedEntities.size(); + } + + bool IsSelected(uint64_t entityId) const override { + return std::find(selectedEntities.begin(), selectedEntities.end(), entityId) != selectedEntities.end(); + } + + uint64_t selectedEntity = 0; + std::vector selectedEntities = {}; +}; + +class StubSceneManager : public ISceneManager { +public: + XCEngine::Components::GameObject* CreateEntity( + const std::string&, + XCEngine::Components::GameObject* = nullptr) override { return nullptr; } + void DeleteEntity(XCEngine::Components::GameObject::ID) override {} + void RenameEntity(XCEngine::Components::GameObject::ID, const std::string&) override {} + XCEngine::Components::GameObject* GetEntity(XCEngine::Components::GameObject::ID entityId) override { + return entity != nullptr && entity->GetID() == entityId ? entity : nullptr; + } + const std::vector& GetRootEntities() const override { return rootEntities; } + void CopyEntity(XCEngine::Components::GameObject::ID) override {} + XCEngine::Components::GameObject::ID PasteEntity(XCEngine::Components::GameObject::ID = 0) override { return 0; } + XCEngine::Components::GameObject::ID DuplicateEntity(XCEngine::Components::GameObject::ID) override { return 0; } + void MoveEntity(XCEngine::Components::GameObject::ID, XCEngine::Components::GameObject::ID) override {} + bool HasClipboardData() const override { return false; } + void NewScene(const std::string& = "Untitled Scene") override {} + bool LoadScene(const std::string&) override { return false; } + bool SaveScene() override { return false; } + bool SaveSceneAs(const std::string&) override { return false; } + bool LoadStartupScene(const std::string&) override { return false; } + bool HasActiveScene() const override { return false; } + bool IsSceneDirty() const override { return false; } + void MarkSceneDirty() override {} + void SetSceneDocumentDirtyTrackingEnabled(bool) override {} + bool IsSceneDocumentDirtyTrackingEnabled() const override { return false; } + const std::string& GetCurrentScenePath() const override { return empty; } + const std::string& GetCurrentSceneName() const override { return empty; } + XCEngine::Components::Scene* GetScene() override { return nullptr; } + const XCEngine::Components::Scene* GetScene() const override { return nullptr; } + SceneSnapshot CaptureSceneSnapshot() const override { return {}; } + bool RestoreSceneSnapshot(const SceneSnapshot&) override { return false; } + void CreateDemoScene() override {} + + XCEngine::Components::GameObject* entity = nullptr; + +private: + std::vector rootEntities = {}; + std::string empty; +}; + +class StubProjectManager : public IProjectManager { +public: + const std::vector& GetCurrentItems() const override { return items; } + AssetItemPtr GetRootFolder() const override { return {}; } + AssetItemPtr GetCurrentFolder() const override { return {}; } + AssetItemPtr GetSelectedItem() const override { return {}; } + const std::string& GetSelectedItemPath() const override { return empty; } + int GetSelectedIndex() const override { return -1; } + void SetSelectedIndex(int) override {} + void SetSelectedItem(const AssetItemPtr&) override {} + void ClearSelection() override {} + int FindCurrentItemIndex(const std::string&) const override { return -1; } + void NavigateToFolder(const AssetItemPtr&) override {} + void NavigateBack() override {} + void NavigateToIndex(size_t) override {} + bool CanNavigateBack() const override { return false; } + std::string GetCurrentPath() const override { return empty; } + size_t GetPathDepth() const override { return 0; } + std::string GetPathName(size_t) const override { return {}; } + void Initialize(const std::string&) override {} + void RefreshCurrentFolder() override {} + AssetItemPtr CreateFolder(const std::string&) override { return {}; } + bool DeleteItem(const std::string&) override { return false; } + bool MoveItem(const std::string&, const std::string&) override { return false; } + bool RenameItem(const std::string&, const std::string&) override { return false; } + const std::string& GetProjectPath() const override { return empty; } + +private: + std::vector items = {}; + std::string empty; +}; + +class StubUndoManager : public XCEngine::Editor::IUndoManager { +public: + void ClearHistory() override {} + bool CanUndo() const override { return false; } + bool CanRedo() const override { return false; } + const std::string& GetUndoLabel() const override { return empty; } + const std::string& GetRedoLabel() const override { return empty; } + void Undo() override {} + void Redo() override {} + XCEngine::Editor::UndoStateSnapshot CaptureCurrentState() const override { return {}; } + void PushCommand(const std::string&, XCEngine::Editor::UndoStateSnapshot, XCEngine::Editor::UndoStateSnapshot) override {} + void BeginInteractiveChange(const std::string&) override {} + bool HasPendingInteractiveChange() const override { return false; } + void FinalizeInteractiveChange() override {} + void CancelInteractiveChange() override {} + +private: + std::string empty; +}; + +class StubEditorContext : public IEditorContext { +public: + EventBus& GetEventBus() override { return eventBus; } + ISelectionManager& GetSelectionManager() override { return selectionManager; } + ISceneManager& GetSceneManager() override { return sceneManager; } + IProjectManager& GetProjectManager() override { return projectManager; } + XCEngine::Editor::IUndoManager& GetUndoManager() override { return undoManager; } + IViewportHostService* GetViewportHostService() override { return viewportHostService; } + void SetActiveActionRoute(EditorActionRoute route) override { activeRoute = route; } + EditorActionRoute GetActiveActionRoute() const override { return activeRoute; } + void SetRuntimeMode(EditorRuntimeMode mode) override { runtimeMode = mode; } + EditorRuntimeMode GetRuntimeMode() const override { return runtimeMode; } + void SetProjectPath(const std::string& path) override { projectPath = path; } + const std::string& GetProjectPath() const override { return projectPath; } + + EventBus eventBus = {}; + StubSelectionManager selectionManager = {}; + StubSceneManager sceneManager = {}; + StubProjectManager projectManager = {}; + StubUndoManager undoManager = {}; + IViewportHostService* viewportHostService = nullptr; + EditorActionRoute activeRoute = EditorActionRoute::None; + EditorRuntimeMode runtimeMode = EditorRuntimeMode::Edit; + std::string projectPath; +}; class StubViewportHostService : public IViewportHostService { public: @@ -77,6 +273,55 @@ TEST(SceneViewportTransformGizmoCoordinatorTest, BuildFrameCommandMapsUpdateAndE EXPECT_EQ(endCommand.gizmoKind, SceneViewportActiveGizmoKind::Scale); } +TEST(SceneViewportTransformGizmoCoordinatorTest, BuildFrameOptionsCopiesVisibilityAndModes) { + const SceneViewportTransformGizmoFrameOptions options = BuildSceneViewportTransformGizmoFrameOptions( + true, + true, + false, + true, + false, + true); + + EXPECT_TRUE(options.useCenterPivot); + EXPECT_TRUE(options.localSpace); + EXPECT_FALSE(options.usingTransformTool); + EXPECT_TRUE(options.showingMoveGizmo); + EXPECT_FALSE(options.showingRotateGizmo); + EXPECT_TRUE(options.showingScaleGizmo); +} + +TEST(SceneViewportTransformGizmoCoordinatorTest, BuildRefreshRequestCopiesInputs) { + StubEditorContext context = {}; + SceneViewportOverlayData overlay = {}; + overlay.valid = true; + overlay.verticalFovDegrees = 47.0f; + const SceneViewportTransformGizmoFrameOptions options = BuildSceneViewportTransformGizmoFrameOptions( + false, + true, + true, + true, + true, + false); + + const auto request = BuildSceneViewportTransformGizmoRefreshRequest( + context, + overlay, + Vector2(320.0f, 180.0f), + Vector2(25.0f, 15.0f), + options); + + EXPECT_TRUE(request.IsValid()); + EXPECT_EQ(request.context, &context); + EXPECT_TRUE(request.overlay.valid); + EXPECT_FLOAT_EQ(request.overlay.verticalFovDegrees, 47.0f); + EXPECT_FLOAT_EQ(request.viewportSize.x, 320.0f); + EXPECT_FLOAT_EQ(request.viewportSize.y, 180.0f); + EXPECT_FLOAT_EQ(request.mousePosition.x, 25.0f); + EXPECT_FLOAT_EQ(request.mousePosition.y, 15.0f); + EXPECT_TRUE(request.options.localSpace); + EXPECT_TRUE(request.options.showingRotateGizmo); +} + TEST(SceneViewportTransformGizmoCoordinatorTest, OverlaySubmissionBuildsAndSubmitsState) { XCEngine::Components::GameObject gameObject("CoordinatorTestObject"); XCEngine::Editor::SceneViewportMoveGizmo moveGizmo = {}; @@ -107,3 +352,44 @@ TEST(SceneViewportTransformGizmoCoordinatorTest, OverlaySubmissionBuildsAndSubmi EXPECT_TRUE(viewportHostService.lastSubmission.hasMoveGizmo); EXPECT_EQ(viewportHostService.lastSubmission.moveEntityId, gameObject.GetID()); } + +TEST(SceneViewportTransformGizmoCoordinatorTest, RefreshAndSubmitFrameUsesCoordinatorRequest) { + XCEngine::Components::GameObject gameObject("CoordinatorRefreshObject"); + StubEditorContext context = {}; + StubViewportHostService viewportHostService = {}; + context.viewportHostService = &viewportHostService; + context.sceneManager.entity = &gameObject; + context.selectionManager.SetSelectedEntity(gameObject.GetID()); + + XCEngine::Editor::SceneViewportMoveGizmo moveGizmo = {}; + XCEngine::Editor::SceneViewportRotateGizmo rotateGizmo = {}; + XCEngine::Editor::SceneViewportScaleGizmo scaleGizmo = {}; + const SceneViewportTransformGizmoFrameOptions options = BuildSceneViewportTransformGizmoFrameOptions( + false, + false, + false, + true, + false, + false); + + const SceneViewportTransformGizmoFrameUpdate update = RefreshAndSubmitSceneViewportTransformGizmoFrame( + viewportHostService, + BuildSceneViewportTransformGizmoRefreshRequest( + context, + {}, + Vector2(640.0f, 360.0f), + Vector2(48.0f, 52.0f), + options), + moveGizmo, + rotateGizmo, + scaleGizmo); + + EXPECT_EQ(update.frameState.moveContext.selectedObject, &gameObject); + EXPECT_EQ(update.frameState.selectionState.primaryObject, &gameObject); + EXPECT_TRUE(update.overlaySubmission.overlayState.hasMoveGizmo); + EXPECT_EQ(update.overlaySubmission.overlayState.moveEntityId, gameObject.GetID()); + EXPECT_TRUE(viewportHostService.lastSubmission.hasMoveGizmo); + EXPECT_EQ(viewportHostService.lastSubmission.moveEntityId, gameObject.GetID()); + + CancelSceneViewportTransformGizmoFrame(context, moveGizmo, rotateGizmo, scaleGizmo); +}