Refactor new editor boundaries and test ownership

This commit is contained in:
2026-04-19 15:52:28 +08:00
parent dc13b56cf3
commit 93f06e84ed
279 changed files with 6349 additions and 3238 deletions

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -4,7 +4,7 @@
#include <gtest/gtest.h>
#include "Platform/Win32/InputModifierTracker.h"
#include "app/Platform/Win32/InputModifierTracker.h"
#include <XCEngine/UI/Types.h>

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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
});

View File

@@ -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

View File

@@ -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());