Contain XCUI ImGui adapters behind explicit host seams

This commit is contained in:
2026-04-05 13:24:14 +08:00
parent 56f596548d
commit f943a07862
23 changed files with 1134 additions and 290 deletions

View File

@@ -64,7 +64,7 @@ Current gap:
### 3. Editor Layer ### 3. Editor Layer
- `new_editor` remains the isolated XCUI sandbox. - `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. - 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` 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. - `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` 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. - `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. - 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. - 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. - 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`. - `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. - `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. - `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. - `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`. - `XCNewEditor` builds successfully to `build/new_editor/bin/Debug/XCNewEditor.exe`.
Current gap: Current gap:
- The shell is still ImGui-hosted. - The shell is still ImGui-hosted.
- Legacy hosted preview still depends on an ImGui-specific inline draw target binding for presentation. - Hosted-preview compatibility presentation still depends on an ImGui-specific inline draw target binding when the native queued surface path is disabled.
- 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. - 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. - 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 ## Validated This Phase
- `new_editor_xcui_demo_panel_tests`: `3/3`
- `new_editor_xcui_demo_runtime_tests`: `12/12` - `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_layout_lab_runtime_tests`: `12/12`
- `new_editor_xcui_rhi_command_compiler_tests`: `6/6` - `new_editor_xcui_rhi_command_compiler_tests`: `6/6`
- `new_editor_xcui_rhi_render_backend_tests`: `5/5` - `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_imgui_window_ui_compositor_tests`: `7/7`
- `new_editor_xcui_editor_command_router_tests`: `5/5` - `new_editor_xcui_editor_command_router_tests`: `5/5`
- `new_editor_application_shell_command_bindings_tests`: `6/6` - `new_editor_application_shell_command_bindings_tests`: `6/6`
- `new_editor_xcui_shell_chrome_state_tests`: `8/8` - `new_editor_xcui_shell_chrome_state_tests`: `11/11`
- `new_editor_xcui_panel_canvas_host_tests`: `2/2` - `new_editor_xcui_panel_canvas_host_tests`: `4/4`
- `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1` - `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 - `XCNewEditor` Debug target builds successfully
- `core_ui_tests`: `52 total` (`50` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`) - `core_ui_tests`: `52 total` (`50` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`)
- `scene_tests`: `68/68` - `scene_tests`: `68/68`
@@ -199,8 +205,12 @@ Current gap:
- shell-level shortcuts now flow from `XCUIWin32InputSource` through `XCUIInputBridge` into command matching - 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 - 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. - `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`. - `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` 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. - 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: - `SceneRuntime` layered XCUI routing now has dedicated regression coverage for:
- top-interactive layer input ownership - top-interactive layer input ownership
@@ -228,13 +238,13 @@ Current gap:
- `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet. - `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. - `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. - 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. - 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 ## 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. 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. 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. 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. 5. Continue phased validation, commit, push, and plan refresh after each stable batch.

View File

@@ -53,7 +53,9 @@ set(NEW_EDITOR_SOURCES
src/XCUIBackend/ImGuiHostCompositor.cpp src/XCUIBackend/ImGuiHostCompositor.cpp
src/XCUIBackend/XCUIEditorFontSetup.cpp src/XCUIBackend/XCUIEditorFontSetup.cpp
src/XCUIBackend/XCUIAssetDocumentSource.cpp src/XCUIBackend/XCUIAssetDocumentSource.cpp
src/XCUIBackend/XCUIEditorCommandRouter.cpp
src/XCUIBackend/XCUIInputBridge.cpp src/XCUIBackend/XCUIInputBridge.cpp
src/XCUIBackend/XCUIShellChromeState.cpp
src/XCUIBackend/XCUIRHICommandCompiler.cpp src/XCUIBackend/XCUIRHICommandCompiler.cpp
src/XCUIBackend/XCUIRHIRenderBackend.cpp src/XCUIBackend/XCUIRHIRenderBackend.cpp
src/XCUIBackend/XCUIStandaloneTextAtlasProvider.cpp src/XCUIBackend/XCUIStandaloneTextAtlasProvider.cpp

View File

@@ -1,4 +1,5 @@
#include "Application.h" #include "Application.h"
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" #include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h"
#include "XCUIBackend/ImGuiWindowUICompositor.h" #include "XCUIBackend/ImGuiWindowUICompositor.h"
@@ -41,12 +42,12 @@ const char* GetHostedPreviewPathLabel(bool nativeRequested, bool nativePresenter
return "native queued offscreen surface"; return "native queued offscreen surface";
} }
if (nativeRequested) { if (nativeRequested) {
return "native requested, legacy presenter bound"; return "native requested, hosted presenter bound";
} }
if (nativePresenterBound) { 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( const char* GetHostedPreviewStateLabel(
@@ -90,70 +91,20 @@ Application::CreateHostedPreviewPresenter(bool nativePreview) {
return ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); 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 Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const {
const std::size_t index = GetShellPanelIndex(panelId); return m_shellChromeState.TryGetPanelState(panelId);
if (index >= m_shellPanels.size()) {
return nullptr;
}
return &m_shellPanels[index];
} }
bool Application::IsShellViewToggleEnabled(ShellViewToggleId toggleId) const { bool Application::IsShellViewToggleEnabled(ShellViewToggleId toggleId) const {
switch (toggleId) { return m_shellChromeState.GetViewToggle(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;
}
} }
void Application::SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled) { void Application::SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled) {
switch (toggleId) { m_shellChromeState.SetViewToggle(toggleId, enabled);
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;
}
} }
bool Application::IsNativeHostedPreviewEnabled(ShellPanelId panelId) const { bool Application::IsNativeHostedPreviewEnabled(ShellPanelId panelId) const {
const ShellPanelChromeState* panelState = TryGetShellPanelState(panelId); return m_shellChromeState.IsNativeHostedPreviewActive(panelId);
return panelState != nullptr &&
panelState->hostedPreviewEnabled &&
panelState->previewMode == ShellHostedPreviewMode::NativeOffscreen;
} }
void Application::ConfigureHostedPreviewPresenters() { void Application::ConfigureHostedPreviewPresenters() {
@@ -178,25 +129,19 @@ void Application::ConfigureShellCommandRouter() {
ShellCommandBindings bindings = {}; ShellCommandBindings bindings = {};
bindings.getXCUIDemoPanelVisible = [this]() { bindings.getXCUIDemoPanelVisible = [this]() {
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo); return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo);
return panelState != nullptr && panelState->visible;
}; };
bindings.setXCUIDemoPanelVisible = [this](bool visible) { bindings.setXCUIDemoPanelVisible = [this](bool visible) {
if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo)) { m_shellChromeState.SetPanelVisible(ShellPanelId::XCUIDemo, visible);
panelState->visible = visible;
}
if (m_demoPanel != nullptr) { if (m_demoPanel != nullptr) {
m_demoPanel->SetVisible(visible); m_demoPanel->SetVisible(visible);
} }
}; };
bindings.getXCUILayoutLabPanelVisible = [this]() { bindings.getXCUILayoutLabPanelVisible = [this]() {
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab); return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab);
return panelState != nullptr && panelState->visible;
}; };
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) { bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab)) { m_shellChromeState.SetPanelVisible(ShellPanelId::XCUILayoutLab, visible);
panelState->visible = visible;
}
if (m_layoutLabPanel != nullptr) { if (m_layoutLabPanel != nullptr) {
m_layoutLabPanel->SetVisible(visible); m_layoutLabPanel->SetVisible(visible);
} }
@@ -235,23 +180,17 @@ void Application::ConfigureShellCommandRouter() {
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo); return IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo);
}; };
bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) { bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) {
if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo)) { m_shellChromeState.SetHostedPreviewMode(
panelState->previewMode = ShellPanelId::XCUIDemo,
enabled enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
? ShellHostedPreviewMode::NativeOffscreen
: ShellHostedPreviewMode::LegacyImGui;
}
}; };
bindings.getNativeLayoutLabPreviewEnabled = [this]() { bindings.getNativeLayoutLabPreviewEnabled = [this]() {
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab); return IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
}; };
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) { bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab)) { m_shellChromeState.SetHostedPreviewMode(
panelState->previewMode = ShellPanelId::XCUILayoutLab,
enabled enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
? ShellHostedPreviewMode::NativeOffscreen
: ShellHostedPreviewMode::LegacyImGui;
}
}; };
bindings.onHostedPreviewModeChanged = [this]() { ConfigureHostedPreviewPresenters(); }; bindings.onHostedPreviewModeChanged = [this]() { ConfigureHostedPreviewPresenters(); };
@@ -356,10 +295,12 @@ int Application::Run(HINSTANCE instance, int nCmdShow) {
InitializeWindowCompositor(); InitializeWindowCompositor();
m_demoPanel = std::make_unique<XCUIDemoPanel>( m_demoPanel = std::make_unique<XCUIDemoPanel>(
&m_xcuiInputSource, &m_xcuiInputSource,
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo))); CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)),
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>( m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
&m_xcuiInputSource, &m_xcuiInputSource,
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab))); CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)),
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
ConfigureHostedPreviewPresenters(); ConfigureHostedPreviewPresenters();
m_shellInputBridge.Reset(); m_shellInputBridge.Reset();
ConfigureShellCommandRouter(); ConfigureShellCommandRouter();
@@ -531,13 +472,12 @@ void Application::DestroyHostedPreviewSurfaces() {
} }
void Application::SyncShellChromePanelStateFromPanels() { void Application::SyncShellChromePanelStateFromPanels() {
if (ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo)) { m_shellChromeState.SetPanelVisible(
demoState->visible = m_demoPanel != nullptr && m_demoPanel->IsVisible(); ShellPanelId::XCUIDemo,
} m_demoPanel != nullptr && m_demoPanel->IsVisible());
m_shellChromeState.SetPanelVisible(
if (ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab)) { ShellPanelId::XCUILayoutLab,
layoutLabState->visible = m_layoutLabPanel != nullptr && m_layoutLabPanel->IsVisible(); m_layoutLabPanel != nullptr && m_layoutLabPanel->IsVisible());
}
} }
void Application::SyncHostedPreviewSurfaces() { void Application::SyncHostedPreviewSurfaces() {
@@ -864,14 +804,14 @@ void Application::RenderShellChrome() {
"XCUI Demo preview: %s", "XCUI Demo preview: %s",
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo) IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)
? "native offscreen preview surface" ? "native offscreen preview surface"
: "ImGui hosted preview"); : "hosted presenter");
} }
if (m_layoutLabPanel != nullptr) { if (m_layoutLabPanel != nullptr) {
ImGui::TextDisabled( ImGui::TextDisabled(
"Layout Lab preview: %s", "Layout Lab preview: %s",
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab) IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)
? "native offscreen preview surface" ? "native offscreen preview surface"
: "ImGui hosted preview"); : "hosted presenter");
} }
ImGui::EndMenuBar(); ImGui::EndMenuBar();
} }
@@ -1102,8 +1042,10 @@ void Application::Frame() {
if (m_layoutLabPanel) { if (m_layoutLabPanel) {
m_layoutLabPanel->RenderIfVisible(); m_layoutLabPanel->RenderIfVisible();
} }
if (m_shellViewToggles.imguiDemoWindowVisible) { bool showImGuiDemoWindow = IsShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow);
ImGui::ShowDemoWindow(&m_shellViewToggles.imguiDemoWindowVisible); if (showImGuiDemoWindow) {
ImGui::ShowDemoWindow(&showImGuiDemoWindow);
SetShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow, showImGuiDemoWindow);
} }
SyncShellChromePanelStateFromPanels(); SyncShellChromePanelStateFromPanels();

View File

@@ -16,7 +16,6 @@
#include "XCUIBackend/XCUIShellChromeState.h" #include "XCUIBackend/XCUIShellChromeState.h"
#include "XCUIBackend/XCUIStandaloneTextAtlasProvider.h" #include "XCUIBackend/XCUIStandaloneTextAtlasProvider.h"
#include <array>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
@@ -31,6 +30,7 @@ namespace NewEditor {
class Application { class Application {
public: public:
using ShellChromeState = ::XCEngine::Editor::XCUIBackend::XCUIShellChromeState;
using ShellPanelId = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelId; using ShellPanelId = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
using ShellViewToggleId = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId; using ShellViewToggleId = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId;
using ShellHostedPreviewMode = ::XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode; using ShellHostedPreviewMode = ::XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
@@ -248,35 +248,6 @@ public:
int Run(HINSTANCE instance, int nCmdShow); int Run(HINSTANCE instance, int nCmdShow);
private: private:
using ShellPanelStateArray = std::array<ShellPanelChromeState, static_cast<std::size_t>(ShellPanelId::Count)>;
static constexpr std::size_t GetShellPanelIndex(ShellPanelId panelId) {
return static_cast<std::size_t>(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 { struct HostedPreviewPanelDiagnostics {
std::string debugName = {}; std::string debugName = {};
std::string debugSource = {}; std::string debugSource = {};
@@ -334,7 +305,6 @@ private:
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> CreateHostedPreviewPresenter( std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> CreateHostedPreviewPresenter(
bool nativePreview); bool nativePreview);
void ConfigureHostedPreviewPresenters(); void ConfigureHostedPreviewPresenters();
ShellPanelChromeState* TryGetShellPanelState(ShellPanelId panelId);
const ShellPanelChromeState* TryGetShellPanelState(ShellPanelId panelId) const; const ShellPanelChromeState* TryGetShellPanelState(ShellPanelId panelId) const;
bool IsShellViewToggleEnabled(ShellViewToggleId toggleId) const; bool IsShellViewToggleEnabled(ShellViewToggleId toggleId) const;
void SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled); void SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled);
@@ -379,8 +349,7 @@ private:
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry m_hostedPreviewSurfaceRegistry; ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry m_hostedPreviewSurfaceRegistry;
::XCEngine::Editor::XCUIBackend::XCUIStandaloneTextAtlasProvider m_hostedPreviewTextAtlasProvider; ::XCEngine::Editor::XCUIBackend::XCUIStandaloneTextAtlasProvider m_hostedPreviewTextAtlasProvider;
::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend m_hostedPreviewRenderBackend; ::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend m_hostedPreviewRenderBackend;
ShellViewToggleState m_shellViewToggles = {}; ShellChromeState m_shellChromeState = {};
ShellPanelStateArray m_shellPanels = CreateDefaultShellPanelStates();
std::vector<HostedPreviewOffscreenSurface> m_hostedPreviewSurfaces = {}; std::vector<HostedPreviewOffscreenSurface> m_hostedPreviewSurfaces = {};
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime; ::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime;
MainWindowNativeBackdropRenderer m_nativeBackdropRenderer; MainWindowNativeBackdropRenderer m_nativeBackdropRenderer;

View File

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

View File

@@ -23,8 +23,8 @@ public:
} }
XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override { XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override {
(void)request; m_canvasSession = BuildPassiveXCUIPanelCanvasSession(request);
return {}; return m_canvasSession;
} }
void DrawFilledRect( void DrawFilledRect(
@@ -59,7 +59,11 @@ public:
} }
void EndCanvas() override { void EndCanvas() override {
m_canvasSession = {};
} }
private:
XCUIPanelCanvasSession m_canvasSession = {};
}; };
inline std::unique_ptr<IXCUIPanelCanvasHost> CreateNullXCUIPanelCanvasHost() { inline std::unique_ptr<IXCUIPanelCanvasHost> CreateNullXCUIPanelCanvasHost() {

View File

@@ -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 { class QueuedNativeXCUIHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter {
public: public:
QueuedNativeXCUIHostedPreviewPresenter( QueuedNativeXCUIHostedPreviewPresenter(
@@ -306,6 +327,10 @@ inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateQueuedNativeXCUIHosted
return std::make_unique<QueuedNativeXCUIHostedPreviewPresenter>(queue, surfaceRegistry); return std::make_unique<QueuedNativeXCUIHostedPreviewPresenter>(queue, surfaceRegistry);
} }
inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateNullXCUIHostedPreviewPresenter() {
return std::make_unique<NullXCUIHostedPreviewPresenter>();
}
} // namespace XCUIBackend } // namespace XCUIBackend
} // namespace Editor } // namespace Editor
} // namespace XCEngine } // namespace XCEngine

View File

@@ -85,6 +85,15 @@ struct XCUIInputBridgeFrameDelta {
bool HasEventType(UI::UIInputEventType type) const; 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 { class XCUIInputBridge {
public: public:
void Reset(); void Reset();
@@ -109,16 +118,16 @@ private:
XCUIInputBridgeFrameSnapshot m_baseline = {}; XCUIInputBridgeFrameSnapshot m_baseline = {};
}; };
class XCUIWin32InputSource { class XCUIWin32InputSource : public IXCUIInputSnapshotSource {
public: public:
void Reset(); void Reset();
void HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); void HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void ClearFrameTransients(); void ClearFrameTransients();
XCUIInputBridgeFrameSnapshot CaptureSnapshot( 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; return m_pointerPosition;
} }

View File

@@ -46,6 +46,36 @@ struct XCUIPanelCanvasSession {
bool windowFocused = false; 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 { class IXCUIPanelCanvasHost {
public: public:
virtual ~IXCUIPanelCanvasHost() = default; virtual ~IXCUIPanelCanvasHost() = default;

View File

@@ -10,6 +10,17 @@ constexpr std::size_t ToIndex(XCUIShellPanelId panelId) {
return static_cast<std::size_t>(panelId); return static_cast<std::size_t>(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 } // namespace
XCUIShellChromeState::XCUIShellChromeState() { XCUIShellChromeState::XCUIShellChromeState() {
@@ -29,7 +40,7 @@ XCUIShellChromeState::XCUIShellChromeState() {
"new_editor.panels.xcui_layout_lab", "new_editor.panels.xcui_layout_lab",
true, true,
true, true,
XCUIShellHostedPreviewMode::LegacyImGui XCUIShellHostedPreviewMode::HostedPresenter
}; };
} }
@@ -104,7 +115,7 @@ XCUIShellHostedPreviewMode XCUIShellChromeState::GetHostedPreviewMode(XCUIShellP
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId); const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
return panelState != nullptr return panelState != nullptr
? panelState->previewMode ? panelState->previewMode
: XCUIShellHostedPreviewMode::LegacyImGui; : XCUIShellHostedPreviewMode::HostedPresenter;
} }
XCUIShellHostedPreviewState XCUIShellChromeState::GetHostedPreviewState(XCUIShellPanelId panelId) const { XCUIShellHostedPreviewState XCUIShellChromeState::GetHostedPreviewState(XCUIShellPanelId panelId) const {
@@ -115,15 +126,15 @@ XCUIShellHostedPreviewState XCUIShellChromeState::GetHostedPreviewState(XCUIShel
return panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen return panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen
? XCUIShellHostedPreviewState::NativeOffscreen ? XCUIShellHostedPreviewState::NativeOffscreen
: XCUIShellHostedPreviewState::LegacyImGui; : XCUIShellHostedPreviewState::HostedPresenter;
} }
bool XCUIShellChromeState::IsNativeHostedPreviewActive(XCUIShellPanelId panelId) const { bool XCUIShellChromeState::IsNativeHostedPreviewActive(XCUIShellPanelId panelId) const {
return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::NativeOffscreen; return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::NativeOffscreen;
} }
bool XCUIShellChromeState::IsLegacyHostedPreviewActive(XCUIShellPanelId panelId) const { bool XCUIShellChromeState::IsHostedPresenterPreviewActive(XCUIShellPanelId panelId) const {
return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::LegacyImGui; return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::HostedPresenter;
} }
bool XCUIShellChromeState::SetHostedPreviewMode( bool XCUIShellChromeState::SetHostedPreviewMode(
@@ -146,7 +157,7 @@ bool XCUIShellChromeState::ToggleHostedPreviewMode(XCUIShellPanelId panelId) {
panelState->previewMode = panelState->previewMode =
panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen
? XCUIShellHostedPreviewMode::LegacyImGui ? XCUIShellHostedPreviewMode::HostedPresenter
: XCUIShellHostedPreviewMode::NativeOffscreen; : XCUIShellHostedPreviewMode::NativeOffscreen;
return true; return true;
} }
@@ -205,15 +216,8 @@ bool XCUIShellChromeState::ToggleViewToggle(XCUIShellViewToggleId toggleId) {
} }
bool XCUIShellChromeState::HasCommand(std::string_view commandId) const { bool XCUIShellChromeState::HasCommand(std::string_view commandId) const {
return commandId == GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo) || XCUIShellCommandDescriptor descriptor = {};
commandId == GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab) || return TryGetCommandDescriptor(commandId, descriptor);
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);
} }
bool XCUIShellChromeState::InvokeCommand(std::string_view commandId) { bool XCUIShellChromeState::InvokeCommand(std::string_view commandId) {
@@ -248,6 +252,142 @@ bool XCUIShellChromeState::InvokeCommand(std::string_view commandId) {
return false; 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) { std::string_view XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId panelId) {
switch (panelId) { switch (panelId) {
case XCUIShellPanelId::XCUIDemo: case XCUIShellPanelId::XCUIDemo:

View File

@@ -3,6 +3,7 @@
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <string_view> #include <string_view>
#include <vector>
namespace XCEngine { namespace XCEngine {
namespace Editor { namespace Editor {
@@ -24,13 +25,13 @@ enum class XCUIShellViewToggleId : std::uint8_t {
}; };
enum class XCUIShellHostedPreviewMode : std::uint8_t { enum class XCUIShellHostedPreviewMode : std::uint8_t {
LegacyImGui = 0, HostedPresenter = 0,
NativeOffscreen NativeOffscreen
}; };
enum class XCUIShellHostedPreviewState : std::uint8_t { enum class XCUIShellHostedPreviewState : std::uint8_t {
Disabled = 0, Disabled = 0,
LegacyImGui, HostedPresenter,
NativeOffscreen NativeOffscreen
}; };
@@ -41,7 +42,7 @@ struct XCUIShellPanelChromeState {
std::string_view previewDebugSource = {}; std::string_view previewDebugSource = {};
bool visible = true; bool visible = true;
bool hostedPreviewEnabled = true; bool hostedPreviewEnabled = true;
XCUIShellHostedPreviewMode previewMode = XCUIShellHostedPreviewMode::LegacyImGui; XCUIShellHostedPreviewMode previewMode = XCUIShellHostedPreviewMode::HostedPresenter;
}; };
struct XCUIShellViewToggleState { struct XCUIShellViewToggleState {
@@ -64,6 +65,30 @@ struct XCUIShellChromeCommandIds {
static constexpr const char* ToggleNativeLayoutLabPreview = "new_editor.view.native_layout_lab_preview"; 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<XCUIShellMenuItemDescriptor> items = {};
};
class XCUIShellChromeState { class XCUIShellChromeState {
public: public:
XCUIShellChromeState(); XCUIShellChromeState();
@@ -82,7 +107,7 @@ public:
XCUIShellHostedPreviewMode GetHostedPreviewMode(XCUIShellPanelId panelId) const; XCUIShellHostedPreviewMode GetHostedPreviewMode(XCUIShellPanelId panelId) const;
XCUIShellHostedPreviewState GetHostedPreviewState(XCUIShellPanelId panelId) const; XCUIShellHostedPreviewState GetHostedPreviewState(XCUIShellPanelId panelId) const;
bool IsNativeHostedPreviewActive(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 SetHostedPreviewMode(XCUIShellPanelId panelId, XCUIShellHostedPreviewMode mode);
bool ToggleHostedPreviewMode(XCUIShellPanelId panelId); bool ToggleHostedPreviewMode(XCUIShellPanelId panelId);
@@ -92,6 +117,10 @@ public:
bool HasCommand(std::string_view commandId) const; bool HasCommand(std::string_view commandId) const;
bool InvokeCommand(std::string_view commandId); 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 GetPanelVisibilityCommandId(XCUIShellPanelId panelId);
static std::string_view GetPanelPreviewModeCommandId(XCUIShellPanelId panelId); static std::string_view GetPanelPreviewModeCommandId(XCUIShellPanelId panelId);

View File

@@ -1,8 +1,6 @@
#include "XCUIDemoPanel.h" #include "XCUIDemoPanel.h"
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" #include "XCUIBackend/NullXCUIPanelCanvasHost.h"
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
#include "XCUIBackend/ImGuiXCUIInputAdapter.h"
#include <XCEngine/UI/Types.h> #include <XCEngine/UI/Types.h>
@@ -55,15 +53,27 @@ void DrawRectOverlay(
} }
} }
const char* GetPreviewPathLabel(bool nativeHostedPreview) { const char* GetPreviewPathLabel(
return nativeHostedPreview ? "native queued offscreen surface" : "legacy imgui transition"; const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter) {
if (previewPresenter == nullptr) {
return "not injected";
}
return previewPresenter->IsNativeQueued()
? "native queued offscreen surface"
: "hosted presenter";
} }
const char* GetPreviewStateLabel( const char* GetPreviewStateLabel(
bool nativeHostedPreview, const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter,
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats, const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats,
bool hasHostedSurfaceDescriptor, bool hasHostedSurfaceDescriptor,
bool showHostedSurfaceImage) { bool showHostedSurfaceImage) {
if (previewPresenter == nullptr) {
return "disabled";
}
const bool nativeHostedPreview = previewPresenter->IsNativeQueued();
if (nativeHostedPreview) { if (nativeHostedPreview) {
if (showHostedSurfaceImage) { if (showHostedSurfaceImage) {
return "live"; return "live";
@@ -77,20 +87,37 @@ const char* GetPreviewStateLabel(
return previewStats.presented ? "live" : "idle"; 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 } // namespace
XCUIDemoPanel::XCUIDemoPanel(::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource) XCUIDemoPanel::XCUIDemoPanel(::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource)
: XCUIDemoPanel(inputSource, ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter()) {} : XCUIDemoPanel(
inputSource,
nullptr,
::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost()) {}
XCUIDemoPanel::XCUIDemoPanel( XCUIDemoPanel::XCUIDemoPanel(
::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource, ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost)
: Panel("XCUI Demo") : Panel("XCUI Demo")
, m_inputSource(inputSource) , m_inputSource(inputSource)
, m_previewPresenter(std::move(previewPresenter)) , m_previewPresenter(std::move(previewPresenter))
, m_canvasHost(::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost()) { , m_canvasHost(std::move(canvasHost)) {
if (m_previewPresenter == nullptr) { if (m_canvasHost == nullptr) {
m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
} }
m_lastReloadSucceeded = m_runtime.ReloadDocuments(); m_lastReloadSucceeded = m_runtime.ReloadDocuments();
} }
@@ -113,9 +140,6 @@ const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& XCUIDemoPanel::Ge
void XCUIDemoPanel::SetHostedPreviewPresenter( void XCUIDemoPanel::SetHostedPreviewPresenter(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) { std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) {
m_previewPresenter = std::move(previewPresenter); m_previewPresenter = std::move(previewPresenter);
if (m_previewPresenter == nullptr) {
m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter();
}
m_lastPreviewStats = {}; m_lastPreviewStats = {};
} }
@@ -123,7 +147,7 @@ void XCUIDemoPanel::SetCanvasHost(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost) { std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost) {
m_canvasHost = std::move(canvasHost); m_canvasHost = std::move(canvasHost);
if (m_canvasHost == nullptr) { 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); const float canvasHeight = (std::max)(140.0f, hostRegion.y - diagnosticsHeight);
if (m_canvasHost == nullptr) { if (m_canvasHost == nullptr) {
m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost(); m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
} }
const bool nativeHostedPreview = IsUsingNativeHostedPreview(); const bool nativeHostedPreview = IsUsingNativeHostedPreview();
@@ -174,7 +198,7 @@ void XCUIDemoPanel::Render() {
nativeHostedPreview && nativeHostedPreview &&
m_previewPresenter != nullptr && m_previewPresenter != nullptr &&
m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage); 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; const float topInset = m_showCanvasHud ? (kCanvasHudHeight + kCanvasHudPadding) : 0.0f;
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {}; ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {};
canvasRequest.childId = "XCUIDemoCanvasHost"; canvasRequest.childId = "XCUIDemoCanvasHost";
@@ -183,11 +207,11 @@ void XCUIDemoPanel::Render() {
canvasRequest.showSurfaceImage = showHostedSurfaceImage; canvasRequest.showSurfaceImage = showHostedSurfaceImage;
canvasRequest.surfaceImage = hostedSurfaceImage; canvasRequest.surfaceImage = hostedSurfaceImage;
canvasRequest.placeholderTitle = canvasRequest.placeholderTitle =
nativeHostedPreview ? "Native XCUI preview pending" : "Legacy XCUI canvas host"; nativeHostedPreview ? "Native XCUI preview pending" : "Injected XCUI canvas host";
canvasRequest.placeholderSubtitle = canvasRequest.placeholderSubtitle =
nativeHostedPreview nativeHostedPreview
? "Waiting for native queued render output to publish back into the sandbox panel." ? "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 = const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession =
m_canvasHost->BeginCanvas(canvasRequest); m_canvasHost->BeginCanvas(canvasRequest);
const UI::UIRect canvasRect = canvasSession.canvasRect; const UI::UIRect canvasRect = canvasSession.canvasRect;
@@ -211,10 +235,7 @@ void XCUIDemoPanel::Render() {
bridgeOptions.hasPointerInsideOverride = true; bridgeOptions.hasPointerInsideOverride = true;
bridgeOptions.pointerInsideOverride = validCanvas && canvasSession.hovered; bridgeOptions.pointerInsideOverride = validCanvas && canvasSession.hovered;
bridgeOptions.windowFocused = canvasSession.windowFocused; bridgeOptions.windowFocused = canvasSession.windowFocused;
snapshot = ::XCEngine::Editor::XCUIBackend::ImGuiXCUIInputAdapter::CaptureSnapshot( snapshot = BuildPassiveSnapshot(canvasSession, bridgeOptions);
ImGui::GetIO(),
bridgeOptions);
snapshot.pointerPosition = canvasSession.pointerPosition;
} }
if (!m_inputBridge.HasBaseline()) { if (!m_inputBridge.HasBaseline()) {
@@ -253,7 +274,7 @@ void XCUIDemoPanel::Render() {
const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameStats& stats = frame.stats; const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameStats& stats = frame.stats;
const char* const previewStateLabel = GetPreviewStateLabel( const char* const previewStateLabel = GetPreviewStateLabel(
nativeHostedPreview, m_previewPresenter.get(),
m_lastPreviewStats, m_lastPreviewStats,
hasHostedSurfaceDescriptor, hasHostedSurfaceDescriptor,
showHostedSurfaceImage); showHostedSurfaceImage);

View File

@@ -15,10 +15,11 @@ namespace NewEditor {
class XCUIDemoPanel : public Panel { class XCUIDemoPanel : public Panel {
public: public:
explicit XCUIDemoPanel( explicit XCUIDemoPanel(
::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource = nullptr); ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource = nullptr);
XCUIDemoPanel( XCUIDemoPanel(
::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource, ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter); std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost = nullptr);
~XCUIDemoPanel() override = default; ~XCUIDemoPanel() override = default;
void Render() override; void Render() override;
@@ -37,7 +38,7 @@ private:
bool m_hostedPreviewEnabled = true; bool m_hostedPreviewEnabled = true;
bool m_showCanvasHud = true; bool m_showCanvasHud = true;
bool m_showDebugRects = 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::XCUIInputBridge m_inputBridge;
::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime m_runtime; ::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime m_runtime;
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter; std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter;

View File

@@ -1,12 +1,13 @@
#include "XCUILayoutLabPanel.h" #include "XCUILayoutLabPanel.h"
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" #include "XCUIBackend/NullXCUIPanelCanvasHost.h"
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
#include <XCEngine/UI/Types.h> #include <XCEngine/UI/Types.h>
#include <imgui.h> #include <imgui.h>
#include <algorithm> #include <algorithm>
#include <chrono>
#include <cstdint>
#include <utility> #include <utility>
namespace XCEngine { namespace XCEngine {
@@ -24,15 +25,27 @@ bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) {
point.y <= rect.y + rect.height; point.y <= rect.y + rect.height;
} }
const char* GetPreviewPathLabel(bool nativeHostedPreview) { const char* GetPreviewPathLabel(
return nativeHostedPreview ? "native queued offscreen surface" : "legacy imgui transition"; const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter) {
if (previewPresenter == nullptr) {
return "not injected";
}
return previewPresenter->IsNativeQueued()
? "native queued offscreen surface"
: "hosted presenter";
} }
const char* GetPreviewStateLabel( const char* GetPreviewStateLabel(
bool nativeHostedPreview, const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter,
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats, const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats,
bool hasHostedSurfaceDescriptor, bool hasHostedSurfaceDescriptor,
bool showHostedSurfaceImage) { bool showHostedSurfaceImage) {
if (previewPresenter == nullptr) {
return "disabled";
}
const bool nativeHostedPreview = previewPresenter->IsNativeQueued();
if (nativeHostedPreview) { if (nativeHostedPreview) {
if (showHostedSurfaceImage) { if (showHostedSurfaceImage) {
return "live"; return "live";
@@ -46,6 +59,12 @@ const char* GetPreviewStateLabel(
return previewStats.presented ? "live" : "idle"; return previewStats.presented ? "live" : "idle";
} }
bool ContainsKeyTransition(
const std::vector<std::int32_t>& keys,
std::int32_t keyCode) {
return std::find(keys.begin(), keys.end(), keyCode) != keys.end();
}
bool ShouldCaptureKeyboardNavigation( bool ShouldCaptureKeyboardNavigation(
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession, const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession,
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& previousFrame) { const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& previousFrame) {
@@ -60,33 +79,56 @@ bool ShouldCaptureKeyboardNavigation(
void PopulateKeyboardNavigationInput( void PopulateKeyboardNavigationInput(
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState& input, ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState& input,
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta,
bool captureKeyboardNavigation) { bool captureKeyboardNavigation) {
if (!captureKeyboardNavigation) { if (!captureKeyboardNavigation) {
return; return;
} }
input.navigatePrevious = ImGui::IsKeyPressed(ImGuiKey_UpArrow); using ::XCEngine::Input::KeyCode;
input.navigateNext = ImGui::IsKeyPressed(ImGuiKey_DownArrow); const auto pressedThisFrame =
input.navigateHome = ImGui::IsKeyPressed(ImGuiKey_Home); [&frameDelta](KeyCode keyCode) {
input.navigateEnd = ImGui::IsKeyPressed(ImGuiKey_End); const std::int32_t code = static_cast<std::int32_t>(keyCode);
input.navigateCollapse = ImGui::IsKeyPressed(ImGuiKey_LeftArrow); return ContainsKeyTransition(frameDelta.keyboard.pressedKeys, code) ||
input.navigateExpand = ImGui::IsKeyPressed(ImGuiKey_RightArrow); 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 } // namespace
XCUILayoutLabPanel::XCUILayoutLabPanel(::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource) XCUILayoutLabPanel::XCUILayoutLabPanel(::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource)
: XCUILayoutLabPanel(inputSource, ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter()) {} : XCUILayoutLabPanel(inputSource, nullptr, ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost()) {}
XCUILayoutLabPanel::XCUILayoutLabPanel( XCUILayoutLabPanel::XCUILayoutLabPanel(
::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource, ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost)
: Panel("XCUI Layout Lab") : Panel("XCUI Layout Lab")
, m_inputSource(inputSource) , m_inputSource(inputSource)
, m_previewPresenter(std::move(previewPresenter)) , m_previewPresenter(std::move(previewPresenter))
, m_canvasHost(::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost()) { , m_canvasHost(std::move(canvasHost)) {
if (m_previewPresenter == nullptr) { if (m_canvasHost == nullptr) {
m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
} }
m_lastReloadSucceeded = m_runtime.ReloadDocuments(); m_lastReloadSucceeded = m_runtime.ReloadDocuments();
} }
@@ -113,9 +155,6 @@ bool XCUILayoutLabPanel::TryGetElementRect(const std::string& elementId, ::XCEng
void XCUILayoutLabPanel::SetHostedPreviewPresenter( void XCUILayoutLabPanel::SetHostedPreviewPresenter(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) { std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) {
m_previewPresenter = std::move(previewPresenter); m_previewPresenter = std::move(previewPresenter);
if (m_previewPresenter == nullptr) {
m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter();
}
m_lastPreviewStats = {}; m_lastPreviewStats = {};
} }
@@ -123,7 +162,7 @@ void XCUILayoutLabPanel::SetCanvasHost(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost) { std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost) {
m_canvasHost = std::move(canvasHost); m_canvasHost = std::move(canvasHost);
if (m_canvasHost == nullptr) { 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); const float canvasHeight = (std::max)(140.0f, hostRegion.y - diagnosticsHeight);
if (m_canvasHost == nullptr) { if (m_canvasHost == nullptr) {
m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost(); m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
} }
const bool nativeHostedPreview = IsUsingNativeHostedPreview(); const bool nativeHostedPreview = IsUsingNativeHostedPreview();
@@ -170,18 +209,18 @@ void XCUILayoutLabPanel::Render() {
nativeHostedPreview && nativeHostedPreview &&
m_previewPresenter != nullptr && m_previewPresenter != nullptr &&
m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage); m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage);
const char* const previewPathLabel = GetPreviewPathLabel(nativeHostedPreview); const char* const previewPathLabel = GetPreviewPathLabel(m_previewPresenter.get());
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {}; ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {};
canvasRequest.childId = "XCUILayoutLabCanvasHost"; canvasRequest.childId = "XCUILayoutLabCanvasHost";
canvasRequest.height = canvasHeight; canvasRequest.height = canvasHeight;
canvasRequest.showSurfaceImage = showHostedSurfaceImage; canvasRequest.showSurfaceImage = showHostedSurfaceImage;
canvasRequest.surfaceImage = hostedSurfaceImage; canvasRequest.surfaceImage = hostedSurfaceImage;
canvasRequest.placeholderTitle = canvasRequest.placeholderTitle =
nativeHostedPreview ? "Native layout preview pending" : "Legacy layout canvas host"; nativeHostedPreview ? "Native layout preview pending" : "Injected layout canvas host";
canvasRequest.placeholderSubtitle = canvasRequest.placeholderSubtitle =
nativeHostedPreview nativeHostedPreview
? "Waiting for native queued render output to publish back into the layout sandbox." ? "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.badgeTitle = "Layout Lab";
canvasRequest.badgeSubtitle = previewPathLabel; canvasRequest.badgeSubtitle = previewPathLabel;
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession =
@@ -189,18 +228,40 @@ void XCUILayoutLabPanel::Render() {
const UI::UIRect canvasRect = canvasSession.canvasRect; const UI::UIRect canvasRect = canvasSession.canvasRect;
const bool validCanvas = canvasSession.validCanvas; const bool validCanvas = canvasSession.validCanvas;
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions bridgeOptions = {};
bridgeOptions.timestampNanoseconds = static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
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 = {}; ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = {};
input.canvasRect = canvasRect; input.canvasRect = canvasRect;
if (m_inputSource != nullptr) { input.pointerPosition = snapshot.pointerPosition;
input.pointerPosition = m_inputSource->GetPointerPosition(); input.pointerInside = snapshot.pointerInside;
input.pointerInside = validCanvas && ContainsPoint(input.canvasRect, input.pointerPosition); input.pointerPressed = input.pointerInside && frameDelta.pointer.pressed[0];
} else {
input.pointerPosition = canvasSession.pointerPosition;
input.pointerInside = validCanvas && canvasSession.hovered;
}
input.pointerPressed = input.pointerInside && ImGui::IsMouseClicked(ImGuiMouseButton_Left);
PopulateKeyboardNavigationInput( PopulateKeyboardNavigationInput(
input, input,
frameDelta,
ShouldCaptureKeyboardNavigation(canvasSession, m_runtime.GetFrameResult())); ShouldCaptureKeyboardNavigation(canvasSession, m_runtime.GetFrameResult()));
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& frame = m_runtime.Update(input); 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 ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameStats& stats = frame.stats;
const char* const previewStateLabel = GetPreviewStateLabel( const char* const previewStateLabel = GetPreviewStateLabel(
nativeHostedPreview, m_previewPresenter.get(),
m_lastPreviewStats, m_lastPreviewStats,
hasHostedSurfaceDescriptor, hasHostedSurfaceDescriptor,
showHostedSurfaceImage); showHostedSurfaceImage);
@@ -266,7 +327,7 @@ void XCUILayoutLabPanel::Render() {
ImGui::TextDisabled("No native surface descriptor has been published back yet."); ImGui::TextDisabled("No native surface descriptor has been published back yet.");
} }
} else { } 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"); ImGui::SeparatorText("Runtime");

View File

@@ -16,10 +16,11 @@ namespace NewEditor {
class XCUILayoutLabPanel : public Panel { class XCUILayoutLabPanel : public Panel {
public: public:
explicit XCUILayoutLabPanel( explicit XCUILayoutLabPanel(
::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource = nullptr); ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource = nullptr);
XCUILayoutLabPanel( XCUILayoutLabPanel(
::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource, ::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter); std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost = nullptr);
~XCUILayoutLabPanel() override = default; ~XCUILayoutLabPanel() override = default;
void Render() override; void Render() override;
@@ -37,7 +38,8 @@ public:
private: private:
bool m_lastReloadSucceeded = false; bool m_lastReloadSucceeded = false;
bool m_hostedPreviewEnabled = true; 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; ::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_runtime;
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter; std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter;
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost; std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost;

View File

@@ -46,6 +46,12 @@ set(NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER
set(NEW_EDITOR_LAYOUT_LAB_PANEL_SOURCE set(NEW_EDITOR_LAYOUT_LAB_PANEL_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUILayoutLabPanel.cpp ${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 set(NEW_EDITOR_BASE_PANEL_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/panels/Panel.cpp ${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_LAYOUT_LAB_RUNTIME_SOURCE}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}" 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_CURRENT_SOURCE_DIR}/test_xcui_layout_lab_panel.cpp" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp") EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_xcui_layout_lab_panel_tests 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_BASE_PANEL_SOURCE}
${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE} ${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE}
${NEW_EDITOR_ASSET_DOCUMENT_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.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.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_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) xcengine_discover_new_editor_gtests(new_editor_xcui_layout_lab_panel_tests)
else() 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() endif()
if(EXISTS "${NEW_EDITOR_BACKEND_HEADER}" AND EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp") 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 if(EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}" 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_CURRENT_SOURCE_DIR}/test_imgui_xcui_input_adapter.cpp" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp") EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_imgui_xcui_input_adapter_tests add_executable(new_editor_imgui_xcui_input_adapter_tests
test_imgui_xcui_input_adapter.cpp test_imgui_xcui_input_adapter.cpp
${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE} ${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.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.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_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) xcengine_discover_new_editor_gtests(new_editor_imgui_xcui_input_adapter_tests)
else() 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() endif()
if(EXISTS "${NEW_EDITOR_RHI_COMMAND_SUPPORT_HEADER}") if(EXISTS "${NEW_EDITOR_RHI_COMMAND_SUPPORT_HEADER}")

View File

@@ -38,59 +38,77 @@ struct ShellCommandHarness {
"new_editor.panels.xcui_layout_lab", "new_editor.panels.xcui_layout_lab",
true, true,
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 { const Application::ShellPanelChromeState& Panel(Application::ShellPanelId panelId) const {
return panels[ToPanelIndex(panelId)]; return panels[ToPanelIndex(panelId)];
} }
Application::ShellCommandBindings BuildBindings() { Application::ShellCommandBindings BuildBindings() {
Application::ShellCommandBindings bindings = {}; 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) { bindings.setXCUIDemoPanelVisible = [this](bool visible) {
Panel(Application::ShellPanelId::XCUIDemo).visible = visible; panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)].visible = visible;
}; };
bindings.getXCUILayoutLabPanelVisible = [this]() { bindings.getXCUILayoutLabPanelVisible = [this]() {
return Panel(Application::ShellPanelId::XCUILayoutLab).visible; return Panel(Application::ShellPanelId::XCUILayoutLab).visible;
}; };
bindings.setXCUILayoutLabPanelVisible = [this](bool 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]() { bindings.getNativeDemoPanelPreviewEnabled = [this]() {
return Panel(Application::ShellPanelId::XCUIDemo).previewMode == return Panel(Application::ShellPanelId::XCUIDemo).previewMode ==
Application::ShellHostedPreviewMode::NativeOffscreen; Application::ShellHostedPreviewMode::NativeOffscreen;
}; };
bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) { bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) {
Panel(Application::ShellPanelId::XCUIDemo).previewMode = panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)].previewMode =
enabled enabled
? Application::ShellHostedPreviewMode::NativeOffscreen ? Application::ShellHostedPreviewMode::NativeOffscreen
: Application::ShellHostedPreviewMode::LegacyImGui; : Application::ShellHostedPreviewMode::HostedPresenter;
}; };
bindings.getNativeLayoutLabPreviewEnabled = [this]() { bindings.getNativeLayoutLabPreviewEnabled = [this]() {
return Panel(Application::ShellPanelId::XCUILayoutLab).previewMode == return Panel(Application::ShellPanelId::XCUILayoutLab).previewMode ==
Application::ShellHostedPreviewMode::NativeOffscreen; Application::ShellHostedPreviewMode::NativeOffscreen;
}; };
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) { bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
Panel(Application::ShellPanelId::XCUILayoutLab).previewMode = panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].previewMode =
enabled enabled
? Application::ShellHostedPreviewMode::NativeOffscreen ? Application::ShellHostedPreviewMode::NativeOffscreen
: Application::ShellHostedPreviewMode::LegacyImGui; : Application::ShellHostedPreviewMode::HostedPresenter;
}; };
bindings.onHostedPreviewModeChanged = [this]() { ++hostedPreviewReconfigureCount; }; bindings.onHostedPreviewModeChanged = [this]() { ++hostedPreviewReconfigureCount; };
return bindings; return bindings;
@@ -134,7 +152,7 @@ TEST(ApplicationShellCommandBindingsTest, PreviewModeCommandsTriggerHostedPrevie
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeDemoPanelPreview)); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeDemoPanelPreview));
EXPECT_EQ( EXPECT_EQ(
harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode, harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode,
Application::ShellHostedPreviewMode::LegacyImGui); Application::ShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1); EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1);
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeLayoutLabPreview)); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeLayoutLabPreview));
@@ -243,7 +261,7 @@ TEST(ApplicationShellCommandBindingsTest, PreviewShortcutInvokesCommandHandlerAn
EXPECT_TRUE(router.InvokeMatchingShortcut({ &previewSnapshot })); EXPECT_TRUE(router.InvokeMatchingShortcut({ &previewSnapshot }));
EXPECT_EQ( EXPECT_EQ(
harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode, harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode,
Application::ShellHostedPreviewMode::LegacyImGui); Application::ShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1); EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1);
} }

View File

@@ -27,6 +27,11 @@ void PrepareImGui(float width = 1024.0f, float height = 768.0f) {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2(width, height); io.DisplaySize = ImVec2(width, height);
io.DeltaTime = 1.0f / 60.0f; 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<ImTextureID>(1));
} }
} // namespace } // namespace
@@ -36,17 +41,17 @@ TEST(ImGuiXCUIInputAdapterTest, CaptureSnapshotMapsImGuiStateIntoXCUIFrameSnapsh
PrepareImGui(800.0f, 600.0f); PrepareImGui(800.0f, 600.0f);
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
io.MousePos = ImVec2(120.0f, 72.0f);
io.MouseDown[0] = true;
io.MouseWheel = 1.0f;
io.WantCaptureMouse = true; io.WantCaptureMouse = true;
io.WantCaptureKeyboard = true; io.WantCaptureKeyboard = true;
io.WantTextInput = true; io.WantTextInput = true;
io.AddMousePosEvent(120.0f, 72.0f); io.KeyCtrl = true;
io.AddMouseButtonEvent(0, true); io.KeysData[ImGuiKey_LeftCtrl - ImGuiKey_NamedKey_BEGIN].Down = true;
io.AddMouseWheelEvent(0.0f, 1.0f); io.KeysData[ImGuiKey_P - ImGuiKey_NamedKey_BEGIN].Down = true;
io.AddKeyEvent(ImGuiKey_LeftCtrl, true); io.InputQueueCharacters.resize(0);
io.AddKeyEvent(ImGuiKey_P, true); io.InputQueueCharacters.push_back(static_cast<ImWchar>('p'));
io.AddInputCharacter('p');
ImGui::NewFrame();
XCUIInputBridgeCaptureOptions options = {}; XCUIInputBridgeCaptureOptions options = {};
options.pointerOffset = XCEngine::UI::UIPoint(20.0f, 12.0f); options.pointerOffset = XCEngine::UI::UIPoint(20.0f, 12.0f);
@@ -55,8 +60,6 @@ TEST(ImGuiXCUIInputAdapterTest, CaptureSnapshotMapsImGuiStateIntoXCUIFrameSnapsh
const auto snapshot = ImGuiXCUIInputAdapter::CaptureSnapshot(io, options); const auto snapshot = ImGuiXCUIInputAdapter::CaptureSnapshot(io, options);
ImGui::EndFrame();
EXPECT_FLOAT_EQ(snapshot.pointerPosition.x, 100.0f); EXPECT_FLOAT_EQ(snapshot.pointerPosition.x, 100.0f);
EXPECT_FLOAT_EQ(snapshot.pointerPosition.y, 60.0f); EXPECT_FLOAT_EQ(snapshot.pointerPosition.y, 60.0f);
EXPECT_TRUE(snapshot.pointerInside); EXPECT_TRUE(snapshot.pointerInside);

View File

@@ -0,0 +1,213 @@
#include <gtest/gtest.h>
#include "panels/XCUIDemoPanel.h"
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
#include "XCUIBackend/XCUIPanelCanvasHost.h"
#include <imgui.h>
#include <memory>
#include <string>
#include <string_view>
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<ImTextureID>(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<StubCanvasHost>();
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>();
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>();
RecordingHostedPreviewPresenter* previewPresenterPtr = previewPresenter.get();
auto canvasHost = std::make_unique<StubCanvasHost>();
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

View File

@@ -12,6 +12,7 @@
namespace { namespace {
using XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter; using XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::CreateNullXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter; using XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::IImGuiXCUIHostedPreviewTargetBinding; using XCEngine::Editor::XCUIBackend::IImGuiXCUIHostedPreviewTargetBinding;
using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter; using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter;
@@ -92,6 +93,80 @@ TEST(XCUIHostedPreviewPresenterTest, PresentReturnsFalseAndClearsStatsWhenFrameH
EXPECT_EQ(stats.flushedCommandCount, 0u); EXPECT_EQ(stats.flushedCommandCount, 0u);
} }
TEST(XCUIHostedPreviewPresenterTest, NullPresenterReturnsFalseAndClearsStatsWhenFrameHasNoDrawData) {
std::unique_ptr<IXCUIHostedPreviewPresenter> 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<IXCUIHostedPreviewPresenter> 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<IXCUIHostedPreviewPresenter> 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) { TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoExplicitBindingResolvedImGuiDrawList) {
ImGuiContextScope contextScope; ImGuiContextScope contextScope;
PrepareImGui(800.0f, 600.0f); PrepareImGui(800.0f, 600.0f);
@@ -229,7 +304,7 @@ TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoExplicitBindingRe
EXPECT_GE(targetDrawList->CmdBuffer.Size, baselineCommandCount); EXPECT_GE(targetDrawList->CmdBuffer.Size, baselineCommandCount);
} }
TEST(XCUIHostedPreviewPresenterTest, DefaultFactoryStillUsesCurrentWindowBindingForLegacyImGuiPath) { TEST(XCUIHostedPreviewPresenterTest, DefaultFactoryStillUsesCurrentWindowBindingForImGuiPresenterPath) {
ImGuiContextScope contextScope; ImGuiContextScope contextScope;
PrepareImGui(800.0f, 600.0f); PrepareImGui(800.0f, 600.0f);

View File

@@ -2,6 +2,7 @@
#include "panels/XCUILayoutLabPanel.h" #include "panels/XCUILayoutLabPanel.h"
#include "XCUIBackend/ImGuiXCUIInputSource.h"
#include "XCUIBackend/XCUIHostedPreviewPresenter.h" #include "XCUIBackend/XCUIHostedPreviewPresenter.h"
#include "XCUIBackend/XCUIPanelCanvasHost.h" #include "XCUIBackend/XCUIPanelCanvasHost.h"
@@ -15,6 +16,7 @@ namespace {
using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter; using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost; using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::ImGuiXCUIInputSnapshotSource;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame; using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats; using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest; using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
@@ -155,13 +157,15 @@ void ClickElement(
RenderPanelFrame( RenderPanelFrame(
panel, panel,
contextScope, contextScope,
[](ImGuiIO& io) { [clickPoint](ImGuiIO& io) {
io.AddMousePosEvent(clickPoint.x, clickPoint.y);
io.AddMouseButtonEvent(ImGuiMouseButton_Left, true); io.AddMouseButtonEvent(ImGuiMouseButton_Left, true);
}); });
RenderPanelFrame( RenderPanelFrame(
panel, panel,
contextScope, contextScope,
[](ImGuiIO& io) { [clickPoint](ImGuiIO& io) {
io.AddMousePosEvent(clickPoint.x, clickPoint.y);
io.AddMouseButtonEvent(ImGuiMouseButton_Left, false); io.AddMouseButtonEvent(ImGuiMouseButton_Left, false);
}); });
} }
@@ -190,10 +194,10 @@ TEST(NewEditorXCUILayoutLabPanelTest, MapsPreviousNextHomeAndEndIntoRuntimeNavig
auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>(); auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>();
auto canvasHost = std::make_unique<StubCanvasHost>(); auto canvasHost = std::make_unique<StubCanvasHost>();
StubCanvasHost* canvasHostPtr = canvasHost.get(); 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.SetHostedPreviewEnabled(false);
panel.SetCanvasHost(std::move(canvasHost));
RenderPanelFrame(panel, contextScope); RenderPanelFrame(panel, contextScope);
ClickElement(panel, *canvasHostPtr, "assetLighting", contextScope); ClickElement(panel, *canvasHostPtr, "assetLighting", contextScope);
@@ -223,10 +227,10 @@ TEST(NewEditorXCUILayoutLabPanelTest, MapsCollapseAndExpandIntoRuntimeNavigation
auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>(); auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>();
auto canvasHost = std::make_unique<StubCanvasHost>(); auto canvasHost = std::make_unique<StubCanvasHost>();
StubCanvasHost* canvasHostPtr = canvasHost.get(); 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.SetHostedPreviewEnabled(false);
panel.SetCanvasHost(std::move(canvasHost));
RenderPanelFrame(panel, contextScope); RenderPanelFrame(panel, contextScope);
ClickElement(panel, *canvasHostPtr, "treeScenes", contextScope); ClickElement(panel, *canvasHostPtr, "treeScenes", contextScope);
@@ -250,4 +254,17 @@ TEST(NewEditorXCUILayoutLabPanelTest, MapsCollapseAndExpandIntoRuntimeNavigation
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeScenes"); 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 } // namespace

View File

@@ -5,7 +5,9 @@
namespace { namespace {
using XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost; using XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::BuildPassiveXCUIPanelCanvasSession;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost; using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::ResolveXCUIPanelCanvasChildId;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend; using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostCapabilities; using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostCapabilities;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest; using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
@@ -24,7 +26,41 @@ TEST(NewEditorXCUIPanelCanvasHostTest, NullHostReportsExplicitBackendAndCapabili
EXPECT_FALSE(capabilities.supportsPrimitiveOverlays); 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<IXCUIPanelCanvasHost> host = CreateNullXCUIPanelCanvasHost(); std::unique_ptr<IXCUIPanelCanvasHost> host = CreateNullXCUIPanelCanvasHost();
ASSERT_NE(host, nullptr); ASSERT_NE(host, nullptr);
@@ -43,9 +79,12 @@ TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsEmptySessionAnd
EXPECT_FALSE(session.hovered); EXPECT_FALSE(session.hovered);
EXPECT_FALSE(session.windowFocused); EXPECT_FALSE(session.windowFocused);
EXPECT_FLOAT_EQ(session.hostRect.width, 0.0f); 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.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( host->DrawFilledRect(
XCEngine::UI::UIRect(10.0f, 12.0f, 48.0f, 64.0f), 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), XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
16.0f); 16.0f);
host->EndCanvas(); 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 } // namespace

View File

@@ -5,9 +5,11 @@
namespace { namespace {
using XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds; using XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds;
using XCEngine::Editor::XCUIBackend::XCUIShellCommandDescriptor;
using XCEngine::Editor::XCUIBackend::XCUIShellChromeState; using XCEngine::Editor::XCUIBackend::XCUIShellChromeState;
using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode; using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewState; using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewState;
using XCEngine::Editor::XCUIBackend::XCUIShellMenuItemKind;
using XCEngine::Editor::XCUIBackend::XCUIShellPanelId; using XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
using XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId; using XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId;
@@ -33,7 +35,7 @@ TEST(XCUIShellChromeStateTest, DefaultsMatchCurrentShellChromeConfiguration) {
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo), state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::NativeOffscreen); XCUIShellHostedPreviewState::NativeOffscreen);
EXPECT_TRUE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); 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); const auto* layoutLabPanel = state.TryGetPanelState(XCUIShellPanelId::XCUILayoutLab);
ASSERT_NE(layoutLabPanel, nullptr); ASSERT_NE(layoutLabPanel, nullptr);
@@ -42,12 +44,12 @@ TEST(XCUIShellChromeStateTest, DefaultsMatchCurrentShellChromeConfiguration) {
EXPECT_EQ(layoutLabPanel->previewDebugSource, "new_editor.panels.xcui_layout_lab"); EXPECT_EQ(layoutLabPanel->previewDebugSource, "new_editor.panels.xcui_layout_lab");
EXPECT_TRUE(layoutLabPanel->visible); EXPECT_TRUE(layoutLabPanel->visible);
EXPECT_TRUE(layoutLabPanel->hostedPreviewEnabled); EXPECT_TRUE(layoutLabPanel->hostedPreviewEnabled);
EXPECT_EQ(layoutLabPanel->previewMode, XCUIShellHostedPreviewMode::LegacyImGui); EXPECT_EQ(layoutLabPanel->previewMode, XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ( EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUILayoutLab), state.GetHostedPreviewState(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewState::LegacyImGui); XCUIShellHostedPreviewState::HostedPresenter);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab)); EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab));
EXPECT_TRUE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab)); EXPECT_TRUE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUILayoutLab));
} }
TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateChanges) { TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateChanges) {
@@ -64,7 +66,7 @@ TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateCh
EXPECT_FALSE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUILayoutLab, false)); EXPECT_FALSE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUILayoutLab, false));
EXPECT_EQ( EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab), state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::LegacyImGui); XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_TRUE(state.ToggleHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab)); EXPECT_TRUE(state.ToggleHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab));
EXPECT_EQ( EXPECT_EQ(
@@ -72,13 +74,13 @@ TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateCh
XCUIShellHostedPreviewMode::NativeOffscreen); XCUIShellHostedPreviewMode::NativeOffscreen);
EXPECT_TRUE(state.SetHostedPreviewMode( EXPECT_TRUE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUILayoutLab, XCUIShellPanelId::XCUILayoutLab,
XCUIShellHostedPreviewMode::LegacyImGui)); XCUIShellHostedPreviewMode::HostedPresenter));
EXPECT_EQ( EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab), state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::LegacyImGui); XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_FALSE(state.SetHostedPreviewMode( EXPECT_FALSE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUILayoutLab, XCUIShellPanelId::XCUILayoutLab,
XCUIShellHostedPreviewMode::LegacyImGui)); XCUIShellHostedPreviewMode::HostedPresenter));
} }
TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequestedMode) { TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequestedMode) {
@@ -93,14 +95,14 @@ TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequeste
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo), state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::Disabled); XCUIShellHostedPreviewState::Disabled);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_FALSE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); EXPECT_FALSE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.SetHostedPreviewMode( EXPECT_TRUE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUIDemo, XCUIShellPanelId::XCUIDemo,
XCUIShellHostedPreviewMode::LegacyImGui)); XCUIShellHostedPreviewMode::HostedPresenter));
EXPECT_EQ( EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUIDemo), state.GetHostedPreviewMode(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewMode::LegacyImGui); XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ( EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo), state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::Disabled); XCUIShellHostedPreviewState::Disabled);
@@ -108,9 +110,9 @@ TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequeste
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, true)); EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, true));
EXPECT_EQ( EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo), state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::LegacyImGui); XCUIShellHostedPreviewState::HostedPresenter);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo)); EXPECT_TRUE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUIDemo));
} }
TEST(XCUIShellChromeStateTest, ViewToggleMutatorsOnlyFlipRequestedFlags) { TEST(XCUIShellChromeStateTest, ViewToggleMutatorsOnlyFlipRequestedFlags) {
@@ -148,11 +150,115 @@ TEST(XCUIShellChromeStateTest, CommandInterfaceTogglesShellViewAndPreviewStates)
EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview)); EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview));
EXPECT_EQ( EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab), state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::LegacyImGui); XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_FALSE(state.InvokeCommand("new_editor.view.unknown")); 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) { TEST(XCUIShellChromeStateTest, PanelCommandIdHelpersMatchCurrentShellCommands) {
EXPECT_EQ( EXPECT_EQ(
XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo), XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo),
@@ -200,12 +306,12 @@ TEST(XCUIShellChromeStateTest, InvalidPanelAndToggleIdsFailGracefully) {
EXPECT_FALSE(state.SetHostedPreviewEnabled(invalidPanelId, false)); EXPECT_FALSE(state.SetHostedPreviewEnabled(invalidPanelId, false));
EXPECT_EQ( EXPECT_EQ(
state.GetHostedPreviewMode(invalidPanelId), state.GetHostedPreviewMode(invalidPanelId),
XCUIShellHostedPreviewMode::LegacyImGui); XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ( EXPECT_EQ(
state.GetHostedPreviewState(invalidPanelId), state.GetHostedPreviewState(invalidPanelId),
XCUIShellHostedPreviewState::Disabled); XCUIShellHostedPreviewState::Disabled);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(invalidPanelId)); 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.SetHostedPreviewMode(invalidPanelId, XCUIShellHostedPreviewMode::NativeOffscreen));
EXPECT_FALSE(state.ToggleHostedPreviewMode(invalidPanelId)); EXPECT_FALSE(state.ToggleHostedPreviewMode(invalidPanelId));