Refactor new editor boundaries and test ownership
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
set(EDITOR_UI_UNIT_TEST_SOURCES
|
||||
test_input_modifier_tracker.cpp
|
||||
test_ui_editor_command_dispatcher.cpp
|
||||
test_ui_editor_command_registry.cpp
|
||||
test_ui_editor_dock_host_interaction.cpp
|
||||
@@ -116,8 +115,10 @@ if(TARGET XCUIEditorAppLib)
|
||||
endif()
|
||||
|
||||
list(APPEND EDITOR_APP_FEATURE_TEST_SOURCES
|
||||
test_input_modifier_tracker.cpp
|
||||
test_editor_host_command_bridge.cpp
|
||||
test_editor_shell_asset_validation.cpp
|
||||
test_editor_window_tab_drag_drop_target.cpp
|
||||
test_editor_window_workspace_store.cpp
|
||||
test_structured_editor_shell.cpp
|
||||
test_editor_window_input_routing.cpp
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/Platform/Win32/WindowManager/TabDragDropTarget.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Editor::App::Internal::EditorWindowTabDragDropTarget;
|
||||
using XCEngine::UI::Editor::App::Internal::ResolveEditorWindowTabDragDropTarget;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceDockPlacement;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostLayout;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostTabItemLayout;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostTabStackLayout;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorTabStripInvalidIndex;
|
||||
|
||||
UIEditorDockHostLayout BuildDockLayout() {
|
||||
UIEditorDockHostLayout layout = {};
|
||||
layout.bounds = UIRect(0.0f, 0.0f, 420.0f, 280.0f);
|
||||
|
||||
UIEditorDockHostTabStackLayout tabStack = {};
|
||||
tabStack.nodeId = "stack-a";
|
||||
tabStack.bounds = UIRect(20.0f, 20.0f, 300.0f, 200.0f);
|
||||
tabStack.items = {
|
||||
UIEditorDockHostTabItemLayout{ "doc-a", "Document A", false },
|
||||
UIEditorDockHostTabItemLayout{ "doc-b", "Document B", true },
|
||||
UIEditorDockHostTabItemLayout{ "doc-c", "Document C", false },
|
||||
};
|
||||
tabStack.tabStripLayout.headerRect = UIRect(20.0f, 20.0f, 180.0f, 24.0f);
|
||||
tabStack.tabStripLayout.tabHeaderRects = {
|
||||
UIRect(20.0f, 20.0f, 40.0f, 24.0f),
|
||||
UIRect(64.0f, 20.0f, 44.0f, 24.0f),
|
||||
UIRect(112.0f, 20.0f, 48.0f, 24.0f),
|
||||
};
|
||||
layout.tabStacks.push_back(std::move(tabStack));
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(EditorWindowTabDragDropTargetTests, ReturnsInvalidTargetOutsideDockHostBounds) {
|
||||
const UIEditorDockHostLayout layout = BuildDockLayout();
|
||||
|
||||
const EditorWindowTabDragDropTarget target =
|
||||
ResolveEditorWindowTabDragDropTarget(layout, UIPoint(460.0f, 40.0f));
|
||||
|
||||
EXPECT_FALSE(target.valid);
|
||||
EXPECT_TRUE(target.nodeId.empty());
|
||||
EXPECT_EQ(target.placement, UIEditorWorkspaceDockPlacement::Center);
|
||||
EXPECT_EQ(target.insertionIndex, UIEditorTabStripInvalidIndex);
|
||||
}
|
||||
|
||||
TEST(EditorWindowTabDragDropTargetTests, HeaderGapResolvesCenterPlacementAndAppendInsertionIndex) {
|
||||
const UIEditorDockHostLayout layout = BuildDockLayout();
|
||||
|
||||
const EditorWindowTabDragDropTarget target =
|
||||
ResolveEditorWindowTabDragDropTarget(layout, UIPoint(190.0f, 32.0f));
|
||||
|
||||
ASSERT_TRUE(target.valid);
|
||||
EXPECT_EQ(target.nodeId, "stack-a");
|
||||
EXPECT_EQ(target.placement, UIEditorWorkspaceDockPlacement::Center);
|
||||
EXPECT_EQ(target.insertionIndex, 3u);
|
||||
}
|
||||
|
||||
TEST(EditorWindowTabDragDropTargetTests, BodyEdgesResolveDirectionalDockPlacement) {
|
||||
const UIEditorDockHostLayout layout = BuildDockLayout();
|
||||
|
||||
EXPECT_EQ(
|
||||
ResolveEditorWindowTabDragDropTarget(layout, UIPoint(24.0f, 120.0f)).placement,
|
||||
UIEditorWorkspaceDockPlacement::Left);
|
||||
EXPECT_EQ(
|
||||
ResolveEditorWindowTabDragDropTarget(layout, UIPoint(314.0f, 120.0f)).placement,
|
||||
UIEditorWorkspaceDockPlacement::Right);
|
||||
EXPECT_EQ(
|
||||
ResolveEditorWindowTabDragDropTarget(layout, UIPoint(120.0f, 48.0f)).placement,
|
||||
UIEditorWorkspaceDockPlacement::Top);
|
||||
EXPECT_EQ(
|
||||
ResolveEditorWindowTabDragDropTarget(layout, UIPoint(120.0f, 214.0f)).placement,
|
||||
UIEditorWorkspaceDockPlacement::Bottom);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Platform/Win32/WindowManager/EditorWindowWorkspaceStore.h"
|
||||
#include "Composition/EditorWindowWorkspaceStore.h"
|
||||
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceModel.h>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Platform/Win32/InputModifierTracker.h"
|
||||
#include "app/Platform/Win32/InputModifierTracker.h"
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "Features/Project/ProjectPanel.h"
|
||||
#include "Ports/SystemInteractionPort.h"
|
||||
#include "Rendering/Assets/BuiltInIcons.h"
|
||||
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
@@ -13,6 +14,32 @@
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
namespace {
|
||||
|
||||
class FakeSystemInteractionHost final : public Ports::SystemInteractionPort {
|
||||
public:
|
||||
bool CopyTextToClipboard(std::string_view text) override {
|
||||
lastClipboardText = std::string(text);
|
||||
++clipboardCallCount;
|
||||
return clipboardResult;
|
||||
}
|
||||
|
||||
bool RevealPathInFileBrowser(
|
||||
const std::filesystem::path& path,
|
||||
bool selectTarget) override {
|
||||
lastRevealPath = path;
|
||||
lastRevealSelectTarget = selectTarget;
|
||||
++revealCallCount;
|
||||
return revealResult;
|
||||
}
|
||||
|
||||
bool clipboardResult = true;
|
||||
bool revealResult = true;
|
||||
int clipboardCallCount = 0;
|
||||
int revealCallCount = 0;
|
||||
bool lastRevealSelectTarget = false;
|
||||
std::string lastClipboardText = {};
|
||||
std::filesystem::path lastRevealPath = {};
|
||||
};
|
||||
|
||||
class TemporaryRepo final {
|
||||
public:
|
||||
TemporaryRepo() {
|
||||
@@ -83,6 +110,13 @@ UIEditorPanelContentHostFrame MakeProjectHostFrame() {
|
||||
return event;
|
||||
}
|
||||
|
||||
PanelInputContext MakeFocusedPanelInputContext() {
|
||||
PanelInputContext inputContext = {};
|
||||
inputContext.allowInteraction = true;
|
||||
inputContext.hasInputFocus = true;
|
||||
return inputContext;
|
||||
}
|
||||
|
||||
TEST(ProjectPanelTests, CreateFolderCommandCreatesDirectoryAndQueuesRename) {
|
||||
TemporaryRepo repo = {};
|
||||
|
||||
@@ -141,13 +175,11 @@ TEST(ProjectPanelTests, BackgroundContextMenuCreateFolderUsesCurrentFolder) {
|
||||
panel.Update(
|
||||
hostFrame,
|
||||
{ MakePointerButtonDown(520.0f, 180.0f, ::XCEngine::UI::UIPointerButton::Right) },
|
||||
true,
|
||||
true);
|
||||
MakeFocusedPanelInputContext());
|
||||
panel.Update(
|
||||
hostFrame,
|
||||
{ MakePointerButtonDown(520.0f, 194.0f, ::XCEngine::UI::UIPointerButton::Left) },
|
||||
true,
|
||||
true);
|
||||
MakeFocusedPanelInputContext());
|
||||
|
||||
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/New Folder"));
|
||||
const UIEditorHostCommandEvaluationResult renameEvaluation =
|
||||
@@ -172,13 +204,11 @@ TEST(ProjectPanelTests, FolderContextMenuCreateFolderUsesFolderTarget) {
|
||||
panel.Update(
|
||||
hostFrame,
|
||||
{ MakePointerButtonDown(300.0f, 80.0f, ::XCEngine::UI::UIPointerButton::Right) },
|
||||
true,
|
||||
true);
|
||||
MakeFocusedPanelInputContext());
|
||||
panel.Update(
|
||||
hostFrame,
|
||||
{ MakePointerButtonDown(320.0f, 120.0f, ::XCEngine::UI::UIPointerButton::Left) },
|
||||
true,
|
||||
true);
|
||||
MakeFocusedPanelInputContext());
|
||||
|
||||
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/FolderA/New Folder"));
|
||||
}
|
||||
@@ -241,5 +271,56 @@ TEST(ProjectPanelTests, BuiltInIconsCanBeConfiguredBeforeRuntimeInitialization)
|
||||
EXPECT_TRUE(evaluation.executable);
|
||||
}
|
||||
|
||||
TEST(ProjectPanelTests, CopyPathCommandUsesInjectedSystemHost) {
|
||||
TemporaryRepo repo = {};
|
||||
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scripts"));
|
||||
ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs"));
|
||||
|
||||
EditorProjectRuntime runtime = {};
|
||||
ASSERT_TRUE(runtime.Initialize(repo.Root()));
|
||||
ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scripts"));
|
||||
ASSERT_TRUE(runtime.SetSelection("Assets/Scripts/Player.cs"));
|
||||
|
||||
ProjectPanel panel = {};
|
||||
FakeSystemInteractionHost systemHost = {};
|
||||
panel.SetProjectRuntime(&runtime);
|
||||
panel.SetSystemInteractionHost(&systemHost);
|
||||
|
||||
const UIEditorHostCommandEvaluationResult evaluation =
|
||||
panel.EvaluateAssetCommand("assets.copy_path");
|
||||
EXPECT_TRUE(evaluation.executable);
|
||||
|
||||
const UIEditorHostCommandDispatchResult dispatch =
|
||||
panel.DispatchAssetCommand("assets.copy_path");
|
||||
EXPECT_TRUE(dispatch.commandExecuted);
|
||||
EXPECT_EQ(systemHost.clipboardCallCount, 1);
|
||||
EXPECT_EQ(systemHost.lastClipboardText, "Assets/Scripts/Player.cs");
|
||||
}
|
||||
|
||||
TEST(ProjectPanelTests, ShowInExplorerCommandUsesInjectedSystemHost) {
|
||||
TemporaryRepo repo = {};
|
||||
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scripts"));
|
||||
ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs"));
|
||||
|
||||
EditorProjectRuntime runtime = {};
|
||||
ASSERT_TRUE(runtime.Initialize(repo.Root()));
|
||||
ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scripts"));
|
||||
ASSERT_TRUE(runtime.SetSelection("Assets/Scripts/Player.cs"));
|
||||
|
||||
ProjectPanel panel = {};
|
||||
FakeSystemInteractionHost systemHost = {};
|
||||
panel.SetProjectRuntime(&runtime);
|
||||
panel.SetSystemInteractionHost(&systemHost);
|
||||
|
||||
const UIEditorHostCommandDispatchResult dispatch =
|
||||
panel.DispatchAssetCommand("assets.show_in_explorer");
|
||||
EXPECT_TRUE(dispatch.commandExecuted);
|
||||
EXPECT_EQ(systemHost.revealCallCount, 1);
|
||||
EXPECT_EQ(
|
||||
systemHost.lastRevealPath.lexically_normal(),
|
||||
(repo.Root() / "project/Assets/Scripts/Player.cs").lexically_normal());
|
||||
EXPECT_TRUE(systemHost.lastRevealSelectTarget);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
#include "Features/Scene/SceneViewportController.h"
|
||||
#include "Features/Inspector/InspectorSubject.h"
|
||||
#include "Rendering/Viewport/SceneViewportRenderService.h"
|
||||
#include "Rendering/Viewport/ViewportHostService.h"
|
||||
#include "Rendering/Viewport/ViewportRenderTargetInternal.h"
|
||||
#include "State/EditorSelectionService.h"
|
||||
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
@@ -482,7 +484,7 @@ TEST(SceneViewportRuntimeTests, RightMouseDragRotatesSceneCameraThroughViewportC
|
||||
const Math::Vector3 beforeForward = transform->GetForward();
|
||||
|
||||
SceneViewportController controller = {};
|
||||
ViewportHostService viewportHostService = {};
|
||||
SceneViewportRenderService sceneViewportRenderService = {};
|
||||
UIEditorViewportInputBridgeState inputBridgeState = {};
|
||||
const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f);
|
||||
const UISize viewportSize(640.0f, 360.0f);
|
||||
@@ -501,7 +503,7 @@ TEST(SceneViewportRuntimeTests, RightMouseDragRotatesSceneCameraThroughViewportC
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(pressFrame, inputRect, viewportSize));
|
||||
|
||||
@@ -517,7 +519,7 @@ TEST(SceneViewportRuntimeTests, RightMouseDragRotatesSceneCameraThroughViewportC
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(dragFrame, inputRect, viewportSize));
|
||||
|
||||
@@ -566,7 +568,7 @@ TEST(SceneViewportRuntimeTests, MiddleMouseDragPansSceneCameraWithGrabSemantics)
|
||||
const Math::Vector3 before = transform->GetPosition();
|
||||
|
||||
SceneViewportController controller = {};
|
||||
ViewportHostService viewportHostService = {};
|
||||
SceneViewportRenderService sceneViewportRenderService = {};
|
||||
UIEditorViewportInputBridgeState inputBridgeState = {};
|
||||
const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f);
|
||||
const UISize viewportSize(640.0f, 360.0f);
|
||||
@@ -585,7 +587,7 @@ TEST(SceneViewportRuntimeTests, MiddleMouseDragPansSceneCameraWithGrabSemantics)
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(pressFrame, inputRect, viewportSize));
|
||||
|
||||
@@ -601,7 +603,7 @@ TEST(SceneViewportRuntimeTests, MiddleMouseDragPansSceneCameraWithGrabSemantics)
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(dragFrame, inputRect, viewportSize));
|
||||
|
||||
@@ -625,7 +627,7 @@ TEST(SceneViewportRuntimeTests, ViewToolLeftMouseDragPansSceneCameraWithGrabSema
|
||||
const Math::Vector3 before = transform->GetPosition();
|
||||
|
||||
SceneViewportController controller = {};
|
||||
ViewportHostService viewportHostService = {};
|
||||
SceneViewportRenderService sceneViewportRenderService = {};
|
||||
UIEditorViewportInputBridgeState inputBridgeState = {};
|
||||
const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f);
|
||||
const UISize viewportSize(640.0f, 360.0f);
|
||||
@@ -644,7 +646,7 @@ TEST(SceneViewportRuntimeTests, ViewToolLeftMouseDragPansSceneCameraWithGrabSema
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(pressFrame, inputRect, viewportSize));
|
||||
|
||||
@@ -660,7 +662,7 @@ TEST(SceneViewportRuntimeTests, ViewToolLeftMouseDragPansSceneCameraWithGrabSema
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(dragFrame, inputRect, viewportSize));
|
||||
|
||||
@@ -679,7 +681,7 @@ TEST(SceneViewportRuntimeTests, MouseWheelUsesSingleNotchNormalizationForSceneZo
|
||||
runtime.BuildSceneViewportRenderRequest().orbitDistance;
|
||||
|
||||
SceneViewportController controller = {};
|
||||
ViewportHostService viewportHostService = {};
|
||||
SceneViewportRenderService sceneViewportRenderService = {};
|
||||
UIEditorViewportInputBridgeState inputBridgeState = {};
|
||||
const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f);
|
||||
const UISize viewportSize(640.0f, 360.0f);
|
||||
@@ -692,7 +694,7 @@ TEST(SceneViewportRuntimeTests, MouseWheelUsesSingleNotchNormalizationForSceneZo
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(hoverFrame, inputRect, viewportSize));
|
||||
|
||||
@@ -704,7 +706,7 @@ TEST(SceneViewportRuntimeTests, MouseWheelUsesSingleNotchNormalizationForSceneZo
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(wheelFrame, inputRect, viewportSize));
|
||||
|
||||
@@ -725,7 +727,7 @@ TEST(SceneViewportRuntimeTests, ToolShortcutSwitchesFocusedSceneViewportIntoTran
|
||||
EXPECT_EQ(runtime.GetToolMode(), SceneToolMode::View);
|
||||
|
||||
SceneViewportController controller = {};
|
||||
ViewportHostService viewportHostService = {};
|
||||
SceneViewportRenderService sceneViewportRenderService = {};
|
||||
UIEditorViewportInputBridgeState inputBridgeState = {};
|
||||
const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f);
|
||||
const UISize viewportSize(640.0f, 360.0f);
|
||||
@@ -749,7 +751,7 @@ TEST(SceneViewportRuntimeTests, ToolShortcutSwitchesFocusedSceneViewportIntoTran
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(focusFrame, inputRect, viewportSize));
|
||||
|
||||
@@ -765,7 +767,7 @@ TEST(SceneViewportRuntimeTests, ToolShortcutSwitchesFocusedSceneViewportIntoTran
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(shortcutFrame, inputRect, viewportSize));
|
||||
|
||||
@@ -782,7 +784,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayClickSwitchesModeOnPointerDown)
|
||||
runtime.SetToolMode(SceneToolMode::Translate);
|
||||
|
||||
SceneViewportController controller = {};
|
||||
ViewportHostService viewportHostService = {};
|
||||
SceneViewportRenderService sceneViewportRenderService = {};
|
||||
UIEditorViewportInputBridgeState inputBridgeState = {};
|
||||
const UIRect inputRect(100.0f, 104.0f, 640.0f, 360.0f);
|
||||
const UISize viewportSize(640.0f, 360.0f);
|
||||
@@ -803,7 +805,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayClickSwitchesModeOnPointerDown)
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(frame, inputRect, viewportSize));
|
||||
|
||||
@@ -820,7 +822,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayIncludesTransformButtonAndSwitch
|
||||
runtime.SetToolMode(SceneToolMode::Translate);
|
||||
|
||||
SceneViewportController controller = {};
|
||||
ViewportHostService viewportHostService = {};
|
||||
SceneViewportRenderService sceneViewportRenderService = {};
|
||||
UIEditorViewportInputBridgeState inputBridgeState = {};
|
||||
const UIRect inputRect(100.0f, 104.0f, 640.0f, 360.0f);
|
||||
const UISize viewportSize(640.0f, 360.0f);
|
||||
@@ -844,7 +846,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayIncludesTransformButtonAndSwitch
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(frame, inputRect, viewportSize));
|
||||
|
||||
@@ -861,7 +863,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayHandlesCoalescedClickInSingleFra
|
||||
runtime.SetToolMode(SceneToolMode::Translate);
|
||||
|
||||
SceneViewportController controller = {};
|
||||
ViewportHostService viewportHostService = {};
|
||||
SceneViewportRenderService sceneViewportRenderService = {};
|
||||
UIEditorViewportInputBridgeState inputBridgeState = {};
|
||||
const UIRect inputRect(100.0f, 104.0f, 640.0f, 360.0f);
|
||||
const UISize viewportSize(640.0f, 360.0f);
|
||||
@@ -890,12 +892,87 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayHandlesCoalescedClickInSingleFra
|
||||
});
|
||||
controller.Update(
|
||||
runtime,
|
||||
viewportHostService,
|
||||
sceneViewportRenderService,
|
||||
BuildSceneComposeState(inputBridgeState),
|
||||
BuildSceneComposeFrame(frame, inputRect, viewportSize));
|
||||
|
||||
EXPECT_EQ(runtime.GetToolMode(), SceneToolMode::Rotate);
|
||||
}
|
||||
|
||||
TEST(SceneViewportRuntimeTests, SceneViewportRendererDeclaresExplicitAuxiliaryResourceRequirements) {
|
||||
const ViewportResourceRequirements requirements =
|
||||
SceneViewportRenderService::GetViewportResourceRequirements();
|
||||
|
||||
EXPECT_TRUE(requirements.requiresDepthSampling);
|
||||
EXPECT_TRUE(requirements.requiresObjectIdSurface);
|
||||
EXPECT_TRUE(requirements.requiresSelectionMaskSurface);
|
||||
}
|
||||
|
||||
TEST(SceneViewportRuntimeTests, ViewportResourceReuseQueryHonorsExplicitRequirementsInsteadOfViewportKind) {
|
||||
ViewportRenderTargets targets = {};
|
||||
targets.width = 1280u;
|
||||
targets.height = 720u;
|
||||
targets.colorTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(1);
|
||||
targets.colorView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(2);
|
||||
targets.depthTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(3);
|
||||
targets.depthView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(4);
|
||||
targets.textureHandle = {
|
||||
5u,
|
||||
1280u,
|
||||
720u,
|
||||
::XCEngine::UI::UITextureHandleKind::ShaderResourceView,
|
||||
6u
|
||||
};
|
||||
|
||||
ViewportResourceRequirements requirements = {};
|
||||
EXPECT_TRUE(CanReuseViewportResources(
|
||||
BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u)));
|
||||
|
||||
requirements.requiresDepthSampling = true;
|
||||
EXPECT_FALSE(CanReuseViewportResources(
|
||||
BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u)));
|
||||
|
||||
targets.depthShaderView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(7);
|
||||
EXPECT_TRUE(CanReuseViewportResources(
|
||||
BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u)));
|
||||
|
||||
requirements.requiresObjectIdSurface = true;
|
||||
EXPECT_FALSE(CanReuseViewportResources(
|
||||
BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u)));
|
||||
|
||||
targets.objectIdTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(8);
|
||||
targets.objectIdDepthTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(9);
|
||||
targets.objectIdDepthView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(10);
|
||||
targets.objectIdView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(11);
|
||||
targets.objectIdShaderView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(12);
|
||||
EXPECT_TRUE(CanReuseViewportResources(
|
||||
BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u)));
|
||||
|
||||
requirements.requiresSelectionMaskSurface = true;
|
||||
EXPECT_FALSE(CanReuseViewportResources(
|
||||
BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u)));
|
||||
|
||||
targets.selectionMaskTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(13);
|
||||
targets.selectionMaskView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(14);
|
||||
targets.selectionMaskShaderView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(15);
|
||||
EXPECT_TRUE(CanReuseViewportResources(
|
||||
BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u)));
|
||||
}
|
||||
|
||||
TEST(SceneViewportRuntimeTests, ViewportHostServiceAcceptsArbitraryViewportIds) {
|
||||
ViewportHostService hostService = {};
|
||||
hostService.BeginFrame();
|
||||
|
||||
const ViewportFrame frame =
|
||||
hostService.RequestViewport("preview_inspector", UISize(320.0f, 200.0f));
|
||||
|
||||
EXPECT_TRUE(frame.wasRequested);
|
||||
EXPECT_FALSE(frame.hasTexture);
|
||||
EXPECT_FLOAT_EQ(frame.requestedSize.width, 320.0f);
|
||||
EXPECT_FLOAT_EQ(frame.requestedSize.height, 200.0f);
|
||||
EXPECT_FLOAT_EQ(frame.renderSize.width, 0.0f);
|
||||
EXPECT_FLOAT_EQ(frame.renderSize.height, 0.0f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
namespace {
|
||||
|
||||
using XCEngine::UI::Editor::FilterUIEditorPanelInputEvents;
|
||||
using XCEngine::UI::Editor::BuildUIEditorPanelInputEvents;
|
||||
using XCEngine::UI::Editor::UIEditorPanelInputFilterOptions;
|
||||
using ::XCEngine::UI::UIInputEvent;
|
||||
using ::XCEngine::UI::UIInputEventType;
|
||||
@@ -93,3 +94,28 @@ TEST(UIEditorPanelInputFilterTests, CapturedPointerEventsBypassBoundsWhenEnabled
|
||||
EXPECT_EQ(filtered[0].type, UIInputEventType::PointerMove);
|
||||
EXPECT_EQ(filtered[1].type, UIInputEventType::PointerButtonDown);
|
||||
}
|
||||
|
||||
TEST(UIEditorPanelInputFilterTests, SyntheticFocusTransitionsArePrependedToFilteredEvents) {
|
||||
const std::vector<UIInputEvent> filtered =
|
||||
BuildUIEditorPanelInputEvents(
|
||||
UIRect(0.0f, 0.0f, 100.0f, 100.0f),
|
||||
{
|
||||
MakePointerMove(20.0f, 20.0f),
|
||||
MakeKeyDown()
|
||||
},
|
||||
UIEditorPanelInputFilterOptions{
|
||||
.allowPointerInBounds = true,
|
||||
.allowPointerWhileCaptured = false,
|
||||
.allowKeyboardInput = true,
|
||||
.allowFocusEvents = false,
|
||||
.includePointerLeave = false
|
||||
},
|
||||
true,
|
||||
true);
|
||||
|
||||
ASSERT_EQ(filtered.size(), 4u);
|
||||
EXPECT_EQ(filtered[0].type, UIInputEventType::FocusLost);
|
||||
EXPECT_EQ(filtered[1].type, UIInputEventType::FocusGained);
|
||||
EXPECT_EQ(filtered[2].type, UIInputEventType::PointerMove);
|
||||
EXPECT_EQ(filtered[3].type, UIInputEventType::KeyDown);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace TreeDrag = XCEngine::UI::Editor::Collections::TreeDragDrop;
|
||||
namespace Widgets = XCEngine::UI::Editor::Widgets;
|
||||
|
||||
using XCEngine::UI::Editor::BuildUIEditorTreePanelInlineRenameBounds;
|
||||
using XCEngine::UI::Editor::BuildUIEditorTreePanelInputEvents;
|
||||
using XCEngine::UI::Editor::BuildUIEditorTreePanelInteractionInputEvents;
|
||||
using XCEngine::UI::Editor::FilterUIEditorTreePanelInputEvents;
|
||||
using XCEngine::UI::Editor::FilterUIEditorTreePanelPointerInputEvents;
|
||||
using XCEngine::UI::Editor::FindUIEditorTreePanelVisibleItemIndex;
|
||||
using XCEngine::UI::Editor::UIEditorTreePanelInputFilterOptions;
|
||||
@@ -80,9 +80,9 @@ TreeFixture BuildTreeFixture() {
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UIEditorTreePanelBehaviorTests, FilterPanelInputHonorsBoundsAndPanelActivity) {
|
||||
TEST(UIEditorTreePanelBehaviorTests, FilterPanelInputHonorsBoundsAndInputFocus) {
|
||||
const std::vector<UIInputEvent> filtered =
|
||||
FilterUIEditorTreePanelInputEvents(
|
||||
BuildUIEditorTreePanelInputEvents(
|
||||
UIRect(0.0f, 0.0f, 100.0f, 100.0f),
|
||||
{
|
||||
MakePointerMove(40.0f, 40.0f),
|
||||
@@ -92,7 +92,7 @@ TEST(UIEditorTreePanelBehaviorTests, FilterPanelInputHonorsBoundsAndPanelActivit
|
||||
},
|
||||
UIEditorTreePanelInputFilterOptions{
|
||||
.allowInteraction = true,
|
||||
.panelActive = true,
|
||||
.hasInputFocus = true,
|
||||
.captureActive = false
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Editor::IsUIEditorViewportInputBridgeKeyDown;
|
||||
using XCEngine::UI::Editor::IsUIEditorViewportInputBridgePointerButtonDown;
|
||||
using XCEngine::UI::Editor::UIEditorViewportInputBridgeFocusMode;
|
||||
using XCEngine::UI::Editor::UIEditorViewportInputBridgeRequest;
|
||||
using XCEngine::UI::Editor::UIEditorViewportInputBridgeState;
|
||||
using XCEngine::UI::Editor::UpdateUIEditorViewportInputBridge;
|
||||
|
||||
@@ -385,4 +387,40 @@ TEST(UIEditorViewportInputBridgeTest, FocusLostClearsCapturedStateAndHeldKeys) {
|
||||
EXPECT_FALSE(IsUIEditorViewportInputBridgePointerButtonDown(state, UIPointerButton::Left));
|
||||
}
|
||||
|
||||
TEST(UIEditorViewportInputBridgeTest, ExternalFocusModeTransfersFocusAndReleasesCaptureFromOwnerState) {
|
||||
UIEditorViewportInputBridgeState state = {};
|
||||
UIEditorViewportInputBridgeRequest focusedRequest = {};
|
||||
focusedRequest.focusMode = UIEditorViewportInputBridgeFocusMode::External;
|
||||
focusedRequest.focused = true;
|
||||
|
||||
auto frame = UpdateUIEditorViewportInputBridge(
|
||||
state,
|
||||
UIRect(100.0f, 200.0f, 640.0f, 360.0f),
|
||||
{
|
||||
MakePointerEvent(UIInputEventType::PointerButtonDown, 220.0f, 280.0f, UIPointerButton::Left)
|
||||
},
|
||||
{},
|
||||
focusedRequest);
|
||||
|
||||
EXPECT_TRUE(frame.focusGained);
|
||||
EXPECT_TRUE(frame.focused);
|
||||
EXPECT_TRUE(frame.captured);
|
||||
|
||||
UIEditorViewportInputBridgeRequest unfocusedRequest = focusedRequest;
|
||||
unfocusedRequest.focused = false;
|
||||
frame = UpdateUIEditorViewportInputBridge(
|
||||
state,
|
||||
UIRect(100.0f, 200.0f, 640.0f, 360.0f),
|
||||
{},
|
||||
{},
|
||||
unfocusedRequest);
|
||||
|
||||
EXPECT_TRUE(frame.focusLost);
|
||||
EXPECT_TRUE(frame.captureEnded);
|
||||
EXPECT_FALSE(frame.focused);
|
||||
EXPECT_FALSE(frame.captured);
|
||||
EXPECT_FALSE(state.focused);
|
||||
EXPECT_FALSE(state.captured);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Docking/UIEditorDockHost.h>
|
||||
#include <XCEditor/Panels/UIEditorPanelContentHost.h>
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceInteraction.h>
|
||||
|
||||
namespace {
|
||||
@@ -14,7 +15,10 @@ using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::UI::Editor::FindUIEditorPanelContentHostPanelState;
|
||||
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame;
|
||||
using XCEngine::UI::Editor::IsUIEditorWorkspaceHostedPanelInputOwner;
|
||||
using XCEngine::UI::Editor::IsUIEditorWorkspaceViewportInputOwner;
|
||||
using XCEngine::UI::Editor::UIEditorPanelPresentationKind;
|
||||
using XCEngine::UI::Editor::UIEditorPanelRegistry;
|
||||
using XCEngine::UI::Editor::UIEditorViewportShellModel;
|
||||
@@ -33,7 +37,7 @@ UIEditorPanelRegistry BuildPanelRegistry() {
|
||||
registry.panels = {
|
||||
{ "viewport", "Viewport", UIEditorPanelPresentationKind::ViewportShell, false, true, true },
|
||||
{ "doc", "Document", UIEditorPanelPresentationKind::Placeholder, true, true, true },
|
||||
{ "details", "Details", UIEditorPanelPresentationKind::Placeholder, true, true, true }
|
||||
{ "details", "Details", UIEditorPanelPresentationKind::HostedContent, true, true, true }
|
||||
};
|
||||
return registry;
|
||||
}
|
||||
@@ -138,12 +142,72 @@ TEST(UIEditorWorkspaceInteractionTest, PointerDownInsideViewportBubblesPointerCa
|
||||
EXPECT_EQ(frame.result.viewportPanelId, "viewport");
|
||||
EXPECT_TRUE(frame.result.viewportInputFrame.captureStarted);
|
||||
EXPECT_TRUE(frame.result.viewportInputFrame.focused);
|
||||
EXPECT_TRUE(frame.inputOwnerChanged);
|
||||
EXPECT_TRUE(IsUIEditorWorkspaceViewportInputOwner(frame.inputOwner, "viewport"));
|
||||
|
||||
viewportFrame = FindUIEditorWorkspaceViewportPresentationFrame(frame.composeFrame, "viewport");
|
||||
ASSERT_NE(viewportFrame, nullptr);
|
||||
EXPECT_TRUE(viewportFrame->viewportShellFrame.inputFrame.captured);
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceInteractionTest, HostedPanelPointerDownTransfersOwnerAndClearsViewportFocus) {
|
||||
auto controller =
|
||||
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
|
||||
UIEditorWorkspaceInteractionState state = {};
|
||||
const UIEditorWorkspaceInteractionModel model = BuildInteractionModel();
|
||||
|
||||
auto frame = UpdateUIEditorWorkspaceInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
model,
|
||||
{});
|
||||
const auto* viewportFrame =
|
||||
FindUIEditorWorkspaceViewportPresentationFrame(frame.composeFrame, "viewport");
|
||||
ASSERT_NE(viewportFrame, nullptr);
|
||||
const UIPoint viewportCenter = RectCenter(viewportFrame->viewportShellFrame.slotLayout.inputRect);
|
||||
|
||||
frame = UpdateUIEditorWorkspaceInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
model,
|
||||
{
|
||||
MakePointerEvent(UIInputEventType::PointerMove, viewportCenter.x, viewportCenter.y),
|
||||
MakePointerEvent(
|
||||
UIInputEventType::PointerButtonDown,
|
||||
viewportCenter.x,
|
||||
viewportCenter.y,
|
||||
UIPointerButton::Left)
|
||||
});
|
||||
ASSERT_TRUE(IsUIEditorWorkspaceViewportInputOwner(frame.inputOwner, "viewport"));
|
||||
|
||||
const auto* detailsPanel = FindUIEditorPanelContentHostPanelState(
|
||||
frame.composeFrame.contentHostFrame,
|
||||
"details");
|
||||
ASSERT_NE(detailsPanel, nullptr);
|
||||
const UIPoint detailsCenter = RectCenter(detailsPanel->bounds);
|
||||
|
||||
frame = UpdateUIEditorWorkspaceInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
model,
|
||||
{
|
||||
MakePointerEvent(
|
||||
UIInputEventType::PointerButtonDown,
|
||||
detailsCenter.x,
|
||||
detailsCenter.y,
|
||||
UIPointerButton::Left)
|
||||
});
|
||||
|
||||
EXPECT_TRUE(frame.inputOwnerChanged);
|
||||
EXPECT_TRUE(IsUIEditorWorkspaceHostedPanelInputOwner(frame.inputOwner, "details"));
|
||||
EXPECT_EQ(frame.result.viewportPanelId, "viewport");
|
||||
EXPECT_TRUE(frame.result.viewportInputFrame.focusLost);
|
||||
EXPECT_TRUE(frame.result.viewportInputFrame.captureEnded);
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceInteractionTest, PointerUpInsideViewportBubblesPointerReleaseRequest) {
|
||||
auto controller =
|
||||
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
|
||||
|
||||
Reference in New Issue
Block a user