Formalize scene viewport interaction frame helpers
This commit is contained in:
@@ -1,5 +1,26 @@
|
|||||||
# SceneViewport Overlay/Gizmo Rework Checkpoint
|
# SceneViewport Overlay/Gizmo Rework Checkpoint
|
||||||
|
|
||||||
|
## Update 2026-04-04 Phase 5F
|
||||||
|
|
||||||
|
### Interaction Frame/Request Glue Formalization Completed
|
||||||
|
|
||||||
|
- Added `SceneViewportInteractionFrame.h` to formalize:
|
||||||
|
- scene viewport tool visibility/state derivation
|
||||||
|
- frame geometry derivation from viewport rect and mouse position
|
||||||
|
- per-frame interaction/gizmo context assembly
|
||||||
|
- interaction resolve request construction
|
||||||
|
- `SceneViewPanel` no longer assembles tool visibility booleans, local mouse coordinates, overlay frame references, or interaction resolve requests inline.
|
||||||
|
- The panel now consumes `SceneViewportToolState`, `SceneViewportFrameGeometry`, and `SceneViewportInteractionFrameState` instead of stitching together those frame-level inputs itself.
|
||||||
|
- Added focused editor tests covering tool-state mapping, frame geometry, interaction frame state, and resolve request assembly.
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
|
||||||
|
- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false`
|
||||||
|
- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportNavigationTest.*:SceneViewportInteractionFrameTest.*: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 5E
|
## Update 2026-04-04 Phase 5E
|
||||||
|
|
||||||
### Navigation/Input State Formalization Completed
|
### Navigation/Input State Formalization Completed
|
||||||
|
|||||||
132
editor/src/Viewport/SceneViewportInteractionFrame.h
Normal file
132
editor/src/Viewport/SceneViewportInteractionFrame.h
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Core/IEditorContext.h"
|
||||||
|
#include "IViewportHostService.h"
|
||||||
|
#include "SceneViewportEditorModes.h"
|
||||||
|
#include "SceneViewportHudOverlay.h"
|
||||||
|
#include "SceneViewportInteractionResolver.h"
|
||||||
|
#include "SceneViewportTransformGizmoCoordinator.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
struct SceneViewportToolState {
|
||||||
|
bool usingViewMoveTool = false;
|
||||||
|
bool usingTransformTool = false;
|
||||||
|
bool showingMoveGizmo = false;
|
||||||
|
bool showingRotateGizmo = false;
|
||||||
|
bool showingScaleGizmo = false;
|
||||||
|
bool useCenterPivot = false;
|
||||||
|
bool localSpace = false;
|
||||||
|
SceneViewportTransformGizmoFrameOptions gizmoFrameOptions = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
inline SceneViewportToolState BuildSceneViewportToolState(
|
||||||
|
SceneViewportToolMode toolMode,
|
||||||
|
SceneViewportPivotMode pivotMode,
|
||||||
|
SceneViewportTransformSpaceMode transformSpaceMode) {
|
||||||
|
SceneViewportToolState state = {};
|
||||||
|
state.usingViewMoveTool = toolMode == SceneViewportToolMode::ViewMove;
|
||||||
|
state.usingTransformTool = toolMode == SceneViewportToolMode::Transform;
|
||||||
|
state.showingMoveGizmo = toolMode == SceneViewportToolMode::Move || state.usingTransformTool;
|
||||||
|
state.showingRotateGizmo = toolMode == SceneViewportToolMode::Rotate || state.usingTransformTool;
|
||||||
|
state.showingScaleGizmo = toolMode == SceneViewportToolMode::Scale || state.usingTransformTool;
|
||||||
|
state.useCenterPivot = pivotMode == SceneViewportPivotMode::Center;
|
||||||
|
state.localSpace = transformSpaceMode == SceneViewportTransformSpaceMode::Local;
|
||||||
|
state.gizmoFrameOptions = BuildSceneViewportTransformGizmoFrameOptions(
|
||||||
|
state.useCenterPivot,
|
||||||
|
state.localSpace,
|
||||||
|
state.usingTransformTool,
|
||||||
|
state.showingMoveGizmo,
|
||||||
|
state.showingRotateGizmo,
|
||||||
|
state.showingScaleGizmo);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SceneViewportFrameGeometry {
|
||||||
|
Math::Vector2 viewportSize = Math::Vector2::Zero();
|
||||||
|
Math::Vector2 localMousePosition = Math::Vector2::Zero();
|
||||||
|
};
|
||||||
|
|
||||||
|
inline SceneViewportFrameGeometry BuildSceneViewportFrameGeometry(
|
||||||
|
const ImVec2& viewportSize,
|
||||||
|
const ImVec2& viewportMin,
|
||||||
|
const ImVec2& absoluteMousePosition) {
|
||||||
|
SceneViewportFrameGeometry geometry = {};
|
||||||
|
geometry.viewportSize = Math::Vector2(viewportSize.x, viewportSize.y);
|
||||||
|
geometry.localMousePosition = Math::Vector2(
|
||||||
|
absoluteMousePosition.x - viewportMin.x,
|
||||||
|
absoluteMousePosition.y - viewportMin.y);
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SceneViewportInteractionFrameState {
|
||||||
|
bool hasInteractiveViewport = false;
|
||||||
|
SceneViewportOverlayData overlay = {};
|
||||||
|
SceneViewportTransformGizmoFrameUpdate gizmoFrameUpdate = {};
|
||||||
|
SceneViewportTransformGizmoFrameState gizmoFrameState = {};
|
||||||
|
const SceneViewportOverlayFrameData* overlayFrameData = nullptr;
|
||||||
|
SceneViewportActiveGizmoKind activeGizmoKind = SceneViewportActiveGizmoKind::None;
|
||||||
|
bool gizmoActive = false;
|
||||||
|
SceneViewportHudOverlayData hudOverlay = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
inline SceneViewportInteractionFrameState BuildSceneViewportInteractionFrameState(
|
||||||
|
IEditorContext& context,
|
||||||
|
IViewportHostService& viewportHostService,
|
||||||
|
bool hasInteractiveViewport,
|
||||||
|
const SceneViewportFrameGeometry& geometry,
|
||||||
|
const SceneViewportTransformGizmoFrameOptions& gizmoFrameOptions,
|
||||||
|
SceneViewportMoveGizmo& moveGizmo,
|
||||||
|
SceneViewportRotateGizmo& rotateGizmo,
|
||||||
|
SceneViewportScaleGizmo& scaleGizmo,
|
||||||
|
const SceneViewportOverlayFrameData& emptyOverlayFrameData) {
|
||||||
|
SceneViewportInteractionFrameState state = {};
|
||||||
|
state.hasInteractiveViewport = hasInteractiveViewport;
|
||||||
|
state.overlayFrameData = &emptyOverlayFrameData;
|
||||||
|
|
||||||
|
if (!hasInteractiveViewport) {
|
||||||
|
CancelSceneViewportTransformGizmoFrame(context, moveGizmo, rotateGizmo, scaleGizmo);
|
||||||
|
state.hudOverlay = BuildSceneViewportHudOverlayData(state.overlay);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.overlay = viewportHostService.GetSceneViewOverlayData();
|
||||||
|
state.gizmoFrameUpdate = RefreshAndSubmitSceneViewportTransformGizmoFrame(
|
||||||
|
viewportHostService,
|
||||||
|
BuildSceneViewportTransformGizmoRefreshRequest(
|
||||||
|
context,
|
||||||
|
state.overlay,
|
||||||
|
geometry.viewportSize,
|
||||||
|
geometry.localMousePosition,
|
||||||
|
gizmoFrameOptions),
|
||||||
|
moveGizmo,
|
||||||
|
rotateGizmo,
|
||||||
|
scaleGizmo);
|
||||||
|
state.gizmoFrameState = state.gizmoFrameUpdate.frameState;
|
||||||
|
state.overlayFrameData = &viewportHostService.GetSceneViewEditorOverlayFrameData(context);
|
||||||
|
state.activeGizmoKind = state.gizmoFrameUpdate.overlaySubmission.activeGizmoKind;
|
||||||
|
state.gizmoActive = state.gizmoFrameUpdate.overlaySubmission.GizmoActive();
|
||||||
|
state.hudOverlay = BuildSceneViewportHudOverlayData(state.overlay);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline SceneViewportInteractionResolveRequest BuildSceneViewportInteractionResolveRequest(
|
||||||
|
const SceneViewportInteractionFrameState& frameState,
|
||||||
|
const SceneViewportFrameGeometry& geometry,
|
||||||
|
const ImVec2& viewportMin,
|
||||||
|
const ImVec2& viewportMax,
|
||||||
|
const ImVec2& absoluteMousePosition) {
|
||||||
|
SceneViewportInteractionResolveRequest request = {};
|
||||||
|
request.overlayFrameData = frameState.overlayFrameData;
|
||||||
|
request.viewportSize = geometry.viewportSize;
|
||||||
|
request.localMousePosition = geometry.localMousePosition;
|
||||||
|
request.hudOverlay = &frameState.hudOverlay;
|
||||||
|
request.viewportMin = viewportMin;
|
||||||
|
request.viewportMax = viewportMax;
|
||||||
|
request.absoluteMousePosition = absoluteMousePosition;
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "SceneViewPanel.h"
|
#include "SceneViewPanel.h"
|
||||||
#include "Viewport/SceneViewportEditorOverlayData.h"
|
#include "Viewport/SceneViewportEditorOverlayData.h"
|
||||||
#include "Viewport/SceneViewportHudOverlay.h"
|
#include "Viewport/SceneViewportHudOverlay.h"
|
||||||
|
#include "Viewport/SceneViewportInteractionFrame.h"
|
||||||
#include "Viewport/SceneViewportInteractionActions.h"
|
#include "Viewport/SceneViewportInteractionActions.h"
|
||||||
#include "Viewport/SceneViewportInteractionResolver.h"
|
#include "Viewport/SceneViewportInteractionResolver.h"
|
||||||
#include "Viewport/SceneViewportMath.h"
|
#include "Viewport/SceneViewportMath.h"
|
||||||
@@ -292,91 +293,51 @@ void SceneViewPanel::Render() {
|
|||||||
m_toolMode = toolShortcutAction.targetTool;
|
m_toolMode = toolShortcutAction.targetTool;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool usingViewMoveTool = m_toolMode == SceneViewportToolMode::ViewMove;
|
const SceneViewportToolState toolState = BuildSceneViewportToolState(
|
||||||
const bool usingTransformTool = m_toolMode == SceneViewportToolMode::Transform;
|
m_toolMode,
|
||||||
const bool showingMoveGizmo = m_toolMode == SceneViewportToolMode::Move || usingTransformTool;
|
m_pivotMode,
|
||||||
const bool showingRotateGizmo = m_toolMode == SceneViewportToolMode::Rotate || usingTransformTool;
|
m_transformSpaceMode);
|
||||||
const bool showingScaleGizmo = m_toolMode == SceneViewportToolMode::Scale || usingTransformTool;
|
const SceneViewportFrameGeometry frameGeometry = BuildSceneViewportFrameGeometry(
|
||||||
const bool useCenterPivot = m_pivotMode == SceneViewportPivotMode::Center;
|
content.availableSize,
|
||||||
const bool localSpace = m_transformSpaceMode == SceneViewportTransformSpaceMode::Local;
|
content.itemMin,
|
||||||
const SceneViewportTransformGizmoFrameOptions gizmoFrameOptions =
|
io.MousePos);
|
||||||
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 = {};
|
SceneViewportOverlayFrameData emptySceneOverlayFrameData = {};
|
||||||
|
const SceneViewportInteractionFrameState interactionFrameState =
|
||||||
if (hasInteractiveViewport) {
|
BuildSceneViewportInteractionFrameState(
|
||||||
overlay = viewportHostService->GetSceneViewOverlayData();
|
*m_context,
|
||||||
interactionGizmoFrame = RefreshAndSubmitSceneViewportTransformGizmoFrame(
|
|
||||||
*viewportHostService,
|
*viewportHostService,
|
||||||
BuildSceneViewportTransformGizmoRefreshRequest(
|
hasInteractiveViewport,
|
||||||
*m_context,
|
frameGeometry,
|
||||||
overlay,
|
toolState.gizmoFrameOptions,
|
||||||
viewportSize,
|
|
||||||
localMousePosition,
|
|
||||||
gizmoFrameOptions),
|
|
||||||
m_moveGizmo,
|
m_moveGizmo,
|
||||||
m_rotateGizmo,
|
m_rotateGizmo,
|
||||||
m_scaleGizmo);
|
m_scaleGizmo,
|
||||||
gizmoFrameState = interactionGizmoFrame.frameState;
|
emptySceneOverlayFrameData);
|
||||||
} else {
|
|
||||||
CancelSceneViewportTransformGizmoFrame(
|
|
||||||
*m_context,
|
|
||||||
m_moveGizmo,
|
|
||||||
m_rotateGizmo,
|
|
||||||
m_scaleGizmo);
|
|
||||||
}
|
|
||||||
|
|
||||||
const SceneViewportTransformGizmoOverlaySubmission interactionGizmoSubmission =
|
|
||||||
hasInteractiveViewport
|
|
||||||
? interactionGizmoFrame.overlaySubmission
|
|
||||||
: SceneViewportTransformGizmoOverlaySubmission{};
|
|
||||||
const SceneViewportOverlayFrameData& interactionOverlayFrameData =
|
|
||||||
hasInteractiveViewport
|
|
||||||
? viewportHostService->GetSceneViewEditorOverlayFrameData(*m_context)
|
|
||||||
: emptySceneOverlayFrameData;
|
|
||||||
const SceneViewportActiveGizmoKind activeGizmoKind = interactionGizmoSubmission.activeGizmoKind;
|
|
||||||
const bool gizmoActive = interactionGizmoSubmission.GizmoActive();
|
|
||||||
const SceneViewportHudOverlayData interactionHudOverlay =
|
|
||||||
BuildSceneViewportHudOverlayData(overlay);
|
|
||||||
SceneViewportInteractionResult hoveredInteraction = {};
|
SceneViewportInteractionResult hoveredInteraction = {};
|
||||||
const bool canResolveViewportInteraction = CanResolveSceneViewportInteraction(
|
const bool canResolveViewportInteraction = CanResolveSceneViewportInteraction(
|
||||||
hasInteractiveViewport,
|
hasInteractiveViewport,
|
||||||
viewportContentHovered,
|
viewportContentHovered,
|
||||||
usingViewMoveTool,
|
toolState.usingViewMoveTool,
|
||||||
m_navigationState,
|
m_navigationState,
|
||||||
gizmoActive);
|
interactionFrameState.gizmoActive);
|
||||||
if (canResolveViewportInteraction) {
|
if (canResolveViewportInteraction) {
|
||||||
SceneViewportInteractionResolveRequest interactionRequest = {};
|
hoveredInteraction = ResolveSceneViewportInteraction(
|
||||||
interactionRequest.overlayFrameData = &interactionOverlayFrameData;
|
BuildSceneViewportInteractionResolveRequest(
|
||||||
interactionRequest.viewportSize = viewportSize;
|
interactionFrameState,
|
||||||
interactionRequest.localMousePosition = localMousePosition;
|
frameGeometry,
|
||||||
interactionRequest.hudOverlay = &interactionHudOverlay;
|
content.itemMin,
|
||||||
interactionRequest.viewportMin = content.itemMin;
|
content.itemMax,
|
||||||
interactionRequest.viewportMax = content.itemMax;
|
io.MousePos));
|
||||||
interactionRequest.absoluteMousePosition = io.MousePos;
|
|
||||||
hoveredInteraction = ResolveSceneViewportInteraction(interactionRequest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplySceneViewportHoveredHandleState(
|
ApplySceneViewportHoveredHandleState(
|
||||||
BuildSceneViewportHoveredHandleState(hoveredInteraction),
|
BuildSceneViewportHoveredHandleState(hoveredInteraction),
|
||||||
gizmoActive,
|
interactionFrameState.gizmoActive,
|
||||||
showingMoveGizmo,
|
toolState.showingMoveGizmo,
|
||||||
m_moveGizmo,
|
m_moveGizmo,
|
||||||
showingRotateGizmo,
|
toolState.showingRotateGizmo,
|
||||||
m_rotateGizmo,
|
m_rotateGizmo,
|
||||||
showingScaleGizmo,
|
toolState.showingScaleGizmo,
|
||||||
m_scaleGizmo);
|
m_scaleGizmo);
|
||||||
|
|
||||||
const SceneViewportInteractionActions interactionActions =
|
const SceneViewportInteractionActions interactionActions =
|
||||||
@@ -389,8 +350,8 @@ void SceneViewPanel::Render() {
|
|||||||
navigationRequest.state = m_navigationState;
|
navigationRequest.state = m_navigationState;
|
||||||
navigationRequest.hasInteractiveViewport = hasInteractiveViewport;
|
navigationRequest.hasInteractiveViewport = hasInteractiveViewport;
|
||||||
navigationRequest.viewportHovered = viewportContentHovered;
|
navigationRequest.viewportHovered = viewportContentHovered;
|
||||||
navigationRequest.usingViewMoveTool = usingViewMoveTool;
|
navigationRequest.usingViewMoveTool = toolState.usingViewMoveTool;
|
||||||
navigationRequest.gizmoActive = gizmoActive;
|
navigationRequest.gizmoActive = interactionFrameState.gizmoActive;
|
||||||
navigationRequest.clickedLeft = content.clickedLeft;
|
navigationRequest.clickedLeft = content.clickedLeft;
|
||||||
navigationRequest.clickedRight = content.clickedRight;
|
navigationRequest.clickedRight = content.clickedRight;
|
||||||
navigationRequest.clickedMiddle = content.clickedMiddle;
|
navigationRequest.clickedMiddle = content.clickedMiddle;
|
||||||
@@ -409,7 +370,7 @@ void SceneViewPanel::Render() {
|
|||||||
ExecuteSceneViewportTransformGizmoLifecycleCommand(
|
ExecuteSceneViewportTransformGizmoLifecycleCommand(
|
||||||
BuildBeginSceneViewportTransformGizmoLifecycleCommand(interactionActions),
|
BuildBeginSceneViewportTransformGizmoLifecycleCommand(interactionActions),
|
||||||
m_context->GetUndoManager(),
|
m_context->GetUndoManager(),
|
||||||
gizmoFrameState,
|
interactionFrameState.gizmoFrameState,
|
||||||
m_moveGizmo,
|
m_moveGizmo,
|
||||||
m_rotateGizmo,
|
m_rotateGizmo,
|
||||||
m_scaleGizmo);
|
m_scaleGizmo);
|
||||||
@@ -419,14 +380,14 @@ void SceneViewPanel::Render() {
|
|||||||
*m_context,
|
*m_context,
|
||||||
*viewportHostService,
|
*viewportHostService,
|
||||||
content.availableSize,
|
content.availableSize,
|
||||||
localMousePosition);
|
frameGeometry.localMousePosition);
|
||||||
|
|
||||||
ExecuteSceneViewportTransformGizmoLifecycleCommand(
|
ExecuteSceneViewportTransformGizmoLifecycleCommand(
|
||||||
BuildFrameSceneViewportTransformGizmoLifecycleCommand(
|
BuildFrameSceneViewportTransformGizmoLifecycleCommand(
|
||||||
activeGizmoKind,
|
interactionFrameState.activeGizmoKind,
|
||||||
ImGui::IsMouseDown(ImGuiMouseButton_Left)),
|
ImGui::IsMouseDown(ImGuiMouseButton_Left)),
|
||||||
m_context->GetUndoManager(),
|
m_context->GetUndoManager(),
|
||||||
gizmoFrameState,
|
interactionFrameState.gizmoFrameState,
|
||||||
m_moveGizmo,
|
m_moveGizmo,
|
||||||
m_rotateGizmo,
|
m_rotateGizmo,
|
||||||
m_scaleGizmo);
|
m_scaleGizmo);
|
||||||
@@ -466,15 +427,15 @@ void SceneViewPanel::Render() {
|
|||||||
viewportHostService->UpdateSceneViewInput(*m_context, input);
|
viewportHostService->UpdateSceneViewInput(*m_context, input);
|
||||||
|
|
||||||
if (content.hasViewportArea && content.frame.hasTexture) {
|
if (content.hasViewportArea && content.frame.hasTexture) {
|
||||||
overlay = viewportHostService->GetSceneViewOverlayData();
|
const SceneViewportOverlayData overlay = viewportHostService->GetSceneViewOverlayData();
|
||||||
RefreshAndSubmitSceneViewportTransformGizmoFrame(
|
RefreshAndSubmitSceneViewportTransformGizmoFrame(
|
||||||
*viewportHostService,
|
*viewportHostService,
|
||||||
BuildSceneViewportTransformGizmoRefreshRequest(
|
BuildSceneViewportTransformGizmoRefreshRequest(
|
||||||
*m_context,
|
*m_context,
|
||||||
overlay,
|
overlay,
|
||||||
viewportSize,
|
frameGeometry.viewportSize,
|
||||||
localMousePosition,
|
frameGeometry.localMousePosition,
|
||||||
gizmoFrameOptions),
|
toolState.gizmoFrameOptions),
|
||||||
m_moveGizmo,
|
m_moveGizmo,
|
||||||
m_rotateGizmo,
|
m_rotateGizmo,
|
||||||
m_scaleGizmo);
|
m_scaleGizmo);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ set(EDITOR_TEST_SOURCES
|
|||||||
test_scene_viewport_picker.cpp
|
test_scene_viewport_picker.cpp
|
||||||
test_scene_viewport_interaction_actions.cpp
|
test_scene_viewport_interaction_actions.cpp
|
||||||
test_scene_viewport_interaction_resolver.cpp
|
test_scene_viewport_interaction_resolver.cpp
|
||||||
|
test_scene_viewport_interaction_frame.cpp
|
||||||
test_scene_viewport_transform_gizmo_coordinator.cpp
|
test_scene_viewport_transform_gizmo_coordinator.cpp
|
||||||
test_scene_viewport_shader_paths.cpp
|
test_scene_viewport_shader_paths.cpp
|
||||||
test_scene_viewport_overlay_renderer.cpp
|
test_scene_viewport_overlay_renderer.cpp
|
||||||
|
|||||||
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