From f943a07862abb9177f3618893d3c39c7a5620351 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 5 Apr 2026 13:24:14 +0800 Subject: [PATCH] Contain XCUI ImGui adapters behind explicit host seams --- docs/plan/XCUI_Phase_Status_2026-04-05.md | 28 ++- new_editor/CMakeLists.txt | 2 + new_editor/src/Application.cpp | 126 +++-------- new_editor/src/Application.h | 35 +-- .../src/XCUIBackend/ImGuiXCUIInputSource.h | 56 +++++ .../src/XCUIBackend/NullXCUIPanelCanvasHost.h | 8 +- .../XCUIBackend/XCUIHostedPreviewPresenter.h | 25 ++ new_editor/src/XCUIBackend/XCUIInputBridge.h | 15 +- .../src/XCUIBackend/XCUIPanelCanvasHost.h | 30 +++ .../src/XCUIBackend/XCUIShellChromeState.cpp | 170 ++++++++++++-- .../src/XCUIBackend/XCUIShellChromeState.h | 37 ++- new_editor/src/panels/XCUIDemoPanel.cpp | 73 +++--- new_editor/src/panels/XCUIDemoPanel.h | 9 +- new_editor/src/panels/XCUILayoutLabPanel.cpp | 133 ++++++++--- new_editor/src/panels/XCUILayoutLabPanel.h | 10 +- tests/NewEditor/CMakeLists.txt | 71 +++++- ...est_application_shell_command_bindings.cpp | 66 ++++-- .../test_imgui_xcui_input_adapter.cpp | 23 +- tests/NewEditor/test_xcui_demo_panel.cpp | 213 ++++++++++++++++++ .../test_xcui_hosted_preview_presenter.cpp | 77 ++++++- .../NewEditor/test_xcui_layout_lab_panel.cpp | 29 ++- .../NewEditor/test_xcui_panel_canvas_host.cpp | 50 +++- .../test_xcui_shell_chrome_state.cpp | 138 ++++++++++-- 23 files changed, 1134 insertions(+), 290 deletions(-) create mode 100644 new_editor/src/XCUIBackend/ImGuiXCUIInputSource.h create mode 100644 tests/NewEditor/test_xcui_demo_panel.cpp diff --git a/docs/plan/XCUI_Phase_Status_2026-04-05.md b/docs/plan/XCUI_Phase_Status_2026-04-05.md index 4797570e..d3e897cb 100644 --- a/docs/plan/XCUI_Phase_Status_2026-04-05.md +++ b/docs/plan/XCUI_Phase_Status_2026-04-05.md @@ -64,7 +64,7 @@ Current gap: ### 3. Editor Layer - `new_editor` remains the isolated XCUI sandbox. -- Native hosted preview is working as `RHI offscreen surface -> ImGui shell texture embed`. +- Native hosted preview is working as `RHI offscreen surface -> hosted surface image present` through the current shell adapter path. - Hosted preview surface descriptors now stay on XCUI-owned value types (`UITextureHandle`, `UIPoint`, `UIRect`) instead of exposing ImGui texture/UV types through the generic preview contract. - `XCUI Demo` remains the long-lived effect and behavior testbed. - `XCUI Demo` now covers both single-line and multiline text authoring behavior, including click caret placement, delete/backspace, tab indentation, and optional text-area line numbers. @@ -78,6 +78,8 @@ Current gap: - `XCUI Demo` now exports pending per-frame command ids through `DrainPendingCommandIds()`, so editor-side hosts have a clean seam for observing demo/runtime command traffic without parsing draw data. - `XCUI Demo` and `LayoutLab` panel canvases are now being pulled behind a dedicated `IXCUIPanelCanvasHost` seam, so canvas surface presentation, hover/focus fallback state, and overlay draw hooks no longer have to stay hard-coded inside each ImGui panel implementation. - The panel-canvas seam now also exposes explicit backend/capability metadata and a minimal `NullXCUIPanelCanvasHost`, so non-ImGui host paths have a concrete placeholder backend instead of relying on an implicit ImGui default. +- `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. - 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`. @@ -90,29 +92,33 @@ Current gap: - `LayoutLab` panel input now also maps concrete arrow/home/end keys into those shared navigation actions, so keyboard traversal is reachable from the sandbox UI instead of staying runtime-only. - `XCUIDemoRuntime` now bridges pointer activation, text-edit commands, and shortcut-triggered commands through a unified command path, and `DrainPendingCommandIds()` now preserves mixed pointer/text/shortcut ordering. - `new_editor` now also has a pure `XCUIShellChromeState` model covering panel visibility, hosted-preview mode, and shell-level view toggles without depending on ImGui, `Application`, or the old editor. +- `XCUIShellChromeState` hosted-preview mode naming is now backend-neutral (`HostedPresenter` / `NativeOffscreen`) instead of encoding `ImGui` into the XCUI shell model. - `XCNewEditor` builds successfully to `build/new_editor/bin/Debug/XCNewEditor.exe`. Current gap: - The shell is still ImGui-hosted. -- Legacy hosted preview still depends on an ImGui-specific inline draw target binding for presentation. -- The new panel-canvas seam still only has an ImGui adapter today; a native panel/shell host still needs to replace it before ImGui can stop being the default editor host path. +- Hosted-preview compatibility presentation still depends on an ImGui-specific inline draw target binding when the native queued surface path is disabled. +- The panel-canvas seam still only has an ImGui adapter today; a native panel/shell host still needs to replace it before the editor shell can stop depending on ImGui host chrome. - Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, and icon-atlas widgets are not yet extracted into reusable XCUI modules, and broader editor-host keybinding plus full shell-state adoption are still only partially integrated. ## Validated This Phase +- `new_editor_xcui_demo_panel_tests`: `3/3` - `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` - `new_editor_xcui_layout_lab_runtime_tests`: `12/12` - `new_editor_xcui_rhi_command_compiler_tests`: `6/6` - `new_editor_xcui_rhi_render_backend_tests`: `5/5` -- `new_editor_xcui_hosted_preview_presenter_tests`: `17/17` +- `new_editor_xcui_hosted_preview_presenter_tests`: `20/20` - `new_editor_imgui_window_ui_compositor_tests`: `7/7` - `new_editor_xcui_editor_command_router_tests`: `5/5` - `new_editor_application_shell_command_bindings_tests`: `6/6` -- `new_editor_xcui_shell_chrome_state_tests`: `8/8` -- `new_editor_xcui_panel_canvas_host_tests`: `2/2` +- `new_editor_xcui_shell_chrome_state_tests`: `11/11` +- `new_editor_xcui_panel_canvas_host_tests`: `4/4` - `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1` -- `new_editor_xcui_layout_lab_panel_tests`: `2/2` +- `new_editor_xcui_layout_lab_panel_tests`: `3/3` - `XCNewEditor` Debug target builds successfully - `core_ui_tests`: `52 total` (`50` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`) - `scene_tests`: `68/68` @@ -199,8 +205,12 @@ Current gap: - shell-level shortcuts now flow from `XCUIWin32InputSource` through `XCUIInputBridge` into command matching - hosted-preview mode toggles still trigger presenter reconfiguration through the routed command bindings - `new_editor` panel canvas ownership is now being split behind `IXCUIPanelCanvasHost`, with an `ImGuiXCUIPanelCanvasHost` adapter carrying the legacy path so panel code stops directly owning `ImGui::Image` / `ImGui::InvisibleButton` / draw-list preview plumbing. +- `XCUIDemoPanel` and `XCUILayoutLabPanel` no longer create an ImGui hosted-preview presenter or ImGui panel canvas host implicitly; default construction now stays on null/explicitly injected backends until the outer shell binds a concrete host adapter. +- `Application` now binds `ImGuiXCUIPanelCanvasHost` explicitly at the shell composition root, so the current ImGui panel host path is visible as a host-layer decision instead of a panel-layer fallback. +- `XCUIDemoPanel` and `XCUILayoutLabPanel` no longer read ImGui input directly; both now consume an injected `IXCUIInputSnapshotSource`, and the new `ImGuiXCUIInputSnapshotSource` keeps the current ImGui-backed input path isolated behind an explicit adapter. - `new_editor` now also has a pure `XCUIShellChromeState` model with dedicated tests, covering shell panel visibility, hosted-preview mode, and shell view toggles without depending on ImGui or `Application`. - `XCUIShellChromeState` now also exposes effective hosted-preview state helpers and shell view-toggle command-id helpers, so shell routing code no longer has to manually combine enablement and requested preview mode. +- `XCUIShellChromeState` hosted-preview modes were renamed away from `LegacyImGui`, so XCUI shell state no longer treats ImGui as the generic fallback concept. - The panel-canvas seam now has dedicated null/imgui backend coverage, including explicit backend/capability reporting and a non-ImGui placeholder host path for future native shell adoption. - `SceneRuntime` layered XCUI routing now has dedicated regression coverage for: - top-interactive layer input ownership @@ -228,13 +238,13 @@ Current gap: - `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet. - `Image` widgets still do not have source-rect/atlas-subregion level API in the high-level draw command model. - Editor shell still depends on ImGui as host chrome. -- Legacy hosted preview still depends on an ImGui-only inline presenter path when not using the queued native surface path. +- Hosted-preview compatibility presentation still depends on an ImGui-only inline presenter path when not using the queued native surface path. - Editor widget coverage is still prototype-driven inside `LayoutLab`; it has not yet been promoted into a full reusable shared widget/runtime layer with command routing, virtualization, and property-edit transactions. ## Next Phase 1. Expand runtime/game-layer ownership from the current `SceneRuntime` UI context into scene-declared HUD/menu bootstrapping, draw submission, and higher-level runtime UI policies. -2. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu chrome, shell-state adoption, and panel-level keyboard/navigation input plumbing. +2. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu chrome, shell-state adoption, virtualization, and broader focus/multi-selection behavior. 3. Add a native XCUI host compositor on the existing window-level compositor seam so `new_editor` can present without going through ImGui-owned draw data. 4. Replace the remaining ImGui-only fallback seams in hosted preview and panel canvas hosting with native host implementations so ImGui can become compatibility-only instead of the default shell path. 5. Continue phased validation, commit, push, and plan refresh after each stable batch. diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 3c928997..b71b0eaa 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -53,7 +53,9 @@ set(NEW_EDITOR_SOURCES src/XCUIBackend/ImGuiHostCompositor.cpp src/XCUIBackend/XCUIEditorFontSetup.cpp src/XCUIBackend/XCUIAssetDocumentSource.cpp + src/XCUIBackend/XCUIEditorCommandRouter.cpp src/XCUIBackend/XCUIInputBridge.cpp + src/XCUIBackend/XCUIShellChromeState.cpp src/XCUIBackend/XCUIRHICommandCompiler.cpp src/XCUIBackend/XCUIRHIRenderBackend.cpp src/XCUIBackend/XCUIStandaloneTextAtlasProvider.cpp diff --git a/new_editor/src/Application.cpp b/new_editor/src/Application.cpp index b212ad25..08cdb8fc 100644 --- a/new_editor/src/Application.cpp +++ b/new_editor/src/Application.cpp @@ -1,4 +1,5 @@ #include "Application.h" +#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h" #include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" #include "XCUIBackend/ImGuiWindowUICompositor.h" @@ -41,12 +42,12 @@ const char* GetHostedPreviewPathLabel(bool nativeRequested, bool nativePresenter return "native queued offscreen surface"; } if (nativeRequested) { - return "native requested, legacy presenter bound"; + return "native requested, hosted presenter bound"; } if (nativePresenterBound) { - return "legacy requested, native presenter still bound"; + return "hosted presenter requested, native presenter still bound"; } - return "legacy imgui transition"; + return "hosted presenter"; } const char* GetHostedPreviewStateLabel( @@ -90,70 +91,20 @@ Application::CreateHostedPreviewPresenter(bool nativePreview) { return ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); } -Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) { - const std::size_t index = GetShellPanelIndex(panelId); - if (index >= m_shellPanels.size()) { - return nullptr; - } - - return &m_shellPanels[index]; -} - const Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const { - const std::size_t index = GetShellPanelIndex(panelId); - if (index >= m_shellPanels.size()) { - return nullptr; - } - - return &m_shellPanels[index]; + return m_shellChromeState.TryGetPanelState(panelId); } bool Application::IsShellViewToggleEnabled(ShellViewToggleId toggleId) const { - switch (toggleId) { - case ShellViewToggleId::ImGuiDemoWindow: - return m_shellViewToggles.imguiDemoWindowVisible; - case ShellViewToggleId::NativeBackdrop: - return m_shellViewToggles.nativeBackdropVisible; - case ShellViewToggleId::PulseAccent: - return m_shellViewToggles.pulseAccentEnabled; - case ShellViewToggleId::NativeXCUIOverlay: - return m_shellViewToggles.nativeXCUIOverlayVisible; - case ShellViewToggleId::HostedPreviewHud: - return m_shellViewToggles.hostedPreviewHudVisible; - case ShellViewToggleId::Count: - default: - return false; - } + return m_shellChromeState.GetViewToggle(toggleId); } void Application::SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled) { - switch (toggleId) { - case ShellViewToggleId::ImGuiDemoWindow: - m_shellViewToggles.imguiDemoWindowVisible = enabled; - return; - case ShellViewToggleId::NativeBackdrop: - m_shellViewToggles.nativeBackdropVisible = enabled; - return; - case ShellViewToggleId::PulseAccent: - m_shellViewToggles.pulseAccentEnabled = enabled; - return; - case ShellViewToggleId::NativeXCUIOverlay: - m_shellViewToggles.nativeXCUIOverlayVisible = enabled; - return; - case ShellViewToggleId::HostedPreviewHud: - m_shellViewToggles.hostedPreviewHudVisible = enabled; - return; - case ShellViewToggleId::Count: - default: - return; - } + m_shellChromeState.SetViewToggle(toggleId, enabled); } bool Application::IsNativeHostedPreviewEnabled(ShellPanelId panelId) const { - const ShellPanelChromeState* panelState = TryGetShellPanelState(panelId); - return panelState != nullptr && - panelState->hostedPreviewEnabled && - panelState->previewMode == ShellHostedPreviewMode::NativeOffscreen; + return m_shellChromeState.IsNativeHostedPreviewActive(panelId); } void Application::ConfigureHostedPreviewPresenters() { @@ -178,25 +129,19 @@ void Application::ConfigureShellCommandRouter() { ShellCommandBindings bindings = {}; bindings.getXCUIDemoPanelVisible = [this]() { - const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo); - return panelState != nullptr && panelState->visible; + return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo); }; bindings.setXCUIDemoPanelVisible = [this](bool visible) { - if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo)) { - panelState->visible = visible; - } + m_shellChromeState.SetPanelVisible(ShellPanelId::XCUIDemo, visible); if (m_demoPanel != nullptr) { m_demoPanel->SetVisible(visible); } }; bindings.getXCUILayoutLabPanelVisible = [this]() { - const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab); - return panelState != nullptr && panelState->visible; + return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab); }; bindings.setXCUILayoutLabPanelVisible = [this](bool visible) { - if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab)) { - panelState->visible = visible; - } + m_shellChromeState.SetPanelVisible(ShellPanelId::XCUILayoutLab, visible); if (m_layoutLabPanel != nullptr) { m_layoutLabPanel->SetVisible(visible); } @@ -235,23 +180,17 @@ void Application::ConfigureShellCommandRouter() { return IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo); }; bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) { - if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo)) { - panelState->previewMode = - enabled - ? ShellHostedPreviewMode::NativeOffscreen - : ShellHostedPreviewMode::LegacyImGui; - } + m_shellChromeState.SetHostedPreviewMode( + ShellPanelId::XCUIDemo, + enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter); }; bindings.getNativeLayoutLabPreviewEnabled = [this]() { return IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab); }; bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) { - if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab)) { - panelState->previewMode = - enabled - ? ShellHostedPreviewMode::NativeOffscreen - : ShellHostedPreviewMode::LegacyImGui; - } + m_shellChromeState.SetHostedPreviewMode( + ShellPanelId::XCUILayoutLab, + enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter); }; bindings.onHostedPreviewModeChanged = [this]() { ConfigureHostedPreviewPresenters(); }; @@ -356,10 +295,12 @@ int Application::Run(HINSTANCE instance, int nCmdShow) { InitializeWindowCompositor(); m_demoPanel = std::make_unique( &m_xcuiInputSource, - CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo))); + CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)), + ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost()); m_layoutLabPanel = std::make_unique( &m_xcuiInputSource, - CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab))); + CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)), + ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost()); ConfigureHostedPreviewPresenters(); m_shellInputBridge.Reset(); ConfigureShellCommandRouter(); @@ -531,13 +472,12 @@ void Application::DestroyHostedPreviewSurfaces() { } void Application::SyncShellChromePanelStateFromPanels() { - if (ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo)) { - demoState->visible = m_demoPanel != nullptr && m_demoPanel->IsVisible(); - } - - if (ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab)) { - layoutLabState->visible = m_layoutLabPanel != nullptr && m_layoutLabPanel->IsVisible(); - } + m_shellChromeState.SetPanelVisible( + ShellPanelId::XCUIDemo, + m_demoPanel != nullptr && m_demoPanel->IsVisible()); + m_shellChromeState.SetPanelVisible( + ShellPanelId::XCUILayoutLab, + m_layoutLabPanel != nullptr && m_layoutLabPanel->IsVisible()); } void Application::SyncHostedPreviewSurfaces() { @@ -864,14 +804,14 @@ void Application::RenderShellChrome() { "XCUI Demo preview: %s", IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo) ? "native offscreen preview surface" - : "ImGui hosted preview"); + : "hosted presenter"); } if (m_layoutLabPanel != nullptr) { ImGui::TextDisabled( "Layout Lab preview: %s", IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab) ? "native offscreen preview surface" - : "ImGui hosted preview"); + : "hosted presenter"); } ImGui::EndMenuBar(); } @@ -1102,8 +1042,10 @@ void Application::Frame() { if (m_layoutLabPanel) { m_layoutLabPanel->RenderIfVisible(); } - if (m_shellViewToggles.imguiDemoWindowVisible) { - ImGui::ShowDemoWindow(&m_shellViewToggles.imguiDemoWindowVisible); + bool showImGuiDemoWindow = IsShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow); + if (showImGuiDemoWindow) { + ImGui::ShowDemoWindow(&showImGuiDemoWindow); + SetShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow, showImGuiDemoWindow); } SyncShellChromePanelStateFromPanels(); diff --git a/new_editor/src/Application.h b/new_editor/src/Application.h index 5bf1613d..9dd130b9 100644 --- a/new_editor/src/Application.h +++ b/new_editor/src/Application.h @@ -16,7 +16,6 @@ #include "XCUIBackend/XCUIShellChromeState.h" #include "XCUIBackend/XCUIStandaloneTextAtlasProvider.h" -#include #include #include #include @@ -31,6 +30,7 @@ namespace NewEditor { class Application { public: + using ShellChromeState = ::XCEngine::Editor::XCUIBackend::XCUIShellChromeState; using ShellPanelId = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelId; using ShellViewToggleId = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId; using ShellHostedPreviewMode = ::XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode; @@ -248,35 +248,6 @@ public: int Run(HINSTANCE instance, int nCmdShow); private: - using ShellPanelStateArray = std::array(ShellPanelId::Count)>; - - static constexpr std::size_t GetShellPanelIndex(ShellPanelId panelId) { - return static_cast(panelId); - } - - static ShellPanelStateArray CreateDefaultShellPanelStates() { - ShellPanelStateArray panels = {}; - panels[GetShellPanelIndex(ShellPanelId::XCUIDemo)] = { - ShellPanelId::XCUIDemo, - "XCUI Demo", - "XCUI Demo", - "new_editor.panels.xcui_demo", - true, - true, - ShellHostedPreviewMode::NativeOffscreen - }; - panels[GetShellPanelIndex(ShellPanelId::XCUILayoutLab)] = { - ShellPanelId::XCUILayoutLab, - "XCUI Layout Lab", - "XCUI Layout Lab", - "new_editor.panels.xcui_layout_lab", - true, - true, - ShellHostedPreviewMode::LegacyImGui - }; - return panels; - } - struct HostedPreviewPanelDiagnostics { std::string debugName = {}; std::string debugSource = {}; @@ -334,7 +305,6 @@ private: std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> CreateHostedPreviewPresenter( bool nativePreview); void ConfigureHostedPreviewPresenters(); - ShellPanelChromeState* TryGetShellPanelState(ShellPanelId panelId); const ShellPanelChromeState* TryGetShellPanelState(ShellPanelId panelId) const; bool IsShellViewToggleEnabled(ShellViewToggleId toggleId) const; void SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled); @@ -379,8 +349,7 @@ private: ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry m_hostedPreviewSurfaceRegistry; ::XCEngine::Editor::XCUIBackend::XCUIStandaloneTextAtlasProvider m_hostedPreviewTextAtlasProvider; ::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend m_hostedPreviewRenderBackend; - ShellViewToggleState m_shellViewToggles = {}; - ShellPanelStateArray m_shellPanels = CreateDefaultShellPanelStates(); + ShellChromeState m_shellChromeState = {}; std::vector m_hostedPreviewSurfaces = {}; ::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime; MainWindowNativeBackdropRenderer m_nativeBackdropRenderer; diff --git a/new_editor/src/XCUIBackend/ImGuiXCUIInputSource.h b/new_editor/src/XCUIBackend/ImGuiXCUIInputSource.h new file mode 100644 index 00000000..47c0efbb --- /dev/null +++ b/new_editor/src/XCUIBackend/ImGuiXCUIInputSource.h @@ -0,0 +1,56 @@ +#pragma once + +#include "XCUIBackend/ImGuiXCUIInputAdapter.h" + +namespace XCEngine { +namespace Editor { +namespace XCUIBackend { + +class ImGuiXCUIInputSnapshotSource final : public IXCUIInputSnapshotSource { +public: + explicit ImGuiXCUIInputSnapshotSource(const ImGuiIO* io = nullptr) + : m_io(io) { + } + + void SetIO(const ImGuiIO* io) { + m_io = io; + } + + const ImGuiIO* GetIO() const { + return ResolveIO(); + } + + XCUIInputBridgeFrameSnapshot CaptureSnapshot( + const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions()) const override { + const ImGuiIO* io = ResolveIO(); + return io != nullptr + ? ImGuiXCUIInputAdapter::CaptureSnapshot(*io, options) + : XCUIInputBridgeFrameSnapshot(); + } + + const ::XCEngine::UI::UIPoint& GetPointerPosition() const override { + m_cachedPointerPosition = {}; + const ImGuiIO* io = ResolveIO(); + if (io != nullptr) { + m_cachedPointerPosition = ::XCEngine::UI::UIPoint(io->MousePos.x, io->MousePos.y); + } + + return m_cachedPointerPosition; + } + +private: + const ImGuiIO* ResolveIO() const { + if (m_io != nullptr) { + return m_io; + } + + return ImGui::GetCurrentContext() != nullptr ? &ImGui::GetIO() : nullptr; + } + + const ImGuiIO* m_io = nullptr; + mutable ::XCEngine::UI::UIPoint m_cachedPointerPosition = {}; +}; + +} // namespace XCUIBackend +} // namespace Editor +} // namespace XCEngine diff --git a/new_editor/src/XCUIBackend/NullXCUIPanelCanvasHost.h b/new_editor/src/XCUIBackend/NullXCUIPanelCanvasHost.h index a9697b1b..49fdc000 100644 --- a/new_editor/src/XCUIBackend/NullXCUIPanelCanvasHost.h +++ b/new_editor/src/XCUIBackend/NullXCUIPanelCanvasHost.h @@ -23,8 +23,8 @@ public: } XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override { - (void)request; - return {}; + m_canvasSession = BuildPassiveXCUIPanelCanvasSession(request); + return m_canvasSession; } void DrawFilledRect( @@ -59,7 +59,11 @@ public: } void EndCanvas() override { + m_canvasSession = {}; } + +private: + XCUIPanelCanvasSession m_canvasSession = {}; }; inline std::unique_ptr CreateNullXCUIPanelCanvasHost() { diff --git a/new_editor/src/XCUIBackend/XCUIHostedPreviewPresenter.h b/new_editor/src/XCUIBackend/XCUIHostedPreviewPresenter.h index 79708d2c..94927868 100644 --- a/new_editor/src/XCUIBackend/XCUIHostedPreviewPresenter.h +++ b/new_editor/src/XCUIBackend/XCUIHostedPreviewPresenter.h @@ -260,6 +260,27 @@ public: } }; +class NullXCUIHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter { +public: + bool Present(const XCUIHostedPreviewFrame& frame) override { + m_lastStats = {}; + if (frame.drawData == nullptr) { + return false; + } + + m_lastStats.submittedDrawListCount = frame.drawData->GetDrawListCount(); + m_lastStats.submittedCommandCount = frame.drawData->GetTotalCommandCount(); + return false; + } + + const XCUIHostedPreviewStats& GetLastStats() const override { + return m_lastStats; + } + +private: + XCUIHostedPreviewStats m_lastStats = {}; +}; + class QueuedNativeXCUIHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter { public: QueuedNativeXCUIHostedPreviewPresenter( @@ -306,6 +327,10 @@ inline std::unique_ptr CreateQueuedNativeXCUIHosted return std::make_unique(queue, surfaceRegistry); } +inline std::unique_ptr CreateNullXCUIHostedPreviewPresenter() { + return std::make_unique(); +} + } // namespace XCUIBackend } // namespace Editor } // namespace XCEngine diff --git a/new_editor/src/XCUIBackend/XCUIInputBridge.h b/new_editor/src/XCUIBackend/XCUIInputBridge.h index 21fb300c..6ad237b0 100644 --- a/new_editor/src/XCUIBackend/XCUIInputBridge.h +++ b/new_editor/src/XCUIBackend/XCUIInputBridge.h @@ -85,6 +85,15 @@ struct XCUIInputBridgeFrameDelta { bool HasEventType(UI::UIInputEventType type) const; }; +class IXCUIInputSnapshotSource { +public: + virtual ~IXCUIInputSnapshotSource() = default; + + virtual XCUIInputBridgeFrameSnapshot CaptureSnapshot( + const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions()) const = 0; + virtual const UI::UIPoint& GetPointerPosition() const = 0; +}; + class XCUIInputBridge { public: void Reset(); @@ -109,16 +118,16 @@ private: XCUIInputBridgeFrameSnapshot m_baseline = {}; }; -class XCUIWin32InputSource { +class XCUIWin32InputSource : public IXCUIInputSnapshotSource { public: void Reset(); void HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); void ClearFrameTransients(); XCUIInputBridgeFrameSnapshot CaptureSnapshot( - const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions()) const; + const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions()) const override; - const UI::UIPoint& GetPointerPosition() const { + const UI::UIPoint& GetPointerPosition() const override { return m_pointerPosition; } diff --git a/new_editor/src/XCUIBackend/XCUIPanelCanvasHost.h b/new_editor/src/XCUIBackend/XCUIPanelCanvasHost.h index 933b161c..1e1426b2 100644 --- a/new_editor/src/XCUIBackend/XCUIPanelCanvasHost.h +++ b/new_editor/src/XCUIBackend/XCUIPanelCanvasHost.h @@ -46,6 +46,36 @@ struct XCUIPanelCanvasSession { bool windowFocused = false; }; +inline const char* ResolveXCUIPanelCanvasChildId( + const XCUIPanelCanvasRequest& request, + const char* fallback = "XCUIPanelCanvasHost") { + if (request.childId != nullptr && request.childId[0] != '\0') { + return request.childId; + } + + return fallback != nullptr ? fallback : "XCUIPanelCanvasHost"; +} + +inline XCUIPanelCanvasSession BuildPassiveXCUIPanelCanvasSession( + const XCUIPanelCanvasRequest& request) { + const float hostHeight = request.height > 0.0f ? request.height : 0.0f; + const float topInset = request.topInset > 0.0f ? request.topInset : 0.0f; + const float clampedTopInset = topInset < hostHeight ? topInset : hostHeight; + + XCUIPanelCanvasSession session = {}; + session.hostRect = ::XCEngine::UI::UIRect(0.0f, 0.0f, 0.0f, hostHeight); + session.canvasRect = ::XCEngine::UI::UIRect( + 0.0f, + clampedTopInset, + 0.0f, + hostHeight - clampedTopInset); + session.pointerPosition = {}; + session.validCanvas = false; + session.hovered = false; + session.windowFocused = false; + return session; +} + class IXCUIPanelCanvasHost { public: virtual ~IXCUIPanelCanvasHost() = default; diff --git a/new_editor/src/XCUIBackend/XCUIShellChromeState.cpp b/new_editor/src/XCUIBackend/XCUIShellChromeState.cpp index 8eac9848..4c66683b 100644 --- a/new_editor/src/XCUIBackend/XCUIShellChromeState.cpp +++ b/new_editor/src/XCUIBackend/XCUIShellChromeState.cpp @@ -10,6 +10,17 @@ constexpr std::size_t ToIndex(XCUIShellPanelId panelId) { return static_cast(panelId); } +constexpr std::string_view kViewMenuLabel = "View"; +constexpr std::string_view kXCUIDemoShortcut = "Ctrl+1"; +constexpr std::string_view kXCUILayoutLabShortcut = "Ctrl+2"; +constexpr std::string_view kImGuiDemoShortcut = "Ctrl+3"; +constexpr std::string_view kNativeBackdropShortcut = "Ctrl+Shift+B"; +constexpr std::string_view kPulseAccentShortcut = "Ctrl+Shift+P"; +constexpr std::string_view kNativeXCUIOverlayShortcut = "Ctrl+Shift+O"; +constexpr std::string_view kHostedPreviewHudShortcut = "Ctrl+Shift+H"; +constexpr std::string_view kNativeDemoPanelPreviewShortcut = "Ctrl+Alt+1"; +constexpr std::string_view kNativeLayoutLabPreviewShortcut = "Ctrl+Alt+2"; + } // namespace XCUIShellChromeState::XCUIShellChromeState() { @@ -29,7 +40,7 @@ XCUIShellChromeState::XCUIShellChromeState() { "new_editor.panels.xcui_layout_lab", true, true, - XCUIShellHostedPreviewMode::LegacyImGui + XCUIShellHostedPreviewMode::HostedPresenter }; } @@ -104,7 +115,7 @@ XCUIShellHostedPreviewMode XCUIShellChromeState::GetHostedPreviewMode(XCUIShellP const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId); return panelState != nullptr ? panelState->previewMode - : XCUIShellHostedPreviewMode::LegacyImGui; + : XCUIShellHostedPreviewMode::HostedPresenter; } XCUIShellHostedPreviewState XCUIShellChromeState::GetHostedPreviewState(XCUIShellPanelId panelId) const { @@ -115,15 +126,15 @@ XCUIShellHostedPreviewState XCUIShellChromeState::GetHostedPreviewState(XCUIShel return panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen ? XCUIShellHostedPreviewState::NativeOffscreen - : XCUIShellHostedPreviewState::LegacyImGui; + : XCUIShellHostedPreviewState::HostedPresenter; } bool XCUIShellChromeState::IsNativeHostedPreviewActive(XCUIShellPanelId panelId) const { return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::NativeOffscreen; } -bool XCUIShellChromeState::IsLegacyHostedPreviewActive(XCUIShellPanelId panelId) const { - return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::LegacyImGui; +bool XCUIShellChromeState::IsHostedPresenterPreviewActive(XCUIShellPanelId panelId) const { + return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::HostedPresenter; } bool XCUIShellChromeState::SetHostedPreviewMode( @@ -146,7 +157,7 @@ bool XCUIShellChromeState::ToggleHostedPreviewMode(XCUIShellPanelId panelId) { panelState->previewMode = panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen - ? XCUIShellHostedPreviewMode::LegacyImGui + ? XCUIShellHostedPreviewMode::HostedPresenter : XCUIShellHostedPreviewMode::NativeOffscreen; return true; } @@ -205,15 +216,8 @@ bool XCUIShellChromeState::ToggleViewToggle(XCUIShellViewToggleId toggleId) { } bool XCUIShellChromeState::HasCommand(std::string_view commandId) const { - return commandId == GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo) || - commandId == GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab) || - commandId == GetViewToggleCommandId(XCUIShellViewToggleId::ImGuiDemoWindow) || - commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop) || - commandId == GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent) || - commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay) || - commandId == GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud) || - commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo) || - commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab); + XCUIShellCommandDescriptor descriptor = {}; + return TryGetCommandDescriptor(commandId, descriptor); } bool XCUIShellChromeState::InvokeCommand(std::string_view commandId) { @@ -248,6 +252,142 @@ bool XCUIShellChromeState::InvokeCommand(std::string_view commandId) { return false; } +bool XCUIShellChromeState::TryGetCommandDescriptor( + std::string_view commandId, + XCUIShellCommandDescriptor& outDescriptor) const { + outDescriptor = {}; + outDescriptor.checkable = false; + outDescriptor.enabled = false; + + const auto tryBuildPanelVisibilityDescriptor = + [this, commandId, &outDescriptor]( + XCUIShellPanelId panelId, + std::string_view shortcut) { + if (commandId != GetPanelVisibilityCommandId(panelId)) { + return false; + } + + const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId); + if (panelState == nullptr) { + return false; + } + + outDescriptor.label = panelState->panelTitle; + outDescriptor.shortcut = shortcut; + outDescriptor.commandId = commandId; + outDescriptor.checkable = true; + outDescriptor.checked = panelState->visible; + outDescriptor.enabled = true; + return true; + }; + + if (tryBuildPanelVisibilityDescriptor(XCUIShellPanelId::XCUIDemo, kXCUIDemoShortcut)) { + return true; + } + if (tryBuildPanelVisibilityDescriptor(XCUIShellPanelId::XCUILayoutLab, kXCUILayoutLabShortcut)) { + return true; + } + + if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::ImGuiDemoWindow)) { + outDescriptor.label = "ImGui Demo"; + outDescriptor.shortcut = kImGuiDemoShortcut; + outDescriptor.commandId = commandId; + outDescriptor.checkable = true; + outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::ImGuiDemoWindow); + outDescriptor.enabled = true; + return true; + } + if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop)) { + outDescriptor.label = "Native Backdrop"; + outDescriptor.shortcut = kNativeBackdropShortcut; + outDescriptor.commandId = commandId; + outDescriptor.checkable = true; + outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::NativeBackdrop); + outDescriptor.enabled = true; + return true; + } + if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent)) { + outDescriptor.label = "Pulse Accent"; + outDescriptor.shortcut = kPulseAccentShortcut; + outDescriptor.commandId = commandId; + outDescriptor.checkable = true; + outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::PulseAccent); + outDescriptor.enabled = true; + return true; + } + if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay)) { + outDescriptor.label = "Native XCUI Overlay"; + outDescriptor.shortcut = kNativeXCUIOverlayShortcut; + outDescriptor.commandId = commandId; + outDescriptor.checkable = true; + outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::NativeXCUIOverlay); + outDescriptor.enabled = true; + return true; + } + if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud)) { + outDescriptor.label = "Hosted Preview HUD"; + outDescriptor.shortcut = kHostedPreviewHudShortcut; + outDescriptor.commandId = commandId; + outDescriptor.checkable = true; + outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::HostedPreviewHud); + outDescriptor.enabled = true; + return true; + } + if (commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo)) { + outDescriptor.label = "Native Demo Panel Preview"; + outDescriptor.shortcut = kNativeDemoPanelPreviewShortcut; + outDescriptor.commandId = commandId; + outDescriptor.checkable = true; + outDescriptor.checked = IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo); + outDescriptor.enabled = true; + return true; + } + if (commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab)) { + outDescriptor.label = "Native Layout Lab Preview"; + outDescriptor.shortcut = kNativeLayoutLabPreviewShortcut; + outDescriptor.commandId = commandId; + outDescriptor.checkable = true; + outDescriptor.checked = IsNativeHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab); + outDescriptor.enabled = true; + return true; + } + + return false; +} + +XCUIShellMenuDescriptor XCUIShellChromeState::BuildViewMenuDescriptor() const { + XCUIShellMenuDescriptor descriptor = {}; + descriptor.label = kViewMenuLabel; + descriptor.items.reserve(10u); + + const auto appendCommandItem = [this, &descriptor](std::string_view commandId) { + XCUIShellCommandDescriptor commandDescriptor = {}; + if (!TryGetCommandDescriptor(commandId, commandDescriptor)) { + return; + } + + XCUIShellMenuItemDescriptor item = {}; + item.kind = XCUIShellMenuItemKind::Command; + item.command = commandDescriptor; + descriptor.items.push_back(item); + }; + + appendCommandItem(GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo)); + appendCommandItem(GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab)); + appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::ImGuiDemoWindow)); + + descriptor.items.push_back({ XCUIShellMenuItemKind::Separator, {} }); + + appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop)); + appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent)); + appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay)); + appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud)); + appendCommandItem(GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo)); + appendCommandItem(GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab)); + + return descriptor; +} + std::string_view XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId panelId) { switch (panelId) { case XCUIShellPanelId::XCUIDemo: diff --git a/new_editor/src/XCUIBackend/XCUIShellChromeState.h b/new_editor/src/XCUIBackend/XCUIShellChromeState.h index a369f7dc..9ad416f1 100644 --- a/new_editor/src/XCUIBackend/XCUIShellChromeState.h +++ b/new_editor/src/XCUIBackend/XCUIShellChromeState.h @@ -3,6 +3,7 @@ #include #include #include +#include namespace XCEngine { namespace Editor { @@ -24,13 +25,13 @@ enum class XCUIShellViewToggleId : std::uint8_t { }; enum class XCUIShellHostedPreviewMode : std::uint8_t { - LegacyImGui = 0, + HostedPresenter = 0, NativeOffscreen }; enum class XCUIShellHostedPreviewState : std::uint8_t { Disabled = 0, - LegacyImGui, + HostedPresenter, NativeOffscreen }; @@ -41,7 +42,7 @@ struct XCUIShellPanelChromeState { std::string_view previewDebugSource = {}; bool visible = true; bool hostedPreviewEnabled = true; - XCUIShellHostedPreviewMode previewMode = XCUIShellHostedPreviewMode::LegacyImGui; + XCUIShellHostedPreviewMode previewMode = XCUIShellHostedPreviewMode::HostedPresenter; }; struct XCUIShellViewToggleState { @@ -64,6 +65,30 @@ struct XCUIShellChromeCommandIds { static constexpr const char* ToggleNativeLayoutLabPreview = "new_editor.view.native_layout_lab_preview"; }; +enum class XCUIShellMenuItemKind : std::uint8_t { + Command = 0, + Separator +}; + +struct XCUIShellCommandDescriptor { + std::string_view label = {}; + std::string_view shortcut = {}; + std::string_view commandId = {}; + bool checkable = true; + bool checked = false; + bool enabled = true; +}; + +struct XCUIShellMenuItemDescriptor { + XCUIShellMenuItemKind kind = XCUIShellMenuItemKind::Command; + XCUIShellCommandDescriptor command = {}; +}; + +struct XCUIShellMenuDescriptor { + std::string_view label = {}; + std::vector items = {}; +}; + class XCUIShellChromeState { public: XCUIShellChromeState(); @@ -82,7 +107,7 @@ public: XCUIShellHostedPreviewMode GetHostedPreviewMode(XCUIShellPanelId panelId) const; XCUIShellHostedPreviewState GetHostedPreviewState(XCUIShellPanelId panelId) const; bool IsNativeHostedPreviewActive(XCUIShellPanelId panelId) const; - bool IsLegacyHostedPreviewActive(XCUIShellPanelId panelId) const; + bool IsHostedPresenterPreviewActive(XCUIShellPanelId panelId) const; bool SetHostedPreviewMode(XCUIShellPanelId panelId, XCUIShellHostedPreviewMode mode); bool ToggleHostedPreviewMode(XCUIShellPanelId panelId); @@ -92,6 +117,10 @@ public: bool HasCommand(std::string_view commandId) const; bool InvokeCommand(std::string_view commandId); + bool TryGetCommandDescriptor( + std::string_view commandId, + XCUIShellCommandDescriptor& outDescriptor) const; + XCUIShellMenuDescriptor BuildViewMenuDescriptor() const; static std::string_view GetPanelVisibilityCommandId(XCUIShellPanelId panelId); static std::string_view GetPanelPreviewModeCommandId(XCUIShellPanelId panelId); diff --git a/new_editor/src/panels/XCUIDemoPanel.cpp b/new_editor/src/panels/XCUIDemoPanel.cpp index 71b4ae06..f8772fb7 100644 --- a/new_editor/src/panels/XCUIDemoPanel.cpp +++ b/new_editor/src/panels/XCUIDemoPanel.cpp @@ -1,8 +1,6 @@ #include "XCUIDemoPanel.h" -#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" -#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h" -#include "XCUIBackend/ImGuiXCUIInputAdapter.h" +#include "XCUIBackend/NullXCUIPanelCanvasHost.h" #include @@ -55,15 +53,27 @@ void DrawRectOverlay( } } -const char* GetPreviewPathLabel(bool nativeHostedPreview) { - return nativeHostedPreview ? "native queued offscreen surface" : "legacy imgui transition"; +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( - bool nativeHostedPreview, + 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"; @@ -77,20 +87,37 @@ const char* GetPreviewStateLabel( 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::XCUIWin32InputSource* inputSource) - : XCUIDemoPanel(inputSource, ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter()) {} +XCUIDemoPanel::XCUIDemoPanel(::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource) + : XCUIDemoPanel( + inputSource, + nullptr, + ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost()) {} XCUIDemoPanel::XCUIDemoPanel( - ::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource, - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) + ::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(::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost()) { - if (m_previewPresenter == nullptr) { - m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); + , m_canvasHost(std::move(canvasHost)) { + if (m_canvasHost == nullptr) { + m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); } m_lastReloadSucceeded = m_runtime.ReloadDocuments(); } @@ -113,9 +140,6 @@ const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& XCUIDemoPanel::Ge void XCUIDemoPanel::SetHostedPreviewPresenter( std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) { m_previewPresenter = std::move(previewPresenter); - if (m_previewPresenter == nullptr) { - m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); - } m_lastPreviewStats = {}; } @@ -123,7 +147,7 @@ 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::CreateImGuiXCUIPanelCanvasHost(); + m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); } } @@ -160,7 +184,7 @@ void XCUIDemoPanel::Render() { const float canvasHeight = (std::max)(140.0f, hostRegion.y - diagnosticsHeight); if (m_canvasHost == nullptr) { - m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost(); + m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); } const bool nativeHostedPreview = IsUsingNativeHostedPreview(); @@ -174,7 +198,7 @@ void XCUIDemoPanel::Render() { nativeHostedPreview && m_previewPresenter != nullptr && m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage); - const char* const previewPathLabel = GetPreviewPathLabel(nativeHostedPreview); + 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"; @@ -183,11 +207,11 @@ void XCUIDemoPanel::Render() { canvasRequest.showSurfaceImage = showHostedSurfaceImage; canvasRequest.surfaceImage = hostedSurfaceImage; canvasRequest.placeholderTitle = - nativeHostedPreview ? "Native XCUI preview pending" : "Legacy XCUI canvas host"; + 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." - : "Legacy ImGui transition path stays active until native offscreen preview is enabled."; + : "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; @@ -211,10 +235,7 @@ void XCUIDemoPanel::Render() { bridgeOptions.hasPointerInsideOverride = true; bridgeOptions.pointerInsideOverride = validCanvas && canvasSession.hovered; bridgeOptions.windowFocused = canvasSession.windowFocused; - snapshot = ::XCEngine::Editor::XCUIBackend::ImGuiXCUIInputAdapter::CaptureSnapshot( - ImGui::GetIO(), - bridgeOptions); - snapshot.pointerPosition = canvasSession.pointerPosition; + snapshot = BuildPassiveSnapshot(canvasSession, bridgeOptions); } if (!m_inputBridge.HasBaseline()) { @@ -253,7 +274,7 @@ void XCUIDemoPanel::Render() { const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameStats& stats = frame.stats; const char* const previewStateLabel = GetPreviewStateLabel( - nativeHostedPreview, + m_previewPresenter.get(), m_lastPreviewStats, hasHostedSurfaceDescriptor, showHostedSurfaceImage); diff --git a/new_editor/src/panels/XCUIDemoPanel.h b/new_editor/src/panels/XCUIDemoPanel.h index 43c91e8c..86ce4a03 100644 --- a/new_editor/src/panels/XCUIDemoPanel.h +++ b/new_editor/src/panels/XCUIDemoPanel.h @@ -15,10 +15,11 @@ namespace NewEditor { class XCUIDemoPanel : public Panel { public: explicit XCUIDemoPanel( - ::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource = nullptr); + ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource = nullptr); XCUIDemoPanel( - ::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource, - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter); + ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource, + std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter, + std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost = nullptr); ~XCUIDemoPanel() override = default; void Render() override; @@ -37,7 +38,7 @@ private: bool m_hostedPreviewEnabled = true; bool m_showCanvasHud = true; bool m_showDebugRects = true; - ::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* m_inputSource = nullptr; + ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* m_inputSource = nullptr; ::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_inputBridge; ::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime m_runtime; std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter; diff --git a/new_editor/src/panels/XCUILayoutLabPanel.cpp b/new_editor/src/panels/XCUILayoutLabPanel.cpp index 849bf7a3..f2426350 100644 --- a/new_editor/src/panels/XCUILayoutLabPanel.cpp +++ b/new_editor/src/panels/XCUILayoutLabPanel.cpp @@ -1,12 +1,13 @@ #include "XCUILayoutLabPanel.h" -#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" -#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h" +#include "XCUIBackend/NullXCUIPanelCanvasHost.h" #include #include #include +#include +#include #include namespace XCEngine { @@ -24,15 +25,27 @@ bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) { point.y <= rect.y + rect.height; } -const char* GetPreviewPathLabel(bool nativeHostedPreview) { - return nativeHostedPreview ? "native queued offscreen surface" : "legacy imgui transition"; +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( - bool nativeHostedPreview, + 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"; @@ -46,6 +59,12 @@ const char* GetPreviewStateLabel( 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) { @@ -60,33 +79,56 @@ bool ShouldCaptureKeyboardNavigation( void PopulateKeyboardNavigationInput( ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState& input, + const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta, bool captureKeyboardNavigation) { if (!captureKeyboardNavigation) { return; } - input.navigatePrevious = ImGui::IsKeyPressed(ImGuiKey_UpArrow); - input.navigateNext = ImGui::IsKeyPressed(ImGuiKey_DownArrow); - input.navigateHome = ImGui::IsKeyPressed(ImGuiKey_Home); - input.navigateEnd = ImGui::IsKeyPressed(ImGuiKey_End); - input.navigateCollapse = ImGui::IsKeyPressed(ImGuiKey_LeftArrow); - input.navigateExpand = ImGui::IsKeyPressed(ImGuiKey_RightArrow); + 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::XCUIWin32InputSource* inputSource) - : XCUILayoutLabPanel(inputSource, ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter()) {} +XCUILayoutLabPanel::XCUILayoutLabPanel(::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource) + : XCUILayoutLabPanel(inputSource, nullptr, ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost()) {} XCUILayoutLabPanel::XCUILayoutLabPanel( - ::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource, - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) + ::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(::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost()) { - if (m_previewPresenter == nullptr) { - m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); + , m_canvasHost(std::move(canvasHost)) { + if (m_canvasHost == nullptr) { + m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); } m_lastReloadSucceeded = m_runtime.ReloadDocuments(); } @@ -113,9 +155,6 @@ bool XCUILayoutLabPanel::TryGetElementRect(const std::string& elementId, ::XCEng void XCUILayoutLabPanel::SetHostedPreviewPresenter( std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) { m_previewPresenter = std::move(previewPresenter); - if (m_previewPresenter == nullptr) { - m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); - } m_lastPreviewStats = {}; } @@ -123,7 +162,7 @@ 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::CreateImGuiXCUIPanelCanvasHost(); + m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); } } @@ -156,7 +195,7 @@ void XCUILayoutLabPanel::Render() { const float canvasHeight = (std::max)(140.0f, hostRegion.y - diagnosticsHeight); if (m_canvasHost == nullptr) { - m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost(); + m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost(); } const bool nativeHostedPreview = IsUsingNativeHostedPreview(); @@ -170,18 +209,18 @@ void XCUILayoutLabPanel::Render() { nativeHostedPreview && m_previewPresenter != nullptr && m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage); - const char* const previewPathLabel = GetPreviewPathLabel(nativeHostedPreview); + 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" : "Legacy layout canvas host"; + 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." - : "Legacy ImGui transition path remains active until native offscreen preview is enabled."; + : "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 = @@ -189,18 +228,40 @@ void XCUILayoutLabPanel::Render() { 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; - if (m_inputSource != nullptr) { - input.pointerPosition = m_inputSource->GetPointerPosition(); - input.pointerInside = validCanvas && ContainsPoint(input.canvasRect, input.pointerPosition); - } else { - input.pointerPosition = canvasSession.pointerPosition; - input.pointerInside = validCanvas && canvasSession.hovered; - } - input.pointerPressed = input.pointerInside && ImGui::IsMouseClicked(ImGuiMouseButton_Left); + 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); @@ -220,7 +281,7 @@ void XCUILayoutLabPanel::Render() { const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameStats& stats = frame.stats; const char* const previewStateLabel = GetPreviewStateLabel( - nativeHostedPreview, + m_previewPresenter.get(), m_lastPreviewStats, hasHostedSurfaceDescriptor, showHostedSurfaceImage); @@ -266,7 +327,7 @@ void XCUILayoutLabPanel::Render() { ImGui::TextDisabled("No native surface descriptor has been published back yet."); } } else { - ImGui::TextDisabled("Legacy path renders directly into the panel draw list. No native surface descriptor exists."); + ImGui::TextDisabled("No native surface descriptor is available without a native queued presenter."); } ImGui::SeparatorText("Runtime"); diff --git a/new_editor/src/panels/XCUILayoutLabPanel.h b/new_editor/src/panels/XCUILayoutLabPanel.h index a7cb650b..adf8146b 100644 --- a/new_editor/src/panels/XCUILayoutLabPanel.h +++ b/new_editor/src/panels/XCUILayoutLabPanel.h @@ -16,10 +16,11 @@ namespace NewEditor { class XCUILayoutLabPanel : public Panel { public: explicit XCUILayoutLabPanel( - ::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource = nullptr); + ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource = nullptr); XCUILayoutLabPanel( - ::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource, - std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter); + ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource, + std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter, + std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost = nullptr); ~XCUILayoutLabPanel() override = default; void Render() override; @@ -37,7 +38,8 @@ public: private: bool m_lastReloadSucceeded = false; bool m_hostedPreviewEnabled = true; - ::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* m_inputSource = nullptr; + ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* m_inputSource = nullptr; + ::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_inputBridge; ::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_runtime; std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter; std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost; diff --git a/tests/NewEditor/CMakeLists.txt b/tests/NewEditor/CMakeLists.txt index 9d92b123..ea94bc19 100644 --- a/tests/NewEditor/CMakeLists.txt +++ b/tests/NewEditor/CMakeLists.txt @@ -46,6 +46,12 @@ set(NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER set(NEW_EDITOR_LAYOUT_LAB_PANEL_SOURCE ${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUILayoutLabPanel.cpp ) +set(NEW_EDITOR_DEMO_PANEL_HEADER + ${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUIDemoPanel.h +) +set(NEW_EDITOR_DEMO_PANEL_SOURCE + ${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUIDemoPanel.cpp +) set(NEW_EDITOR_BASE_PANEL_SOURCE ${CMAKE_SOURCE_DIR}/new_editor/src/panels/Panel.cpp ) @@ -237,6 +243,10 @@ if(EXISTS "${NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER}" AND EXISTS "${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE}" AND EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}" AND + EXISTS "${NEW_EDITOR_INPUT_BRIDGE_HEADER}" AND + EXISTS "${NEW_EDITOR_INPUT_BRIDGE_SOURCE}" AND + EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND + EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_layout_lab_panel.cpp" AND EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp") add_executable(new_editor_xcui_layout_lab_panel_tests @@ -245,6 +255,8 @@ if(EXISTS "${NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER}" AND ${NEW_EDITOR_BASE_PANEL_SOURCE} ${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE} ${NEW_EDITOR_ASSET_DOCUMENT_SOURCE} + ${NEW_EDITOR_INPUT_BRIDGE_SOURCE} + ${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE} ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp @@ -274,7 +286,59 @@ if(EXISTS "${NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER}" AND xcengine_discover_new_editor_gtests(new_editor_xcui_layout_lab_panel_tests) else() - message(STATUS "Skipping new_editor_xcui_layout_lab_panel_tests because panel, runtime, test, or ImGui sources are missing.") + message(STATUS "Skipping new_editor_xcui_layout_lab_panel_tests because panel, runtime, test, input bridge, or ImGui sources are missing.") +endif() + +if(EXISTS "${NEW_EDITOR_DEMO_PANEL_HEADER}" AND + EXISTS "${NEW_EDITOR_DEMO_PANEL_SOURCE}" AND + EXISTS "${NEW_EDITOR_RUNTIME_HEADER}" AND + EXISTS "${NEW_EDITOR_RUNTIME_SOURCE}" AND + EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND + EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}" AND + EXISTS "${NEW_EDITOR_INPUT_BRIDGE_HEADER}" AND + EXISTS "${NEW_EDITOR_INPUT_BRIDGE_SOURCE}" AND + EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND + EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}" AND + EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_demo_panel.cpp" AND + EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp") + add_executable(new_editor_xcui_demo_panel_tests + test_xcui_demo_panel.cpp + ${NEW_EDITOR_DEMO_PANEL_SOURCE} + ${NEW_EDITOR_BASE_PANEL_SOURCE} + ${NEW_EDITOR_RUNTIME_SOURCE} + ${NEW_EDITOR_ASSET_DOCUMENT_SOURCE} + ${NEW_EDITOR_INPUT_BRIDGE_SOURCE} + ${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE} + ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp + ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp + ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp + ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_widgets.cpp + ) + + xcengine_configure_new_editor_test_target(new_editor_xcui_demo_panel_tests) + + target_link_libraries(new_editor_xcui_demo_panel_tests + PRIVATE + XCEngine + GTest::gtest + GTest::gtest_main + user32 + comdlg32 + ) + + target_include_directories(new_editor_xcui_demo_panel_tests PRIVATE + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_SOURCE_DIR}/new_editor/src + ${CMAKE_BINARY_DIR}/_deps/imgui-src + ${CMAKE_BINARY_DIR}/_deps/imgui-src/backends + ) + target_compile_definitions(new_editor_xcui_demo_panel_tests PRIVATE + XCENGINE_NEW_EDITOR_REPO_ROOT="${XCENGINE_TEST_REPO_ROOT_CMAKE}" + ) + + xcengine_discover_new_editor_gtests(new_editor_xcui_demo_panel_tests) +else() + message(STATUS "Skipping new_editor_xcui_demo_panel_tests because panel, runtime, test, input bridge, or ImGui sources are missing.") endif() if(EXISTS "${NEW_EDITOR_BACKEND_HEADER}" AND EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp") @@ -511,11 +575,14 @@ endif() if(EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}" AND + EXISTS "${NEW_EDITOR_INPUT_BRIDGE_HEADER}" AND + EXISTS "${NEW_EDITOR_INPUT_BRIDGE_SOURCE}" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_imgui_xcui_input_adapter.cpp" AND EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp") add_executable(new_editor_imgui_xcui_input_adapter_tests test_imgui_xcui_input_adapter.cpp ${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE} + ${NEW_EDITOR_INPUT_BRIDGE_SOURCE} ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp @@ -542,7 +609,7 @@ if(EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND xcengine_discover_new_editor_gtests(new_editor_imgui_xcui_input_adapter_tests) else() - message(STATUS "Skipping new_editor_imgui_xcui_input_adapter_tests because ImGui adapter files, test source, or ImGui sources are missing.") + message(STATUS "Skipping new_editor_imgui_xcui_input_adapter_tests because ImGui adapter files, input bridge files, test source, or ImGui sources are missing.") endif() if(EXISTS "${NEW_EDITOR_RHI_COMMAND_SUPPORT_HEADER}") diff --git a/tests/NewEditor/test_application_shell_command_bindings.cpp b/tests/NewEditor/test_application_shell_command_bindings.cpp index af8a5b2e..284fd706 100644 --- a/tests/NewEditor/test_application_shell_command_bindings.cpp +++ b/tests/NewEditor/test_application_shell_command_bindings.cpp @@ -38,59 +38,77 @@ struct ShellCommandHarness { "new_editor.panels.xcui_layout_lab", true, true, - Application::ShellHostedPreviewMode::LegacyImGui + Application::ShellHostedPreviewMode::HostedPresenter }; } - Application::ShellPanelChromeState& Panel(Application::ShellPanelId panelId) { - return panels[ToPanelIndex(panelId)]; - } - 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.getXCUIDemoPanelVisible = [this]() { + return Panel(Application::ShellPanelId::XCUIDemo).visible; + }; bindings.setXCUIDemoPanelVisible = [this](bool visible) { - Panel(Application::ShellPanelId::XCUIDemo).visible = visible; + panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)].visible = visible; }; bindings.getXCUILayoutLabPanelVisible = [this]() { return Panel(Application::ShellPanelId::XCUILayoutLab).visible; }; bindings.setXCUILayoutLabPanelVisible = [this](bool visible) { - Panel(Application::ShellPanelId::XCUILayoutLab).visible = visible; + panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].visible = visible; + }; + bindings.getImGuiDemoWindowVisible = [this]() { + return viewToggles.imguiDemoWindowVisible; + }; + bindings.setImGuiDemoWindowVisible = [this](bool visible) { + viewToggles.imguiDemoWindowVisible = 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.getImGuiDemoWindowVisible = [this]() { return viewToggles.imguiDemoWindowVisible; }; - bindings.setImGuiDemoWindowVisible = [this](bool visible) { viewToggles.imguiDemoWindowVisible = 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) { - Panel(Application::ShellPanelId::XCUIDemo).previewMode = + panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)].previewMode = enabled ? Application::ShellHostedPreviewMode::NativeOffscreen - : Application::ShellHostedPreviewMode::LegacyImGui; + : Application::ShellHostedPreviewMode::HostedPresenter; }; bindings.getNativeLayoutLabPreviewEnabled = [this]() { return Panel(Application::ShellPanelId::XCUILayoutLab).previewMode == Application::ShellHostedPreviewMode::NativeOffscreen; }; bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) { - Panel(Application::ShellPanelId::XCUILayoutLab).previewMode = + panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].previewMode = enabled ? Application::ShellHostedPreviewMode::NativeOffscreen - : Application::ShellHostedPreviewMode::LegacyImGui; + : Application::ShellHostedPreviewMode::HostedPresenter; }; bindings.onHostedPreviewModeChanged = [this]() { ++hostedPreviewReconfigureCount; }; return bindings; @@ -134,7 +152,7 @@ TEST(ApplicationShellCommandBindingsTest, PreviewModeCommandsTriggerHostedPrevie EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeDemoPanelPreview)); EXPECT_EQ( harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode, - Application::ShellHostedPreviewMode::LegacyImGui); + Application::ShellHostedPreviewMode::HostedPresenter); EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeLayoutLabPreview)); @@ -243,7 +261,7 @@ TEST(ApplicationShellCommandBindingsTest, PreviewShortcutInvokesCommandHandlerAn EXPECT_TRUE(router.InvokeMatchingShortcut({ &previewSnapshot })); EXPECT_EQ( harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode, - Application::ShellHostedPreviewMode::LegacyImGui); + Application::ShellHostedPreviewMode::HostedPresenter); EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1); } diff --git a/tests/NewEditor/test_imgui_xcui_input_adapter.cpp b/tests/NewEditor/test_imgui_xcui_input_adapter.cpp index 9b15634c..1d5a04b0 100644 --- a/tests/NewEditor/test_imgui_xcui_input_adapter.cpp +++ b/tests/NewEditor/test_imgui_xcui_input_adapter.cpp @@ -27,6 +27,11 @@ void PrepareImGui(float width = 1024.0f, float height = 768.0f) { ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2(width, height); io.DeltaTime = 1.0f / 60.0f; + unsigned char* fontPixels = nullptr; + int fontWidth = 0; + int fontHeight = 0; + io.Fonts->GetTexDataAsRGBA32(&fontPixels, &fontWidth, &fontHeight); + io.Fonts->SetTexID(static_cast(1)); } } // namespace @@ -36,17 +41,17 @@ TEST(ImGuiXCUIInputAdapterTest, CaptureSnapshotMapsImGuiStateIntoXCUIFrameSnapsh PrepareImGui(800.0f, 600.0f); ImGuiIO& io = ImGui::GetIO(); + io.MousePos = ImVec2(120.0f, 72.0f); + io.MouseDown[0] = true; + io.MouseWheel = 1.0f; io.WantCaptureMouse = true; io.WantCaptureKeyboard = true; io.WantTextInput = true; - io.AddMousePosEvent(120.0f, 72.0f); - io.AddMouseButtonEvent(0, true); - io.AddMouseWheelEvent(0.0f, 1.0f); - io.AddKeyEvent(ImGuiKey_LeftCtrl, true); - io.AddKeyEvent(ImGuiKey_P, true); - io.AddInputCharacter('p'); - - ImGui::NewFrame(); + io.KeyCtrl = true; + io.KeysData[ImGuiKey_LeftCtrl - ImGuiKey_NamedKey_BEGIN].Down = true; + io.KeysData[ImGuiKey_P - ImGuiKey_NamedKey_BEGIN].Down = true; + io.InputQueueCharacters.resize(0); + io.InputQueueCharacters.push_back(static_cast('p')); XCUIInputBridgeCaptureOptions options = {}; options.pointerOffset = XCEngine::UI::UIPoint(20.0f, 12.0f); @@ -55,8 +60,6 @@ TEST(ImGuiXCUIInputAdapterTest, CaptureSnapshotMapsImGuiStateIntoXCUIFrameSnapsh const auto snapshot = ImGuiXCUIInputAdapter::CaptureSnapshot(io, options); - ImGui::EndFrame(); - EXPECT_FLOAT_EQ(snapshot.pointerPosition.x, 100.0f); EXPECT_FLOAT_EQ(snapshot.pointerPosition.y, 60.0f); EXPECT_TRUE(snapshot.pointerInside); diff --git a/tests/NewEditor/test_xcui_demo_panel.cpp b/tests/NewEditor/test_xcui_demo_panel.cpp new file mode 100644 index 00000000..8ce6863e --- /dev/null +++ b/tests/NewEditor/test_xcui_demo_panel.cpp @@ -0,0 +1,213 @@ +#include + +#include "panels/XCUIDemoPanel.h" + +#include "XCUIBackend/XCUIHostedPreviewPresenter.h" +#include "XCUIBackend/XCUIPanelCanvasHost.h" + +#include + +#include +#include +#include + +namespace { + +using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter; +using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost; +using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame; +using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats; +using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest; +using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession; +using XCEngine::NewEditor::XCUIDemoPanel; + +class ImGuiContextScope { +public: + ImGuiContextScope() { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + } + + ~ImGuiContextScope() { + ImGui::DestroyContext(); + } +}; + +void PrepareImGui(float width = 1280.0f, float height = 900.0f) { + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(width, height); + io.DeltaTime = 1.0f / 60.0f; + unsigned char* fontPixels = nullptr; + int fontWidth = 0; + int fontHeight = 0; + io.Fonts->GetTexDataAsRGBA32(&fontPixels, &fontWidth, &fontHeight); + io.Fonts->SetTexID(static_cast(1)); +} + +class RecordingHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter { +public: + bool Present(const XCUIHostedPreviewFrame& frame) override { + ++presentCallCount; + lastCanvasRect = frame.canvasRect; + lastLogicalSize = frame.logicalSize; + lastDebugName = frame.debugName != nullptr ? frame.debugName : ""; + + m_lastStats = {}; + if (frame.drawData == nullptr) { + return false; + } + + lastDrawListCount = frame.drawData->GetDrawListCount(); + lastCommandCount = frame.drawData->GetTotalCommandCount(); + m_lastStats.presented = true; + m_lastStats.submittedDrawListCount = lastDrawListCount; + m_lastStats.submittedCommandCount = lastCommandCount; + return true; + } + + const XCUIHostedPreviewStats& GetLastStats() const override { + return m_lastStats; + } + + std::size_t presentCallCount = 0u; + XCEngine::UI::UIRect lastCanvasRect = {}; + XCEngine::UI::UISize lastLogicalSize = {}; + std::size_t lastDrawListCount = 0u; + std::size_t lastCommandCount = 0u; + std::string lastDebugName = {}; + +private: + XCUIHostedPreviewStats m_lastStats = {}; +}; + +class StubCanvasHost final : public IXCUIPanelCanvasHost { +public: + const char* GetDebugName() const override { + return "StubCanvasHost"; + } + + XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend GetBackend() const override { + return XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend::Null; + } + + XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostCapabilities GetCapabilities() const override { + return {}; + } + + XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override { + lastRequest = request; + ++beginCanvasCallCount; + return session; + } + + void DrawFilledRect( + const XCEngine::UI::UIRect&, + const XCEngine::UI::UIColor&, + float) override { + } + + void DrawOutlineRect( + const XCEngine::UI::UIRect&, + const XCEngine::UI::UIColor&, + float, + float) override { + } + + void DrawText( + const XCEngine::UI::UIPoint&, + std::string_view, + const XCEngine::UI::UIColor&, + float) override { + } + + void EndCanvas() override { + ++endCanvasCallCount; + } + + std::size_t beginCanvasCallCount = 0u; + std::size_t endCanvasCallCount = 0u; + XCUIPanelCanvasRequest lastRequest = {}; + XCUIPanelCanvasSession session = { + XCEngine::UI::UIRect(0.0f, 0.0f, 960.0f, 640.0f), + XCEngine::UI::UIRect(12.0f, 18.0f, 936.0f, 512.0f), + XCEngine::UI::UIPoint(120.0f, 140.0f), + true, + true, + true + }; +}; + +void RenderPanelFrame(XCUIDemoPanel& panel, ImGuiContextScope&) { + PrepareImGui(); + ImGui::NewFrame(); + panel.Render(); + ImGui::Render(); +} + +TEST(NewEditorXCUIDemoPanelTest, DefaultConstructionDoesNotAutoCreateHostedPreviewPresenter) { + ImGuiContextScope contextScope; + + auto canvasHost = std::make_unique(); + XCUIDemoPanel panel; + panel.SetCanvasHost(std::move(canvasHost)); + + RenderPanelFrame(panel, contextScope); + + const XCUIHostedPreviewStats& stats = panel.GetLastPreviewStats(); + EXPECT_FALSE(stats.presented); + EXPECT_FALSE(stats.queuedToNativePass); + EXPECT_EQ(stats.submittedDrawListCount, 0u); + EXPECT_EQ(stats.submittedCommandCount, 0u); + EXPECT_EQ(stats.flushedDrawListCount, 0u); + EXPECT_EQ(stats.flushedCommandCount, 0u); +} + +TEST(NewEditorXCUIDemoPanelTest, ConstructorUsesGenericNullCanvasHostUntilOuterLayerInjectsOne) { + ImGuiContextScope contextScope; + + auto previewPresenter = std::make_unique(); + RecordingHostedPreviewPresenter* previewPresenterPtr = previewPresenter.get(); + + XCUIDemoPanel panel(nullptr, std::move(previewPresenter)); + RenderPanelFrame(panel, contextScope); + + ASSERT_EQ(previewPresenterPtr->presentCallCount, 1u); + EXPECT_EQ(previewPresenterPtr->lastDebugName, "XCUI Demo"); + EXPECT_FLOAT_EQ(previewPresenterPtr->lastCanvasRect.x, 0.0f); + EXPECT_GT(previewPresenterPtr->lastCanvasRect.y, 0.0f); + EXPECT_FLOAT_EQ(previewPresenterPtr->lastCanvasRect.width, 0.0f); + EXPECT_GT(previewPresenterPtr->lastCanvasRect.height, 0.0f); + EXPECT_FLOAT_EQ(previewPresenterPtr->lastLogicalSize.width, 0.0f); + EXPECT_FLOAT_EQ( + previewPresenterPtr->lastLogicalSize.height, + previewPresenterPtr->lastCanvasRect.height); +} + +TEST(NewEditorXCUIDemoPanelTest, ClearingHostedPreviewPresenterDoesNotRestoreImGuiFallback) { + ImGuiContextScope contextScope; + + auto previewPresenter = std::make_unique(); + RecordingHostedPreviewPresenter* previewPresenterPtr = previewPresenter.get(); + auto canvasHost = std::make_unique(); + + XCUIDemoPanel panel(nullptr, std::move(previewPresenter)); + panel.SetCanvasHost(std::move(canvasHost)); + + RenderPanelFrame(panel, contextScope); + ASSERT_EQ(previewPresenterPtr->presentCallCount, 1u); + EXPECT_TRUE(panel.GetLastPreviewStats().presented); + + panel.SetHostedPreviewPresenter(nullptr); + RenderPanelFrame(panel, contextScope); + + const XCUIHostedPreviewStats& stats = panel.GetLastPreviewStats(); + EXPECT_FALSE(stats.presented); + EXPECT_FALSE(stats.queuedToNativePass); + EXPECT_EQ(stats.submittedDrawListCount, 0u); + EXPECT_EQ(stats.submittedCommandCount, 0u); + EXPECT_EQ(stats.flushedDrawListCount, 0u); + EXPECT_EQ(stats.flushedCommandCount, 0u); +} + +} // namespace diff --git a/tests/NewEditor/test_xcui_hosted_preview_presenter.cpp b/tests/NewEditor/test_xcui_hosted_preview_presenter.cpp index 525d19a3..1fca119d 100644 --- a/tests/NewEditor/test_xcui_hosted_preview_presenter.cpp +++ b/tests/NewEditor/test_xcui_hosted_preview_presenter.cpp @@ -12,6 +12,7 @@ namespace { using XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter; +using XCEngine::Editor::XCUIBackend::CreateNullXCUIHostedPreviewPresenter; using XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter; using XCEngine::Editor::XCUIBackend::IImGuiXCUIHostedPreviewTargetBinding; using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter; @@ -92,6 +93,80 @@ TEST(XCUIHostedPreviewPresenterTest, PresentReturnsFalseAndClearsStatsWhenFrameH EXPECT_EQ(stats.flushedCommandCount, 0u); } +TEST(XCUIHostedPreviewPresenterTest, NullPresenterReturnsFalseAndClearsStatsWhenFrameHasNoDrawData) { + std::unique_ptr presenter = CreateNullXCUIHostedPreviewPresenter(); + ASSERT_NE(presenter, nullptr); + + XCUIHostedPreviewFrame frame = {}; + const bool presented = presenter->Present(frame); + const XCUIHostedPreviewStats& stats = presenter->GetLastStats(); + + EXPECT_FALSE(presented); + EXPECT_FALSE(stats.presented); + EXPECT_FALSE(stats.queuedToNativePass); + EXPECT_EQ(stats.submittedDrawListCount, 0u); + EXPECT_EQ(stats.submittedCommandCount, 0u); + EXPECT_EQ(stats.flushedDrawListCount, 0u); + EXPECT_EQ(stats.flushedCommandCount, 0u); +} + +TEST(XCUIHostedPreviewPresenterTest, NullPresenterAcceptsDrawDataWithoutPresentingOrQueueing) { + std::unique_ptr presenter = CreateNullXCUIHostedPreviewPresenter(); + ASSERT_NE(presenter, nullptr); + + XCEngine::UI::UIDrawData drawData = {}; + XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreviewNull"); + drawList.AddFilledRect( + XCEngine::UI::UIRect(12.0f, 14.0f, 48.0f, 30.0f), + XCEngine::UI::UIColor(0.25f, 0.5f, 0.8f, 1.0f)); + drawList.AddText( + XCEngine::UI::UIPoint(18.0f, 24.0f), + "null", + XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f), + 14.0f); + + XCUIHostedPreviewFrame frame = {}; + frame.drawData = &drawData; + frame.debugName = "XCUI Null Preview"; + + const bool presented = presenter->Present(frame); + const XCUIHostedPreviewStats& stats = presenter->GetLastStats(); + + EXPECT_FALSE(presented); + EXPECT_FALSE(stats.presented); + EXPECT_FALSE(stats.queuedToNativePass); + EXPECT_EQ(stats.submittedDrawListCount, 1u); + EXPECT_EQ(stats.submittedCommandCount, 2u); + EXPECT_EQ(stats.flushedDrawListCount, 0u); + EXPECT_EQ(stats.flushedCommandCount, 0u); +} + +TEST(XCUIHostedPreviewPresenterTest, NullPresenterSurfaceQueriesReturnFalseAndClearOutputs) { + std::unique_ptr presenter = CreateNullXCUIHostedPreviewPresenter(); + ASSERT_NE(presenter, nullptr); + + XCUIHostedPreviewSurfaceDescriptor descriptor = {}; + descriptor.debugName = "stale"; + descriptor.debugSource = "stale"; + descriptor.queuedThisFrame = true; + + XCUIHostedPreviewSurfaceImage image = {}; + image.texture = MakeHostedPreviewTextureHandle(29u, 256u, 128u); + image.surfaceWidth = 256u; + image.surfaceHeight = 128u; + + EXPECT_FALSE(presenter->TryGetSurfaceDescriptor("XCUI Demo", descriptor)); + EXPECT_TRUE(descriptor.debugName.empty()); + EXPECT_TRUE(descriptor.debugSource.empty()); + EXPECT_FALSE(descriptor.queuedThisFrame); + EXPECT_FALSE(descriptor.image.IsValid()); + + EXPECT_FALSE(presenter->TryGetSurfaceImage("XCUI Demo", image)); + EXPECT_FALSE(image.IsValid()); + EXPECT_EQ(image.surfaceWidth, 0u); + EXPECT_EQ(image.surfaceHeight, 0u); +} + TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoExplicitBindingResolvedImGuiDrawList) { ImGuiContextScope contextScope; PrepareImGui(800.0f, 600.0f); @@ -229,7 +304,7 @@ TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoExplicitBindingRe EXPECT_GE(targetDrawList->CmdBuffer.Size, baselineCommandCount); } -TEST(XCUIHostedPreviewPresenterTest, DefaultFactoryStillUsesCurrentWindowBindingForLegacyImGuiPath) { +TEST(XCUIHostedPreviewPresenterTest, DefaultFactoryStillUsesCurrentWindowBindingForImGuiPresenterPath) { ImGuiContextScope contextScope; PrepareImGui(800.0f, 600.0f); diff --git a/tests/NewEditor/test_xcui_layout_lab_panel.cpp b/tests/NewEditor/test_xcui_layout_lab_panel.cpp index bd967630..1553a2f2 100644 --- a/tests/NewEditor/test_xcui_layout_lab_panel.cpp +++ b/tests/NewEditor/test_xcui_layout_lab_panel.cpp @@ -2,6 +2,7 @@ #include "panels/XCUILayoutLabPanel.h" +#include "XCUIBackend/ImGuiXCUIInputSource.h" #include "XCUIBackend/XCUIHostedPreviewPresenter.h" #include "XCUIBackend/XCUIPanelCanvasHost.h" @@ -15,6 +16,7 @@ namespace { using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter; using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost; +using XCEngine::Editor::XCUIBackend::ImGuiXCUIInputSnapshotSource; using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame; using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats; using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest; @@ -155,13 +157,15 @@ void ClickElement( RenderPanelFrame( panel, contextScope, - [](ImGuiIO& io) { + [clickPoint](ImGuiIO& io) { + io.AddMousePosEvent(clickPoint.x, clickPoint.y); io.AddMouseButtonEvent(ImGuiMouseButton_Left, true); }); RenderPanelFrame( panel, contextScope, - [](ImGuiIO& io) { + [clickPoint](ImGuiIO& io) { + io.AddMousePosEvent(clickPoint.x, clickPoint.y); io.AddMouseButtonEvent(ImGuiMouseButton_Left, false); }); } @@ -190,10 +194,10 @@ TEST(NewEditorXCUILayoutLabPanelTest, MapsPreviousNextHomeAndEndIntoRuntimeNavig auto previewPresenter = std::make_unique(); auto canvasHost = std::make_unique(); StubCanvasHost* canvasHostPtr = canvasHost.get(); + ImGuiXCUIInputSnapshotSource inputSource(nullptr); - XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter)); + XCUILayoutLabPanel panel(&inputSource, std::move(previewPresenter), std::move(canvasHost)); panel.SetHostedPreviewEnabled(false); - panel.SetCanvasHost(std::move(canvasHost)); RenderPanelFrame(panel, contextScope); ClickElement(panel, *canvasHostPtr, "assetLighting", contextScope); @@ -223,10 +227,10 @@ TEST(NewEditorXCUILayoutLabPanelTest, MapsCollapseAndExpandIntoRuntimeNavigation auto previewPresenter = std::make_unique(); auto canvasHost = std::make_unique(); StubCanvasHost* canvasHostPtr = canvasHost.get(); + ImGuiXCUIInputSnapshotSource inputSource(nullptr); - XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter)); + XCUILayoutLabPanel panel(&inputSource, std::move(previewPresenter), std::move(canvasHost)); panel.SetHostedPreviewEnabled(false); - panel.SetCanvasHost(std::move(canvasHost)); RenderPanelFrame(panel, contextScope); ClickElement(panel, *canvasHostPtr, "treeScenes", contextScope); @@ -250,4 +254,17 @@ TEST(NewEditorXCUILayoutLabPanelTest, MapsCollapseAndExpandIntoRuntimeNavigation EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeScenes"); } +TEST(NewEditorXCUILayoutLabPanelTest, DefaultFallbackDoesNotCreateImplicitHostedPreviewPresenter) { + ImGuiContextScope contextScope; + + XCUILayoutLabPanel panel(nullptr); + + RenderPanelFrame(panel, contextScope); + + EXPECT_FALSE(panel.IsUsingNativeHostedPreview()); + EXPECT_FALSE(panel.GetLastPreviewStats().presented); + EXPECT_EQ(panel.GetLastPreviewStats().submittedDrawListCount, 0u); + EXPECT_EQ(panel.GetLastPreviewStats().submittedCommandCount, 0u); +} + } // namespace diff --git a/tests/NewEditor/test_xcui_panel_canvas_host.cpp b/tests/NewEditor/test_xcui_panel_canvas_host.cpp index cfb52c0e..695fe19f 100644 --- a/tests/NewEditor/test_xcui_panel_canvas_host.cpp +++ b/tests/NewEditor/test_xcui_panel_canvas_host.cpp @@ -5,7 +5,9 @@ namespace { using XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost; +using XCEngine::Editor::XCUIBackend::BuildPassiveXCUIPanelCanvasSession; using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost; +using XCEngine::Editor::XCUIBackend::ResolveXCUIPanelCanvasChildId; using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend; using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostCapabilities; using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest; @@ -24,7 +26,41 @@ TEST(NewEditorXCUIPanelCanvasHostTest, NullHostReportsExplicitBackendAndCapabili EXPECT_FALSE(capabilities.supportsPrimitiveOverlays); } -TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsEmptySessionAndDrawCallsAreNoops) { +TEST(NewEditorXCUIPanelCanvasHostTest, ResolveChildIdFallsBackToStableDefaultForMissingNames) { + XCUIPanelCanvasRequest request = {}; + EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request), "XCUIPanelCanvasHost"); + EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request, "FallbackCanvas"), "FallbackCanvas"); + + request.childId = ""; + EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request), "XCUIPanelCanvasHost"); + + request.childId = "CanvasHost"; + EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request, "FallbackCanvas"), "CanvasHost"); +} + +TEST(NewEditorXCUIPanelCanvasHostTest, PassiveSessionClampsRequestGeometryIntoSafeDefaultState) { + XCUIPanelCanvasRequest request = {}; + request.height = -18.0f; + request.topInset = 42.0f; + + XCUIPanelCanvasSession session = BuildPassiveXCUIPanelCanvasSession(request); + EXPECT_FALSE(session.validCanvas); + EXPECT_FALSE(session.hovered); + EXPECT_FALSE(session.windowFocused); + EXPECT_FLOAT_EQ(session.hostRect.width, 0.0f); + EXPECT_FLOAT_EQ(session.hostRect.height, 0.0f); + EXPECT_FLOAT_EQ(session.canvasRect.y, 0.0f); + EXPECT_FLOAT_EQ(session.canvasRect.height, 0.0f); + + request.height = 120.0f; + request.topInset = 180.0f; + session = BuildPassiveXCUIPanelCanvasSession(request); + EXPECT_FLOAT_EQ(session.hostRect.height, 120.0f); + EXPECT_FLOAT_EQ(session.canvasRect.y, 120.0f); + EXPECT_FLOAT_EQ(session.canvasRect.height, 0.0f); +} + +TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsSafePassiveSessionAndDrawCallsAreNoops) { std::unique_ptr host = CreateNullXCUIPanelCanvasHost(); ASSERT_NE(host, nullptr); @@ -43,9 +79,12 @@ TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsEmptySessionAnd EXPECT_FALSE(session.hovered); EXPECT_FALSE(session.windowFocused); EXPECT_FLOAT_EQ(session.hostRect.width, 0.0f); - EXPECT_FLOAT_EQ(session.hostRect.height, 0.0f); + EXPECT_FLOAT_EQ(session.hostRect.height, 280.0f); EXPECT_FLOAT_EQ(session.canvasRect.width, 0.0f); - EXPECT_FLOAT_EQ(session.canvasRect.height, 0.0f); + EXPECT_FLOAT_EQ(session.canvasRect.y, 24.0f); + EXPECT_FLOAT_EQ(session.canvasRect.height, 256.0f); + EXPECT_FLOAT_EQ(session.pointerPosition.x, 0.0f); + EXPECT_FLOAT_EQ(session.pointerPosition.y, 0.0f); host->DrawFilledRect( XCEngine::UI::UIRect(10.0f, 12.0f, 48.0f, 64.0f), @@ -62,6 +101,11 @@ TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsEmptySessionAnd XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f), 16.0f); host->EndCanvas(); + + const XCUIPanelCanvasSession secondSession = host->BeginCanvas({}); + EXPECT_FLOAT_EQ(secondSession.hostRect.height, 0.0f); + EXPECT_FLOAT_EQ(secondSession.canvasRect.height, 0.0f); + host->EndCanvas(); } } // namespace diff --git a/tests/NewEditor/test_xcui_shell_chrome_state.cpp b/tests/NewEditor/test_xcui_shell_chrome_state.cpp index cb05e119..3ee58afd 100644 --- a/tests/NewEditor/test_xcui_shell_chrome_state.cpp +++ b/tests/NewEditor/test_xcui_shell_chrome_state.cpp @@ -5,9 +5,11 @@ namespace { using XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds; +using XCEngine::Editor::XCUIBackend::XCUIShellCommandDescriptor; using XCEngine::Editor::XCUIBackend::XCUIShellChromeState; using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode; using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewState; +using XCEngine::Editor::XCUIBackend::XCUIShellMenuItemKind; using XCEngine::Editor::XCUIBackend::XCUIShellPanelId; using XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId; @@ -33,7 +35,7 @@ TEST(XCUIShellChromeStateTest, DefaultsMatchCurrentShellChromeConfiguration) { state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo), XCUIShellHostedPreviewState::NativeOffscreen); EXPECT_TRUE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); - EXPECT_FALSE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); + EXPECT_FALSE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUIDemo)); const auto* layoutLabPanel = state.TryGetPanelState(XCUIShellPanelId::XCUILayoutLab); ASSERT_NE(layoutLabPanel, nullptr); @@ -42,12 +44,12 @@ TEST(XCUIShellChromeStateTest, DefaultsMatchCurrentShellChromeConfiguration) { EXPECT_EQ(layoutLabPanel->previewDebugSource, "new_editor.panels.xcui_layout_lab"); EXPECT_TRUE(layoutLabPanel->visible); EXPECT_TRUE(layoutLabPanel->hostedPreviewEnabled); - EXPECT_EQ(layoutLabPanel->previewMode, XCUIShellHostedPreviewMode::LegacyImGui); + EXPECT_EQ(layoutLabPanel->previewMode, XCUIShellHostedPreviewMode::HostedPresenter); EXPECT_EQ( state.GetHostedPreviewState(XCUIShellPanelId::XCUILayoutLab), - XCUIShellHostedPreviewState::LegacyImGui); + XCUIShellHostedPreviewState::HostedPresenter); EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab)); - EXPECT_TRUE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab)); + EXPECT_TRUE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUILayoutLab)); } TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateChanges) { @@ -64,7 +66,7 @@ TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateCh EXPECT_FALSE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUILayoutLab, false)); EXPECT_EQ( state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab), - XCUIShellHostedPreviewMode::LegacyImGui); + XCUIShellHostedPreviewMode::HostedPresenter); EXPECT_TRUE(state.ToggleHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab)); EXPECT_EQ( @@ -72,13 +74,13 @@ TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateCh XCUIShellHostedPreviewMode::NativeOffscreen); EXPECT_TRUE(state.SetHostedPreviewMode( XCUIShellPanelId::XCUILayoutLab, - XCUIShellHostedPreviewMode::LegacyImGui)); + XCUIShellHostedPreviewMode::HostedPresenter)); EXPECT_EQ( state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab), - XCUIShellHostedPreviewMode::LegacyImGui); + XCUIShellHostedPreviewMode::HostedPresenter); EXPECT_FALSE(state.SetHostedPreviewMode( XCUIShellPanelId::XCUILayoutLab, - XCUIShellHostedPreviewMode::LegacyImGui)); + XCUIShellHostedPreviewMode::HostedPresenter)); } TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequestedMode) { @@ -93,14 +95,14 @@ TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequeste state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo), XCUIShellHostedPreviewState::Disabled); EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); - EXPECT_FALSE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); + EXPECT_FALSE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUIDemo)); EXPECT_TRUE(state.SetHostedPreviewMode( XCUIShellPanelId::XCUIDemo, - XCUIShellHostedPreviewMode::LegacyImGui)); + XCUIShellHostedPreviewMode::HostedPresenter)); EXPECT_EQ( state.GetHostedPreviewMode(XCUIShellPanelId::XCUIDemo), - XCUIShellHostedPreviewMode::LegacyImGui); + XCUIShellHostedPreviewMode::HostedPresenter); EXPECT_EQ( state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo), XCUIShellHostedPreviewState::Disabled); @@ -108,9 +110,9 @@ TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequeste EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, true)); EXPECT_EQ( state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo), - XCUIShellHostedPreviewState::LegacyImGui); + XCUIShellHostedPreviewState::HostedPresenter); EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); - EXPECT_TRUE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); + EXPECT_TRUE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUIDemo)); } TEST(XCUIShellChromeStateTest, ViewToggleMutatorsOnlyFlipRequestedFlags) { @@ -148,11 +150,115 @@ TEST(XCUIShellChromeStateTest, CommandInterfaceTogglesShellViewAndPreviewStates) EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview)); EXPECT_EQ( state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab), - XCUIShellHostedPreviewMode::LegacyImGui); + XCUIShellHostedPreviewMode::HostedPresenter); EXPECT_FALSE(state.InvokeCommand("new_editor.view.unknown")); } +TEST(XCUIShellChromeStateTest, CommandDescriptorsMatchCurrentShellMenuLabelsShortcutsAndCheckedState) { + XCUIShellChromeState state = {}; + XCUIShellCommandDescriptor descriptor = {}; + + ASSERT_TRUE(state.TryGetCommandDescriptor(XCUIShellChromeCommandIds::ToggleXCUIDemoPanel, descriptor)); + EXPECT_EQ(descriptor.label, "XCUI Demo"); + EXPECT_EQ(descriptor.shortcut, "Ctrl+1"); + EXPECT_EQ(descriptor.commandId, XCUIShellChromeCommandIds::ToggleXCUIDemoPanel); + EXPECT_TRUE(descriptor.checkable); + EXPECT_TRUE(descriptor.checked); + EXPECT_TRUE(descriptor.enabled); + + ASSERT_TRUE(state.TryGetCommandDescriptor(XCUIShellChromeCommandIds::ToggleHostedPreviewHud, descriptor)); + EXPECT_EQ(descriptor.label, "Hosted Preview HUD"); + EXPECT_EQ(descriptor.shortcut, "Ctrl+Shift+H"); + EXPECT_TRUE(descriptor.checked); + + ASSERT_TRUE(state.TryGetCommandDescriptor(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview, descriptor)); + EXPECT_EQ(descriptor.label, "Native Layout Lab Preview"); + EXPECT_EQ(descriptor.shortcut, "Ctrl+Alt+2"); + EXPECT_FALSE(descriptor.checked); + + EXPECT_FALSE(state.TryGetCommandDescriptor("new_editor.view.unknown", descriptor)); + EXPECT_TRUE(descriptor.label.empty()); + EXPECT_TRUE(descriptor.shortcut.empty()); + EXPECT_TRUE(descriptor.commandId.empty()); + EXPECT_FALSE(descriptor.checked); + EXPECT_FALSE(descriptor.enabled); +} + +TEST(XCUIShellChromeStateTest, ViewMenuDescriptorMatchesCurrentApplicationOrderingAndSeparators) { + XCUIShellChromeState state = {}; + + const auto menu = state.BuildViewMenuDescriptor(); + ASSERT_EQ(menu.label, "View"); + ASSERT_EQ(menu.items.size(), 10u); + + ASSERT_EQ(menu.items[0].kind, XCUIShellMenuItemKind::Command); + EXPECT_EQ(menu.items[0].command.label, "XCUI Demo"); + EXPECT_EQ(menu.items[0].command.shortcut, "Ctrl+1"); + EXPECT_EQ(menu.items[0].command.commandId, XCUIShellChromeCommandIds::ToggleXCUIDemoPanel); + EXPECT_TRUE(menu.items[0].command.checked); + + ASSERT_EQ(menu.items[1].kind, XCUIShellMenuItemKind::Command); + EXPECT_EQ(menu.items[1].command.label, "XCUI Layout Lab"); + EXPECT_EQ(menu.items[1].command.shortcut, "Ctrl+2"); + EXPECT_EQ(menu.items[1].command.commandId, XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel); + + ASSERT_EQ(menu.items[2].kind, XCUIShellMenuItemKind::Command); + EXPECT_EQ(menu.items[2].command.label, "ImGui Demo"); + EXPECT_EQ(menu.items[2].command.shortcut, "Ctrl+3"); + EXPECT_EQ(menu.items[2].command.commandId, XCUIShellChromeCommandIds::ToggleImGuiDemoWindow); + + EXPECT_EQ(menu.items[3].kind, XCUIShellMenuItemKind::Separator); + + ASSERT_EQ(menu.items[4].kind, XCUIShellMenuItemKind::Command); + EXPECT_EQ(menu.items[4].command.label, "Native Backdrop"); + EXPECT_EQ(menu.items[4].command.shortcut, "Ctrl+Shift+B"); + EXPECT_EQ(menu.items[4].command.commandId, XCUIShellChromeCommandIds::ToggleNativeBackdrop); + + ASSERT_EQ(menu.items[5].kind, XCUIShellMenuItemKind::Command); + EXPECT_EQ(menu.items[5].command.label, "Pulse Accent"); + EXPECT_EQ(menu.items[5].command.shortcut, "Ctrl+Shift+P"); + EXPECT_EQ(menu.items[5].command.commandId, XCUIShellChromeCommandIds::TogglePulseAccent); + + ASSERT_EQ(menu.items[6].kind, XCUIShellMenuItemKind::Command); + EXPECT_EQ(menu.items[6].command.label, "Native XCUI Overlay"); + EXPECT_EQ(menu.items[6].command.shortcut, "Ctrl+Shift+O"); + EXPECT_EQ(menu.items[6].command.commandId, XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay); + + ASSERT_EQ(menu.items[7].kind, XCUIShellMenuItemKind::Command); + EXPECT_EQ(menu.items[7].command.label, "Hosted Preview HUD"); + EXPECT_EQ(menu.items[7].command.shortcut, "Ctrl+Shift+H"); + EXPECT_EQ(menu.items[7].command.commandId, XCUIShellChromeCommandIds::ToggleHostedPreviewHud); + + ASSERT_EQ(menu.items[8].kind, XCUIShellMenuItemKind::Command); + EXPECT_EQ(menu.items[8].command.label, "Native Demo Panel Preview"); + EXPECT_EQ(menu.items[8].command.shortcut, "Ctrl+Alt+1"); + EXPECT_EQ(menu.items[8].command.commandId, XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview); + + ASSERT_EQ(menu.items[9].kind, XCUIShellMenuItemKind::Command); + EXPECT_EQ(menu.items[9].command.label, "Native Layout Lab Preview"); + EXPECT_EQ(menu.items[9].command.shortcut, "Ctrl+Alt+2"); + EXPECT_EQ(menu.items[9].command.commandId, XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview); +} + +TEST(XCUIShellChromeStateTest, ViewMenuDescriptorCheckedStateTracksShellStateChanges) { + XCUIShellChromeState state = {}; + + EXPECT_TRUE(state.SetPanelVisible(XCUIShellPanelId::XCUIDemo, false)); + EXPECT_TRUE(state.SetViewToggle(XCUIShellViewToggleId::HostedPreviewHud, false)); + EXPECT_TRUE(state.SetHostedPreviewMode( + XCUIShellPanelId::XCUILayoutLab, + XCUIShellHostedPreviewMode::NativeOffscreen)); + EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, false)); + + const auto menu = state.BuildViewMenuDescriptor(); + + EXPECT_FALSE(menu.items[0].command.checked); + EXPECT_FALSE(menu.items[7].command.checked); + EXPECT_FALSE(menu.items[8].command.checked); + EXPECT_TRUE(menu.items[9].command.checked); +} + TEST(XCUIShellChromeStateTest, PanelCommandIdHelpersMatchCurrentShellCommands) { EXPECT_EQ( XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo), @@ -200,12 +306,12 @@ TEST(XCUIShellChromeStateTest, InvalidPanelAndToggleIdsFailGracefully) { EXPECT_FALSE(state.SetHostedPreviewEnabled(invalidPanelId, false)); EXPECT_EQ( state.GetHostedPreviewMode(invalidPanelId), - XCUIShellHostedPreviewMode::LegacyImGui); + XCUIShellHostedPreviewMode::HostedPresenter); EXPECT_EQ( state.GetHostedPreviewState(invalidPanelId), XCUIShellHostedPreviewState::Disabled); EXPECT_FALSE(state.IsNativeHostedPreviewActive(invalidPanelId)); - EXPECT_FALSE(state.IsLegacyHostedPreviewActive(invalidPanelId)); + EXPECT_FALSE(state.IsHostedPresenterPreviewActive(invalidPanelId)); EXPECT_FALSE(state.SetHostedPreviewMode(invalidPanelId, XCUIShellHostedPreviewMode::NativeOffscreen)); EXPECT_FALSE(state.ToggleHostedPreviewMode(invalidPanelId));