#include "Application.h" #include #include namespace { using XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter; using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame; using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewQueue; using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry; using XCEngine::Editor::XCUIBackend::UITextureRegistration; using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta; using XCEngine::Input::KeyCode; using XCEngine::NewEditor::Application; using XCEngine::UI::UIColor; using XCEngine::UI::UIDrawData; using XCEngine::UI::UIDrawList; using XCEngine::UI::UIRect; using XCEngine::UI::UISize; using XCEngine::UI::UITextureHandle; using XCEngine::UI::UITextureHandleKind; constexpr std::size_t ToPanelIndex(Application::ShellPanelId panelId) { return static_cast(panelId); } struct ShellCommandHarness { Application::ShellViewToggleState viewToggles = {}; std::array(Application::ShellPanelId::Count)> panels = {}; int hostedPreviewReconfigureCount = 0; ShellCommandHarness() { panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)] = { Application::ShellPanelId::XCUIDemo, "XCUI Demo", "XCUI Demo", "new_editor.panels.xcui_demo", true, true, Application::ShellHostedPreviewMode::NativeOffscreen }; panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)] = { Application::ShellPanelId::XCUILayoutLab, "XCUI Layout Lab", "XCUI Layout Lab", "new_editor.panels.xcui_layout_lab", true, true, Application::ShellHostedPreviewMode::HostedPresenter }; } const Application::ShellPanelChromeState& Panel(Application::ShellPanelId panelId) const { return panels[ToPanelIndex(panelId)]; } Application::ShellCommandBindings BuildBindings() { Application::ShellCommandBindings bindings = {}; bindings.getXCUIDemoPanelVisible = [this]() { return Panel(Application::ShellPanelId::XCUIDemo).visible; }; bindings.setXCUIDemoPanelVisible = [this](bool visible) { panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)].visible = visible; }; bindings.getXCUILayoutLabPanelVisible = [this]() { return Panel(Application::ShellPanelId::XCUILayoutLab).visible; }; bindings.setXCUILayoutLabPanelVisible = [this](bool visible) { panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].visible = visible; }; bindings.getNativeBackdropVisible = [this]() { return viewToggles.nativeBackdropVisible; }; bindings.setNativeBackdropVisible = [this](bool visible) { viewToggles.nativeBackdropVisible = visible; }; bindings.getPulseAccentEnabled = [this]() { return viewToggles.pulseAccentEnabled; }; bindings.setPulseAccentEnabled = [this](bool enabled) { viewToggles.pulseAccentEnabled = enabled; }; bindings.getNativeXCUIOverlayVisible = [this]() { return viewToggles.nativeXCUIOverlayVisible; }; bindings.setNativeXCUIOverlayVisible = [this](bool visible) { viewToggles.nativeXCUIOverlayVisible = visible; }; bindings.getHostedPreviewHudVisible = [this]() { return viewToggles.hostedPreviewHudVisible; }; bindings.setHostedPreviewHudVisible = [this](bool visible) { viewToggles.hostedPreviewHudVisible = visible; }; bindings.getNativeDemoPanelPreviewEnabled = [this]() { return Panel(Application::ShellPanelId::XCUIDemo).previewMode == Application::ShellHostedPreviewMode::NativeOffscreen; }; bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) { panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)].previewMode = enabled ? Application::ShellHostedPreviewMode::NativeOffscreen : Application::ShellHostedPreviewMode::HostedPresenter; }; bindings.getNativeLayoutLabPreviewEnabled = [this]() { return Panel(Application::ShellPanelId::XCUILayoutLab).previewMode == Application::ShellHostedPreviewMode::NativeOffscreen; }; bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) { panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].previewMode = enabled ? Application::ShellHostedPreviewMode::NativeOffscreen : Application::ShellHostedPreviewMode::HostedPresenter; }; bindings.onHostedPreviewModeChanged = [this]() { ++hostedPreviewReconfigureCount; }; return bindings; } }; TEST(ApplicationShellCommandBindingsTest, RegisterShellViewCommandsInvokesBoundToggleHandlers) { ShellCommandHarness harness = {}; XCUIEditorCommandRouter router = {}; Application::RegisterShellViewCommands(router, harness.BuildBindings()); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleXCUIDemoPanel)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleXCUILayoutLabPanel)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeBackdrop)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::TogglePulseAccent)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeXCUIOverlay)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleHostedPreviewHud)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeDemoPanelPreview)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeLayoutLabPreview)); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleXCUIDemoPanel)); EXPECT_FALSE(harness.Panel(Application::ShellPanelId::XCUIDemo).visible); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeBackdrop)); EXPECT_FALSE(harness.viewToggles.nativeBackdropVisible); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeXCUIOverlay)); EXPECT_FALSE(harness.viewToggles.nativeXCUIOverlayVisible); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleHostedPreviewHud)); EXPECT_FALSE(harness.viewToggles.hostedPreviewHudVisible); } TEST(ApplicationShellCommandBindingsTest, PreviewModeCommandsTriggerHostedPreviewReconfigureCallback) { ShellCommandHarness harness = {}; XCUIEditorCommandRouter router = {}; Application::RegisterShellViewCommands(router, harness.BuildBindings()); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeDemoPanelPreview)); EXPECT_EQ( harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode, Application::ShellHostedPreviewMode::HostedPresenter); EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeLayoutLabPreview)); EXPECT_EQ( harness.Panel(Application::ShellPanelId::XCUILayoutLab).previewMode, Application::ShellHostedPreviewMode::NativeOffscreen); EXPECT_EQ(harness.hostedPreviewReconfigureCount, 2); } TEST(ApplicationShellCommandBindingsTest, BuildShellShortcutSnapshotCarriesBridgeStateAndKeyboardEdges) { XCUIInputBridgeFrameDelta frameDelta = {}; frameDelta.state.windowFocused = true; frameDelta.state.wantCaptureKeyboard = true; frameDelta.state.wantTextInput = true; frameDelta.state.modifiers.control = true; frameDelta.state.modifiers.shift = true; frameDelta.keyboard.pressedKeys.push_back(static_cast(KeyCode::One)); frameDelta.keyboard.repeatedKeys.push_back(static_cast(KeyCode::Two)); const auto snapshot = Application::BuildShellShortcutSnapshot(frameDelta); EXPECT_TRUE(snapshot.windowFocused); EXPECT_TRUE(snapshot.wantCaptureKeyboard); EXPECT_TRUE(snapshot.wantTextInput); EXPECT_TRUE(snapshot.modifiers.control); EXPECT_TRUE(snapshot.modifiers.shift); EXPECT_TRUE(snapshot.IsKeyDown(static_cast(KeyCode::One))); EXPECT_TRUE(snapshot.IsKeyDown(static_cast(KeyCode::Two))); const auto* repeatedKey = snapshot.FindKeyState(static_cast(KeyCode::Two)); ASSERT_NE(repeatedKey, nullptr); EXPECT_TRUE(repeatedKey->repeat); } TEST(ApplicationShellCommandBindingsTest, RegisteredShellShortcutsMatchExpectedViewCommands) { ShellCommandHarness harness = {}; XCUIEditorCommandRouter router = {}; Application::RegisterShellViewCommands(router, harness.BuildBindings()); XCUIInputBridgeFrameDelta frameDelta = {}; frameDelta.state.windowFocused = true; frameDelta.state.modifiers.control = true; frameDelta.keyboard.pressedKeys.push_back(static_cast(KeyCode::One)); const auto demoSnapshot = Application::BuildShellShortcutSnapshot(frameDelta); const auto demoMatch = router.MatchShortcut({ &demoSnapshot }); ASSERT_TRUE(demoMatch.matched); EXPECT_EQ(demoMatch.commandId, Application::ShellCommandIds::ToggleXCUIDemoPanel); XCUIInputBridgeFrameDelta previewDelta = {}; previewDelta.state.windowFocused = true; previewDelta.state.modifiers.control = true; previewDelta.state.modifiers.alt = true; previewDelta.keyboard.pressedKeys.push_back(static_cast(KeyCode::Two)); const auto previewSnapshot = Application::BuildShellShortcutSnapshot(previewDelta); const auto previewMatch = router.MatchShortcut({ &previewSnapshot }); ASSERT_TRUE(previewMatch.matched); EXPECT_EQ(previewMatch.commandId, Application::ShellCommandIds::ToggleNativeLayoutLabPreview); } TEST(ApplicationShellCommandBindingsTest, RegisteredShellShortcutsRespectCaptureAndRepeatGuards) { ShellCommandHarness harness = {}; XCUIEditorCommandRouter router = {}; Application::RegisterShellViewCommands(router, harness.BuildBindings()); XCUIInputBridgeFrameDelta capturedDelta = {}; capturedDelta.state.windowFocused = true; capturedDelta.state.wantCaptureKeyboard = true; capturedDelta.state.modifiers.control = true; capturedDelta.keyboard.pressedKeys.push_back(static_cast(KeyCode::One)); const auto capturedSnapshot = Application::BuildShellShortcutSnapshot(capturedDelta); EXPECT_FALSE(router.MatchShortcut({ &capturedSnapshot }).matched); XCUIInputBridgeFrameDelta textInputDelta = {}; textInputDelta.state.windowFocused = true; textInputDelta.state.wantTextInput = true; textInputDelta.state.modifiers.control = true; textInputDelta.keyboard.pressedKeys.push_back(static_cast(KeyCode::One)); const auto textInputSnapshot = Application::BuildShellShortcutSnapshot(textInputDelta); EXPECT_FALSE(router.MatchShortcut({ &textInputSnapshot }).matched); XCUIInputBridgeFrameDelta repeatedDelta = {}; repeatedDelta.state.windowFocused = true; repeatedDelta.state.modifiers.control = true; repeatedDelta.keyboard.repeatedKeys.push_back(static_cast(KeyCode::One)); const auto repeatedSnapshot = Application::BuildShellShortcutSnapshot(repeatedDelta); EXPECT_FALSE(router.MatchShortcut({ &repeatedSnapshot }).matched); } TEST(ApplicationShellCommandBindingsTest, PreviewShortcutInvokesCommandHandlerAndReconfigureCallback) { ShellCommandHarness harness = {}; XCUIEditorCommandRouter router = {}; Application::RegisterShellViewCommands(router, harness.BuildBindings()); XCUIInputBridgeFrameDelta previewDelta = {}; previewDelta.state.windowFocused = true; previewDelta.state.modifiers.control = true; previewDelta.state.modifiers.alt = true; previewDelta.keyboard.pressedKeys.push_back(static_cast(KeyCode::One)); const auto previewSnapshot = Application::BuildShellShortcutSnapshot(previewDelta); EXPECT_TRUE(router.InvokeMatchingShortcut({ &previewSnapshot })); EXPECT_EQ( harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode, Application::ShellHostedPreviewMode::HostedPresenter); EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1); } TEST(ApplicationShellCommandBindingsTest, BeginHostedPreviewFrameLifecycleClearsQueueAndResetsRegistryFlags) { XCUIHostedPreviewQueue queue = {}; XCUIHostedPreviewSurfaceRegistry registry = {}; UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreview"); drawList.AddFilledRect(UIRect(4.0f, 6.0f, 24.0f, 16.0f), UIColor(1.0f, 1.0f, 1.0f, 1.0f)); XCUIHostedPreviewFrame frame = {}; frame.drawData = &drawData; frame.canvasRect = UIRect(0.0f, 0.0f, 160.0f, 90.0f); frame.logicalSize = UISize(160.0f, 90.0f); frame.debugName = "HostedPreview"; frame.debugSource = "tests.application"; ASSERT_TRUE(queue.Submit(frame)); ASSERT_EQ(queue.GetQueuedFrames().size(), 1u); registry.RecordQueuedFrame(queue.GetQueuedFrames().front(), 0u); ASSERT_EQ(registry.GetDescriptors().size(), 1u); EXPECT_TRUE(registry.GetDescriptors().front().queuedThisFrame); Application::BeginHostedPreviewFrameLifecycle(queue, registry); EXPECT_TRUE(queue.GetQueuedFrames().empty()); ASSERT_EQ(registry.GetDescriptors().size(), 1u); EXPECT_FALSE(registry.GetDescriptors().front().queuedThisFrame); } TEST(ApplicationShellCommandBindingsTest, HostedPreviewRegistrationGuardsAllowTextureOnlyNativePublication) { UITextureRegistration registration = {}; registration.texture = UITextureHandle{ 99u, 256u, 128u, UITextureHandleKind::ShaderResourceView }; EXPECT_TRUE(Application::HasHostedPreviewTextureRegistration(registration)); EXPECT_TRUE(Application::HasHostedPreviewPublishedTexture(registration)); registration = {}; registration.cpuHandle.ptr = 11u; registration.gpuHandle.ptr = 29u; EXPECT_TRUE(Application::HasHostedPreviewTextureRegistration(registration)); EXPECT_FALSE(Application::HasHostedPreviewPublishedTexture(registration)); } TEST(ApplicationShellCommandBindingsTest, NativeHostedPreviewConsumptionLeavesHostedPathInDirectAppendMode) { const auto consumption = Application::ResolveNativeHostedPreviewConsumption( false, false, false, "Pending", "Waiting"); EXPECT_EQ( consumption.surfaceState, Application::NativeHostedPreviewSurfaceState::Disabled); EXPECT_FALSE(consumption.queueRuntimeFrame); EXPECT_TRUE(consumption.appendRuntimeDrawDataToShell); EXPECT_FALSE(consumption.showSurfaceImage); EXPECT_TRUE(consumption.drawRuntimeDebugRects); EXPECT_TRUE(consumption.placeholderTitle.empty()); EXPECT_TRUE(consumption.placeholderSubtitle.empty()); EXPECT_EQ( Application::ComposeNativeHostedPreviewStatusLine(consumption, "Runtime status"), "Runtime status"); } TEST(ApplicationShellCommandBindingsTest, NativeHostedPreviewConsumptionQueuesFrameWhileAwaitingFirstSurfacePublish) { const auto consumption = Application::ResolveNativeHostedPreviewConsumption( true, false, false, "Native XCUI preview pending", "Waiting for queued native preview output to publish into the shell card."); EXPECT_EQ( consumption.surfaceState, Application::NativeHostedPreviewSurfaceState::AwaitingSubmit); EXPECT_TRUE(consumption.queueRuntimeFrame); EXPECT_FALSE(consumption.appendRuntimeDrawDataToShell); EXPECT_FALSE(consumption.showSurfaceImage); EXPECT_FALSE(consumption.drawRuntimeDebugRects); EXPECT_EQ(consumption.placeholderTitle, "Native XCUI preview pending"); EXPECT_EQ( consumption.placeholderSubtitle, "Waiting for queued native preview output to publish into the shell card."); EXPECT_EQ( Application::ComposeNativeHostedPreviewStatusLine(consumption, "Runtime status"), "Native surface awaiting submit | Runtime status"); } TEST(ApplicationShellCommandBindingsTest, NativeHostedPreviewConsumptionDistinguishesWarmingFromLiveSurfaceStates) { const auto warming = Application::ResolveNativeHostedPreviewConsumption( true, true, false, "Native layout preview pending", "Waiting for queued native preview output to publish into the layout card."); const auto live = Application::ResolveNativeHostedPreviewConsumption( true, true, true, "Native layout preview pending", "Waiting for queued native preview output to publish into the layout card."); EXPECT_EQ( warming.surfaceState, Application::NativeHostedPreviewSurfaceState::Warming); EXPECT_TRUE(warming.queueRuntimeFrame); EXPECT_FALSE(warming.appendRuntimeDrawDataToShell); EXPECT_FALSE(warming.showSurfaceImage); EXPECT_FALSE(warming.drawRuntimeDebugRects); EXPECT_EQ(warming.placeholderTitle, "Native layout preview pending"); EXPECT_EQ( Application::ComposeNativeHostedPreviewStatusLine(warming, "Layout status"), "Native surface warming | Layout status"); EXPECT_EQ( live.surfaceState, Application::NativeHostedPreviewSurfaceState::Live); EXPECT_TRUE(live.queueRuntimeFrame); EXPECT_FALSE(live.appendRuntimeDrawDataToShell); EXPECT_TRUE(live.showSurfaceImage); EXPECT_TRUE(live.drawRuntimeDebugRects); EXPECT_TRUE(live.placeholderTitle.empty()); EXPECT_TRUE(live.placeholderSubtitle.empty()); EXPECT_EQ( Application::ComposeNativeHostedPreviewStatusLine(live, "Layout status"), "Native surface live | Layout status"); } } // namespace