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
- `new_editor` remains the isolated XCUI sandbox.
- Native hosted preview is working as `RHI offscreen surface -> ImGui shell texture embed`.
- Native hosted preview is working as `RHI offscreen surface -> hosted surface image present` through the current shell adapter path.
- Hosted preview surface descriptors now stay on XCUI-owned value types (`UITextureHandle`, `UIPoint`, `UIRect`) instead of exposing ImGui texture/UV types through the generic preview contract.
- `XCUI Demo` remains the long-lived effect and behavior testbed.
- `XCUI Demo` now covers both single-line and multiline text authoring behavior, including click caret placement, delete/backspace, tab indentation, and optional text-area line numbers.
@@ -78,6 +78,8 @@ Current gap:
- `XCUI Demo` now exports pending per-frame command ids through `DrainPendingCommandIds()`, so editor-side hosts have a clean seam for observing demo/runtime command traffic without parsing draw data.
- `XCUI Demo` and `LayoutLab` panel canvases are now being pulled behind a dedicated `IXCUIPanelCanvasHost` seam, so canvas surface presentation, hover/focus fallback state, and overlay draw hooks no longer have to stay hard-coded inside each ImGui panel implementation.
- The panel-canvas seam now also exposes explicit backend/capability metadata and a minimal `NullXCUIPanelCanvasHost`, so non-ImGui host paths have a concrete placeholder backend instead of relying on an implicit ImGui default.
- `XCUI Demo` and `LayoutLab` panel input now also flows through an explicit `IXCUIInputSnapshotSource` seam, so panel/runtime code no longer reads `ImGuiIO` / `ImGui::IsKeyPressed` / `ImGui::IsMouseClicked` directly when the shell wants to use an ImGui adapter.
- `new_editor` now also has an explicit `ImGuiXCUIInputSnapshotSource` adapter, keeping ImGui-specific input capture in the host adapter layer instead of inside panel/runtime update code.
- Panel diagnostics were expanded to clearly separate preview/runtime/input state and native vs legacy paths.
- The editor bridge layer now has smoke coverage for swapchain after-UI rendering hooks and SRV-backed ImGui texture descriptor registration.
- `Application` no longer owns the ImGui backend directly; window presentation now routes through `IWindowUICompositor` with an `ImGuiWindowUICompositor` implementation, which currently delegates to `IEditorHostCompositor` / `ImGuiHostCompositor`.
@@ -90,29 +92,33 @@ Current gap:
- `LayoutLab` panel input now also maps concrete arrow/home/end keys into those shared navigation actions, so keyboard traversal is reachable from the sandbox UI instead of staying runtime-only.
- `XCUIDemoRuntime` now bridges pointer activation, text-edit commands, and shortcut-triggered commands through a unified command path, and `DrainPendingCommandIds()` now preserves mixed pointer/text/shortcut ordering.
- `new_editor` now also has a pure `XCUIShellChromeState` model covering panel visibility, hosted-preview mode, and shell-level view toggles without depending on ImGui, `Application`, or the old editor.
- `XCUIShellChromeState` hosted-preview mode naming is now backend-neutral (`HostedPresenter` / `NativeOffscreen`) instead of encoding `ImGui` into the XCUI shell model.
- `XCNewEditor` builds successfully to `build/new_editor/bin/Debug/XCNewEditor.exe`.
Current gap:
- The shell is still ImGui-hosted.
- Legacy hosted preview still depends on an ImGui-specific inline draw target binding for presentation.
- The new panel-canvas seam still only has an ImGui adapter today; a native panel/shell host still needs to replace it before ImGui can stop being the default editor host path.
- Hosted-preview compatibility presentation still depends on an ImGui-specific inline draw target binding when the native queued surface path is disabled.
- The panel-canvas seam still only has an ImGui adapter today; a native panel/shell host still needs to replace it before the editor shell can stop depending on ImGui host chrome.
- Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, and icon-atlas widgets are not yet extracted into reusable XCUI modules, and broader editor-host keybinding plus full shell-state adoption are still only partially integrated.
## Validated This Phase
- `new_editor_xcui_demo_panel_tests`: `3/3`
- `new_editor_xcui_demo_runtime_tests`: `12/12`
- `new_editor_xcui_input_bridge_tests`: `4/4`
- `new_editor_imgui_xcui_input_adapter_tests`: `2/2`
- `new_editor_xcui_layout_lab_runtime_tests`: `12/12`
- `new_editor_xcui_rhi_command_compiler_tests`: `6/6`
- `new_editor_xcui_rhi_render_backend_tests`: `5/5`
- `new_editor_xcui_hosted_preview_presenter_tests`: `17/17`
- `new_editor_xcui_hosted_preview_presenter_tests`: `20/20`
- `new_editor_imgui_window_ui_compositor_tests`: `7/7`
- `new_editor_xcui_editor_command_router_tests`: `5/5`
- `new_editor_application_shell_command_bindings_tests`: `6/6`
- `new_editor_xcui_shell_chrome_state_tests`: `8/8`
- `new_editor_xcui_panel_canvas_host_tests`: `2/2`
- `new_editor_xcui_shell_chrome_state_tests`: `11/11`
- `new_editor_xcui_panel_canvas_host_tests`: `4/4`
- `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1`
- `new_editor_xcui_layout_lab_panel_tests`: `2/2`
- `new_editor_xcui_layout_lab_panel_tests`: `3/3`
- `XCNewEditor` Debug target builds successfully
- `core_ui_tests`: `52 total` (`50` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`)
- `scene_tests`: `68/68`
@@ -199,8 +205,12 @@ Current gap:
- shell-level shortcuts now flow from `XCUIWin32InputSource` through `XCUIInputBridge` into command matching
- hosted-preview mode toggles still trigger presenter reconfiguration through the routed command bindings
- `new_editor` panel canvas ownership is now being split behind `IXCUIPanelCanvasHost`, with an `ImGuiXCUIPanelCanvasHost` adapter carrying the legacy path so panel code stops directly owning `ImGui::Image` / `ImGui::InvisibleButton` / draw-list preview plumbing.
- `XCUIDemoPanel` and `XCUILayoutLabPanel` no longer create an ImGui hosted-preview presenter or ImGui panel canvas host implicitly; default construction now stays on null/explicitly injected backends until the outer shell binds a concrete host adapter.
- `Application` now binds `ImGuiXCUIPanelCanvasHost` explicitly at the shell composition root, so the current ImGui panel host path is visible as a host-layer decision instead of a panel-layer fallback.
- `XCUIDemoPanel` and `XCUILayoutLabPanel` no longer read ImGui input directly; both now consume an injected `IXCUIInputSnapshotSource`, and the new `ImGuiXCUIInputSnapshotSource` keeps the current ImGui-backed input path isolated behind an explicit adapter.
- `new_editor` now also has a pure `XCUIShellChromeState` model with dedicated tests, covering shell panel visibility, hosted-preview mode, and shell view toggles without depending on ImGui or `Application`.
- `XCUIShellChromeState` now also exposes effective hosted-preview state helpers and shell view-toggle command-id helpers, so shell routing code no longer has to manually combine enablement and requested preview mode.
- `XCUIShellChromeState` hosted-preview modes were renamed away from `LegacyImGui`, so XCUI shell state no longer treats ImGui as the generic fallback concept.
- The panel-canvas seam now has dedicated null/imgui backend coverage, including explicit backend/capability reporting and a non-ImGui placeholder host path for future native shell adoption.
- `SceneRuntime` layered XCUI routing now has dedicated regression coverage for:
- top-interactive layer input ownership
@@ -228,13 +238,13 @@ Current gap:
- `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet.
- `Image` widgets still do not have source-rect/atlas-subregion level API in the high-level draw command model.
- Editor shell still depends on ImGui as host chrome.
- Legacy hosted preview still depends on an ImGui-only inline presenter path when not using the queued native surface path.
- Hosted-preview compatibility presentation still depends on an ImGui-only inline presenter path when not using the queued native surface path.
- Editor widget coverage is still prototype-driven inside `LayoutLab`; it has not yet been promoted into a full reusable shared widget/runtime layer with command routing, virtualization, and property-edit transactions.
## Next Phase
1. Expand runtime/game-layer ownership from the current `SceneRuntime` UI context into scene-declared HUD/menu bootstrapping, draw submission, and higher-level runtime UI policies.
2. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu chrome, shell-state adoption, and panel-level keyboard/navigation input plumbing.
2. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu chrome, shell-state adoption, virtualization, and broader focus/multi-selection behavior.
3. Add a native XCUI host compositor on the existing window-level compositor seam so `new_editor` can present without going through ImGui-owned draw data.
4. Replace the remaining ImGui-only fallback seams in hosted preview and panel canvas hosting with native host implementations so ImGui can become compatibility-only instead of the default shell path.
5. Continue phased validation, commit, push, and plan refresh after each stable batch.

View File

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

View File

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

View File

@@ -16,7 +16,6 @@
#include "XCUIBackend/XCUIShellChromeState.h"
#include "XCUIBackend/XCUIStandaloneTextAtlasProvider.h"
#include <array>
#include <chrono>
#include <cstdint>
#include <functional>
@@ -31,6 +30,7 @@ namespace NewEditor {
class Application {
public:
using ShellChromeState = ::XCEngine::Editor::XCUIBackend::XCUIShellChromeState;
using ShellPanelId = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
using ShellViewToggleId = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId;
using ShellHostedPreviewMode = ::XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
@@ -248,35 +248,6 @@ public:
int Run(HINSTANCE instance, int nCmdShow);
private:
using ShellPanelStateArray = std::array<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 {
std::string debugName = {};
std::string debugSource = {};
@@ -334,7 +305,6 @@ private:
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> CreateHostedPreviewPresenter(
bool nativePreview);
void ConfigureHostedPreviewPresenters();
ShellPanelChromeState* TryGetShellPanelState(ShellPanelId panelId);
const ShellPanelChromeState* TryGetShellPanelState(ShellPanelId panelId) const;
bool IsShellViewToggleEnabled(ShellViewToggleId toggleId) const;
void SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled);
@@ -379,8 +349,7 @@ private:
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry m_hostedPreviewSurfaceRegistry;
::XCEngine::Editor::XCUIBackend::XCUIStandaloneTextAtlasProvider m_hostedPreviewTextAtlasProvider;
::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend m_hostedPreviewRenderBackend;
ShellViewToggleState m_shellViewToggles = {};
ShellPanelStateArray m_shellPanels = CreateDefaultShellPanelStates();
ShellChromeState m_shellChromeState = {};
std::vector<HostedPreviewOffscreenSurface> m_hostedPreviewSurfaces = {};
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime;
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 {
(void)request;
return {};
m_canvasSession = BuildPassiveXCUIPanelCanvasSession(request);
return m_canvasSession;
}
void DrawFilledRect(
@@ -59,7 +59,11 @@ public:
}
void EndCanvas() override {
m_canvasSession = {};
}
private:
XCUIPanelCanvasSession m_canvasSession = {};
};
inline std::unique_ptr<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 {
public:
QueuedNativeXCUIHostedPreviewPresenter(
@@ -306,6 +327,10 @@ inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateQueuedNativeXCUIHosted
return std::make_unique<QueuedNativeXCUIHostedPreviewPresenter>(queue, surfaceRegistry);
}
inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateNullXCUIHostedPreviewPresenter() {
return std::make_unique<NullXCUIHostedPreviewPresenter>();
}
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine

View File

@@ -85,6 +85,15 @@ struct XCUIInputBridgeFrameDelta {
bool HasEventType(UI::UIInputEventType type) const;
};
class IXCUIInputSnapshotSource {
public:
virtual ~IXCUIInputSnapshotSource() = default;
virtual XCUIInputBridgeFrameSnapshot CaptureSnapshot(
const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions()) const = 0;
virtual const UI::UIPoint& GetPointerPosition() const = 0;
};
class XCUIInputBridge {
public:
void Reset();
@@ -109,16 +118,16 @@ private:
XCUIInputBridgeFrameSnapshot m_baseline = {};
};
class XCUIWin32InputSource {
class XCUIWin32InputSource : public IXCUIInputSnapshotSource {
public:
void Reset();
void HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void ClearFrameTransients();
XCUIInputBridgeFrameSnapshot CaptureSnapshot(
const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions()) const;
const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions()) const override;
const UI::UIPoint& GetPointerPosition() const {
const UI::UIPoint& GetPointerPosition() const override {
return m_pointerPosition;
}

View File

@@ -46,6 +46,36 @@ struct XCUIPanelCanvasSession {
bool windowFocused = false;
};
inline const char* ResolveXCUIPanelCanvasChildId(
const XCUIPanelCanvasRequest& request,
const char* fallback = "XCUIPanelCanvasHost") {
if (request.childId != nullptr && request.childId[0] != '\0') {
return request.childId;
}
return fallback != nullptr ? fallback : "XCUIPanelCanvasHost";
}
inline XCUIPanelCanvasSession BuildPassiveXCUIPanelCanvasSession(
const XCUIPanelCanvasRequest& request) {
const float hostHeight = request.height > 0.0f ? request.height : 0.0f;
const float topInset = request.topInset > 0.0f ? request.topInset : 0.0f;
const float clampedTopInset = topInset < hostHeight ? topInset : hostHeight;
XCUIPanelCanvasSession session = {};
session.hostRect = ::XCEngine::UI::UIRect(0.0f, 0.0f, 0.0f, hostHeight);
session.canvasRect = ::XCEngine::UI::UIRect(
0.0f,
clampedTopInset,
0.0f,
hostHeight - clampedTopInset);
session.pointerPosition = {};
session.validCanvas = false;
session.hovered = false;
session.windowFocused = false;
return session;
}
class IXCUIPanelCanvasHost {
public:
virtual ~IXCUIPanelCanvasHost() = default;

View File

@@ -10,6 +10,17 @@ constexpr std::size_t ToIndex(XCUIShellPanelId 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
XCUIShellChromeState::XCUIShellChromeState() {
@@ -29,7 +40,7 @@ XCUIShellChromeState::XCUIShellChromeState() {
"new_editor.panels.xcui_layout_lab",
true,
true,
XCUIShellHostedPreviewMode::LegacyImGui
XCUIShellHostedPreviewMode::HostedPresenter
};
}
@@ -104,7 +115,7 @@ XCUIShellHostedPreviewMode XCUIShellChromeState::GetHostedPreviewMode(XCUIShellP
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
return panelState != nullptr
? panelState->previewMode
: XCUIShellHostedPreviewMode::LegacyImGui;
: XCUIShellHostedPreviewMode::HostedPresenter;
}
XCUIShellHostedPreviewState XCUIShellChromeState::GetHostedPreviewState(XCUIShellPanelId panelId) const {
@@ -115,15 +126,15 @@ XCUIShellHostedPreviewState XCUIShellChromeState::GetHostedPreviewState(XCUIShel
return panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen
? XCUIShellHostedPreviewState::NativeOffscreen
: XCUIShellHostedPreviewState::LegacyImGui;
: XCUIShellHostedPreviewState::HostedPresenter;
}
bool XCUIShellChromeState::IsNativeHostedPreviewActive(XCUIShellPanelId panelId) const {
return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::NativeOffscreen;
}
bool XCUIShellChromeState::IsLegacyHostedPreviewActive(XCUIShellPanelId panelId) const {
return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::LegacyImGui;
bool XCUIShellChromeState::IsHostedPresenterPreviewActive(XCUIShellPanelId panelId) const {
return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::HostedPresenter;
}
bool XCUIShellChromeState::SetHostedPreviewMode(
@@ -146,7 +157,7 @@ bool XCUIShellChromeState::ToggleHostedPreviewMode(XCUIShellPanelId panelId) {
panelState->previewMode =
panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen
? XCUIShellHostedPreviewMode::LegacyImGui
? XCUIShellHostedPreviewMode::HostedPresenter
: XCUIShellHostedPreviewMode::NativeOffscreen;
return true;
}
@@ -205,15 +216,8 @@ bool XCUIShellChromeState::ToggleViewToggle(XCUIShellViewToggleId toggleId) {
}
bool XCUIShellChromeState::HasCommand(std::string_view commandId) const {
return commandId == GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo) ||
commandId == GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab) ||
commandId == GetViewToggleCommandId(XCUIShellViewToggleId::ImGuiDemoWindow) ||
commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop) ||
commandId == GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent) ||
commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay) ||
commandId == GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud) ||
commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo) ||
commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab);
XCUIShellCommandDescriptor descriptor = {};
return TryGetCommandDescriptor(commandId, descriptor);
}
bool XCUIShellChromeState::InvokeCommand(std::string_view commandId) {
@@ -248,6 +252,142 @@ bool XCUIShellChromeState::InvokeCommand(std::string_view commandId) {
return false;
}
bool XCUIShellChromeState::TryGetCommandDescriptor(
std::string_view commandId,
XCUIShellCommandDescriptor& outDescriptor) const {
outDescriptor = {};
outDescriptor.checkable = false;
outDescriptor.enabled = false;
const auto tryBuildPanelVisibilityDescriptor =
[this, commandId, &outDescriptor](
XCUIShellPanelId panelId,
std::string_view shortcut) {
if (commandId != GetPanelVisibilityCommandId(panelId)) {
return false;
}
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
if (panelState == nullptr) {
return false;
}
outDescriptor.label = panelState->panelTitle;
outDescriptor.shortcut = shortcut;
outDescriptor.commandId = commandId;
outDescriptor.checkable = true;
outDescriptor.checked = panelState->visible;
outDescriptor.enabled = true;
return true;
};
if (tryBuildPanelVisibilityDescriptor(XCUIShellPanelId::XCUIDemo, kXCUIDemoShortcut)) {
return true;
}
if (tryBuildPanelVisibilityDescriptor(XCUIShellPanelId::XCUILayoutLab, kXCUILayoutLabShortcut)) {
return true;
}
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::ImGuiDemoWindow)) {
outDescriptor.label = "ImGui Demo";
outDescriptor.shortcut = kImGuiDemoShortcut;
outDescriptor.commandId = commandId;
outDescriptor.checkable = true;
outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::ImGuiDemoWindow);
outDescriptor.enabled = true;
return true;
}
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop)) {
outDescriptor.label = "Native Backdrop";
outDescriptor.shortcut = kNativeBackdropShortcut;
outDescriptor.commandId = commandId;
outDescriptor.checkable = true;
outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::NativeBackdrop);
outDescriptor.enabled = true;
return true;
}
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent)) {
outDescriptor.label = "Pulse Accent";
outDescriptor.shortcut = kPulseAccentShortcut;
outDescriptor.commandId = commandId;
outDescriptor.checkable = true;
outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::PulseAccent);
outDescriptor.enabled = true;
return true;
}
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay)) {
outDescriptor.label = "Native XCUI Overlay";
outDescriptor.shortcut = kNativeXCUIOverlayShortcut;
outDescriptor.commandId = commandId;
outDescriptor.checkable = true;
outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::NativeXCUIOverlay);
outDescriptor.enabled = true;
return true;
}
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud)) {
outDescriptor.label = "Hosted Preview HUD";
outDescriptor.shortcut = kHostedPreviewHudShortcut;
outDescriptor.commandId = commandId;
outDescriptor.checkable = true;
outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::HostedPreviewHud);
outDescriptor.enabled = true;
return true;
}
if (commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo)) {
outDescriptor.label = "Native Demo Panel Preview";
outDescriptor.shortcut = kNativeDemoPanelPreviewShortcut;
outDescriptor.commandId = commandId;
outDescriptor.checkable = true;
outDescriptor.checked = IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo);
outDescriptor.enabled = true;
return true;
}
if (commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab)) {
outDescriptor.label = "Native Layout Lab Preview";
outDescriptor.shortcut = kNativeLayoutLabPreviewShortcut;
outDescriptor.commandId = commandId;
outDescriptor.checkable = true;
outDescriptor.checked = IsNativeHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab);
outDescriptor.enabled = true;
return true;
}
return false;
}
XCUIShellMenuDescriptor XCUIShellChromeState::BuildViewMenuDescriptor() const {
XCUIShellMenuDescriptor descriptor = {};
descriptor.label = kViewMenuLabel;
descriptor.items.reserve(10u);
const auto appendCommandItem = [this, &descriptor](std::string_view commandId) {
XCUIShellCommandDescriptor commandDescriptor = {};
if (!TryGetCommandDescriptor(commandId, commandDescriptor)) {
return;
}
XCUIShellMenuItemDescriptor item = {};
item.kind = XCUIShellMenuItemKind::Command;
item.command = commandDescriptor;
descriptor.items.push_back(item);
};
appendCommandItem(GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo));
appendCommandItem(GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab));
appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::ImGuiDemoWindow));
descriptor.items.push_back({ XCUIShellMenuItemKind::Separator, {} });
appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop));
appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent));
appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay));
appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud));
appendCommandItem(GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo));
appendCommandItem(GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab));
return descriptor;
}
std::string_view XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId panelId) {
switch (panelId) {
case XCUIShellPanelId::XCUIDemo:

View File

@@ -3,6 +3,7 @@
#include <array>
#include <cstdint>
#include <string_view>
#include <vector>
namespace XCEngine {
namespace Editor {
@@ -24,13 +25,13 @@ enum class XCUIShellViewToggleId : std::uint8_t {
};
enum class XCUIShellHostedPreviewMode : std::uint8_t {
LegacyImGui = 0,
HostedPresenter = 0,
NativeOffscreen
};
enum class XCUIShellHostedPreviewState : std::uint8_t {
Disabled = 0,
LegacyImGui,
HostedPresenter,
NativeOffscreen
};
@@ -41,7 +42,7 @@ struct XCUIShellPanelChromeState {
std::string_view previewDebugSource = {};
bool visible = true;
bool hostedPreviewEnabled = true;
XCUIShellHostedPreviewMode previewMode = XCUIShellHostedPreviewMode::LegacyImGui;
XCUIShellHostedPreviewMode previewMode = XCUIShellHostedPreviewMode::HostedPresenter;
};
struct XCUIShellViewToggleState {
@@ -64,6 +65,30 @@ struct XCUIShellChromeCommandIds {
static constexpr const char* ToggleNativeLayoutLabPreview = "new_editor.view.native_layout_lab_preview";
};
enum class XCUIShellMenuItemKind : std::uint8_t {
Command = 0,
Separator
};
struct XCUIShellCommandDescriptor {
std::string_view label = {};
std::string_view shortcut = {};
std::string_view commandId = {};
bool checkable = true;
bool checked = false;
bool enabled = true;
};
struct XCUIShellMenuItemDescriptor {
XCUIShellMenuItemKind kind = XCUIShellMenuItemKind::Command;
XCUIShellCommandDescriptor command = {};
};
struct XCUIShellMenuDescriptor {
std::string_view label = {};
std::vector<XCUIShellMenuItemDescriptor> items = {};
};
class XCUIShellChromeState {
public:
XCUIShellChromeState();
@@ -82,7 +107,7 @@ public:
XCUIShellHostedPreviewMode GetHostedPreviewMode(XCUIShellPanelId panelId) const;
XCUIShellHostedPreviewState GetHostedPreviewState(XCUIShellPanelId panelId) const;
bool IsNativeHostedPreviewActive(XCUIShellPanelId panelId) const;
bool IsLegacyHostedPreviewActive(XCUIShellPanelId panelId) const;
bool IsHostedPresenterPreviewActive(XCUIShellPanelId panelId) const;
bool SetHostedPreviewMode(XCUIShellPanelId panelId, XCUIShellHostedPreviewMode mode);
bool ToggleHostedPreviewMode(XCUIShellPanelId panelId);
@@ -92,6 +117,10 @@ public:
bool HasCommand(std::string_view commandId) const;
bool InvokeCommand(std::string_view commandId);
bool TryGetCommandDescriptor(
std::string_view commandId,
XCUIShellCommandDescriptor& outDescriptor) const;
XCUIShellMenuDescriptor BuildViewMenuDescriptor() const;
static std::string_view GetPanelVisibilityCommandId(XCUIShellPanelId panelId);
static std::string_view GetPanelPreviewModeCommandId(XCUIShellPanelId panelId);

View File

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

View File

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

View File

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

View File

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

View File

@@ -46,6 +46,12 @@ set(NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER
set(NEW_EDITOR_LAYOUT_LAB_PANEL_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUILayoutLabPanel.cpp
)
set(NEW_EDITOR_DEMO_PANEL_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUIDemoPanel.h
)
set(NEW_EDITOR_DEMO_PANEL_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUIDemoPanel.cpp
)
set(NEW_EDITOR_BASE_PANEL_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/panels/Panel.cpp
)
@@ -237,6 +243,10 @@ if(EXISTS "${NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER}" AND
EXISTS "${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_HEADER}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_SOURCE}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_layout_lab_panel.cpp" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_xcui_layout_lab_panel_tests
@@ -245,6 +255,8 @@ if(EXISTS "${NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER}" AND
${NEW_EDITOR_BASE_PANEL_SOURCE}
${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE}
${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}
${NEW_EDITOR_INPUT_BRIDGE_SOURCE}
${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp
@@ -274,7 +286,59 @@ if(EXISTS "${NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER}" AND
xcengine_discover_new_editor_gtests(new_editor_xcui_layout_lab_panel_tests)
else()
message(STATUS "Skipping new_editor_xcui_layout_lab_panel_tests because panel, runtime, test, or ImGui sources are missing.")
message(STATUS "Skipping new_editor_xcui_layout_lab_panel_tests because panel, runtime, test, input bridge, or ImGui sources are missing.")
endif()
if(EXISTS "${NEW_EDITOR_DEMO_PANEL_HEADER}" AND
EXISTS "${NEW_EDITOR_DEMO_PANEL_SOURCE}" AND
EXISTS "${NEW_EDITOR_RUNTIME_HEADER}" AND
EXISTS "${NEW_EDITOR_RUNTIME_SOURCE}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_HEADER}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_SOURCE}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_demo_panel.cpp" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_xcui_demo_panel_tests
test_xcui_demo_panel.cpp
${NEW_EDITOR_DEMO_PANEL_SOURCE}
${NEW_EDITOR_BASE_PANEL_SOURCE}
${NEW_EDITOR_RUNTIME_SOURCE}
${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}
${NEW_EDITOR_INPUT_BRIDGE_SOURCE}
${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_widgets.cpp
)
xcengine_configure_new_editor_test_target(new_editor_xcui_demo_panel_tests)
target_link_libraries(new_editor_xcui_demo_panel_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
comdlg32
)
target_include_directories(new_editor_xcui_demo_panel_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
${CMAKE_BINARY_DIR}/_deps/imgui-src
${CMAKE_BINARY_DIR}/_deps/imgui-src/backends
)
target_compile_definitions(new_editor_xcui_demo_panel_tests PRIVATE
XCENGINE_NEW_EDITOR_REPO_ROOT="${XCENGINE_TEST_REPO_ROOT_CMAKE}"
)
xcengine_discover_new_editor_gtests(new_editor_xcui_demo_panel_tests)
else()
message(STATUS "Skipping new_editor_xcui_demo_panel_tests because panel, runtime, test, input bridge, or ImGui sources are missing.")
endif()
if(EXISTS "${NEW_EDITOR_BACKEND_HEADER}" AND EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
@@ -511,11 +575,14 @@ endif()
if(EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_HEADER}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_imgui_xcui_input_adapter.cpp" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_imgui_xcui_input_adapter_tests
test_imgui_xcui_input_adapter.cpp
${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}
${NEW_EDITOR_INPUT_BRIDGE_SOURCE}
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp
@@ -542,7 +609,7 @@ if(EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND
xcengine_discover_new_editor_gtests(new_editor_imgui_xcui_input_adapter_tests)
else()
message(STATUS "Skipping new_editor_imgui_xcui_input_adapter_tests because ImGui adapter files, test source, or ImGui sources are missing.")
message(STATUS "Skipping new_editor_imgui_xcui_input_adapter_tests because ImGui adapter files, input bridge files, test source, or ImGui sources are missing.")
endif()
if(EXISTS "${NEW_EDITOR_RHI_COMMAND_SUPPORT_HEADER}")

View File

@@ -38,59 +38,77 @@ struct ShellCommandHarness {
"new_editor.panels.xcui_layout_lab",
true,
true,
Application::ShellHostedPreviewMode::LegacyImGui
Application::ShellHostedPreviewMode::HostedPresenter
};
}
Application::ShellPanelChromeState& Panel(Application::ShellPanelId panelId) {
return panels[ToPanelIndex(panelId)];
}
const Application::ShellPanelChromeState& Panel(Application::ShellPanelId panelId) const {
return panels[ToPanelIndex(panelId)];
}
Application::ShellCommandBindings BuildBindings() {
Application::ShellCommandBindings bindings = {};
bindings.getXCUIDemoPanelVisible = [this]() { return Panel(Application::ShellPanelId::XCUIDemo).visible; };
bindings.getXCUIDemoPanelVisible = [this]() {
return Panel(Application::ShellPanelId::XCUIDemo).visible;
};
bindings.setXCUIDemoPanelVisible = [this](bool visible) {
Panel(Application::ShellPanelId::XCUIDemo).visible = visible;
panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)].visible = visible;
};
bindings.getXCUILayoutLabPanelVisible = [this]() {
return Panel(Application::ShellPanelId::XCUILayoutLab).visible;
};
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
Panel(Application::ShellPanelId::XCUILayoutLab).visible = visible;
panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].visible = visible;
};
bindings.getImGuiDemoWindowVisible = [this]() {
return viewToggles.imguiDemoWindowVisible;
};
bindings.setImGuiDemoWindowVisible = [this](bool visible) {
viewToggles.imguiDemoWindowVisible = visible;
};
bindings.getNativeBackdropVisible = [this]() {
return viewToggles.nativeBackdropVisible;
};
bindings.setNativeBackdropVisible = [this](bool visible) {
viewToggles.nativeBackdropVisible = visible;
};
bindings.getPulseAccentEnabled = [this]() {
return viewToggles.pulseAccentEnabled;
};
bindings.setPulseAccentEnabled = [this](bool enabled) {
viewToggles.pulseAccentEnabled = enabled;
};
bindings.getNativeXCUIOverlayVisible = [this]() {
return viewToggles.nativeXCUIOverlayVisible;
};
bindings.setNativeXCUIOverlayVisible = [this](bool visible) {
viewToggles.nativeXCUIOverlayVisible = visible;
};
bindings.getHostedPreviewHudVisible = [this]() {
return viewToggles.hostedPreviewHudVisible;
};
bindings.setHostedPreviewHudVisible = [this](bool visible) {
viewToggles.hostedPreviewHudVisible = visible;
};
bindings.getImGuiDemoWindowVisible = [this]() { return viewToggles.imguiDemoWindowVisible; };
bindings.setImGuiDemoWindowVisible = [this](bool visible) { viewToggles.imguiDemoWindowVisible = visible; };
bindings.getNativeBackdropVisible = [this]() { return viewToggles.nativeBackdropVisible; };
bindings.setNativeBackdropVisible = [this](bool visible) { viewToggles.nativeBackdropVisible = visible; };
bindings.getPulseAccentEnabled = [this]() { return viewToggles.pulseAccentEnabled; };
bindings.setPulseAccentEnabled = [this](bool enabled) { viewToggles.pulseAccentEnabled = enabled; };
bindings.getNativeXCUIOverlayVisible = [this]() { return viewToggles.nativeXCUIOverlayVisible; };
bindings.setNativeXCUIOverlayVisible = [this](bool visible) { viewToggles.nativeXCUIOverlayVisible = visible; };
bindings.getHostedPreviewHudVisible = [this]() { return viewToggles.hostedPreviewHudVisible; };
bindings.setHostedPreviewHudVisible = [this](bool visible) { viewToggles.hostedPreviewHudVisible = visible; };
bindings.getNativeDemoPanelPreviewEnabled = [this]() {
return Panel(Application::ShellPanelId::XCUIDemo).previewMode ==
Application::ShellHostedPreviewMode::NativeOffscreen;
};
bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) {
Panel(Application::ShellPanelId::XCUIDemo).previewMode =
panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)].previewMode =
enabled
? Application::ShellHostedPreviewMode::NativeOffscreen
: Application::ShellHostedPreviewMode::LegacyImGui;
: Application::ShellHostedPreviewMode::HostedPresenter;
};
bindings.getNativeLayoutLabPreviewEnabled = [this]() {
return Panel(Application::ShellPanelId::XCUILayoutLab).previewMode ==
Application::ShellHostedPreviewMode::NativeOffscreen;
};
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
Panel(Application::ShellPanelId::XCUILayoutLab).previewMode =
panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].previewMode =
enabled
? Application::ShellHostedPreviewMode::NativeOffscreen
: Application::ShellHostedPreviewMode::LegacyImGui;
: Application::ShellHostedPreviewMode::HostedPresenter;
};
bindings.onHostedPreviewModeChanged = [this]() { ++hostedPreviewReconfigureCount; };
return bindings;
@@ -134,7 +152,7 @@ TEST(ApplicationShellCommandBindingsTest, PreviewModeCommandsTriggerHostedPrevie
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeDemoPanelPreview));
EXPECT_EQ(
harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode,
Application::ShellHostedPreviewMode::LegacyImGui);
Application::ShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1);
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeLayoutLabPreview));
@@ -243,7 +261,7 @@ TEST(ApplicationShellCommandBindingsTest, PreviewShortcutInvokesCommandHandlerAn
EXPECT_TRUE(router.InvokeMatchingShortcut({ &previewSnapshot }));
EXPECT_EQ(
harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode,
Application::ShellHostedPreviewMode::LegacyImGui);
Application::ShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1);
}

View File

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

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

View File

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

View File

@@ -5,7 +5,9 @@
namespace {
using XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::BuildPassiveXCUIPanelCanvasSession;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::ResolveXCUIPanelCanvasChildId;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostCapabilities;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
@@ -24,7 +26,41 @@ TEST(NewEditorXCUIPanelCanvasHostTest, NullHostReportsExplicitBackendAndCapabili
EXPECT_FALSE(capabilities.supportsPrimitiveOverlays);
}
TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsEmptySessionAndDrawCallsAreNoops) {
TEST(NewEditorXCUIPanelCanvasHostTest, ResolveChildIdFallsBackToStableDefaultForMissingNames) {
XCUIPanelCanvasRequest request = {};
EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request), "XCUIPanelCanvasHost");
EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request, "FallbackCanvas"), "FallbackCanvas");
request.childId = "";
EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request), "XCUIPanelCanvasHost");
request.childId = "CanvasHost";
EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request, "FallbackCanvas"), "CanvasHost");
}
TEST(NewEditorXCUIPanelCanvasHostTest, PassiveSessionClampsRequestGeometryIntoSafeDefaultState) {
XCUIPanelCanvasRequest request = {};
request.height = -18.0f;
request.topInset = 42.0f;
XCUIPanelCanvasSession session = BuildPassiveXCUIPanelCanvasSession(request);
EXPECT_FALSE(session.validCanvas);
EXPECT_FALSE(session.hovered);
EXPECT_FALSE(session.windowFocused);
EXPECT_FLOAT_EQ(session.hostRect.width, 0.0f);
EXPECT_FLOAT_EQ(session.hostRect.height, 0.0f);
EXPECT_FLOAT_EQ(session.canvasRect.y, 0.0f);
EXPECT_FLOAT_EQ(session.canvasRect.height, 0.0f);
request.height = 120.0f;
request.topInset = 180.0f;
session = BuildPassiveXCUIPanelCanvasSession(request);
EXPECT_FLOAT_EQ(session.hostRect.height, 120.0f);
EXPECT_FLOAT_EQ(session.canvasRect.y, 120.0f);
EXPECT_FLOAT_EQ(session.canvasRect.height, 0.0f);
}
TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsSafePassiveSessionAndDrawCallsAreNoops) {
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateNullXCUIPanelCanvasHost();
ASSERT_NE(host, nullptr);
@@ -43,9 +79,12 @@ TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsEmptySessionAnd
EXPECT_FALSE(session.hovered);
EXPECT_FALSE(session.windowFocused);
EXPECT_FLOAT_EQ(session.hostRect.width, 0.0f);
EXPECT_FLOAT_EQ(session.hostRect.height, 0.0f);
EXPECT_FLOAT_EQ(session.hostRect.height, 280.0f);
EXPECT_FLOAT_EQ(session.canvasRect.width, 0.0f);
EXPECT_FLOAT_EQ(session.canvasRect.height, 0.0f);
EXPECT_FLOAT_EQ(session.canvasRect.y, 24.0f);
EXPECT_FLOAT_EQ(session.canvasRect.height, 256.0f);
EXPECT_FLOAT_EQ(session.pointerPosition.x, 0.0f);
EXPECT_FLOAT_EQ(session.pointerPosition.y, 0.0f);
host->DrawFilledRect(
XCEngine::UI::UIRect(10.0f, 12.0f, 48.0f, 64.0f),
@@ -62,6 +101,11 @@ TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsEmptySessionAnd
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
16.0f);
host->EndCanvas();
const XCUIPanelCanvasSession secondSession = host->BeginCanvas({});
EXPECT_FLOAT_EQ(secondSession.hostRect.height, 0.0f);
EXPECT_FLOAT_EQ(secondSession.canvasRect.height, 0.0f);
host->EndCanvas();
}
} // namespace

View File

@@ -5,9 +5,11 @@
namespace {
using XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds;
using XCEngine::Editor::XCUIBackend::XCUIShellCommandDescriptor;
using XCEngine::Editor::XCUIBackend::XCUIShellChromeState;
using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewState;
using XCEngine::Editor::XCUIBackend::XCUIShellMenuItemKind;
using XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
using XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId;
@@ -33,7 +35,7 @@ TEST(XCUIShellChromeStateTest, DefaultsMatchCurrentShellChromeConfiguration) {
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::NativeOffscreen);
EXPECT_TRUE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_FALSE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_FALSE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUIDemo));
const auto* layoutLabPanel = state.TryGetPanelState(XCUIShellPanelId::XCUILayoutLab);
ASSERT_NE(layoutLabPanel, nullptr);
@@ -42,12 +44,12 @@ TEST(XCUIShellChromeStateTest, DefaultsMatchCurrentShellChromeConfiguration) {
EXPECT_EQ(layoutLabPanel->previewDebugSource, "new_editor.panels.xcui_layout_lab");
EXPECT_TRUE(layoutLabPanel->visible);
EXPECT_TRUE(layoutLabPanel->hostedPreviewEnabled);
EXPECT_EQ(layoutLabPanel->previewMode, XCUIShellHostedPreviewMode::LegacyImGui);
EXPECT_EQ(layoutLabPanel->previewMode, XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewState::LegacyImGui);
XCUIShellHostedPreviewState::HostedPresenter);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab));
EXPECT_TRUE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab));
EXPECT_TRUE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUILayoutLab));
}
TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateChanges) {
@@ -64,7 +66,7 @@ TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateCh
EXPECT_FALSE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUILayoutLab, false));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::LegacyImGui);
XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_TRUE(state.ToggleHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab));
EXPECT_EQ(
@@ -72,13 +74,13 @@ TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateCh
XCUIShellHostedPreviewMode::NativeOffscreen);
EXPECT_TRUE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUILayoutLab,
XCUIShellHostedPreviewMode::LegacyImGui));
XCUIShellHostedPreviewMode::HostedPresenter));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::LegacyImGui);
XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_FALSE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUILayoutLab,
XCUIShellHostedPreviewMode::LegacyImGui));
XCUIShellHostedPreviewMode::HostedPresenter));
}
TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequestedMode) {
@@ -93,14 +95,14 @@ TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequeste
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::Disabled);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_FALSE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_FALSE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUIDemo,
XCUIShellHostedPreviewMode::LegacyImGui));
XCUIShellHostedPreviewMode::HostedPresenter));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewMode::LegacyImGui);
XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::Disabled);
@@ -108,9 +110,9 @@ TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequeste
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, true));
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::LegacyImGui);
XCUIShellHostedPreviewState::HostedPresenter);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUIDemo));
}
TEST(XCUIShellChromeStateTest, ViewToggleMutatorsOnlyFlipRequestedFlags) {
@@ -148,11 +150,115 @@ TEST(XCUIShellChromeStateTest, CommandInterfaceTogglesShellViewAndPreviewStates)
EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::LegacyImGui);
XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_FALSE(state.InvokeCommand("new_editor.view.unknown"));
}
TEST(XCUIShellChromeStateTest, CommandDescriptorsMatchCurrentShellMenuLabelsShortcutsAndCheckedState) {
XCUIShellChromeState state = {};
XCUIShellCommandDescriptor descriptor = {};
ASSERT_TRUE(state.TryGetCommandDescriptor(XCUIShellChromeCommandIds::ToggleXCUIDemoPanel, descriptor));
EXPECT_EQ(descriptor.label, "XCUI Demo");
EXPECT_EQ(descriptor.shortcut, "Ctrl+1");
EXPECT_EQ(descriptor.commandId, XCUIShellChromeCommandIds::ToggleXCUIDemoPanel);
EXPECT_TRUE(descriptor.checkable);
EXPECT_TRUE(descriptor.checked);
EXPECT_TRUE(descriptor.enabled);
ASSERT_TRUE(state.TryGetCommandDescriptor(XCUIShellChromeCommandIds::ToggleHostedPreviewHud, descriptor));
EXPECT_EQ(descriptor.label, "Hosted Preview HUD");
EXPECT_EQ(descriptor.shortcut, "Ctrl+Shift+H");
EXPECT_TRUE(descriptor.checked);
ASSERT_TRUE(state.TryGetCommandDescriptor(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview, descriptor));
EXPECT_EQ(descriptor.label, "Native Layout Lab Preview");
EXPECT_EQ(descriptor.shortcut, "Ctrl+Alt+2");
EXPECT_FALSE(descriptor.checked);
EXPECT_FALSE(state.TryGetCommandDescriptor("new_editor.view.unknown", descriptor));
EXPECT_TRUE(descriptor.label.empty());
EXPECT_TRUE(descriptor.shortcut.empty());
EXPECT_TRUE(descriptor.commandId.empty());
EXPECT_FALSE(descriptor.checked);
EXPECT_FALSE(descriptor.enabled);
}
TEST(XCUIShellChromeStateTest, ViewMenuDescriptorMatchesCurrentApplicationOrderingAndSeparators) {
XCUIShellChromeState state = {};
const auto menu = state.BuildViewMenuDescriptor();
ASSERT_EQ(menu.label, "View");
ASSERT_EQ(menu.items.size(), 10u);
ASSERT_EQ(menu.items[0].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[0].command.label, "XCUI Demo");
EXPECT_EQ(menu.items[0].command.shortcut, "Ctrl+1");
EXPECT_EQ(menu.items[0].command.commandId, XCUIShellChromeCommandIds::ToggleXCUIDemoPanel);
EXPECT_TRUE(menu.items[0].command.checked);
ASSERT_EQ(menu.items[1].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[1].command.label, "XCUI Layout Lab");
EXPECT_EQ(menu.items[1].command.shortcut, "Ctrl+2");
EXPECT_EQ(menu.items[1].command.commandId, XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel);
ASSERT_EQ(menu.items[2].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[2].command.label, "ImGui Demo");
EXPECT_EQ(menu.items[2].command.shortcut, "Ctrl+3");
EXPECT_EQ(menu.items[2].command.commandId, XCUIShellChromeCommandIds::ToggleImGuiDemoWindow);
EXPECT_EQ(menu.items[3].kind, XCUIShellMenuItemKind::Separator);
ASSERT_EQ(menu.items[4].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[4].command.label, "Native Backdrop");
EXPECT_EQ(menu.items[4].command.shortcut, "Ctrl+Shift+B");
EXPECT_EQ(menu.items[4].command.commandId, XCUIShellChromeCommandIds::ToggleNativeBackdrop);
ASSERT_EQ(menu.items[5].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[5].command.label, "Pulse Accent");
EXPECT_EQ(menu.items[5].command.shortcut, "Ctrl+Shift+P");
EXPECT_EQ(menu.items[5].command.commandId, XCUIShellChromeCommandIds::TogglePulseAccent);
ASSERT_EQ(menu.items[6].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[6].command.label, "Native XCUI Overlay");
EXPECT_EQ(menu.items[6].command.shortcut, "Ctrl+Shift+O");
EXPECT_EQ(menu.items[6].command.commandId, XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay);
ASSERT_EQ(menu.items[7].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[7].command.label, "Hosted Preview HUD");
EXPECT_EQ(menu.items[7].command.shortcut, "Ctrl+Shift+H");
EXPECT_EQ(menu.items[7].command.commandId, XCUIShellChromeCommandIds::ToggleHostedPreviewHud);
ASSERT_EQ(menu.items[8].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[8].command.label, "Native Demo Panel Preview");
EXPECT_EQ(menu.items[8].command.shortcut, "Ctrl+Alt+1");
EXPECT_EQ(menu.items[8].command.commandId, XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview);
ASSERT_EQ(menu.items[9].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[9].command.label, "Native Layout Lab Preview");
EXPECT_EQ(menu.items[9].command.shortcut, "Ctrl+Alt+2");
EXPECT_EQ(menu.items[9].command.commandId, XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview);
}
TEST(XCUIShellChromeStateTest, ViewMenuDescriptorCheckedStateTracksShellStateChanges) {
XCUIShellChromeState state = {};
EXPECT_TRUE(state.SetPanelVisible(XCUIShellPanelId::XCUIDemo, false));
EXPECT_TRUE(state.SetViewToggle(XCUIShellViewToggleId::HostedPreviewHud, false));
EXPECT_TRUE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUILayoutLab,
XCUIShellHostedPreviewMode::NativeOffscreen));
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, false));
const auto menu = state.BuildViewMenuDescriptor();
EXPECT_FALSE(menu.items[0].command.checked);
EXPECT_FALSE(menu.items[7].command.checked);
EXPECT_FALSE(menu.items[8].command.checked);
EXPECT_TRUE(menu.items[9].command.checked);
}
TEST(XCUIShellChromeStateTest, PanelCommandIdHelpersMatchCurrentShellCommands) {
EXPECT_EQ(
XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo),
@@ -200,12 +306,12 @@ TEST(XCUIShellChromeStateTest, InvalidPanelAndToggleIdsFailGracefully) {
EXPECT_FALSE(state.SetHostedPreviewEnabled(invalidPanelId, false));
EXPECT_EQ(
state.GetHostedPreviewMode(invalidPanelId),
XCUIShellHostedPreviewMode::LegacyImGui);
XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ(
state.GetHostedPreviewState(invalidPanelId),
XCUIShellHostedPreviewState::Disabled);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(invalidPanelId));
EXPECT_FALSE(state.IsLegacyHostedPreviewActive(invalidPanelId));
EXPECT_FALSE(state.IsHostedPresenterPreviewActive(invalidPanelId));
EXPECT_FALSE(state.SetHostedPreviewMode(invalidPanelId, XCUIShellHostedPreviewMode::NativeOffscreen));
EXPECT_FALSE(state.ToggleHostedPreviewMode(invalidPanelId));