Formalize scene viewport chrome and presentation helpers

This commit is contained in:
2026-04-04 13:50:34 +08:00
parent a3ba08bb99
commit bd35b8b4e8
9 changed files with 626 additions and 269 deletions

View File

@@ -15,6 +15,7 @@ set(EDITOR_TEST_SOURCES
test_scene_viewport_interaction_actions.cpp
test_scene_viewport_interaction_resolver.cpp
test_scene_viewport_interaction_frame.cpp
test_scene_viewport_chrome.cpp
test_scene_viewport_transform_gizmo_coordinator.cpp
test_scene_viewport_shader_paths.cpp
test_scene_viewport_overlay_renderer.cpp

View File

@@ -0,0 +1,113 @@
#include <gtest/gtest.h>
#include "Core/IUndoManager.h"
#include "Viewport/SceneViewportChrome.h"
#include "Viewport/SceneViewportMoveGizmo.h"
#include "Viewport/SceneViewportRotateGizmo.h"
#include "Viewport/SceneViewportScaleGizmo.h"
namespace {
using XCEngine::Editor::BuildSceneViewportToolCommand;
using XCEngine::Editor::ExecuteSceneViewportToolCommand;
using XCEngine::Editor::SceneViewportToolCommand;
using XCEngine::Editor::SceneViewportToolMode;
using XCEngine::Editor::SceneViewportToolOverlayResult;
using XCEngine::Editor::SceneViewportToolShortcutAction;
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;
};
} // namespace
TEST(SceneViewportChromeTest, ToolCommandUsesOverlayClickAsFullToolSwitch) {
SceneViewportToolOverlayResult overlay = {};
overlay.clicked = true;
overlay.clickedTool = SceneViewportToolMode::Transform;
const SceneViewportToolShortcutAction shortcut = {};
const SceneViewportToolCommand command = BuildSceneViewportToolCommand(overlay, shortcut);
EXPECT_TRUE(command.HasAction());
EXPECT_EQ(command.targetTool, SceneViewportToolMode::Transform);
EXPECT_TRUE(command.cancelMoveGizmo);
EXPECT_TRUE(command.cancelRotateGizmo);
EXPECT_TRUE(command.cancelScaleGizmo);
}
TEST(SceneViewportChromeTest, ToolCommandUsesShortcutWhenOverlayDidNotTrigger) {
const SceneViewportToolOverlayResult overlay = {};
SceneViewportToolShortcutAction shortcut = {};
shortcut.triggered = true;
shortcut.targetTool = SceneViewportToolMode::Rotate;
shortcut.cancelMoveGizmo = true;
shortcut.cancelScaleGizmo = true;
const SceneViewportToolCommand command = BuildSceneViewportToolCommand(overlay, shortcut);
EXPECT_TRUE(command.HasAction());
EXPECT_EQ(command.targetTool, SceneViewportToolMode::Rotate);
EXPECT_TRUE(command.cancelMoveGizmo);
EXPECT_FALSE(command.cancelRotateGizmo);
EXPECT_TRUE(command.cancelScaleGizmo);
}
TEST(SceneViewportChromeTest, ShortcutOverridesOverlayTargetWhilePreservingCancelUnion) {
SceneViewportToolOverlayResult overlay = {};
overlay.clicked = true;
overlay.clickedTool = SceneViewportToolMode::Move;
SceneViewportToolShortcutAction shortcut = {};
shortcut.triggered = true;
shortcut.targetTool = SceneViewportToolMode::Scale;
shortcut.cancelMoveGizmo = true;
shortcut.cancelRotateGizmo = true;
const SceneViewportToolCommand command = BuildSceneViewportToolCommand(overlay, shortcut);
EXPECT_TRUE(command.HasAction());
EXPECT_EQ(command.targetTool, SceneViewportToolMode::Scale);
EXPECT_TRUE(command.cancelMoveGizmo);
EXPECT_TRUE(command.cancelRotateGizmo);
EXPECT_TRUE(command.cancelScaleGizmo);
}
TEST(SceneViewportChromeTest, ExecuteToolCommandUpdatesToolModeWhenTriggered) {
StubUndoManager undoManager = {};
XCEngine::Editor::SceneViewportMoveGizmo moveGizmo = {};
XCEngine::Editor::SceneViewportRotateGizmo rotateGizmo = {};
XCEngine::Editor::SceneViewportScaleGizmo scaleGizmo = {};
SceneViewportToolMode toolMode = SceneViewportToolMode::Move;
SceneViewportToolCommand command = {};
command.triggered = true;
command.targetTool = SceneViewportToolMode::ViewMove;
ExecuteSceneViewportToolCommand(
command,
undoManager,
moveGizmo,
rotateGizmo,
scaleGizmo,
toolMode);
EXPECT_EQ(toolMode, SceneViewportToolMode::ViewMove);
}

View File

@@ -28,16 +28,21 @@ using XCEngine::Editor::IProjectManager;
using XCEngine::Editor::ISceneManager;
using XCEngine::Editor::ISelectionManager;
using XCEngine::Editor::IViewportHostService;
using XCEngine::Editor::RefreshAndDrawSceneViewportPresentation;
using XCEngine::Editor::SceneSnapshot;
using XCEngine::Editor::SceneViewportInteractionActions;
using XCEngine::Editor::SceneViewportInput;
using XCEngine::Editor::SceneViewportInteractionFrameState;
using XCEngine::Editor::SceneViewportNavigationUpdate;
using XCEngine::Editor::SceneViewportOrientationAxis;
using XCEngine::Editor::SceneViewportOverlayData;
using XCEngine::Editor::SceneViewportOverlayFrameData;
using XCEngine::Editor::SceneViewportPivotMode;
using XCEngine::Editor::SceneViewportPresentationRequest;
using XCEngine::Editor::SceneViewportToolMode;
using XCEngine::Editor::SceneViewportTransformGizmoOverlayState;
using XCEngine::Editor::SceneViewportTransformSpaceMode;
using XCEngine::Editor::ShouldFocusSceneViewportAfterInteraction;
using XCEngine::Rendering::RenderContext;
class EmptySelectionManager : public ISelectionManager {
@@ -335,3 +340,82 @@ TEST(SceneViewportInteractionFrameTest, ResolveRequestCopiesFrameStateAndViewpor
EXPECT_FLOAT_EQ(request.absoluteMousePosition.x, 180.0f);
EXPECT_FLOAT_EQ(request.absoluteMousePosition.y, 260.0f);
}
TEST(SceneViewportInteractionFrameTest, FocusHelperTracksToolActionsAndNavigationBegins) {
const SceneViewportInteractionActions noActions = {};
const SceneViewportNavigationUpdate noNavigation = {};
EXPECT_FALSE(ShouldFocusSceneViewportAfterInteraction(false, noActions, noNavigation));
EXPECT_TRUE(ShouldFocusSceneViewportAfterInteraction(true, noActions, noNavigation));
SceneViewportInteractionActions clickActions = {};
clickActions.sceneIconClick = true;
EXPECT_TRUE(ShouldFocusSceneViewportAfterInteraction(false, clickActions, noNavigation));
SceneViewportNavigationUpdate navigationUpdate = {};
navigationUpdate.beginPanDrag = true;
EXPECT_TRUE(ShouldFocusSceneViewportAfterInteraction(false, noActions, navigationUpdate));
}
TEST(SceneViewportInteractionFrameTest, PresentationHelperSkipsNonInteractiveViewport) {
StubEditorContext context = {};
StubViewportHostService viewportHostService = {};
context.viewportHostService = &viewportHostService;
XCEngine::Editor::SceneViewportMoveGizmo moveGizmo = {};
XCEngine::Editor::SceneViewportRotateGizmo rotateGizmo = {};
XCEngine::Editor::SceneViewportScaleGizmo scaleGizmo = {};
SceneViewportPresentationRequest request = {};
request.context = &context;
request.viewportHostService = &viewportHostService;
request.hasInteractiveViewport = false;
request.geometry = BuildSceneViewportFrameGeometry(
ImVec2(640.0f, 360.0f),
ImVec2(10.0f, 20.0f),
ImVec2(40.0f, 60.0f));
request.gizmoFrameOptions = BuildSceneViewportToolState(
SceneViewportToolMode::Move,
SceneViewportPivotMode::Pivot,
SceneViewportTransformSpaceMode::Global)
.gizmoFrameOptions;
request.moveGizmo = &moveGizmo;
request.rotateGizmo = &rotateGizmo;
request.scaleGizmo = &scaleGizmo;
RefreshAndDrawSceneViewportPresentation(request);
EXPECT_EQ(viewportHostService.submissionCount, 0);
}
TEST(SceneViewportInteractionFrameTest, PresentationHelperRefreshesInteractiveViewportWithoutDrawList) {
StubEditorContext context = {};
StubViewportHostService viewportHostService = {};
context.viewportHostService = &viewportHostService;
viewportHostService.overlay.valid = true;
viewportHostService.overlay.verticalFovDegrees = 50.0f;
XCEngine::Editor::SceneViewportMoveGizmo moveGizmo = {};
XCEngine::Editor::SceneViewportRotateGizmo rotateGizmo = {};
XCEngine::Editor::SceneViewportScaleGizmo scaleGizmo = {};
SceneViewportPresentationRequest request = {};
request.context = &context;
request.viewportHostService = &viewportHostService;
request.hasInteractiveViewport = true;
request.geometry = BuildSceneViewportFrameGeometry(
ImVec2(640.0f, 360.0f),
ImVec2(10.0f, 20.0f),
ImVec2(40.0f, 60.0f));
request.gizmoFrameOptions = BuildSceneViewportToolState(
SceneViewportToolMode::Move,
SceneViewportPivotMode::Pivot,
SceneViewportTransformSpaceMode::Global)
.gizmoFrameOptions;
request.moveGizmo = &moveGizmo;
request.rotateGizmo = &rotateGizmo;
request.scaleGizmo = &scaleGizmo;
request.viewportMin = ImVec2(10.0f, 20.0f);
request.viewportMax = ImVec2(650.0f, 380.0f);
RefreshAndDrawSceneViewportPresentation(request);
EXPECT_EQ(viewportHostService.submissionCount, 1);
}