From 3db09ea5d0a837d66257d0648d01d0178d74b281 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 5 Apr 2026 17:41:31 +0800 Subject: [PATCH] Reuse panel frame composition in native XCUI shell --- docs/plan/XCUI_Phase_Status_2026-04-05.md | 11 +- new_editor/src/Application.cpp | 377 ++++++++--------- new_editor/src/Application.h | 7 - new_editor/src/ApplicationLegacyImGui.cpp | 9 +- new_editor/src/panels/XCUIDemoPanel.cpp | 378 +++-------------- new_editor/src/panels/XCUIDemoPanel.h | 41 ++ new_editor/src/panels/XCUILayoutLabPanel.cpp | 391 ------------------ new_editor/src/panels/XCUILayoutLabPanel.h | 28 ++ tests/NewEditor/test_xcui_demo_panel.cpp | 43 ++ .../NewEditor/test_xcui_layout_lab_panel.cpp | 320 ++++++++++++-- 10 files changed, 651 insertions(+), 954 deletions(-) delete mode 100644 new_editor/src/panels/XCUILayoutLabPanel.cpp diff --git a/docs/plan/XCUI_Phase_Status_2026-04-05.md b/docs/plan/XCUI_Phase_Status_2026-04-05.md index 7eca3b5b..da95086b 100644 --- a/docs/plan/XCUI_Phase_Status_2026-04-05.md +++ b/docs/plan/XCUI_Phase_Status_2026-04-05.md @@ -19,7 +19,7 @@ Old `editor` replacement is explicitly out of scope for this phase. - The native-host / hosted-preview publication follow-up is now landed in `new_editor`: - `NativeWindowUICompositor` is now buildable alongside the legacy ImGui compositor - `Application` now defaults to a native XCUI host path instead of creating the ImGui shell by default - - the native default path now drives `XCUIDemoRuntime` and `XCUILayoutLabRuntime` directly, composes their `UIDrawData`, and submits one swapchain packet through the native compositor + - the native default path now reuses shell-agnostic `XCUIDemoPanel::ComposeFrame(...)` / `XCUILayoutLabPanel::ComposeFrame(...)` results instead of keeping a second demo/layout runtime + input-bridge stack inside `Application` - the native compositor now publishes hosted-preview textures as SRV-backed `UITextureRegistration` / `UITextureHandle` values instead of relying on ImGui-only descriptor semantics - the native shell now begins the hosted-preview queue/registry lifecycle each frame, queues native preview frames, drains them during the native render pass, and consumes published hosted-surface images directly in panel cards with live/warming placeholder states - the old ImGui shell path remains present as an explicit compatibility host instead of the default host @@ -113,6 +113,8 @@ Current gap: - The panel-canvas seam now keeps the generic host contract on observable canvas behavior only; backend/capability identity probing has been removed from `IXCUIPanelCanvasHost`, and the minimal `NullXCUIPanelCanvasHost` remains the concrete placeholder host for non-ImGui paths. - `XCUI Demo` and `LayoutLab` panel input now also flows through an explicit `IXCUIInputSnapshotSource` seam, so panel/runtime code no longer reads `ImGuiIO` / `ImGui::IsKeyPressed` / `ImGui::IsMouseClicked` directly when the shell wants to use an ImGui adapter. - `new_editor` now also has an explicit `ImGuiXCUIInputSnapshotSource` adapter, keeping ImGui-specific input capture in the host adapter layer instead of inside panel/runtime update code. +- `XCUI Demo` and `LayoutLab` panels now both expose shell-agnostic per-frame composition results (`ComposeFrame(...)` + `GetLastFrameComposition()`), so native/compat shells can reuse one panel-local frame pipeline instead of duplicating runtime/input/preview assembly logic. +- `XCUIDemoPanel::ComposeFrame(...)` now also accepts an injected input snapshot plus explicit placeholder/frame options, which lets the native shell reuse the panel pipeline without falling back to the compatibility-path placeholder behavior on direct draw-data cards. - Panel diagnostics were expanded to clearly separate preview/runtime/input state and native vs legacy paths. - The editor bridge layer now has smoke coverage for swapchain after-UI rendering hooks and SRV-backed ImGui texture descriptor registration. - `Application` no longer owns the ImGui backend directly; window presentation now routes through `IWindowUICompositor` with an `ImGuiWindowUICompositor` implementation, which currently delegates to `IEditorHostCompositor` / `ImGuiHostCompositor`. @@ -132,8 +134,9 @@ Current gap: - becomes the default `new_editor` startup path - lays out `XCUI Demo` and `Layout Lab` as native cards directly in the swapchain window - routes shell shortcuts through the same command router without reading ImGui capture state in the default host path - - drives both sandbox runtimes directly from Win32/XCUI input snapshots instead of routing through ImGui panel hosts + - reuses panel-local `ComposeFrame(...)` entry points for demo/layout runtime, input, hosted-preview, and overlay composition instead of maintaining duplicate native-shell runtime/input state in `Application` - composes one native `UIDrawData` packet and submits it through `NativeWindowUICompositor` +- Native shell preview-mode reconfiguration now rebuilds the native panel bindings instead of rebinding a legacy hosted presenter, so the default host path no longer needs the ImGui presenter when a card stays on direct draw-data composition. - The native shell layout policy now also lives behind `XCUINativeShellLayout`, so top-bar/footer/workspace geometry, panel split rules, and active-panel transfer on pointer press are no longer hard-coded inline inside `Application.cpp`. - `NativeXCUIPanelCanvasHost` now backs that direct shell path as an externally driven canvas/session host for native cards instead of assuming an ImGui child-window model, and it now emits native `Image` draw commands for hosted surface-image previews while preserving per-surface UVs. - `NativeWindowUICompositor` now creates and frees SRV-backed texture registrations for hosted preview surfaces, so native publication no longer depends on ImGui descriptor handles. @@ -152,7 +155,7 @@ Current gap: ## Validated This Phase -- `new_editor_xcui_demo_panel_tests`: `3/3` +- `new_editor_xcui_demo_panel_tests`: `4/4` - `new_editor_xcui_demo_runtime_tests`: `12/12` - `new_editor_xcui_input_bridge_tests`: `4/4` - `new_editor_imgui_xcui_input_adapter_tests`: `2/2` @@ -170,7 +173,7 @@ Current gap: - `new_editor_xcui_panel_canvas_host_tests`: `4/4` - `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1` - `new_editor_native_xcui_panel_canvas_host_tests`: `4/4` -- `new_editor_xcui_layout_lab_panel_tests`: `3/3` +- `new_editor_xcui_layout_lab_panel_tests`: `6/6` - `XCNewEditor` Debug target builds successfully - `XCNewEditor.exe` native-default smoke run stayed alive for `5s` - `core_ui_tests`: `52 total` (`50` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`) diff --git a/new_editor/src/Application.cpp b/new_editor/src/Application.cpp index 5ca32369..0df84f92 100644 --- a/new_editor/src/Application.cpp +++ b/new_editor/src/Application.cpp @@ -1,6 +1,9 @@ #include "Application.h" #include "XCUIBackend/XCUINativeShellLayout.h" #include "XCUIBackend/NativeWindowUICompositor.h" +#include "XCUIBackend/XCUIPanelCanvasHost.h" +#include "panels/XCUIDemoPanel.h" +#include "panels/XCUILayoutLabPanel.h" #include #include @@ -41,54 +44,64 @@ std::uint64_t MakeFrameTimestampNanoseconds() { .count()); } +class ForwardingNativePanelCanvasHost final : public ::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost { +public: + explicit ForwardingNativePanelCanvasHost(::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& host) + : m_host(host) {} + + const char* GetDebugName() const override { + return m_host.GetDebugName(); + } + + auto BeginCanvas( + const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest& request) + -> decltype(std::declval<::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost&>().BeginCanvas(request)) + override { + return m_host.BeginCanvas(request); + } + + void DrawFilledRect( + const ::XCEngine::UI::UIRect& rect, + const ::XCEngine::UI::UIColor& color, + float rounding) override { + m_host.DrawFilledRect(rect, color, rounding); + } + + void DrawOutlineRect( + const ::XCEngine::UI::UIRect& rect, + const ::XCEngine::UI::UIColor& color, + float thickness, + float rounding) override { + m_host.DrawOutlineRect(rect, color, thickness, rounding); + } + + void DrawText( + const ::XCEngine::UI::UIPoint& position, + std::string_view text, + const ::XCEngine::UI::UIColor& color, + float fontSize) override { + m_host.DrawText(position, text, color, fontSize); + } + + void EndCanvas() override { + m_host.EndCanvas(); + } + + bool TryGetLatestFrameSnapshot( + ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasFrameSnapshot& outSnapshot) const override { + return m_host.TryGetLatestFrameSnapshot(outSnapshot); + } + +private: + ::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& m_host; +}; + void AppendDrawData(::XCEngine::UI::UIDrawData& destination, const ::XCEngine::UI::UIDrawData& source) { for (const ::XCEngine::UI::UIDrawList& drawList : source.GetDrawLists()) { destination.AddDrawList(drawList); } } -bool ContainsKeyTransition( - const std::vector& keys, - std::int32_t keyCode) { - return std::find(keys.begin(), keys.end(), keyCode) != keys.end(); -} - -bool ShouldCaptureKeyboardNavigation( - const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession, - const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& previousFrame) { - if (!canvasSession.validCanvas) { - return false; - } - - return canvasSession.hovered || - (canvasSession.windowFocused && - !previousFrame.stats.selectedElementId.empty()); -} - -void PopulateKeyboardNavigationInput( - ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState& input, - const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta, - bool captureKeyboardNavigation) { - if (!captureKeyboardNavigation) { - return; - } - - using ::XCEngine::Input::KeyCode; - const auto pressedThisFrame = - [&frameDelta](KeyCode keyCode) { - const std::int32_t code = static_cast(keyCode); - return ContainsKeyTransition(frameDelta.keyboard.pressedKeys, code) || - ContainsKeyTransition(frameDelta.keyboard.repeatedKeys, code); - }; - - input.navigatePrevious = pressedThisFrame(KeyCode::Up); - input.navigateNext = pressedThisFrame(KeyCode::Down); - input.navigateHome = pressedThisFrame(KeyCode::Home); - input.navigateEnd = pressedThisFrame(KeyCode::End); - input.navigateCollapse = pressedThisFrame(KeyCode::Left); - input.navigateExpand = pressedThisFrame(KeyCode::Right); -} - } // namespace bool Application::IsNativeWindowHostEnabled() const { @@ -99,12 +112,35 @@ void Application::InitializeNativeShell() { m_nativeActivePanel = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo) ? ShellPanelId::XCUIDemo : ShellPanelId::XCUILayoutLab; - m_nativeDemoInputBridge.Reset(); - m_nativeLayoutInputBridge.Reset(); m_nativeDemoCanvasHost.ClearCanvasSession(); m_nativeLayoutCanvasHost.ClearCanvasSession(); - m_nativeDemoReloadSucceeded = m_nativeDemoRuntime.ReloadDocuments(); - m_nativeLayoutReloadSucceeded = m_nativeLayoutRuntime.ReloadDocuments(); + + const auto buildNativePreviewPresenter = + [this](ShellPanelId panelId, bool hostedPreviewEnabled) { + if (!hostedPreviewEnabled || !IsNativeHostedPreviewEnabled(panelId)) { + return std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>(); + } + + return CreateHostedPreviewPresenter(true); + }; + + const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo); + const bool demoHostedPreviewEnabled = demoState == nullptr || demoState->hostedPreviewEnabled; + m_demoPanel = std::make_unique( + &m_xcuiInputSource, + buildNativePreviewPresenter(ShellPanelId::XCUIDemo, demoHostedPreviewEnabled), + std::make_unique(m_nativeDemoCanvasHost)); + m_demoPanel->SetVisible(demoState != nullptr && demoState->visible); + m_demoPanel->SetHostedPreviewEnabled(demoHostedPreviewEnabled); + + const ShellPanelChromeState* layoutState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab); + const bool layoutHostedPreviewEnabled = layoutState == nullptr || layoutState->hostedPreviewEnabled; + m_layoutLabPanel = std::make_unique( + &m_xcuiInputSource, + buildNativePreviewPresenter(ShellPanelId::XCUILayoutLab, layoutHostedPreviewEnabled), + std::make_unique(m_nativeLayoutCanvasHost)); + m_layoutLabPanel->SetVisible(layoutState != nullptr && layoutState->visible); + m_layoutLabPanel->SetHostedPreviewEnabled(layoutHostedPreviewEnabled); } const Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const { @@ -683,33 +719,10 @@ bool Application::RenderHostedPreviewOffscreenSurface( for (const NativeShellPanelLayout& panelLayout : panelLayouts) { if (panelLayout.panelId == ShellPanelId::XCUIDemo) { const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo); - const bool nativeHostedPreview = + const bool nativeHostedPreviewRequested = panelState != nullptr && panelState->hostedPreviewEnabled && IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo); - ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {}; - const bool hasHostedSurfaceDescriptor = - nativeHostedPreview && - panelState != nullptr && - !panelState->previewDebugName.empty() && - m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor( - panelState->previewDebugName.data(), - hostedSurfaceDescriptor); - ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {}; - const bool showHostedSurfaceImage = - nativeHostedPreview && - panelState != nullptr && - !panelState->previewDebugName.empty() && - m_hostedPreviewSurfaceRegistry.TryGetSurfaceImage( - panelState->previewDebugName.data(), - hostedSurfaceImage); - const Application::NativeHostedPreviewConsumption previewConsumption = - Application::ResolveNativeHostedPreviewConsumption( - nativeHostedPreview, - hasHostedSurfaceDescriptor, - showHostedSurfaceImage, - "Native XCUI preview pending", - "Waiting for queued native preview output to publish into the shell card."); ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {}; canvasSession.hostRect = panelLayout.panelRect; @@ -720,63 +733,54 @@ bool Application::RenderHostedPreviewOffscreenSurface( canvasSession.windowFocused = shellSnapshot.windowFocused; m_nativeDemoCanvasHost.SetCanvasSession(canvasSession); - ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {}; - canvasRequest.childId = "XCUIDemo.NativeCanvas"; - canvasRequest.height = panelLayout.panelRect.height; - canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y; - canvasRequest.drawPreviewFrame = false; - canvasRequest.showSurfaceImage = previewConsumption.showSurfaceImage; - canvasRequest.surfaceImage = hostedSurfaceImage; - canvasRequest.placeholderTitle = - previewConsumption.placeholderTitle.empty() ? nullptr : previewConsumption.placeholderTitle.data(); - canvasRequest.placeholderSubtitle = - previewConsumption.placeholderSubtitle.empty() ? nullptr : previewConsumption.placeholderSubtitle.data(); - const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession resolvedSession = - m_nativeDemoCanvasHost.BeginCanvas(canvasRequest); - const bool wantsKeyboard = panelLayout.active; const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard); - if (!m_nativeDemoInputBridge.HasBaseline()) { - m_nativeDemoInputBridge.Prime(panelSnapshot); - } - const auto panelFrameDelta = m_nativeDemoInputBridge.Translate(panelSnapshot); + const bool showPanelHud = IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud); - ::XCEngine::Editor::XCUIBackend::XCUIDemoInputState input = {}; - input.canvasRect = resolvedSession.canvasRect; - input.pointerPosition = panelSnapshot.pointerPosition; - input.pointerInside = panelSnapshot.pointerInside; - input.pointerPressed = panelFrameDelta.pointer.pressed[0]; - input.pointerReleased = panelFrameDelta.pointer.released[0]; - input.pointerDown = panelSnapshot.pointerButtonsDown[0]; - input.windowFocused = panelSnapshot.windowFocused; - input.wantCaptureMouse = panelSnapshot.wantCaptureMouse; - input.wantCaptureKeyboard = panelSnapshot.wantCaptureKeyboard; - input.wantTextInput = panelSnapshot.wantTextInput; - input.events = panelFrameDelta.events; - - const auto& frame = m_nativeDemoRuntime.Update(input); - if (previewConsumption.queueRuntimeFrame) { - ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {}; - previewFrame.drawData = &frame.drawData; - previewFrame.canvasRect = resolvedSession.canvasRect; - previewFrame.logicalSize = UI::UISize( - resolvedSession.canvasRect.width, - resolvedSession.canvasRect.height); - previewFrame.debugName = - panelState != nullptr ? panelState->previewDebugName.data() : nullptr; - previewFrame.debugSource = - panelState != nullptr ? panelState->previewDebugSource.data() : nullptr; - m_hostedPreviewQueue.Submit(previewFrame); - } else if (previewConsumption.appendRuntimeDrawDataToShell) { - AppendDrawData(composedDrawData, frame.drawData); + const XCUIDemoPanelFrameComposition* composition = nullptr; + if (m_demoPanel != nullptr) { + composition = &m_demoPanel->ComposeFrame( + XCUIDemoPanelFrameComposeOptions{ + panelLayout.panelRect.height, + panelLayout.canvasRect.y - panelLayout.panelRect.y, + panelState == nullptr || panelState->hostedPreviewEnabled, + showPanelHud, + showPanelHud, + panelSnapshot.timestampNanoseconds, + "XCUIDemo.NativeCanvas", + &panelSnapshot, + false, + nativeHostedPreviewRequested ? "Native XCUI preview pending" : nullptr, + nativeHostedPreviewRequested + ? "Waiting for queued native preview output to publish into the shell card." + : nullptr, + false, + nullptr, + nullptr, + }); } - if (IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud)) { - const auto& stats = frame.stats; + const Application::NativeHostedPreviewConsumption previewConsumption = + composition != nullptr + ? Application::ResolveNativeHostedPreviewConsumption( + composition->nativeHostedPreview, + composition->hasHostedSurfaceDescriptor, + composition->showHostedSurfaceImage, + "Native XCUI preview pending", + "Waiting for queued native preview output to publish into the shell card.") + : Application::NativeHostedPreviewConsumption{}; + if (composition != nullptr && + composition->frame != nullptr && + previewConsumption.appendRuntimeDrawDataToShell) { + AppendDrawData(composedDrawData, composition->frame->drawData); + } + + if (showPanelHud && composition != nullptr && composition->frame != nullptr) { + const auto& stats = composition->frame->stats; const UI::UIRect hudRect( - resolvedSession.canvasRect.x + 10.0f, - resolvedSession.canvasRect.y + 10.0f, - (std::min)(resolvedSession.canvasRect.width - 20.0f, 360.0f), + composition->canvasSession.canvasRect.x + 10.0f, + composition->canvasSession.canvasRect.y + 10.0f, + (std::min)(composition->canvasSession.canvasRect.width - 20.0f, 360.0f), 62.0f); if (hudRect.width > 40.0f && hudRect.height > 20.0f) { m_nativeDemoCanvasHost.DrawFilledRect( @@ -799,49 +803,28 @@ bool Application::RenderHostedPreviewOffscreenSurface( stats.statusMessage, textMuted); } - - if (previewConsumption.drawRuntimeDebugRects) { - const auto drawDebugRect = - [this](const std::string& elementId, const UI::UIColor& color, const char* label) { - if (elementId.empty()) { - return; - } - UI::UIRect rect = {}; - if (!m_nativeDemoRuntime.TryGetElementRect(elementId, rect)) { - return; - } - m_nativeDemoCanvasHost.DrawOutlineRect(rect, color, 2.0f, 6.0f); - if (label != nullptr && label[0] != '\0') { - m_nativeDemoCanvasHost.DrawText( - UI::UIPoint(rect.x + 4.0f, rect.y + 4.0f), - label, - color); - } - }; - drawDebugRect( - stats.hoveredElementId, - UI::UIColor(1.0f, 195.0f / 255.0f, 64.0f / 255.0f, 1.0f), - "hover"); - drawDebugRect( - stats.focusedElementId, - UI::UIColor(64.0f / 255.0f, 214.0f / 255.0f, 1.0f, 1.0f), - "focus"); - } } - m_nativeDemoCanvasHost.EndCanvas(); - NativePanelFrameSummary summary = {}; summary.layout = panelLayout; - summary.lineA = m_nativeDemoReloadSucceeded + summary.lineA = + m_demoPanel != nullptr && m_demoPanel->GetLastReloadSucceeded() ? Application::ComposeNativeHostedPreviewStatusLine( previewConsumption, - frame.stats.statusMessage) + composition != nullptr && composition->frame != nullptr + ? composition->frame->stats.statusMessage + : std::string_view("XCUI demo frame unavailable")) : "Document reload failed; showing last retained runtime state."; summary.lineB = std::string(panelLayout.active ? "Active" : "Passive") + - " | " + std::to_string(frame.stats.elementCount) + - " elements | " + std::to_string(frame.stats.commandCount) + + " | " + std::to_string( + composition != nullptr && composition->frame != nullptr + ? composition->frame->stats.elementCount + : 0u) + + " elements | " + std::to_string( + composition != nullptr && composition->frame != nullptr + ? composition->frame->stats.commandCount + : 0u) + " cmds"; summary.overlay = extractCanvasOverlay(m_nativeDemoCanvasHost); panelSummaries.push_back(std::move(summary)); @@ -850,21 +833,22 @@ bool Application::RenderHostedPreviewOffscreenSurface( } const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab); - const bool nativeHostedPreview = + const bool nativeHostedPreviewRequested = panelState != nullptr && panelState->hostedPreviewEnabled && IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab); ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {}; const bool hasHostedSurfaceDescriptor = - nativeHostedPreview && + nativeHostedPreviewRequested && panelState != nullptr && !panelState->previewDebugName.empty() && m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor( panelState->previewDebugName.data(), hostedSurfaceDescriptor); + ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {}; const bool showHostedSurfaceImage = - nativeHostedPreview && + nativeHostedPreviewRequested && panelState != nullptr && !panelState->previewDebugName.empty() && m_hostedPreviewSurfaceRegistry.TryGetSurfaceImage( @@ -872,7 +856,7 @@ bool Application::RenderHostedPreviewOffscreenSurface( hostedSurfaceImage); const Application::NativeHostedPreviewConsumption previewConsumption = Application::ResolveNativeHostedPreviewConsumption( - nativeHostedPreview, + nativeHostedPreviewRequested, hasHostedSurfaceDescriptor, showHostedSurfaceImage, "Native layout preview pending", @@ -902,50 +886,53 @@ bool Application::RenderHostedPreviewOffscreenSurface( const bool wantsKeyboard = panelLayout.active; const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard); - if (!m_nativeLayoutInputBridge.HasBaseline()) { - m_nativeLayoutInputBridge.Prime(panelSnapshot); - } - const auto panelFrameDelta = m_nativeLayoutInputBridge.Translate(panelSnapshot); - - ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = {}; - input.canvasRect = resolvedSession.canvasRect; - input.pointerPosition = panelSnapshot.pointerPosition; - input.pointerInside = panelSnapshot.pointerInside; - input.pointerPressed = input.pointerInside && panelFrameDelta.pointer.pressed[0]; - PopulateKeyboardNavigationInput( - input, - panelFrameDelta, - panelLayout.active && ShouldCaptureKeyboardNavigation(resolvedSession, m_nativeLayoutRuntime.GetFrameResult())); - - const auto& frame = m_nativeLayoutRuntime.Update(input); - if (previewConsumption.queueRuntimeFrame) { - ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {}; - previewFrame.drawData = &frame.drawData; - previewFrame.canvasRect = resolvedSession.canvasRect; - previewFrame.logicalSize = UI::UISize( - resolvedSession.canvasRect.width, - resolvedSession.canvasRect.height); - previewFrame.debugName = - panelState != nullptr ? panelState->previewDebugName.data() : nullptr; - previewFrame.debugSource = - panelState != nullptr ? panelState->previewDebugSource.data() : nullptr; - m_hostedPreviewQueue.Submit(previewFrame); - } else if (previewConsumption.appendRuntimeDrawDataToShell) { - AppendDrawData(composedDrawData, frame.drawData); + const XCUILayoutLabFrameComposition* composition = nullptr; + if (m_layoutLabPanel != nullptr) { + composition = &m_layoutLabPanel->ComposeFrame( + XCUILayoutLabFrameCompositionRequest{ + resolvedSession, + panelSnapshot }); } m_nativeLayoutCanvasHost.EndCanvas(); + const Application::NativeHostedPreviewConsumption composedPreviewConsumption = + composition != nullptr + ? Application::ResolveNativeHostedPreviewConsumption( + composition->nativeHostedPreview, + composition->hasHostedSurfaceDescriptor, + composition->showHostedSurfaceImage, + "Native layout preview pending", + "Waiting for queued native preview output to publish into the layout card.") + : previewConsumption; + if (composition != nullptr && + composition->frameResult != nullptr && + composedPreviewConsumption.appendRuntimeDrawDataToShell) { + AppendDrawData(composedDrawData, composition->frameResult->drawData); + } + NativePanelFrameSummary summary = {}; summary.layout = panelLayout; - summary.lineA = m_nativeLayoutReloadSucceeded + summary.lineA = + m_layoutLabPanel != nullptr && m_layoutLabPanel->GetLastReloadSucceeded() ? Application::ComposeNativeHostedPreviewStatusLine( - previewConsumption, - frame.stats.statusMessage) + composedPreviewConsumption, + composition != nullptr && composition->frameResult != nullptr + ? composition->frameResult->stats.statusMessage + : std::string_view("XCUI layout frame unavailable")) : "Layout lab reload failed; showing last retained runtime state."; summary.lineB = - std::to_string(frame.stats.rowCount) + " rows | " + - std::to_string(frame.stats.columnCount) + " cols | " + - std::to_string(frame.stats.commandCount) + " cmds"; + std::to_string( + composition != nullptr && composition->frameResult != nullptr + ? composition->frameResult->stats.rowCount + : 0u) + " rows | " + + std::to_string( + composition != nullptr && composition->frameResult != nullptr + ? composition->frameResult->stats.columnCount + : 0u) + " cols | " + + std::to_string( + composition != nullptr && composition->frameResult != nullptr + ? composition->frameResult->stats.commandCount + : 0u) + " cmds"; summary.overlay = extractCanvasOverlay(m_nativeLayoutCanvasHost); panelSummaries.push_back(std::move(summary)); m_nativeLayoutCanvasHost.ClearCanvasSession(); diff --git a/new_editor/src/Application.h b/new_editor/src/Application.h index 4f98ad2b..a3a0fb1a 100644 --- a/new_editor/src/Application.h +++ b/new_editor/src/Application.h @@ -4,7 +4,6 @@ #include "XCUIBackend/XCUIEditorCommandRouter.h" #include "XCUIBackend/NativeXCUIPanelCanvasHost.h" -#include "XCUIBackend/XCUIDemoRuntime.h" #include "XCUIBackend/XCUIHostedPreviewPresenter.h" #include "XCUIBackend/XCUIInputBridge.h" #include "XCUIBackend/XCUILayoutLabRuntime.h" @@ -449,16 +448,10 @@ private: ShellChromeState m_shellChromeState = {}; std::vector m_hostedPreviewSurfaces = {}; WindowHostMode m_windowHostMode = WindowHostMode::NativeXCUI; - ::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime m_nativeDemoRuntime; - ::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeLayoutRuntime; - ::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_nativeDemoInputBridge; - ::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_nativeLayoutInputBridge; ::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeDemoCanvasHost; ::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeLayoutCanvasHost; ShellPanelId m_nativeActivePanel = ShellPanelId::XCUIDemo; bool m_legacyHostDemoWindowVisible = false; - bool m_nativeDemoReloadSucceeded = false; - bool m_nativeLayoutReloadSucceeded = false; ::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime; MainWindowNativeBackdropRenderer m_nativeBackdropRenderer; bool m_running = false; diff --git a/new_editor/src/ApplicationLegacyImGui.cpp b/new_editor/src/ApplicationLegacyImGui.cpp index 745c3e10..57615554 100644 --- a/new_editor/src/ApplicationLegacyImGui.cpp +++ b/new_editor/src/ApplicationLegacyImGui.cpp @@ -227,7 +227,14 @@ void Application::ConfigureShellCommandRouter() { ShellPanelId::XCUILayoutLab, enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter); }; - bindings.onHostedPreviewModeChanged = [this]() { ConfigureHostedPreviewPresenters(); }; + bindings.onHostedPreviewModeChanged = [this]() { + if (IsNativeWindowHostEnabled()) { + InitializeNativeShell(); + return; + } + + ConfigureHostedPreviewPresenters(); + }; Application::RegisterShellViewCommands(m_shellCommandRouter, bindings); RegisterLegacyHostDemoWindowCommand( diff --git a/new_editor/src/panels/XCUIDemoPanel.cpp b/new_editor/src/panels/XCUIDemoPanel.cpp index f8772fb7..8d6fe6cd 100644 --- a/new_editor/src/panels/XCUIDemoPanel.cpp +++ b/new_editor/src/panels/XCUIDemoPanel.cpp @@ -1,16 +1,8 @@ #include "XCUIDemoPanel.h" -#include "XCUIBackend/NullXCUIPanelCanvasHost.h" - -#include - #include #include -#include -#include -#include -#include namespace XCEngine { namespace NewEditor { @@ -19,142 +11,10 @@ namespace { constexpr float kCanvasHudHeight = 82.0f; constexpr float kCanvasHudPadding = 10.0f; -constexpr char kPreviewDebugName[] = "XCUI Demo"; constexpr char kPreviewDebugSource[] = "new_editor.panels.xcui_demo"; -bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) { - return point.x >= rect.x && - point.y >= rect.y && - point.x <= rect.x + rect.width && - point.y <= rect.y + rect.height; -} - -void DrawRectOverlay( - ::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost& canvasHost, - ::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime& runtime, - const std::string& elementId, - const UI::UIColor& color, - const char* label) { - if (elementId.empty()) { - return; - } - - UI::UIRect rect = {}; - if (!runtime.TryGetElementRect(elementId, rect)) { - return; - } - - canvasHost.DrawOutlineRect(rect, color, 2.0f, 6.0f); - if (label != nullptr && label[0] != '\0') { - canvasHost.DrawText( - UI::UIPoint(rect.x + 4.0f, rect.y + 4.0f), - label, - color); - } -} - -const char* GetPreviewPathLabel( - const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter) { - if (previewPresenter == nullptr) { - return "not injected"; - } - - return previewPresenter->IsNativeQueued() - ? "native queued offscreen surface" - : "hosted presenter"; -} - -const char* GetPreviewStateLabel( - const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter, - const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats, - bool hasHostedSurfaceDescriptor, - bool showHostedSurfaceImage) { - if (previewPresenter == nullptr) { - return "disabled"; - } - - const bool nativeHostedPreview = previewPresenter->IsNativeQueued(); - if (nativeHostedPreview) { - if (showHostedSurfaceImage) { - return "live"; - } - if (previewStats.queuedToNativePass || hasHostedSurfaceDescriptor) { - return "warming"; - } - return "awaiting submit"; - } - - return previewStats.presented ? "live" : "idle"; -} - -::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot BuildPassiveSnapshot( - const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession, - const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions& options) { - ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot = {}; - snapshot.pointerPosition = canvasSession.pointerPosition; - snapshot.pointerInside = options.hasPointerInsideOverride - ? options.pointerInsideOverride - : (canvasSession.validCanvas && canvasSession.hovered); - snapshot.windowFocused = options.windowFocused; - snapshot.timestampNanoseconds = options.timestampNanoseconds; - return snapshot; -} - } // namespace -XCUIDemoPanel::XCUIDemoPanel(::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource) - : XCUIDemoPanel( - inputSource, - nullptr, - ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost()) {} - -XCUIDemoPanel::XCUIDemoPanel( - ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource, - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter, - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost) - : Panel("XCUI Demo") - , m_inputSource(inputSource) - , m_previewPresenter(std::move(previewPresenter)) - , m_canvasHost(std::move(canvasHost)) { - if (m_canvasHost == nullptr) { - m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); - } - m_lastReloadSucceeded = m_runtime.ReloadDocuments(); -} - -void XCUIDemoPanel::SetHostedPreviewEnabled(bool enabled) { - m_hostedPreviewEnabled = enabled; - if (!m_hostedPreviewEnabled) { - m_lastPreviewStats = {}; - } -} - -const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult& XCUIDemoPanel::GetFrameResult() const { - return m_runtime.GetFrameResult(); -} - -const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& XCUIDemoPanel::GetLastPreviewStats() const { - return m_lastPreviewStats; -} - -void XCUIDemoPanel::SetHostedPreviewPresenter( - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) { - m_previewPresenter = std::move(previewPresenter); - m_lastPreviewStats = {}; -} - -void XCUIDemoPanel::SetCanvasHost( - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost) { - m_canvasHost = std::move(canvasHost); - if (m_canvasHost == nullptr) { - m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); - } -} - -bool XCUIDemoPanel::IsUsingNativeHostedPreview() const { - return m_previewPresenter != nullptr && m_previewPresenter->IsNativeQueued(); -} - void XCUIDemoPanel::Render() { ImGui::SetNextWindowSize(ImVec2(1040.0f, 720.0f), ImGuiCond_Appearing); ImGui::SetNextWindowDockID(ImGui::GetID("XCNewEditorDockSpace"), ImGuiCond_Appearing); @@ -182,198 +42,57 @@ void XCUIDemoPanel::Render() { const float diagnosticsHeight = 232.0f; const ImVec2 hostRegion = ImGui::GetContentRegionAvail(); const float canvasHeight = (std::max)(140.0f, hostRegion.y - diagnosticsHeight); - - if (m_canvasHost == nullptr) { - m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); - } - - const bool nativeHostedPreview = IsUsingNativeHostedPreview(); - ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {}; - const bool hasHostedSurfaceDescriptor = - nativeHostedPreview && - m_previewPresenter != nullptr && - m_previewPresenter->TryGetSurfaceDescriptor(kPreviewDebugName, hostedSurfaceDescriptor); - ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {}; - const bool showHostedSurfaceImage = - nativeHostedPreview && - m_previewPresenter != nullptr && - m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage); - const char* const previewPathLabel = GetPreviewPathLabel(m_previewPresenter.get()); const float topInset = m_showCanvasHud ? (kCanvasHudHeight + kCanvasHudPadding) : 0.0f; - ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {}; - canvasRequest.childId = "XCUIDemoCanvasHost"; - canvasRequest.height = canvasHeight; - canvasRequest.topInset = topInset; - canvasRequest.showSurfaceImage = showHostedSurfaceImage; - canvasRequest.surfaceImage = hostedSurfaceImage; - canvasRequest.placeholderTitle = - nativeHostedPreview ? "Native XCUI preview pending" : "Injected XCUI canvas host"; - canvasRequest.placeholderSubtitle = - nativeHostedPreview - ? "Waiting for native queued render output to publish back into the sandbox panel." - : "Inject a concrete canvas host to render the demo sandbox inside this panel."; - const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = - m_canvasHost->BeginCanvas(canvasRequest); - const UI::UIRect canvasRect = canvasSession.canvasRect; - const bool validCanvas = canvasSession.validCanvas; - - ::XCEngine::Editor::XCUIBackend::XCUIDemoInputState input = {}; - ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions bridgeOptions = {}; - bridgeOptions.timestampNanoseconds = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count()); - - ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot = {}; - if (m_inputSource != nullptr) { - bridgeOptions.hasPointerInsideOverride = true; - bridgeOptions.windowFocused = canvasSession.windowFocused; - const UI::UIPoint pointerPosition = m_inputSource->GetPointerPosition(); - bridgeOptions.pointerInsideOverride = validCanvas && ContainsPoint(canvasRect, pointerPosition); - snapshot = m_inputSource->CaptureSnapshot(bridgeOptions); - } else { - bridgeOptions.hasPointerInsideOverride = true; - bridgeOptions.pointerInsideOverride = validCanvas && canvasSession.hovered; - bridgeOptions.windowFocused = canvasSession.windowFocused; - snapshot = BuildPassiveSnapshot(canvasSession, bridgeOptions); - } - - if (!m_inputBridge.HasBaseline()) { - m_inputBridge.Prime(snapshot); - } - const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta = - m_inputBridge.Translate(snapshot); - - input.canvasRect = canvasRect; - input.pointerPosition = snapshot.pointerPosition; - input.pointerInside = snapshot.pointerInside; - input.pointerPressed = frameDelta.pointer.pressed[0]; - input.pointerReleased = frameDelta.pointer.released[0]; - input.pointerDown = snapshot.pointerButtonsDown[0]; - input.windowFocused = snapshot.windowFocused; - input.shortcutPressed = false; - input.wantCaptureMouse = snapshot.wantCaptureMouse; - input.wantCaptureKeyboard = snapshot.wantCaptureKeyboard; - input.wantTextInput = snapshot.wantTextInput; - input.events = frameDelta.events; - - const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult& frame = m_runtime.Update(input); - - if (m_hostedPreviewEnabled && m_previewPresenter != nullptr) { - ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {}; - previewFrame.drawData = &frame.drawData; - previewFrame.canvasRect = canvasRect; - previewFrame.logicalSize = UI::UISize(canvasRect.width, canvasRect.height); - previewFrame.debugName = kPreviewDebugName; - previewFrame.debugSource = kPreviewDebugSource; - m_previewPresenter->Present(previewFrame); - m_lastPreviewStats = m_previewPresenter->GetLastStats(); - } else { - m_lastPreviewStats = {}; - } - - const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameStats& stats = frame.stats; - const char* const previewStateLabel = GetPreviewStateLabel( - m_previewPresenter.get(), - m_lastPreviewStats, - hasHostedSurfaceDescriptor, - showHostedSurfaceImage); - if (m_showCanvasHud) { - const float hudWidth = (std::min)(canvasSession.hostRect.width - 16.0f, 430.0f); - const UI::UIRect hudRect( - canvasSession.hostRect.x + 8.0f, - canvasSession.hostRect.y + 8.0f, - (std::max)(0.0f, hudWidth), - kCanvasHudHeight - 8.0f); - m_canvasHost->DrawFilledRect( - hudRect, - UI::UIColor(16.0f / 255.0f, 22.0f / 255.0f, 30.0f / 255.0f, 220.0f / 255.0f), - 8.0f); - m_canvasHost->DrawOutlineRect( - hudRect, - UI::UIColor(52.0f / 255.0f, 72.0f / 255.0f, 96.0f / 255.0f, 1.0f), - 1.0f, - 8.0f); - - const UI::UIPoint lineOrigin(hudRect.x + 10.0f, hudRect.y + 8.0f); - const UI::UIColor textColor(191.0f / 255.0f, 205.0f / 255.0f, 224.0f / 255.0f, 1.0f); - m_canvasHost->DrawText(lineOrigin, "XCUI Runtime", UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f)); - - std::string previewLine = std::string(previewPathLabel) + " | " + previewStateLabel; - std::string treeLine = - "Tree " + std::to_string(static_cast(stats.treeGeneration)) + - " | Elements " + std::to_string(stats.elementCount) + - " | Commands " + std::to_string(stats.commandCount); - std::string flushLine = - "Submit " + std::to_string(m_lastPreviewStats.submittedDrawListCount) + - "/" + std::to_string(m_lastPreviewStats.submittedCommandCount) + - " | Flush " + std::to_string(m_lastPreviewStats.flushedDrawListCount) + - "/" + std::to_string(m_lastPreviewStats.flushedCommandCount); - - m_canvasHost->DrawText(UI::UIPoint(lineOrigin.x, lineOrigin.y + 18.0f), previewLine, textColor); - m_canvasHost->DrawText(UI::UIPoint(lineOrigin.x, lineOrigin.y + 36.0f), stats.statusMessage, textColor); - m_canvasHost->DrawText(UI::UIPoint(lineOrigin.x, lineOrigin.y + 54.0f), treeLine, textColor); - m_canvasHost->DrawText(UI::UIPoint(lineOrigin.x + 210.0f, lineOrigin.y + 54.0f), flushLine, textColor); - } - - if (m_showDebugRects && validCanvas && (!nativeHostedPreview || showHostedSurfaceImage)) { - DrawRectOverlay( - *m_canvasHost, - m_runtime, - stats.hoveredElementId, - UI::UIColor(1.0f, 195.0f / 255.0f, 64.0f / 255.0f, 1.0f), - "hover"); - DrawRectOverlay( - *m_canvasHost, - m_runtime, - stats.focusedElementId, - UI::UIColor(64.0f / 255.0f, 214.0f / 255.0f, 1.0f, 1.0f), - "focus"); - DrawRectOverlay( - *m_canvasHost, - m_runtime, - "toggleAccent", - UI::UIColor(150.0f / 255.0f, 1.0f, 150.0f / 255.0f, 160.0f / 255.0f), - "toggle"); - } - m_canvasHost->EndCanvas(); + const XCUIDemoPanelFrameComposition& composition = ComposeFrame( + XCUIDemoPanelFrameComposeOptions{ + canvasHeight, + topInset, + m_hostedPreviewEnabled, + m_showCanvasHud, + m_showDebugRects, + 0u, + "XCUIDemoCanvasHost"}); + const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameStats& stats = composition.frame->stats; ImGui::Separator(); ImGui::BeginChild("XCUIDemoDiagnostics", ImVec2(0.0f, 0.0f), false, ImGuiWindowFlags_NoScrollbar); ImGui::SeparatorText("Preview"); - ImGui::Text("Path: %s | state: %s", previewPathLabel, previewStateLabel); + ImGui::Text( + "Path: %s | state: %s", + composition.previewPathLabel.c_str(), + composition.previewStateLabel.c_str()); ImGui::Text( "Presenter: presented %s | submit->native %s", - m_lastPreviewStats.presented ? "yes" : "no", - m_lastPreviewStats.queuedToNativePass ? "yes" : "no"); + composition.previewStats.presented ? "yes" : "no", + composition.previewStats.queuedToNativePass ? "yes" : "no"); ImGui::Text( "Submitted: %zu lists / %zu cmds | Flushed: %zu lists / %zu cmds", - m_lastPreviewStats.submittedDrawListCount, - m_lastPreviewStats.submittedCommandCount, - m_lastPreviewStats.flushedDrawListCount, - m_lastPreviewStats.flushedCommandCount); + composition.previewStats.submittedDrawListCount, + composition.previewStats.submittedCommandCount, + composition.previewStats.flushedDrawListCount, + composition.previewStats.flushedCommandCount); ImGui::TextWrapped( "Source: %s", - hasHostedSurfaceDescriptor && !hostedSurfaceDescriptor.debugSource.empty() - ? hostedSurfaceDescriptor.debugSource.c_str() + composition.hasHostedSurfaceDescriptor && !composition.hostedSurfaceDescriptor.debugSource.empty() + ? composition.hostedSurfaceDescriptor.debugSource.c_str() : kPreviewDebugSource); - if (nativeHostedPreview) { + if (composition.nativeHostedPreview) { ImGui::Text( "Surface descriptor: %s | image published: %s | queued frame index: %zu", - hasHostedSurfaceDescriptor ? "yes" : "no", - showHostedSurfaceImage ? "yes" : "no", - hasHostedSurfaceDescriptor ? hostedSurfaceDescriptor.queuedFrameIndex : 0u); - if (hasHostedSurfaceDescriptor) { + composition.hasHostedSurfaceDescriptor ? "yes" : "no", + composition.showHostedSurfaceImage ? "yes" : "no", + composition.hasHostedSurfaceDescriptor ? composition.hostedSurfaceDescriptor.queuedFrameIndex : 0u); + if (composition.hasHostedSurfaceDescriptor) { ImGui::Text( "Surface: %ux%u | logical: %.0f x %.0f | rendered rect: %.0f, %.0f %.0f x %.0f", - hostedSurfaceDescriptor.image.surfaceWidth, - hostedSurfaceDescriptor.image.surfaceHeight, - hostedSurfaceDescriptor.logicalSize.width, - hostedSurfaceDescriptor.logicalSize.height, - hostedSurfaceDescriptor.image.renderedCanvasRect.x, - hostedSurfaceDescriptor.image.renderedCanvasRect.y, - hostedSurfaceDescriptor.image.renderedCanvasRect.width, - hostedSurfaceDescriptor.image.renderedCanvasRect.height); + composition.hostedSurfaceDescriptor.image.surfaceWidth, + composition.hostedSurfaceDescriptor.image.surfaceHeight, + composition.hostedSurfaceDescriptor.logicalSize.width, + composition.hostedSurfaceDescriptor.logicalSize.height, + composition.hostedSurfaceDescriptor.image.renderedCanvasRect.x, + composition.hostedSurfaceDescriptor.image.renderedCanvasRect.y, + composition.hostedSurfaceDescriptor.image.renderedCanvasRect.width, + composition.hostedSurfaceDescriptor.image.renderedCanvasRect.height); } else { ImGui::TextDisabled("No native surface descriptor has been published back yet."); } @@ -401,24 +120,27 @@ void XCUIDemoPanel::Render() { "Last command: %s | Accent: %s", stats.lastCommandId.empty() ? "none" : stats.lastCommandId.c_str(), stats.accentEnabled ? "on" : "off"); - ImGui::Text("Canvas: %.0f x %.0f", input.canvasRect.width, input.canvasRect.height); + ImGui::Text( + "Canvas: %.0f x %.0f", + composition.input.canvasRect.width, + composition.input.canvasRect.height); ImGui::SeparatorText("Input"); ImGui::Text( "Pointer: %.0f, %.0f | inside %s | down %s | pressed %s | released %s", - input.pointerPosition.x, - input.pointerPosition.y, - input.pointerInside ? "yes" : "no", - input.pointerDown ? "yes" : "no", - input.pointerPressed ? "yes" : "no", - input.pointerReleased ? "yes" : "no"); + composition.input.pointerPosition.x, + composition.input.pointerPosition.y, + composition.input.pointerInside ? "yes" : "no", + composition.input.pointerDown ? "yes" : "no", + composition.input.pointerPressed ? "yes" : "no", + composition.input.pointerReleased ? "yes" : "no"); ImGui::Text( "Focus %s | capture mouse %s | capture keyboard %s | text input %s | events %zu", - input.windowFocused ? "yes" : "no", - input.wantCaptureMouse ? "yes" : "no", - input.wantCaptureKeyboard ? "yes" : "no", - input.wantTextInput ? "yes" : "no", - input.events.size()); + composition.input.windowFocused ? "yes" : "no", + composition.input.wantCaptureMouse ? "yes" : "no", + composition.input.wantCaptureKeyboard ? "yes" : "no", + composition.input.wantTextInput ? "yes" : "no", + composition.input.events.size()); ImGui::EndChild(); ImGui::End(); diff --git a/new_editor/src/panels/XCUIDemoPanel.h b/new_editor/src/panels/XCUIDemoPanel.h index 86ce4a03..cd566977 100644 --- a/new_editor/src/panels/XCUIDemoPanel.h +++ b/new_editor/src/panels/XCUIDemoPanel.h @@ -7,11 +7,47 @@ #include "XCUIBackend/XCUIPanelCanvasHost.h" #include "XCUIBackend/XCUIDemoRuntime.h" +#include #include +#include namespace XCEngine { namespace NewEditor { +struct XCUIDemoPanelFrameComposeOptions { + float canvasHeight = 0.0f; + float canvasTopInset = 0.0f; + bool hostedPreviewEnabled = true; + bool showCanvasHud = true; + bool showDebugRects = true; + std::uint64_t timestampNanoseconds = 0; + const char* canvasChildId = "XCUIDemoCanvasHost"; + const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot* inputSnapshot = nullptr; + bool useDefaultPlaceholder = true; + const char* placeholderTitle = nullptr; + const char* placeholderSubtitle = nullptr; + bool drawPreviewFrame = true; + const char* badgeTitle = nullptr; + const char* badgeSubtitle = nullptr; +}; + +struct XCUIDemoPanelFrameComposition { + ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {}; + ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot inputSnapshot = {}; + ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta inputDelta = {}; + ::XCEngine::Editor::XCUIBackend::XCUIDemoInputState input = {}; + const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult* frame = nullptr; + ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats previewStats = {}; + ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {}; + ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {}; + bool hostedPreviewEnabled = false; + bool nativeHostedPreview = false; + bool hasHostedSurfaceDescriptor = false; + bool showHostedSurfaceImage = false; + std::string previewPathLabel = {}; + std::string previewStateLabel = {}; +}; + class XCUIDemoPanel : public Panel { public: explicit XCUIDemoPanel( @@ -23,6 +59,8 @@ public: ~XCUIDemoPanel() override = default; void Render() override; + const XCUIDemoPanelFrameComposition& ComposeFrame( + const XCUIDemoPanelFrameComposeOptions& options = XCUIDemoPanelFrameComposeOptions()); void SetHostedPreviewEnabled(bool enabled); void SetHostedPreviewPresenter( std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter); @@ -32,6 +70,8 @@ public: bool IsUsingNativeHostedPreview() const; const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult& GetFrameResult() const; const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& GetLastPreviewStats() const; + const XCUIDemoPanelFrameComposition& GetLastFrameComposition() const; + bool GetLastReloadSucceeded() const { return m_lastReloadSucceeded; } private: bool m_lastReloadSucceeded = false; @@ -44,6 +84,7 @@ private: std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter; std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost; ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats m_lastPreviewStats = {}; + XCUIDemoPanelFrameComposition m_lastFrameComposition = {}; }; } // namespace NewEditor diff --git a/new_editor/src/panels/XCUILayoutLabPanel.cpp b/new_editor/src/panels/XCUILayoutLabPanel.cpp deleted file mode 100644 index f2426350..00000000 --- a/new_editor/src/panels/XCUILayoutLabPanel.cpp +++ /dev/null @@ -1,391 +0,0 @@ -#include "XCUILayoutLabPanel.h" - -#include "XCUIBackend/NullXCUIPanelCanvasHost.h" -#include - -#include - -#include -#include -#include -#include - -namespace XCEngine { -namespace NewEditor { - -namespace { - -constexpr char kPreviewDebugName[] = "XCUI Layout Lab"; -constexpr char kPreviewDebugSource[] = "new_editor.panels.xcui_layout_lab"; - -bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) { - return point.x >= rect.x && - point.y >= rect.y && - point.x <= rect.x + rect.width && - point.y <= rect.y + rect.height; -} - -const char* GetPreviewPathLabel( - const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter) { - if (previewPresenter == nullptr) { - return "not injected"; - } - - return previewPresenter->IsNativeQueued() - ? "native queued offscreen surface" - : "hosted presenter"; -} - -const char* GetPreviewStateLabel( - const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter, - const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats, - bool hasHostedSurfaceDescriptor, - bool showHostedSurfaceImage) { - if (previewPresenter == nullptr) { - return "disabled"; - } - - const bool nativeHostedPreview = previewPresenter->IsNativeQueued(); - if (nativeHostedPreview) { - if (showHostedSurfaceImage) { - return "live"; - } - if (previewStats.queuedToNativePass || hasHostedSurfaceDescriptor) { - return "warming"; - } - return "awaiting submit"; - } - - return previewStats.presented ? "live" : "idle"; -} - -bool ContainsKeyTransition( - const std::vector& keys, - std::int32_t keyCode) { - return std::find(keys.begin(), keys.end(), keyCode) != keys.end(); -} - -bool ShouldCaptureKeyboardNavigation( - const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession, - const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& previousFrame) { - if (!canvasSession.validCanvas) { - return false; - } - - return canvasSession.hovered || - (canvasSession.windowFocused && - !previousFrame.stats.selectedElementId.empty()); -} - -void PopulateKeyboardNavigationInput( - ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState& input, - const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta, - bool captureKeyboardNavigation) { - if (!captureKeyboardNavigation) { - return; - } - - using ::XCEngine::Input::KeyCode; - const auto pressedThisFrame = - [&frameDelta](KeyCode keyCode) { - const std::int32_t code = static_cast(keyCode); - return ContainsKeyTransition(frameDelta.keyboard.pressedKeys, code) || - ContainsKeyTransition(frameDelta.keyboard.repeatedKeys, code); - }; - - input.navigatePrevious = pressedThisFrame(KeyCode::Up); - input.navigateNext = pressedThisFrame(KeyCode::Down); - input.navigateHome = pressedThisFrame(KeyCode::Home); - input.navigateEnd = pressedThisFrame(KeyCode::End); - input.navigateCollapse = pressedThisFrame(KeyCode::Left); - input.navigateExpand = pressedThisFrame(KeyCode::Right); -} - -::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot BuildPassiveSnapshot( - const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession, - const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions& options) { - ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot = {}; - snapshot.pointerPosition = canvasSession.pointerPosition; - snapshot.pointerInside = options.hasPointerInsideOverride - ? options.pointerInsideOverride - : (canvasSession.validCanvas && canvasSession.hovered); - snapshot.windowFocused = options.windowFocused; - snapshot.timestampNanoseconds = options.timestampNanoseconds; - return snapshot; -} - -} // namespace - -XCUILayoutLabPanel::XCUILayoutLabPanel(::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource) - : XCUILayoutLabPanel(inputSource, nullptr, ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost()) {} - -XCUILayoutLabPanel::XCUILayoutLabPanel( - ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource, - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter, - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost) - : Panel("XCUI Layout Lab") - , m_inputSource(inputSource) - , m_previewPresenter(std::move(previewPresenter)) - , m_canvasHost(std::move(canvasHost)) { - if (m_canvasHost == nullptr) { - m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); - } - m_lastReloadSucceeded = m_runtime.ReloadDocuments(); -} - -void XCUILayoutLabPanel::SetHostedPreviewEnabled(bool enabled) { - m_hostedPreviewEnabled = enabled; - if (!m_hostedPreviewEnabled) { - m_lastPreviewStats = {}; - } -} - -const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& XCUILayoutLabPanel::GetFrameResult() const { - return m_runtime.GetFrameResult(); -} - -const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& XCUILayoutLabPanel::GetLastPreviewStats() const { - return m_lastPreviewStats; -} - -bool XCUILayoutLabPanel::TryGetElementRect(const std::string& elementId, ::XCEngine::UI::UIRect& outRect) const { - return m_runtime.TryGetElementRect(elementId, outRect); -} - -void XCUILayoutLabPanel::SetHostedPreviewPresenter( - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) { - m_previewPresenter = std::move(previewPresenter); - m_lastPreviewStats = {}; -} - -void XCUILayoutLabPanel::SetCanvasHost( - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost) { - m_canvasHost = std::move(canvasHost); - if (m_canvasHost == nullptr) { - m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); - } -} - -bool XCUILayoutLabPanel::IsUsingNativeHostedPreview() const { - return m_previewPresenter != nullptr && m_previewPresenter->IsNativeQueued(); -} - -void XCUILayoutLabPanel::Render() { - ImGui::SetNextWindowSize(ImVec2(960.0f, 720.0f), ImGuiCond_Appearing); - ImGui::SetNextWindowDockID(ImGui::GetID("XCNewEditorDockSpace"), ImGuiCond_Appearing); - - bool open = true; - if (!ImGui::Begin(GetName().c_str(), &open)) { - ImGui::End(); - if (!open) { - SetVisible(false); - } - return; - } - - if (ImGui::Button("Reload Layout Lab")) { - m_lastReloadSucceeded = m_runtime.ReloadDocuments(); - } - ImGui::SameLine(); - ImGui::TextUnformatted(m_lastReloadSucceeded ? "Reload: OK" : "Reload: Failed"); - ImGui::Separator(); - - const float diagnosticsHeight = 240.0f; - const ImVec2 hostRegion = ImGui::GetContentRegionAvail(); - const float canvasHeight = (std::max)(140.0f, hostRegion.y - diagnosticsHeight); - - if (m_canvasHost == nullptr) { - m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); - } - - const bool nativeHostedPreview = IsUsingNativeHostedPreview(); - ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {}; - const bool hasHostedSurfaceDescriptor = - nativeHostedPreview && - m_previewPresenter != nullptr && - m_previewPresenter->TryGetSurfaceDescriptor(kPreviewDebugName, hostedSurfaceDescriptor); - ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {}; - const bool showHostedSurfaceImage = - nativeHostedPreview && - m_previewPresenter != nullptr && - m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage); - const char* const previewPathLabel = GetPreviewPathLabel(m_previewPresenter.get()); - ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {}; - canvasRequest.childId = "XCUILayoutLabCanvasHost"; - canvasRequest.height = canvasHeight; - canvasRequest.showSurfaceImage = showHostedSurfaceImage; - canvasRequest.surfaceImage = hostedSurfaceImage; - canvasRequest.placeholderTitle = - nativeHostedPreview ? "Native layout preview pending" : "Injected layout canvas host"; - canvasRequest.placeholderSubtitle = - nativeHostedPreview - ? "Waiting for native queued render output to publish back into the layout sandbox." - : "Inject a concrete canvas host to render the layout sandbox inside this panel."; - canvasRequest.badgeTitle = "Layout Lab"; - canvasRequest.badgeSubtitle = previewPathLabel; - const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = - m_canvasHost->BeginCanvas(canvasRequest); - const UI::UIRect canvasRect = canvasSession.canvasRect; - const bool validCanvas = canvasSession.validCanvas; - - ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions bridgeOptions = {}; - bridgeOptions.timestampNanoseconds = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count()); - - ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot = {}; - if (m_inputSource != nullptr) { - bridgeOptions.hasPointerInsideOverride = true; - bridgeOptions.windowFocused = canvasSession.windowFocused; - const UI::UIPoint pointerPosition = m_inputSource->GetPointerPosition(); - bridgeOptions.pointerInsideOverride = validCanvas && ContainsPoint(canvasRect, pointerPosition); - snapshot = m_inputSource->CaptureSnapshot(bridgeOptions); - } else { - bridgeOptions.hasPointerInsideOverride = true; - bridgeOptions.pointerInsideOverride = validCanvas && canvasSession.hovered; - bridgeOptions.windowFocused = canvasSession.windowFocused; - snapshot = BuildPassiveSnapshot(canvasSession, bridgeOptions); - } - - if (!m_inputBridge.HasBaseline()) { - m_inputBridge.Prime(snapshot); - } - const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta = - m_inputBridge.Translate(snapshot); - - ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = {}; - input.canvasRect = canvasRect; - input.pointerPosition = snapshot.pointerPosition; - input.pointerInside = snapshot.pointerInside; - input.pointerPressed = input.pointerInside && frameDelta.pointer.pressed[0]; - PopulateKeyboardNavigationInput( - input, - frameDelta, - ShouldCaptureKeyboardNavigation(canvasSession, m_runtime.GetFrameResult())); - - const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& frame = m_runtime.Update(input); - - if (m_hostedPreviewEnabled && m_previewPresenter != nullptr) { - ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {}; - previewFrame.drawData = &frame.drawData; - previewFrame.canvasRect = input.canvasRect; - previewFrame.logicalSize = UI::UISize(input.canvasRect.width, input.canvasRect.height); - previewFrame.debugName = kPreviewDebugName; - previewFrame.debugSource = kPreviewDebugSource; - m_previewPresenter->Present(previewFrame); - m_lastPreviewStats = m_previewPresenter->GetLastStats(); - } else { - m_lastPreviewStats = {}; - } - - const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameStats& stats = frame.stats; - const char* const previewStateLabel = GetPreviewStateLabel( - m_previewPresenter.get(), - m_lastPreviewStats, - hasHostedSurfaceDescriptor, - showHostedSurfaceImage); - m_canvasHost->EndCanvas(); - - ImGui::Separator(); - ImGui::BeginChild("XCUILayoutLabDiagnostics", ImVec2(0.0f, 0.0f), false, ImGuiWindowFlags_NoScrollbar); - ImGui::SeparatorText("Preview"); - ImGui::Text("Path: %s | state: %s", previewPathLabel, previewStateLabel); - ImGui::Text( - "Presenter: presented %s | submit->native %s", - m_lastPreviewStats.presented ? "yes" : "no", - m_lastPreviewStats.queuedToNativePass ? "yes" : "no"); - ImGui::Text( - "Submitted: %zu lists / %zu cmds | Flushed: %zu lists / %zu cmds", - m_lastPreviewStats.submittedDrawListCount, - m_lastPreviewStats.submittedCommandCount, - m_lastPreviewStats.flushedDrawListCount, - m_lastPreviewStats.flushedCommandCount); - ImGui::TextWrapped( - "Source: %s", - hasHostedSurfaceDescriptor && !hostedSurfaceDescriptor.debugSource.empty() - ? hostedSurfaceDescriptor.debugSource.c_str() - : kPreviewDebugSource); - if (nativeHostedPreview) { - ImGui::Text( - "Surface descriptor: %s | image published: %s | queued frame index: %zu", - hasHostedSurfaceDescriptor ? "yes" : "no", - showHostedSurfaceImage ? "yes" : "no", - hasHostedSurfaceDescriptor ? hostedSurfaceDescriptor.queuedFrameIndex : 0u); - if (hasHostedSurfaceDescriptor) { - ImGui::Text( - "Surface: %ux%u | logical: %.0f x %.0f | rendered rect: %.0f, %.0f %.0f x %.0f", - hostedSurfaceDescriptor.image.surfaceWidth, - hostedSurfaceDescriptor.image.surfaceHeight, - hostedSurfaceDescriptor.logicalSize.width, - hostedSurfaceDescriptor.logicalSize.height, - hostedSurfaceDescriptor.image.renderedCanvasRect.x, - hostedSurfaceDescriptor.image.renderedCanvasRect.y, - hostedSurfaceDescriptor.image.renderedCanvasRect.width, - hostedSurfaceDescriptor.image.renderedCanvasRect.height); - } else { - ImGui::TextDisabled("No native surface descriptor has been published back yet."); - } - } else { - ImGui::TextDisabled("No native surface descriptor is available without a native queued presenter."); - } - - ImGui::SeparatorText("Runtime"); - ImGui::Text("Status: %s", stats.statusMessage.c_str()); - ImGui::Text( - "Rows: %zu | Columns: %zu | Overlays: %zu | Scroll views: %zu", - stats.rowCount, - stats.columnCount, - stats.overlayCount, - stats.scrollViewCount); - ImGui::Text( - "Tree items: %zu (%zu expanded) | Property sections: %zu (%zu expanded)", - stats.treeItemCount, - stats.expandedTreeItemCount, - stats.propertySectionCount, - stats.expandedPropertySectionCount); - ImGui::Text( - "Draw lists: %zu | Draw commands: %zu", - stats.drawListCount, - stats.commandCount); - ImGui::Text( - "Command types: fill %zu | outline %zu | text %zu | image %zu | clip %zu/%zu", - stats.filledRectCommandCount, - stats.rectOutlineCommandCount, - stats.textCommandCount, - stats.imageCommandCount, - stats.clipPushCommandCount, - stats.clipPopCommandCount); - ImGui::Text( - "Native overlay: %s | supported %zu | unsupported %zu", - stats.nativeOverlayReady ? "preflight OK" : "preflight issues", - stats.nativeSupportedCommandCount, - stats.nativeUnsupportedCommandCount); - ImGui::TextWrapped( - "Native note: %s", - stats.nativeOverlayStatusMessage.empty() ? "none" : stats.nativeOverlayStatusMessage.c_str()); - ImGui::Text( - "Hovered: %s | Selected: %s | canvas: %.0f x %.0f", - stats.hoveredElementId.empty() ? "none" : stats.hoveredElementId.c_str(), - stats.selectedElementId.empty() ? "none" : stats.selectedElementId.c_str(), - input.canvasRect.width, - input.canvasRect.height); - - ImGui::SeparatorText("Input"); - ImGui::Text( - "Pointer: %.0f, %.0f | inside %s | pressed %s", - input.pointerPosition.x, - input.pointerPosition.y, - input.pointerInside ? "yes" : "no", - input.pointerPressed ? "yes" : "no"); - ImGui::EndChild(); - - ImGui::End(); - - if (!open) { - SetVisible(false); - } -} - -} // namespace NewEditor -} // namespace XCEngine diff --git a/new_editor/src/panels/XCUILayoutLabPanel.h b/new_editor/src/panels/XCUILayoutLabPanel.h index adf8146b..bef36bea 100644 --- a/new_editor/src/panels/XCUILayoutLabPanel.h +++ b/new_editor/src/panels/XCUILayoutLabPanel.h @@ -13,6 +13,29 @@ namespace XCEngine { namespace NewEditor { +struct XCUILayoutLabFrameCompositionRequest { + ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {}; + ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot inputSnapshot = {}; +}; + +struct XCUILayoutLabFrameComposition { + ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {}; + ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot inputSnapshot = {}; + ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta inputDelta = {}; + ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState inputState = {}; + ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats previewStats = {}; + ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {}; + ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {}; + const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult* frameResult = nullptr; + std::string previewPathLabel = {}; + std::string previewStateLabel = {}; + std::string previewSourceLabel = {}; + bool hostedPreviewEnabled = true; + bool nativeHostedPreview = false; + bool hasHostedSurfaceDescriptor = false; + bool showHostedSurfaceImage = false; +}; + class XCUILayoutLabPanel : public Panel { public: explicit XCUILayoutLabPanel( @@ -24,6 +47,8 @@ public: ~XCUILayoutLabPanel() override = default; void Render() override; + const XCUILayoutLabFrameComposition& ComposeFrame( + const XCUILayoutLabFrameCompositionRequest& request); void SetHostedPreviewEnabled(bool enabled); void SetHostedPreviewPresenter( std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter); @@ -32,7 +57,9 @@ public: bool IsHostedPreviewEnabled() const { return m_hostedPreviewEnabled; } bool IsUsingNativeHostedPreview() const; const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& GetFrameResult() const; + const XCUILayoutLabFrameComposition& GetLastFrameComposition() const; const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& GetLastPreviewStats() const; + bool GetLastReloadSucceeded() const { return m_lastReloadSucceeded; } bool TryGetElementRect(const std::string& elementId, ::XCEngine::UI::UIRect& outRect) const; private: @@ -44,6 +71,7 @@ private: std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter; std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost; ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats m_lastPreviewStats = {}; + XCUILayoutLabFrameComposition m_lastFrameComposition = {}; }; } // namespace NewEditor diff --git a/tests/NewEditor/test_xcui_demo_panel.cpp b/tests/NewEditor/test_xcui_demo_panel.cpp index a753e9fb..458edba3 100644 --- a/tests/NewEditor/test_xcui_demo_panel.cpp +++ b/tests/NewEditor/test_xcui_demo_panel.cpp @@ -202,4 +202,47 @@ TEST(NewEditorXCUIDemoPanelTest, ClearingHostedPreviewPresenterDoesNotRestoreImG EXPECT_EQ(stats.flushedCommandCount, 0u); } +TEST(NewEditorXCUIDemoPanelTest, ComposeFrameBuildsShellAgnosticFrameState) { + auto previewPresenter = std::make_unique(); + RecordingHostedPreviewPresenter* previewPresenterPtr = previewPresenter.get(); + auto canvasHost = std::make_unique(); + StubCanvasHost* canvasHostPtr = canvasHost.get(); + + XCUIDemoPanel panel(nullptr, std::move(previewPresenter)); + panel.SetCanvasHost(std::move(canvasHost)); + + const XCEngine::NewEditor::XCUIDemoPanelFrameComposition& composition = + panel.ComposeFrame(XCEngine::NewEditor::XCUIDemoPanelFrameComposeOptions{ + 512.0f, + 24.0f, + true, + false, + false, + 123456u, + "XCUIDemoTestCanvas"}); + + EXPECT_EQ(&composition, &panel.GetLastFrameComposition()); + ASSERT_NE(composition.frame, nullptr); + EXPECT_TRUE(composition.hostedPreviewEnabled); + EXPECT_FALSE(composition.nativeHostedPreview); + EXPECT_FALSE(composition.hasHostedSurfaceDescriptor); + EXPECT_FALSE(composition.showHostedSurfaceImage); + EXPECT_EQ(composition.previewPathLabel, "hosted presenter"); + EXPECT_EQ(composition.previewStateLabel, "live"); + EXPECT_EQ(composition.inputSnapshot.timestampNanoseconds, 123456u); + EXPECT_EQ(composition.canvasSession.canvasRect.width, canvasHostPtr->session.canvasRect.width); + EXPECT_EQ(composition.canvasSession.canvasRect.height, canvasHostPtr->session.canvasRect.height); + EXPECT_EQ(composition.input.canvasRect.width, canvasHostPtr->session.canvasRect.width); + EXPECT_EQ(composition.input.canvasRect.height, canvasHostPtr->session.canvasRect.height); + EXPECT_EQ(canvasHostPtr->beginCanvasCallCount, 1u); + EXPECT_EQ(canvasHostPtr->endCanvasCallCount, 1u); + EXPECT_FLOAT_EQ(canvasHostPtr->lastRequest.height, 512.0f); + EXPECT_FLOAT_EQ(canvasHostPtr->lastRequest.topInset, 24.0f); + EXPECT_STREQ(canvasHostPtr->lastRequest.childId, "XCUIDemoTestCanvas"); + EXPECT_EQ(previewPresenterPtr->presentCallCount, 1u); + EXPECT_EQ(previewPresenterPtr->lastDebugName, "XCUI Demo"); + EXPECT_EQ(composition.previewStats.submittedDrawListCount, panel.GetLastPreviewStats().submittedDrawListCount); + EXPECT_EQ(composition.previewStats.submittedCommandCount, panel.GetLastPreviewStats().submittedCommandCount); +} + } // namespace diff --git a/tests/NewEditor/test_xcui_layout_lab_panel.cpp b/tests/NewEditor/test_xcui_layout_lab_panel.cpp index d7f2b1db..f2653459 100644 --- a/tests/NewEditor/test_xcui_layout_lab_panel.cpp +++ b/tests/NewEditor/test_xcui_layout_lab_panel.cpp @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -18,9 +19,16 @@ using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter; using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost; using XCEngine::Editor::XCUIBackend::ImGuiXCUIInputSnapshotSource; using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame; +using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor; +using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage; using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats; +using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot; +using XCEngine::Editor::XCUIBackend::XCUIInputBridgeKeyState; using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest; using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession; +using XCEngine::Input::KeyCode; +using XCEngine::NewEditor::XCUILayoutLabFrameComposition; +using XCEngine::NewEditor::XCUILayoutLabFrameCompositionRequest; using XCEngine::NewEditor::XCUILayoutLabPanel; class ImGuiContextScope { @@ -50,6 +58,7 @@ void PrepareImGui(float width = 1280.0f, float height = 900.0f) { class StubHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter { public: bool Present(const XCUIHostedPreviewFrame& frame) override { + ++m_presentCallCount; m_lastStats = {}; m_lastStats.presented = frame.drawData != nullptr; return m_lastStats.presented; @@ -59,10 +68,79 @@ public: return m_lastStats; } + std::size_t GetPresentCallCount() const { + return m_presentCallCount; + } + private: + std::size_t m_presentCallCount = 0u; XCUIHostedPreviewStats m_lastStats = {}; }; +class StubNativeHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter { +public: + bool Present(const XCUIHostedPreviewFrame& frame) override { + ++m_presentCallCount; + m_lastFrame = frame; + m_lastStats = {}; + m_lastStats.presented = frame.drawData != nullptr; + m_lastStats.queuedToNativePass = frame.drawData != nullptr; + m_lastStats.submittedDrawListCount = frame.drawData != nullptr ? frame.drawData->GetDrawListCount() : 0u; + m_lastStats.submittedCommandCount = frame.drawData != nullptr ? frame.drawData->GetTotalCommandCount() : 0u; + return m_lastStats.presented; + } + + const XCUIHostedPreviewStats& GetLastStats() const override { + return m_lastStats; + } + + bool IsNativeQueued() const override { + return true; + } + + bool TryGetSurfaceImage( + const char* debugName, + XCUIHostedPreviewSurfaceImage& outImage) const override { + outImage = {}; + if (debugName == nullptr || m_descriptor.debugName != debugName) { + return false; + } + + outImage = m_descriptor.image; + return outImage.IsValid(); + } + + bool TryGetSurfaceDescriptor( + const char* debugName, + XCUIHostedPreviewSurfaceDescriptor& outDescriptor) const override { + outDescriptor = {}; + if (debugName == nullptr || m_descriptor.debugName != debugName) { + return false; + } + + outDescriptor = m_descriptor; + return true; + } + + void SetDescriptor(XCUIHostedPreviewSurfaceDescriptor descriptor) { + m_descriptor = std::move(descriptor); + } + + std::size_t GetPresentCallCount() const { + return m_presentCallCount; + } + + const XCUIHostedPreviewFrame& GetLastFrame() const { + return m_lastFrame; + } + +private: + std::size_t m_presentCallCount = 0u; + XCUIHostedPreviewFrame m_lastFrame = {}; + XCUIHostedPreviewStats m_lastStats = {}; + XCUIHostedPreviewSurfaceDescriptor m_descriptor = {}; +}; + class StubCanvasHost final : public IXCUIPanelCanvasHost { public: const char* GetDebugName() const override { @@ -119,6 +197,91 @@ private: }; }; +std::uint64_t NextTimestampNanoseconds() { + static std::uint64_t timestampNanoseconds = 1'000'000u; + timestampNanoseconds += 16'666'667u; + return timestampNanoseconds; +} + +XCUIPanelCanvasSession MakeCanvasSession() { + XCUIPanelCanvasSession session = {}; + session.hostRect = XCEngine::UI::UIRect(0.0f, 0.0f, 960.0f, 640.0f); + session.canvasRect = XCEngine::UI::UIRect(0.0f, 0.0f, 960.0f, 640.0f); + session.pointerPosition = XCEngine::UI::UIPoint(120.0f, 120.0f); + session.validCanvas = true; + session.hovered = true; + session.windowFocused = true; + return session; +} + +XCEngine::UI::UITextureHandle MakeSurfaceTextureHandle( + std::uintptr_t nativeHandle, + std::uint32_t width, + std::uint32_t height) { + XCEngine::UI::UITextureHandle texture = {}; + texture.nativeHandle = nativeHandle; + texture.width = width; + texture.height = height; + texture.kind = XCEngine::UI::UITextureHandleKind::ShaderResourceView; + return texture; +} + +XCUIInputBridgeFrameSnapshot MakePointerSnapshot( + const XCEngine::UI::UIPoint& pointerPosition, + bool pointerInside, + bool pointerDown, + bool windowFocused) { + XCUIInputBridgeFrameSnapshot snapshot = {}; + snapshot.pointerPosition = pointerPosition; + snapshot.pointerInside = pointerInside; + snapshot.pointerButtonsDown[0] = pointerDown; + snapshot.windowFocused = windowFocused; + snapshot.timestampNanoseconds = NextTimestampNanoseconds(); + return snapshot; +} + +XCUIInputBridgeFrameSnapshot MakeKeyboardSnapshot( + const XCEngine::UI::UIPoint& pointerPosition, + bool pointerInside, + bool windowFocused, + KeyCode keyCode) { + XCUIInputBridgeFrameSnapshot snapshot = + MakePointerSnapshot(pointerPosition, pointerInside, false, windowFocused); + snapshot.keys.push_back(XCUIInputBridgeKeyState { + static_cast(keyCode), + true, + false + }); + return snapshot; +} + +const XCUILayoutLabFrameComposition& ComposePanelFrame( + XCUILayoutLabPanel& panel, + const XCUIPanelCanvasSession& canvasSession, + const XCUIInputBridgeFrameSnapshot& snapshot) { + XCUILayoutLabFrameCompositionRequest request = {}; + request.canvasSession = canvasSession; + request.inputSnapshot = snapshot; + return panel.ComposeFrame(request); +} + +void ClickElementWithComposition( + XCUILayoutLabPanel& panel, + const XCUIPanelCanvasSession& baseSession, + const std::string& elementId) { + XCEngine::UI::UIRect elementRect = {}; + ASSERT_TRUE(panel.TryGetElementRect(elementId, elementRect)); + const XCEngine::UI::UIPoint clickPoint( + elementRect.x + elementRect.width * 0.5f, + elementRect.y + elementRect.height * 0.5f); + + XCUIPanelCanvasSession clickSession = baseSession; + clickSession.pointerPosition = clickPoint; + clickSession.hovered = true; + ComposePanelFrame(panel, clickSession, MakePointerSnapshot(clickPoint, true, true, true)); + ComposePanelFrame(panel, clickSession, MakePointerSnapshot(clickPoint, true, false, true)); +} + void RenderPanelFrame( XCUILayoutLabPanel& panel, ImGuiContextScope&, @@ -181,68 +344,92 @@ void PressKeyOnce( } TEST(NewEditorXCUILayoutLabPanelTest, MapsPreviousNextHomeAndEndIntoRuntimeNavigation) { - ImGuiContextScope contextScope; - auto previewPresenter = std::make_unique(); auto canvasHost = std::make_unique(); - StubCanvasHost* canvasHostPtr = canvasHost.get(); - ImGuiXCUIInputSnapshotSource inputSource(nullptr); - - XCUILayoutLabPanel panel(&inputSource, std::move(previewPresenter), std::move(canvasHost)); + XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::move(canvasHost)); panel.SetHostedPreviewEnabled(false); - RenderPanelFrame(panel, contextScope); - ClickElement(panel, *canvasHostPtr, "assetLighting", contextScope); + XCUIPanelCanvasSession session = MakeCanvasSession(); + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true)); + ClickElementWithComposition(panel, session, "assetLighting"); ASSERT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetLighting"); - canvasHostPtr->SetHovered(false); - canvasHostPtr->SetWindowFocused(true); + session.hovered = false; + session.windowFocused = true; - PressKeyOnce(panel, contextScope, ImGuiKey_DownArrow); + const XCUILayoutLabFrameComposition& downComposition = ComposePanelFrame( + panel, + session, + MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Down)); + EXPECT_TRUE(downComposition.inputState.navigateNext); + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true)); EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetMaterials"); - PressKeyOnce(panel, contextScope, ImGuiKey_UpArrow); + const XCUILayoutLabFrameComposition& upComposition = ComposePanelFrame( + panel, + session, + MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Up)); + EXPECT_TRUE(upComposition.inputState.navigatePrevious); + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true)); EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetLighting"); - PressKeyOnce(panel, contextScope, ImGuiKey_End); + const XCUILayoutLabFrameComposition& endComposition = ComposePanelFrame( + panel, + session, + MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::End)); + EXPECT_TRUE(endComposition.inputState.navigateEnd); + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true)); const std::string endSelection = panel.GetFrameResult().stats.selectedElementId; ASSERT_FALSE(endSelection.empty()); EXPECT_NE(endSelection, "assetLighting"); - PressKeyOnce(panel, contextScope, ImGuiKey_Home); + const XCUILayoutLabFrameComposition& homeComposition = ComposePanelFrame( + panel, + session, + MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Home)); + EXPECT_TRUE(homeComposition.inputState.navigateHome); + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true)); EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetLighting"); } TEST(NewEditorXCUILayoutLabPanelTest, MapsCollapseAndExpandIntoRuntimeNavigation) { - ImGuiContextScope contextScope; - auto previewPresenter = std::make_unique(); auto canvasHost = std::make_unique(); - StubCanvasHost* canvasHostPtr = canvasHost.get(); - ImGuiXCUIInputSnapshotSource inputSource(nullptr); - - XCUILayoutLabPanel panel(&inputSource, std::move(previewPresenter), std::move(canvasHost)); + XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::move(canvasHost)); panel.SetHostedPreviewEnabled(false); - RenderPanelFrame(panel, contextScope); - ClickElement(panel, *canvasHostPtr, "treeScenes", contextScope); + XCUIPanelCanvasSession session = MakeCanvasSession(); + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true)); + ClickElementWithComposition(panel, session, "treeScenes"); ASSERT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeScenes"); - canvasHostPtr->SetHovered(false); - canvasHostPtr->SetWindowFocused(true); + session.hovered = false; + session.windowFocused = true; - PressKeyOnce(panel, contextScope, ImGuiKey_LeftArrow); + const XCUILayoutLabFrameComposition& collapseSelection = ComposePanelFrame( + panel, + session, + MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Left)); + EXPECT_TRUE(collapseSelection.inputState.navigateCollapse); + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true)); EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeAssetsRoot"); - PressKeyOnce(panel, contextScope, ImGuiKey_LeftArrow); + ComposePanelFrame(panel, session, MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Left)); + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true)); EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeAssetsRoot"); EXPECT_EQ(panel.GetFrameResult().stats.expandedTreeItemCount, 0u); - PressKeyOnce(panel, contextScope, ImGuiKey_RightArrow); + const XCUILayoutLabFrameComposition& expandRoot = ComposePanelFrame( + panel, + session, + MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Right)); + EXPECT_TRUE(expandRoot.inputState.navigateExpand); + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true)); EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeAssetsRoot"); EXPECT_EQ(panel.GetFrameResult().stats.expandedTreeItemCount, 1u); - PressKeyOnce(panel, contextScope, ImGuiKey_RightArrow); + ComposePanelFrame(panel, session, MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Right)); + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true)); EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeScenes"); } @@ -259,4 +446,81 @@ TEST(NewEditorXCUILayoutLabPanelTest, DefaultFallbackDoesNotCreateImplicitHosted EXPECT_EQ(panel.GetLastPreviewStats().submittedCommandCount, 0u); } +TEST(NewEditorXCUILayoutLabPanelTest, ComposeFramePublishesShellAgnosticLastCompositionState) { + auto previewPresenter = std::make_unique(); + auto canvasHost = std::make_unique(); + + XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::move(canvasHost)); + + XCUIPanelCanvasSession session = MakeCanvasSession(); + const XCUILayoutLabFrameComposition& composition = + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true)); + + EXPECT_EQ(&composition, &panel.GetLastFrameComposition()); + ASSERT_NE(composition.frameResult, nullptr); + EXPECT_TRUE(composition.previewStats.presented); + EXPECT_EQ(composition.previewPathLabel, "hosted presenter"); + EXPECT_EQ(composition.previewStateLabel, "live"); + EXPECT_EQ(composition.previewSourceLabel, "new_editor.panels.xcui_layout_lab"); + EXPECT_TRUE(composition.hostedPreviewEnabled); + EXPECT_EQ(composition.inputState.canvasRect.width, session.canvasRect.width); + EXPECT_EQ(composition.inputState.canvasRect.height, session.canvasRect.height); + EXPECT_TRUE(composition.inputState.pointerInside); +} + +TEST(NewEditorXCUILayoutLabPanelTest, ComposeFrameCarriesNativeQueuedPreviewMetadataIntoComposition) { + auto previewPresenter = std::make_unique(); + XCUIHostedPreviewSurfaceDescriptor descriptor = {}; + descriptor.debugName = "XCUI Layout Lab"; + descriptor.debugSource = "tests.native_layout_preview"; + descriptor.logicalSize = XCEngine::UI::UISize(512.0f, 320.0f); + descriptor.queuedFrameIndex = 7u; + descriptor.image.texture = MakeSurfaceTextureHandle(42u, 512u, 320u); + descriptor.image.surfaceWidth = 512u; + descriptor.image.surfaceHeight = 320u; + descriptor.image.renderedCanvasRect = XCEngine::UI::UIRect(0.0f, 0.0f, 512.0f, 320.0f); + previewPresenter->SetDescriptor(std::move(descriptor)); + StubNativeHostedPreviewPresenter* previewPresenterRaw = previewPresenter.get(); + + XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::make_unique()); + + XCUIPanelCanvasSession session = MakeCanvasSession(); + const XCUILayoutLabFrameComposition& composition = + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true)); + + EXPECT_TRUE(composition.hostedPreviewEnabled); + EXPECT_TRUE(composition.nativeHostedPreview); + EXPECT_TRUE(composition.hasHostedSurfaceDescriptor); + EXPECT_TRUE(composition.showHostedSurfaceImage); + EXPECT_EQ(composition.previewPathLabel, "native queued offscreen surface"); + EXPECT_EQ(composition.previewStateLabel, "live"); + EXPECT_EQ(composition.previewSourceLabel, "tests.native_layout_preview"); + EXPECT_EQ(composition.hostedSurfaceDescriptor.queuedFrameIndex, 7u); + EXPECT_EQ(composition.hostedSurfaceImage.texture.nativeHandle, 42u); + EXPECT_TRUE(composition.previewStats.presented); + EXPECT_TRUE(composition.previewStats.queuedToNativePass); + EXPECT_EQ(previewPresenterRaw->GetPresentCallCount(), 1u); + EXPECT_EQ(previewPresenterRaw->GetLastFrame().debugName, std::string("XCUI Layout Lab")); + EXPECT_EQ(previewPresenterRaw->GetLastFrame().debugSource, std::string("new_editor.panels.xcui_layout_lab")); + EXPECT_FLOAT_EQ(previewPresenterRaw->GetLastFrame().logicalSize.width, session.canvasRect.width); + EXPECT_FLOAT_EQ(previewPresenterRaw->GetLastFrame().logicalSize.height, session.canvasRect.height); +} + +TEST(NewEditorXCUILayoutLabPanelTest, ComposeFrameMarksPreviewDisabledWhenPresenterIsInjectedButDisabled) { + auto previewPresenter = std::make_unique(); + StubHostedPreviewPresenter* previewPresenterRaw = previewPresenter.get(); + XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::make_unique()); + panel.SetHostedPreviewEnabled(false); + + XCUIPanelCanvasSession session = MakeCanvasSession(); + const XCUILayoutLabFrameComposition& composition = + ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true)); + + EXPECT_FALSE(composition.hostedPreviewEnabled); + EXPECT_EQ(composition.previewStateLabel, "disabled"); + EXPECT_FALSE(composition.previewStats.presented); + EXPECT_EQ(previewPresenterRaw->GetPresentCallCount(), 0u); + EXPECT_FALSE(panel.GetLastPreviewStats().presented); +} + } // namespace