Formalize scene viewport interaction frame helpers
This commit is contained in:
337
tests/editor/test_scene_viewport_interaction_frame.cpp
Normal file
337
tests/editor/test_scene_viewport_interaction_frame.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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/SceneViewportInteractionFrame.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Editor::AssetItemPtr;
|
||||
using XCEngine::Editor::BuildSceneViewportFrameGeometry;
|
||||
using XCEngine::Editor::BuildSceneViewportInteractionFrameState;
|
||||
using XCEngine::Editor::BuildSceneViewportInteractionResolveRequest;
|
||||
using XCEngine::Editor::BuildSceneViewportToolState;
|
||||
using XCEngine::Editor::EditorActionRoute;
|
||||
using XCEngine::Editor::EditorRuntimeMode;
|
||||
using XCEngine::Editor::EditorViewportFrame;
|
||||
using XCEngine::Editor::EditorViewportKind;
|
||||
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::SceneSnapshot;
|
||||
using XCEngine::Editor::SceneViewportInput;
|
||||
using XCEngine::Editor::SceneViewportInteractionFrameState;
|
||||
using XCEngine::Editor::SceneViewportOrientationAxis;
|
||||
using XCEngine::Editor::SceneViewportOverlayData;
|
||||
using XCEngine::Editor::SceneViewportOverlayFrameData;
|
||||
using XCEngine::Editor::SceneViewportPivotMode;
|
||||
using XCEngine::Editor::SceneViewportToolMode;
|
||||
using XCEngine::Editor::SceneViewportTransformGizmoOverlayState;
|
||||
using XCEngine::Editor::SceneViewportTransformSpaceMode;
|
||||
using XCEngine::Rendering::RenderContext;
|
||||
|
||||
class EmptySelectionManager : public ISelectionManager {
|
||||
public:
|
||||
void SetSelectedEntity(uint64_t) override {}
|
||||
void SetSelectedEntities(const std::vector<uint64_t>&) override {}
|
||||
void AddToSelection(uint64_t) override {}
|
||||
void RemoveFromSelection(uint64_t) override {}
|
||||
void ClearSelection() override {}
|
||||
uint64_t GetSelectedEntity() const override { return 0; }
|
||||
const std::vector<uint64_t>& GetSelectedEntities() const override { return selectedEntities; }
|
||||
bool HasSelection() const override { return false; }
|
||||
size_t GetSelectionCount() const override { return 0; }
|
||||
bool IsSelected(uint64_t) const override { return false; }
|
||||
|
||||
private:
|
||||
std::vector<uint64_t> selectedEntities = {};
|
||||
};
|
||||
|
||||
class EmptySceneManager : 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) override { return nullptr; }
|
||||
const std::vector<XCEngine::Components::GameObject*>& 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 {}
|
||||
|
||||
private:
|
||||
std::vector<XCEngine::Components::GameObject*> rootEntities = {};
|
||||
std::string empty;
|
||||
};
|
||||
|
||||
class EmptyProjectManager : public IProjectManager {
|
||||
public:
|
||||
const std::vector<AssetItemPtr>& 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<AssetItemPtr> items = {};
|
||||
std::string empty;
|
||||
};
|
||||
|
||||
class EmptyUndoManager : 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 = {};
|
||||
EmptySelectionManager selectionManager = {};
|
||||
EmptySceneManager sceneManager = {};
|
||||
EmptyProjectManager projectManager = {};
|
||||
EmptyUndoManager undoManager = {};
|
||||
IViewportHostService* viewportHostService = nullptr;
|
||||
EditorActionRoute activeRoute = EditorActionRoute::None;
|
||||
EditorRuntimeMode runtimeMode = EditorRuntimeMode::Edit;
|
||||
std::string projectPath;
|
||||
};
|
||||
|
||||
class StubViewportHostService : public IViewportHostService {
|
||||
public:
|
||||
void BeginFrame() override {}
|
||||
EditorViewportFrame RequestViewport(EditorViewportKind, const ImVec2&) override { return {}; }
|
||||
void UpdateSceneViewInput(IEditorContext&, const SceneViewportInput&) override {}
|
||||
uint64_t PickSceneViewEntity(IEditorContext&, const ImVec2&, const ImVec2&) override { return 0; }
|
||||
void AlignSceneViewToOrientationAxis(SceneViewportOrientationAxis) override {}
|
||||
SceneViewportOverlayData GetSceneViewOverlayData() const override { return overlay; }
|
||||
const SceneViewportOverlayFrameData& GetSceneViewEditorOverlayFrameData(IEditorContext&) override {
|
||||
return overlayFrameData;
|
||||
}
|
||||
void SetSceneViewTransformGizmoOverlayState(const SceneViewportTransformGizmoOverlayState& state) override {
|
||||
++submissionCount;
|
||||
lastOverlayState = state;
|
||||
}
|
||||
void RenderRequestedViewports(IEditorContext&, const RenderContext&) override {}
|
||||
|
||||
SceneViewportOverlayData overlay = {};
|
||||
SceneViewportOverlayFrameData overlayFrameData = {};
|
||||
SceneViewportTransformGizmoOverlayState lastOverlayState = {};
|
||||
int submissionCount = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(SceneViewportInteractionFrameTest, ToolStateMapsModesAndTransformSpace) {
|
||||
const auto moveState = BuildSceneViewportToolState(
|
||||
SceneViewportToolMode::Move,
|
||||
SceneViewportPivotMode::Center,
|
||||
SceneViewportTransformSpaceMode::Local);
|
||||
EXPECT_FALSE(moveState.usingViewMoveTool);
|
||||
EXPECT_FALSE(moveState.usingTransformTool);
|
||||
EXPECT_TRUE(moveState.showingMoveGizmo);
|
||||
EXPECT_FALSE(moveState.showingRotateGizmo);
|
||||
EXPECT_FALSE(moveState.showingScaleGizmo);
|
||||
EXPECT_TRUE(moveState.useCenterPivot);
|
||||
EXPECT_TRUE(moveState.localSpace);
|
||||
EXPECT_TRUE(moveState.gizmoFrameOptions.localSpace);
|
||||
|
||||
const auto transformState = BuildSceneViewportToolState(
|
||||
SceneViewportToolMode::Transform,
|
||||
SceneViewportPivotMode::Pivot,
|
||||
SceneViewportTransformSpaceMode::Global);
|
||||
EXPECT_FALSE(transformState.usingViewMoveTool);
|
||||
EXPECT_TRUE(transformState.usingTransformTool);
|
||||
EXPECT_TRUE(transformState.showingMoveGizmo);
|
||||
EXPECT_TRUE(transformState.showingRotateGizmo);
|
||||
EXPECT_TRUE(transformState.showingScaleGizmo);
|
||||
EXPECT_FALSE(transformState.localSpace);
|
||||
EXPECT_TRUE(transformState.gizmoFrameOptions.usingTransformTool);
|
||||
}
|
||||
|
||||
TEST(SceneViewportInteractionFrameTest, FrameGeometryBuildsViewportAndLocalMouseCoordinates) {
|
||||
const auto geometry = BuildSceneViewportFrameGeometry(
|
||||
ImVec2(640.0f, 360.0f),
|
||||
ImVec2(100.0f, 40.0f),
|
||||
ImVec2(148.0f, 92.0f));
|
||||
|
||||
EXPECT_FLOAT_EQ(geometry.viewportSize.x, 640.0f);
|
||||
EXPECT_FLOAT_EQ(geometry.viewportSize.y, 360.0f);
|
||||
EXPECT_FLOAT_EQ(geometry.localMousePosition.x, 48.0f);
|
||||
EXPECT_FLOAT_EQ(geometry.localMousePosition.y, 52.0f);
|
||||
}
|
||||
|
||||
TEST(SceneViewportInteractionFrameTest, FrameStateUsesEmptyOverlayWhenViewportIsNotInteractive) {
|
||||
StubEditorContext context = {};
|
||||
StubViewportHostService viewportHostService = {};
|
||||
context.viewportHostService = &viewportHostService;
|
||||
const SceneViewportOverlayFrameData emptyOverlayFrameData = {};
|
||||
|
||||
XCEngine::Editor::SceneViewportMoveGizmo moveGizmo = {};
|
||||
XCEngine::Editor::SceneViewportRotateGizmo rotateGizmo = {};
|
||||
XCEngine::Editor::SceneViewportScaleGizmo scaleGizmo = {};
|
||||
const SceneViewportInteractionFrameState frameState = BuildSceneViewportInteractionFrameState(
|
||||
context,
|
||||
viewportHostService,
|
||||
false,
|
||||
BuildSceneViewportFrameGeometry(
|
||||
ImVec2(320.0f, 180.0f),
|
||||
ImVec2(10.0f, 20.0f),
|
||||
ImVec2(30.0f, 60.0f)),
|
||||
BuildSceneViewportToolState(
|
||||
SceneViewportToolMode::Move,
|
||||
SceneViewportPivotMode::Pivot,
|
||||
SceneViewportTransformSpaceMode::Global)
|
||||
.gizmoFrameOptions,
|
||||
moveGizmo,
|
||||
rotateGizmo,
|
||||
scaleGizmo,
|
||||
emptyOverlayFrameData);
|
||||
|
||||
EXPECT_FALSE(frameState.hasInteractiveViewport);
|
||||
EXPECT_EQ(frameState.overlayFrameData, &emptyOverlayFrameData);
|
||||
EXPECT_FALSE(frameState.overlay.valid);
|
||||
EXPECT_EQ(frameState.activeGizmoKind, XCEngine::Editor::SceneViewportActiveGizmoKind::None);
|
||||
EXPECT_FALSE(frameState.gizmoActive);
|
||||
EXPECT_EQ(viewportHostService.submissionCount, 0);
|
||||
}
|
||||
|
||||
TEST(SceneViewportInteractionFrameTest, FrameStateConsumesViewportHostOverlayAndSubmission) {
|
||||
StubEditorContext context = {};
|
||||
StubViewportHostService viewportHostService = {};
|
||||
context.viewportHostService = &viewportHostService;
|
||||
viewportHostService.overlay.valid = true;
|
||||
viewportHostService.overlay.verticalFovDegrees = 55.0f;
|
||||
const SceneViewportOverlayFrameData emptyOverlayFrameData = {};
|
||||
|
||||
XCEngine::Editor::SceneViewportMoveGizmo moveGizmo = {};
|
||||
XCEngine::Editor::SceneViewportRotateGizmo rotateGizmo = {};
|
||||
XCEngine::Editor::SceneViewportScaleGizmo scaleGizmo = {};
|
||||
const SceneViewportInteractionFrameState frameState = BuildSceneViewportInteractionFrameState(
|
||||
context,
|
||||
viewportHostService,
|
||||
true,
|
||||
BuildSceneViewportFrameGeometry(
|
||||
ImVec2(320.0f, 180.0f),
|
||||
ImVec2(10.0f, 20.0f),
|
||||
ImVec2(30.0f, 60.0f)),
|
||||
BuildSceneViewportToolState(
|
||||
SceneViewportToolMode::Move,
|
||||
SceneViewportPivotMode::Pivot,
|
||||
SceneViewportTransformSpaceMode::Global)
|
||||
.gizmoFrameOptions,
|
||||
moveGizmo,
|
||||
rotateGizmo,
|
||||
scaleGizmo,
|
||||
emptyOverlayFrameData);
|
||||
|
||||
EXPECT_TRUE(frameState.hasInteractiveViewport);
|
||||
EXPECT_TRUE(frameState.overlay.valid);
|
||||
EXPECT_FLOAT_EQ(frameState.overlay.verticalFovDegrees, 55.0f);
|
||||
EXPECT_EQ(frameState.overlayFrameData, &viewportHostService.overlayFrameData);
|
||||
EXPECT_TRUE(frameState.hudOverlay.sceneOverlay.valid);
|
||||
EXPECT_EQ(viewportHostService.submissionCount, 1);
|
||||
}
|
||||
|
||||
TEST(SceneViewportInteractionFrameTest, ResolveRequestCopiesFrameStateAndViewportGeometry) {
|
||||
SceneViewportInteractionFrameState frameState = {};
|
||||
SceneViewportOverlayFrameData overlayFrameData = {};
|
||||
frameState.overlayFrameData = &overlayFrameData;
|
||||
frameState.hudOverlay = XCEngine::Editor::BuildSceneViewportHudOverlayData(
|
||||
SceneViewportOverlayData{true});
|
||||
const auto geometry = BuildSceneViewportFrameGeometry(
|
||||
ImVec2(800.0f, 600.0f),
|
||||
ImVec2(100.0f, 120.0f),
|
||||
ImVec2(180.0f, 260.0f));
|
||||
|
||||
const auto request = BuildSceneViewportInteractionResolveRequest(
|
||||
frameState,
|
||||
geometry,
|
||||
ImVec2(100.0f, 120.0f),
|
||||
ImVec2(900.0f, 720.0f),
|
||||
ImVec2(180.0f, 260.0f));
|
||||
|
||||
EXPECT_EQ(request.overlayFrameData, &overlayFrameData);
|
||||
EXPECT_EQ(request.hudOverlay, &frameState.hudOverlay);
|
||||
EXPECT_FLOAT_EQ(request.viewportSize.x, 800.0f);
|
||||
EXPECT_FLOAT_EQ(request.viewportSize.y, 600.0f);
|
||||
EXPECT_FLOAT_EQ(request.localMousePosition.x, 80.0f);
|
||||
EXPECT_FLOAT_EQ(request.localMousePosition.y, 140.0f);
|
||||
EXPECT_FLOAT_EQ(request.absoluteMousePosition.x, 180.0f);
|
||||
EXPECT_FLOAT_EQ(request.absoluteMousePosition.y, 260.0f);
|
||||
}
|
||||
Reference in New Issue
Block a user