Reuse panel frame composition in native XCUI shell

This commit is contained in:
2026-04-05 17:41:31 +08:00
parent 63b5f12b93
commit 3db09ea5d0
10 changed files with 651 additions and 954 deletions

View File

@@ -19,7 +19,7 @@ Old `editor` replacement is explicitly out of scope for this phase.
- The native-host / hosted-preview publication follow-up is now landed in `new_editor`:
- `NativeWindowUICompositor` is now buildable alongside the legacy ImGui compositor
- `Application` now defaults to a native XCUI host path instead of creating the ImGui shell by default
- the native default path now drives `XCUIDemoRuntime` and `XCUILayoutLabRuntime` directly, composes their `UIDrawData`, and submits one swapchain packet through the native compositor
- the native default path now reuses shell-agnostic `XCUIDemoPanel::ComposeFrame(...)` / `XCUILayoutLabPanel::ComposeFrame(...)` results instead of keeping a second demo/layout runtime + input-bridge stack inside `Application`
- the native compositor now publishes hosted-preview textures as SRV-backed `UITextureRegistration` / `UITextureHandle` values instead of relying on ImGui-only descriptor semantics
- the native shell now begins the hosted-preview queue/registry lifecycle each frame, queues native preview frames, drains them during the native render pass, and consumes published hosted-surface images directly in panel cards with live/warming placeholder states
- the old ImGui shell path remains present as an explicit compatibility host instead of the default host
@@ -113,6 +113,8 @@ Current gap:
- The panel-canvas seam now keeps the generic host contract on observable canvas behavior only; backend/capability identity probing has been removed from `IXCUIPanelCanvasHost`, and the minimal `NullXCUIPanelCanvasHost` remains the concrete placeholder host for non-ImGui paths.
- `XCUI Demo` and `LayoutLab` panel input now also flows through an explicit `IXCUIInputSnapshotSource` seam, so panel/runtime code no longer reads `ImGuiIO` / `ImGui::IsKeyPressed` / `ImGui::IsMouseClicked` directly when the shell wants to use an ImGui adapter.
- `new_editor` now also has an explicit `ImGuiXCUIInputSnapshotSource` adapter, keeping ImGui-specific input capture in the host adapter layer instead of inside panel/runtime update code.
- `XCUI Demo` and `LayoutLab` panels now both expose shell-agnostic per-frame composition results (`ComposeFrame(...)` + `GetLastFrameComposition()`), so native/compat shells can reuse one panel-local frame pipeline instead of duplicating runtime/input/preview assembly logic.
- `XCUIDemoPanel::ComposeFrame(...)` now also accepts an injected input snapshot plus explicit placeholder/frame options, which lets the native shell reuse the panel pipeline without falling back to the compatibility-path placeholder behavior on direct draw-data cards.
- Panel diagnostics were expanded to clearly separate preview/runtime/input state and native vs legacy paths.
- The editor bridge layer now has smoke coverage for swapchain after-UI rendering hooks and SRV-backed ImGui texture descriptor registration.
- `Application` no longer owns the ImGui backend directly; window presentation now routes through `IWindowUICompositor` with an `ImGuiWindowUICompositor` implementation, which currently delegates to `IEditorHostCompositor` / `ImGuiHostCompositor`.
@@ -132,8 +134,9 @@ Current gap:
- becomes the default `new_editor` startup path
- lays out `XCUI Demo` and `Layout Lab` as native cards directly in the swapchain window
- routes shell shortcuts through the same command router without reading ImGui capture state in the default host path
- drives both sandbox runtimes directly from Win32/XCUI input snapshots instead of routing through ImGui panel hosts
- reuses panel-local `ComposeFrame(...)` entry points for demo/layout runtime, input, hosted-preview, and overlay composition instead of maintaining duplicate native-shell runtime/input state in `Application`
- composes one native `UIDrawData` packet and submits it through `NativeWindowUICompositor`
- Native shell preview-mode reconfiguration now rebuilds the native panel bindings instead of rebinding a legacy hosted presenter, so the default host path no longer needs the ImGui presenter when a card stays on direct draw-data composition.
- The native shell layout policy now also lives behind `XCUINativeShellLayout`, so top-bar/footer/workspace geometry, panel split rules, and active-panel transfer on pointer press are no longer hard-coded inline inside `Application.cpp`.
- `NativeXCUIPanelCanvasHost` now backs that direct shell path as an externally driven canvas/session host for native cards instead of assuming an ImGui child-window model, and it now emits native `Image` draw commands for hosted surface-image previews while preserving per-surface UVs.
- `NativeWindowUICompositor` now creates and frees SRV-backed texture registrations for hosted preview surfaces, so native publication no longer depends on ImGui descriptor handles.
@@ -152,7 +155,7 @@ Current gap:
## Validated This Phase
- `new_editor_xcui_demo_panel_tests`: `3/3`
- `new_editor_xcui_demo_panel_tests`: `4/4`
- `new_editor_xcui_demo_runtime_tests`: `12/12`
- `new_editor_xcui_input_bridge_tests`: `4/4`
- `new_editor_imgui_xcui_input_adapter_tests`: `2/2`
@@ -170,7 +173,7 @@ Current gap:
- `new_editor_xcui_panel_canvas_host_tests`: `4/4`
- `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1`
- `new_editor_native_xcui_panel_canvas_host_tests`: `4/4`
- `new_editor_xcui_layout_lab_panel_tests`: `3/3`
- `new_editor_xcui_layout_lab_panel_tests`: `6/6`
- `XCNewEditor` Debug target builds successfully
- `XCNewEditor.exe` native-default smoke run stayed alive for `5s`
- `core_ui_tests`: `52 total` (`50` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`)

View File

@@ -1,6 +1,9 @@
#include "Application.h"
#include "XCUIBackend/XCUINativeShellLayout.h"
#include "XCUIBackend/NativeWindowUICompositor.h"
#include "XCUIBackend/XCUIPanelCanvasHost.h"
#include "panels/XCUIDemoPanel.h"
#include "panels/XCUILayoutLabPanel.h"
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/UI/DrawData.h>
@@ -41,54 +44,64 @@ std::uint64_t MakeFrameTimestampNanoseconds() {
.count());
}
class ForwardingNativePanelCanvasHost final : public ::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost {
public:
explicit ForwardingNativePanelCanvasHost(::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& host)
: m_host(host) {}
const char* GetDebugName() const override {
return m_host.GetDebugName();
}
auto BeginCanvas(
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest& request)
-> decltype(std::declval<::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost&>().BeginCanvas(request))
override {
return m_host.BeginCanvas(request);
}
void DrawFilledRect(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIColor& color,
float rounding) override {
m_host.DrawFilledRect(rect, color, rounding);
}
void DrawOutlineRect(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIColor& color,
float thickness,
float rounding) override {
m_host.DrawOutlineRect(rect, color, thickness, rounding);
}
void DrawText(
const ::XCEngine::UI::UIPoint& position,
std::string_view text,
const ::XCEngine::UI::UIColor& color,
float fontSize) override {
m_host.DrawText(position, text, color, fontSize);
}
void EndCanvas() override {
m_host.EndCanvas();
}
bool TryGetLatestFrameSnapshot(
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasFrameSnapshot& outSnapshot) const override {
return m_host.TryGetLatestFrameSnapshot(outSnapshot);
}
private:
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& m_host;
};
void AppendDrawData(::XCEngine::UI::UIDrawData& destination, const ::XCEngine::UI::UIDrawData& source) {
for (const ::XCEngine::UI::UIDrawList& drawList : source.GetDrawLists()) {
destination.AddDrawList(drawList);
}
}
bool ContainsKeyTransition(
const std::vector<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) {
if (!canvasSession.validCanvas) {
return false;
}
return canvasSession.hovered ||
(canvasSession.windowFocused &&
!previousFrame.stats.selectedElementId.empty());
}
void PopulateKeyboardNavigationInput(
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState& input,
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta,
bool captureKeyboardNavigation) {
if (!captureKeyboardNavigation) {
return;
}
using ::XCEngine::Input::KeyCode;
const auto pressedThisFrame =
[&frameDelta](KeyCode keyCode) {
const std::int32_t code = static_cast<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);
}
} // namespace
bool Application::IsNativeWindowHostEnabled() const {
@@ -99,12 +112,35 @@ void Application::InitializeNativeShell() {
m_nativeActivePanel = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo)
? ShellPanelId::XCUIDemo
: ShellPanelId::XCUILayoutLab;
m_nativeDemoInputBridge.Reset();
m_nativeLayoutInputBridge.Reset();
m_nativeDemoCanvasHost.ClearCanvasSession();
m_nativeLayoutCanvasHost.ClearCanvasSession();
m_nativeDemoReloadSucceeded = m_nativeDemoRuntime.ReloadDocuments();
m_nativeLayoutReloadSucceeded = m_nativeLayoutRuntime.ReloadDocuments();
const auto buildNativePreviewPresenter =
[this](ShellPanelId panelId, bool hostedPreviewEnabled) {
if (!hostedPreviewEnabled || !IsNativeHostedPreviewEnabled(panelId)) {
return std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>();
}
return CreateHostedPreviewPresenter(true);
};
const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
const bool demoHostedPreviewEnabled = demoState == nullptr || demoState->hostedPreviewEnabled;
m_demoPanel = std::make_unique<XCUIDemoPanel>(
&m_xcuiInputSource,
buildNativePreviewPresenter(ShellPanelId::XCUIDemo, demoHostedPreviewEnabled),
std::make_unique<ForwardingNativePanelCanvasHost>(m_nativeDemoCanvasHost));
m_demoPanel->SetVisible(demoState != nullptr && demoState->visible);
m_demoPanel->SetHostedPreviewEnabled(demoHostedPreviewEnabled);
const ShellPanelChromeState* layoutState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
const bool layoutHostedPreviewEnabled = layoutState == nullptr || layoutState->hostedPreviewEnabled;
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
&m_xcuiInputSource,
buildNativePreviewPresenter(ShellPanelId::XCUILayoutLab, layoutHostedPreviewEnabled),
std::make_unique<ForwardingNativePanelCanvasHost>(m_nativeLayoutCanvasHost));
m_layoutLabPanel->SetVisible(layoutState != nullptr && layoutState->visible);
m_layoutLabPanel->SetHostedPreviewEnabled(layoutHostedPreviewEnabled);
}
const Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const {
@@ -683,33 +719,10 @@ bool Application::RenderHostedPreviewOffscreenSurface(
for (const NativeShellPanelLayout& panelLayout : panelLayouts) {
if (panelLayout.panelId == ShellPanelId::XCUIDemo) {
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
const bool nativeHostedPreview =
const bool nativeHostedPreviewRequested =
panelState != nullptr &&
panelState->hostedPreviewEnabled &&
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
const bool hasHostedSurfaceDescriptor =
nativeHostedPreview &&
panelState != nullptr &&
!panelState->previewDebugName.empty() &&
m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor(
panelState->previewDebugName.data(),
hostedSurfaceDescriptor);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
const bool showHostedSurfaceImage =
nativeHostedPreview &&
panelState != nullptr &&
!panelState->previewDebugName.empty() &&
m_hostedPreviewSurfaceRegistry.TryGetSurfaceImage(
panelState->previewDebugName.data(),
hostedSurfaceImage);
const Application::NativeHostedPreviewConsumption previewConsumption =
Application::ResolveNativeHostedPreviewConsumption(
nativeHostedPreview,
hasHostedSurfaceDescriptor,
showHostedSurfaceImage,
"Native XCUI preview pending",
"Waiting for queued native preview output to publish into the shell card.");
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
canvasSession.hostRect = panelLayout.panelRect;
@@ -720,63 +733,54 @@ bool Application::RenderHostedPreviewOffscreenSurface(
canvasSession.windowFocused = shellSnapshot.windowFocused;
m_nativeDemoCanvasHost.SetCanvasSession(canvasSession);
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {};
canvasRequest.childId = "XCUIDemo.NativeCanvas";
canvasRequest.height = panelLayout.panelRect.height;
canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y;
canvasRequest.drawPreviewFrame = false;
canvasRequest.showSurfaceImage = previewConsumption.showSurfaceImage;
canvasRequest.surfaceImage = hostedSurfaceImage;
canvasRequest.placeholderTitle =
previewConsumption.placeholderTitle.empty() ? nullptr : previewConsumption.placeholderTitle.data();
canvasRequest.placeholderSubtitle =
previewConsumption.placeholderSubtitle.empty() ? nullptr : previewConsumption.placeholderSubtitle.data();
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession resolvedSession =
m_nativeDemoCanvasHost.BeginCanvas(canvasRequest);
const bool wantsKeyboard = panelLayout.active;
const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard);
if (!m_nativeDemoInputBridge.HasBaseline()) {
m_nativeDemoInputBridge.Prime(panelSnapshot);
}
const auto panelFrameDelta = m_nativeDemoInputBridge.Translate(panelSnapshot);
const bool showPanelHud = IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud);
::XCEngine::Editor::XCUIBackend::XCUIDemoInputState input = {};
input.canvasRect = resolvedSession.canvasRect;
input.pointerPosition = panelSnapshot.pointerPosition;
input.pointerInside = panelSnapshot.pointerInside;
input.pointerPressed = panelFrameDelta.pointer.pressed[0];
input.pointerReleased = panelFrameDelta.pointer.released[0];
input.pointerDown = panelSnapshot.pointerButtonsDown[0];
input.windowFocused = panelSnapshot.windowFocused;
input.wantCaptureMouse = panelSnapshot.wantCaptureMouse;
input.wantCaptureKeyboard = panelSnapshot.wantCaptureKeyboard;
input.wantTextInput = panelSnapshot.wantTextInput;
input.events = panelFrameDelta.events;
const auto& frame = m_nativeDemoRuntime.Update(input);
if (previewConsumption.queueRuntimeFrame) {
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {};
previewFrame.drawData = &frame.drawData;
previewFrame.canvasRect = resolvedSession.canvasRect;
previewFrame.logicalSize = UI::UISize(
resolvedSession.canvasRect.width,
resolvedSession.canvasRect.height);
previewFrame.debugName =
panelState != nullptr ? panelState->previewDebugName.data() : nullptr;
previewFrame.debugSource =
panelState != nullptr ? panelState->previewDebugSource.data() : nullptr;
m_hostedPreviewQueue.Submit(previewFrame);
} else if (previewConsumption.appendRuntimeDrawDataToShell) {
AppendDrawData(composedDrawData, frame.drawData);
const XCUIDemoPanelFrameComposition* composition = nullptr;
if (m_demoPanel != nullptr) {
composition = &m_demoPanel->ComposeFrame(
XCUIDemoPanelFrameComposeOptions{
panelLayout.panelRect.height,
panelLayout.canvasRect.y - panelLayout.panelRect.y,
panelState == nullptr || panelState->hostedPreviewEnabled,
showPanelHud,
showPanelHud,
panelSnapshot.timestampNanoseconds,
"XCUIDemo.NativeCanvas",
&panelSnapshot,
false,
nativeHostedPreviewRequested ? "Native XCUI preview pending" : nullptr,
nativeHostedPreviewRequested
? "Waiting for queued native preview output to publish into the shell card."
: nullptr,
false,
nullptr,
nullptr,
});
}
if (IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud)) {
const auto& stats = frame.stats;
const Application::NativeHostedPreviewConsumption previewConsumption =
composition != nullptr
? Application::ResolveNativeHostedPreviewConsumption(
composition->nativeHostedPreview,
composition->hasHostedSurfaceDescriptor,
composition->showHostedSurfaceImage,
"Native XCUI preview pending",
"Waiting for queued native preview output to publish into the shell card.")
: Application::NativeHostedPreviewConsumption{};
if (composition != nullptr &&
composition->frame != nullptr &&
previewConsumption.appendRuntimeDrawDataToShell) {
AppendDrawData(composedDrawData, composition->frame->drawData);
}
if (showPanelHud && composition != nullptr && composition->frame != nullptr) {
const auto& stats = composition->frame->stats;
const UI::UIRect hudRect(
resolvedSession.canvasRect.x + 10.0f,
resolvedSession.canvasRect.y + 10.0f,
(std::min)(resolvedSession.canvasRect.width - 20.0f, 360.0f),
composition->canvasSession.canvasRect.x + 10.0f,
composition->canvasSession.canvasRect.y + 10.0f,
(std::min)(composition->canvasSession.canvasRect.width - 20.0f, 360.0f),
62.0f);
if (hudRect.width > 40.0f && hudRect.height > 20.0f) {
m_nativeDemoCanvasHost.DrawFilledRect(
@@ -799,49 +803,28 @@ bool Application::RenderHostedPreviewOffscreenSurface(
stats.statusMessage,
textMuted);
}
if (previewConsumption.drawRuntimeDebugRects) {
const auto drawDebugRect =
[this](const std::string& elementId, const UI::UIColor& color, const char* label) {
if (elementId.empty()) {
return;
}
UI::UIRect rect = {};
if (!m_nativeDemoRuntime.TryGetElementRect(elementId, rect)) {
return;
}
m_nativeDemoCanvasHost.DrawOutlineRect(rect, color, 2.0f, 6.0f);
if (label != nullptr && label[0] != '\0') {
m_nativeDemoCanvasHost.DrawText(
UI::UIPoint(rect.x + 4.0f, rect.y + 4.0f),
label,
color);
}
};
drawDebugRect(
stats.hoveredElementId,
UI::UIColor(1.0f, 195.0f / 255.0f, 64.0f / 255.0f, 1.0f),
"hover");
drawDebugRect(
stats.focusedElementId,
UI::UIColor(64.0f / 255.0f, 214.0f / 255.0f, 1.0f, 1.0f),
"focus");
}
}
m_nativeDemoCanvasHost.EndCanvas();
NativePanelFrameSummary summary = {};
summary.layout = panelLayout;
summary.lineA = m_nativeDemoReloadSucceeded
summary.lineA =
m_demoPanel != nullptr && m_demoPanel->GetLastReloadSucceeded()
? Application::ComposeNativeHostedPreviewStatusLine(
previewConsumption,
frame.stats.statusMessage)
composition != nullptr && composition->frame != nullptr
? composition->frame->stats.statusMessage
: std::string_view("XCUI demo frame unavailable"))
: "Document reload failed; showing last retained runtime state.";
summary.lineB =
std::string(panelLayout.active ? "Active" : "Passive") +
" | " + std::to_string(frame.stats.elementCount) +
" elements | " + std::to_string(frame.stats.commandCount) +
" | " + std::to_string(
composition != nullptr && composition->frame != nullptr
? composition->frame->stats.elementCount
: 0u) +
" elements | " + std::to_string(
composition != nullptr && composition->frame != nullptr
? composition->frame->stats.commandCount
: 0u) +
" cmds";
summary.overlay = extractCanvasOverlay(m_nativeDemoCanvasHost);
panelSummaries.push_back(std::move(summary));
@@ -850,21 +833,22 @@ bool Application::RenderHostedPreviewOffscreenSurface(
}
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
const bool nativeHostedPreview =
const bool nativeHostedPreviewRequested =
panelState != nullptr &&
panelState->hostedPreviewEnabled &&
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
const bool hasHostedSurfaceDescriptor =
nativeHostedPreview &&
nativeHostedPreviewRequested &&
panelState != nullptr &&
!panelState->previewDebugName.empty() &&
m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor(
panelState->previewDebugName.data(),
hostedSurfaceDescriptor);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
const bool showHostedSurfaceImage =
nativeHostedPreview &&
nativeHostedPreviewRequested &&
panelState != nullptr &&
!panelState->previewDebugName.empty() &&
m_hostedPreviewSurfaceRegistry.TryGetSurfaceImage(
@@ -872,7 +856,7 @@ bool Application::RenderHostedPreviewOffscreenSurface(
hostedSurfaceImage);
const Application::NativeHostedPreviewConsumption previewConsumption =
Application::ResolveNativeHostedPreviewConsumption(
nativeHostedPreview,
nativeHostedPreviewRequested,
hasHostedSurfaceDescriptor,
showHostedSurfaceImage,
"Native layout preview pending",
@@ -902,50 +886,53 @@ bool Application::RenderHostedPreviewOffscreenSurface(
const bool wantsKeyboard = panelLayout.active;
const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard);
if (!m_nativeLayoutInputBridge.HasBaseline()) {
m_nativeLayoutInputBridge.Prime(panelSnapshot);
}
const auto panelFrameDelta = m_nativeLayoutInputBridge.Translate(panelSnapshot);
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = {};
input.canvasRect = resolvedSession.canvasRect;
input.pointerPosition = panelSnapshot.pointerPosition;
input.pointerInside = panelSnapshot.pointerInside;
input.pointerPressed = input.pointerInside && panelFrameDelta.pointer.pressed[0];
PopulateKeyboardNavigationInput(
input,
panelFrameDelta,
panelLayout.active && ShouldCaptureKeyboardNavigation(resolvedSession, m_nativeLayoutRuntime.GetFrameResult()));
const auto& frame = m_nativeLayoutRuntime.Update(input);
if (previewConsumption.queueRuntimeFrame) {
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {};
previewFrame.drawData = &frame.drawData;
previewFrame.canvasRect = resolvedSession.canvasRect;
previewFrame.logicalSize = UI::UISize(
resolvedSession.canvasRect.width,
resolvedSession.canvasRect.height);
previewFrame.debugName =
panelState != nullptr ? panelState->previewDebugName.data() : nullptr;
previewFrame.debugSource =
panelState != nullptr ? panelState->previewDebugSource.data() : nullptr;
m_hostedPreviewQueue.Submit(previewFrame);
} else if (previewConsumption.appendRuntimeDrawDataToShell) {
AppendDrawData(composedDrawData, frame.drawData);
const XCUILayoutLabFrameComposition* composition = nullptr;
if (m_layoutLabPanel != nullptr) {
composition = &m_layoutLabPanel->ComposeFrame(
XCUILayoutLabFrameCompositionRequest{
resolvedSession,
panelSnapshot });
}
m_nativeLayoutCanvasHost.EndCanvas();
const Application::NativeHostedPreviewConsumption composedPreviewConsumption =
composition != nullptr
? Application::ResolveNativeHostedPreviewConsumption(
composition->nativeHostedPreview,
composition->hasHostedSurfaceDescriptor,
composition->showHostedSurfaceImage,
"Native layout preview pending",
"Waiting for queued native preview output to publish into the layout card.")
: previewConsumption;
if (composition != nullptr &&
composition->frameResult != nullptr &&
composedPreviewConsumption.appendRuntimeDrawDataToShell) {
AppendDrawData(composedDrawData, composition->frameResult->drawData);
}
NativePanelFrameSummary summary = {};
summary.layout = panelLayout;
summary.lineA = m_nativeLayoutReloadSucceeded
summary.lineA =
m_layoutLabPanel != nullptr && m_layoutLabPanel->GetLastReloadSucceeded()
? Application::ComposeNativeHostedPreviewStatusLine(
previewConsumption,
frame.stats.statusMessage)
composedPreviewConsumption,
composition != nullptr && composition->frameResult != nullptr
? composition->frameResult->stats.statusMessage
: std::string_view("XCUI layout frame unavailable"))
: "Layout lab reload failed; showing last retained runtime state.";
summary.lineB =
std::to_string(frame.stats.rowCount) + " rows | " +
std::to_string(frame.stats.columnCount) + " cols | " +
std::to_string(frame.stats.commandCount) + " cmds";
std::to_string(
composition != nullptr && composition->frameResult != nullptr
? composition->frameResult->stats.rowCount
: 0u) + " rows | " +
std::to_string(
composition != nullptr && composition->frameResult != nullptr
? composition->frameResult->stats.columnCount
: 0u) + " cols | " +
std::to_string(
composition != nullptr && composition->frameResult != nullptr
? composition->frameResult->stats.commandCount
: 0u) + " cmds";
summary.overlay = extractCanvasOverlay(m_nativeLayoutCanvasHost);
panelSummaries.push_back(std::move(summary));
m_nativeLayoutCanvasHost.ClearCanvasSession();

View File

@@ -4,7 +4,6 @@
#include "XCUIBackend/XCUIEditorCommandRouter.h"
#include "XCUIBackend/NativeXCUIPanelCanvasHost.h"
#include "XCUIBackend/XCUIDemoRuntime.h"
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
#include "XCUIBackend/XCUIInputBridge.h"
#include "XCUIBackend/XCUILayoutLabRuntime.h"
@@ -449,16 +448,10 @@ private:
ShellChromeState m_shellChromeState = {};
std::vector<HostedPreviewOffscreenSurface> m_hostedPreviewSurfaces = {};
WindowHostMode m_windowHostMode = WindowHostMode::NativeXCUI;
::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime m_nativeDemoRuntime;
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeLayoutRuntime;
::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_nativeDemoInputBridge;
::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_nativeLayoutInputBridge;
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeDemoCanvasHost;
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeLayoutCanvasHost;
ShellPanelId m_nativeActivePanel = ShellPanelId::XCUIDemo;
bool m_legacyHostDemoWindowVisible = false;
bool m_nativeDemoReloadSucceeded = false;
bool m_nativeLayoutReloadSucceeded = false;
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime;
MainWindowNativeBackdropRenderer m_nativeBackdropRenderer;
bool m_running = false;

View File

@@ -227,7 +227,14 @@ void Application::ConfigureShellCommandRouter() {
ShellPanelId::XCUILayoutLab,
enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
};
bindings.onHostedPreviewModeChanged = [this]() { ConfigureHostedPreviewPresenters(); };
bindings.onHostedPreviewModeChanged = [this]() {
if (IsNativeWindowHostEnabled()) {
InitializeNativeShell();
return;
}
ConfigureHostedPreviewPresenters();
};
Application::RegisterShellViewCommands(m_shellCommandRouter, bindings);
RegisterLegacyHostDemoWindowCommand(

View File

@@ -1,16 +1,8 @@
#include "XCUIDemoPanel.h"
#include "XCUIBackend/NullXCUIPanelCanvasHost.h"
#include <XCEngine/UI/Types.h>
#include <imgui.h>
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <string>
#include <utility>
namespace XCEngine {
namespace NewEditor {
@@ -19,142 +11,10 @@ namespace {
constexpr float kCanvasHudHeight = 82.0f;
constexpr float kCanvasHudPadding = 10.0f;
constexpr char kPreviewDebugName[] = "XCUI Demo";
constexpr char kPreviewDebugSource[] = "new_editor.panels.xcui_demo";
bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) {
return point.x >= rect.x &&
point.y >= rect.y &&
point.x <= rect.x + rect.width &&
point.y <= rect.y + rect.height;
}
void DrawRectOverlay(
::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost& canvasHost,
::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime& runtime,
const std::string& elementId,
const UI::UIColor& color,
const char* label) {
if (elementId.empty()) {
return;
}
UI::UIRect rect = {};
if (!runtime.TryGetElementRect(elementId, rect)) {
return;
}
canvasHost.DrawOutlineRect(rect, color, 2.0f, 6.0f);
if (label != nullptr && label[0] != '\0') {
canvasHost.DrawText(
UI::UIPoint(rect.x + 4.0f, rect.y + 4.0f),
label,
color);
}
}
const char* GetPreviewPathLabel(
const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter) {
if (previewPresenter == nullptr) {
return "not injected";
}
return previewPresenter->IsNativeQueued()
? "native queued offscreen surface"
: "hosted presenter";
}
const char* GetPreviewStateLabel(
const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter,
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats,
bool hasHostedSurfaceDescriptor,
bool showHostedSurfaceImage) {
if (previewPresenter == nullptr) {
return "disabled";
}
const bool nativeHostedPreview = previewPresenter->IsNativeQueued();
if (nativeHostedPreview) {
if (showHostedSurfaceImage) {
return "live";
}
if (previewStats.queuedToNativePass || hasHostedSurfaceDescriptor) {
return "warming";
}
return "awaiting submit";
}
return previewStats.presented ? "live" : "idle";
}
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot BuildPassiveSnapshot(
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession,
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions& options) {
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot = {};
snapshot.pointerPosition = canvasSession.pointerPosition;
snapshot.pointerInside = options.hasPointerInsideOverride
? options.pointerInsideOverride
: (canvasSession.validCanvas && canvasSession.hovered);
snapshot.windowFocused = options.windowFocused;
snapshot.timestampNanoseconds = options.timestampNanoseconds;
return snapshot;
}
} // namespace
XCUIDemoPanel::XCUIDemoPanel(::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource)
: XCUIDemoPanel(
inputSource,
nullptr,
::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost()) {}
XCUIDemoPanel::XCUIDemoPanel(
::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost)
: Panel("XCUI Demo")
, m_inputSource(inputSource)
, m_previewPresenter(std::move(previewPresenter))
, m_canvasHost(std::move(canvasHost)) {
if (m_canvasHost == nullptr) {
m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
}
m_lastReloadSucceeded = m_runtime.ReloadDocuments();
}
void XCUIDemoPanel::SetHostedPreviewEnabled(bool enabled) {
m_hostedPreviewEnabled = enabled;
if (!m_hostedPreviewEnabled) {
m_lastPreviewStats = {};
}
}
const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult& XCUIDemoPanel::GetFrameResult() const {
return m_runtime.GetFrameResult();
}
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& XCUIDemoPanel::GetLastPreviewStats() const {
return m_lastPreviewStats;
}
void XCUIDemoPanel::SetHostedPreviewPresenter(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) {
m_previewPresenter = std::move(previewPresenter);
m_lastPreviewStats = {};
}
void XCUIDemoPanel::SetCanvasHost(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost) {
m_canvasHost = std::move(canvasHost);
if (m_canvasHost == nullptr) {
m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
}
}
bool XCUIDemoPanel::IsUsingNativeHostedPreview() const {
return m_previewPresenter != nullptr && m_previewPresenter->IsNativeQueued();
}
void XCUIDemoPanel::Render() {
ImGui::SetNextWindowSize(ImVec2(1040.0f, 720.0f), ImGuiCond_Appearing);
ImGui::SetNextWindowDockID(ImGui::GetID("XCNewEditorDockSpace"), ImGuiCond_Appearing);
@@ -182,198 +42,57 @@ void XCUIDemoPanel::Render() {
const float diagnosticsHeight = 232.0f;
const ImVec2 hostRegion = ImGui::GetContentRegionAvail();
const float canvasHeight = (std::max)(140.0f, hostRegion.y - diagnosticsHeight);
if (m_canvasHost == nullptr) {
m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
}
const bool nativeHostedPreview = IsUsingNativeHostedPreview();
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
const bool hasHostedSurfaceDescriptor =
nativeHostedPreview &&
m_previewPresenter != nullptr &&
m_previewPresenter->TryGetSurfaceDescriptor(kPreviewDebugName, hostedSurfaceDescriptor);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
const bool showHostedSurfaceImage =
nativeHostedPreview &&
m_previewPresenter != nullptr &&
m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage);
const char* const previewPathLabel = GetPreviewPathLabel(m_previewPresenter.get());
const float topInset = m_showCanvasHud ? (kCanvasHudHeight + kCanvasHudPadding) : 0.0f;
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {};
canvasRequest.childId = "XCUIDemoCanvasHost";
canvasRequest.height = canvasHeight;
canvasRequest.topInset = topInset;
canvasRequest.showSurfaceImage = showHostedSurfaceImage;
canvasRequest.surfaceImage = hostedSurfaceImage;
canvasRequest.placeholderTitle =
nativeHostedPreview ? "Native XCUI preview pending" : "Injected XCUI canvas host";
canvasRequest.placeholderSubtitle =
nativeHostedPreview
? "Waiting for native queued render output to publish back into the sandbox panel."
: "Inject a concrete canvas host to render the demo sandbox inside this panel.";
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession =
m_canvasHost->BeginCanvas(canvasRequest);
const UI::UIRect canvasRect = canvasSession.canvasRect;
const bool validCanvas = canvasSession.validCanvas;
::XCEngine::Editor::XCUIBackend::XCUIDemoInputState input = {};
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions bridgeOptions = {};
bridgeOptions.timestampNanoseconds = static_cast<std::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);
input.canvasRect = canvasRect;
input.pointerPosition = snapshot.pointerPosition;
input.pointerInside = snapshot.pointerInside;
input.pointerPressed = frameDelta.pointer.pressed[0];
input.pointerReleased = frameDelta.pointer.released[0];
input.pointerDown = snapshot.pointerButtonsDown[0];
input.windowFocused = snapshot.windowFocused;
input.shortcutPressed = false;
input.wantCaptureMouse = snapshot.wantCaptureMouse;
input.wantCaptureKeyboard = snapshot.wantCaptureKeyboard;
input.wantTextInput = snapshot.wantTextInput;
input.events = frameDelta.events;
const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult& frame = m_runtime.Update(input);
if (m_hostedPreviewEnabled && m_previewPresenter != nullptr) {
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {};
previewFrame.drawData = &frame.drawData;
previewFrame.canvasRect = canvasRect;
previewFrame.logicalSize = UI::UISize(canvasRect.width, canvasRect.height);
previewFrame.debugName = kPreviewDebugName;
previewFrame.debugSource = kPreviewDebugSource;
m_previewPresenter->Present(previewFrame);
m_lastPreviewStats = m_previewPresenter->GetLastStats();
} else {
m_lastPreviewStats = {};
}
const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameStats& stats = frame.stats;
const char* const previewStateLabel = GetPreviewStateLabel(
m_previewPresenter.get(),
m_lastPreviewStats,
hasHostedSurfaceDescriptor,
showHostedSurfaceImage);
if (m_showCanvasHud) {
const float hudWidth = (std::min)(canvasSession.hostRect.width - 16.0f, 430.0f);
const UI::UIRect hudRect(
canvasSession.hostRect.x + 8.0f,
canvasSession.hostRect.y + 8.0f,
(std::max)(0.0f, hudWidth),
kCanvasHudHeight - 8.0f);
m_canvasHost->DrawFilledRect(
hudRect,
UI::UIColor(16.0f / 255.0f, 22.0f / 255.0f, 30.0f / 255.0f, 220.0f / 255.0f),
8.0f);
m_canvasHost->DrawOutlineRect(
hudRect,
UI::UIColor(52.0f / 255.0f, 72.0f / 255.0f, 96.0f / 255.0f, 1.0f),
1.0f,
8.0f);
const UI::UIPoint lineOrigin(hudRect.x + 10.0f, hudRect.y + 8.0f);
const UI::UIColor textColor(191.0f / 255.0f, 205.0f / 255.0f, 224.0f / 255.0f, 1.0f);
m_canvasHost->DrawText(lineOrigin, "XCUI Runtime", UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f));
std::string previewLine = std::string(previewPathLabel) + " | " + previewStateLabel;
std::string treeLine =
"Tree " + std::to_string(static_cast<unsigned long long>(stats.treeGeneration)) +
" | Elements " + std::to_string(stats.elementCount) +
" | Commands " + std::to_string(stats.commandCount);
std::string flushLine =
"Submit " + std::to_string(m_lastPreviewStats.submittedDrawListCount) +
"/" + std::to_string(m_lastPreviewStats.submittedCommandCount) +
" | Flush " + std::to_string(m_lastPreviewStats.flushedDrawListCount) +
"/" + std::to_string(m_lastPreviewStats.flushedCommandCount);
m_canvasHost->DrawText(UI::UIPoint(lineOrigin.x, lineOrigin.y + 18.0f), previewLine, textColor);
m_canvasHost->DrawText(UI::UIPoint(lineOrigin.x, lineOrigin.y + 36.0f), stats.statusMessage, textColor);
m_canvasHost->DrawText(UI::UIPoint(lineOrigin.x, lineOrigin.y + 54.0f), treeLine, textColor);
m_canvasHost->DrawText(UI::UIPoint(lineOrigin.x + 210.0f, lineOrigin.y + 54.0f), flushLine, textColor);
}
if (m_showDebugRects && validCanvas && (!nativeHostedPreview || showHostedSurfaceImage)) {
DrawRectOverlay(
*m_canvasHost,
m_runtime,
stats.hoveredElementId,
UI::UIColor(1.0f, 195.0f / 255.0f, 64.0f / 255.0f, 1.0f),
"hover");
DrawRectOverlay(
*m_canvasHost,
m_runtime,
stats.focusedElementId,
UI::UIColor(64.0f / 255.0f, 214.0f / 255.0f, 1.0f, 1.0f),
"focus");
DrawRectOverlay(
*m_canvasHost,
m_runtime,
"toggleAccent",
UI::UIColor(150.0f / 255.0f, 1.0f, 150.0f / 255.0f, 160.0f / 255.0f),
"toggle");
}
m_canvasHost->EndCanvas();
const XCUIDemoPanelFrameComposition& composition = ComposeFrame(
XCUIDemoPanelFrameComposeOptions{
canvasHeight,
topInset,
m_hostedPreviewEnabled,
m_showCanvasHud,
m_showDebugRects,
0u,
"XCUIDemoCanvasHost"});
const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameStats& stats = composition.frame->stats;
ImGui::Separator();
ImGui::BeginChild("XCUIDemoDiagnostics", ImVec2(0.0f, 0.0f), false, ImGuiWindowFlags_NoScrollbar);
ImGui::SeparatorText("Preview");
ImGui::Text("Path: %s | state: %s", previewPathLabel, previewStateLabel);
ImGui::Text(
"Path: %s | state: %s",
composition.previewPathLabel.c_str(),
composition.previewStateLabel.c_str());
ImGui::Text(
"Presenter: presented %s | submit->native %s",
m_lastPreviewStats.presented ? "yes" : "no",
m_lastPreviewStats.queuedToNativePass ? "yes" : "no");
composition.previewStats.presented ? "yes" : "no",
composition.previewStats.queuedToNativePass ? "yes" : "no");
ImGui::Text(
"Submitted: %zu lists / %zu cmds | Flushed: %zu lists / %zu cmds",
m_lastPreviewStats.submittedDrawListCount,
m_lastPreviewStats.submittedCommandCount,
m_lastPreviewStats.flushedDrawListCount,
m_lastPreviewStats.flushedCommandCount);
composition.previewStats.submittedDrawListCount,
composition.previewStats.submittedCommandCount,
composition.previewStats.flushedDrawListCount,
composition.previewStats.flushedCommandCount);
ImGui::TextWrapped(
"Source: %s",
hasHostedSurfaceDescriptor && !hostedSurfaceDescriptor.debugSource.empty()
? hostedSurfaceDescriptor.debugSource.c_str()
composition.hasHostedSurfaceDescriptor && !composition.hostedSurfaceDescriptor.debugSource.empty()
? composition.hostedSurfaceDescriptor.debugSource.c_str()
: kPreviewDebugSource);
if (nativeHostedPreview) {
if (composition.nativeHostedPreview) {
ImGui::Text(
"Surface descriptor: %s | image published: %s | queued frame index: %zu",
hasHostedSurfaceDescriptor ? "yes" : "no",
showHostedSurfaceImage ? "yes" : "no",
hasHostedSurfaceDescriptor ? hostedSurfaceDescriptor.queuedFrameIndex : 0u);
if (hasHostedSurfaceDescriptor) {
composition.hasHostedSurfaceDescriptor ? "yes" : "no",
composition.showHostedSurfaceImage ? "yes" : "no",
composition.hasHostedSurfaceDescriptor ? composition.hostedSurfaceDescriptor.queuedFrameIndex : 0u);
if (composition.hasHostedSurfaceDescriptor) {
ImGui::Text(
"Surface: %ux%u | logical: %.0f x %.0f | rendered rect: %.0f, %.0f %.0f x %.0f",
hostedSurfaceDescriptor.image.surfaceWidth,
hostedSurfaceDescriptor.image.surfaceHeight,
hostedSurfaceDescriptor.logicalSize.width,
hostedSurfaceDescriptor.logicalSize.height,
hostedSurfaceDescriptor.image.renderedCanvasRect.x,
hostedSurfaceDescriptor.image.renderedCanvasRect.y,
hostedSurfaceDescriptor.image.renderedCanvasRect.width,
hostedSurfaceDescriptor.image.renderedCanvasRect.height);
composition.hostedSurfaceDescriptor.image.surfaceWidth,
composition.hostedSurfaceDescriptor.image.surfaceHeight,
composition.hostedSurfaceDescriptor.logicalSize.width,
composition.hostedSurfaceDescriptor.logicalSize.height,
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.x,
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.y,
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.width,
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.height);
} else {
ImGui::TextDisabled("No native surface descriptor has been published back yet.");
}
@@ -401,24 +120,27 @@ void XCUIDemoPanel::Render() {
"Last command: %s | Accent: %s",
stats.lastCommandId.empty() ? "none" : stats.lastCommandId.c_str(),
stats.accentEnabled ? "on" : "off");
ImGui::Text("Canvas: %.0f x %.0f", input.canvasRect.width, input.canvasRect.height);
ImGui::Text(
"Canvas: %.0f x %.0f",
composition.input.canvasRect.width,
composition.input.canvasRect.height);
ImGui::SeparatorText("Input");
ImGui::Text(
"Pointer: %.0f, %.0f | inside %s | down %s | pressed %s | released %s",
input.pointerPosition.x,
input.pointerPosition.y,
input.pointerInside ? "yes" : "no",
input.pointerDown ? "yes" : "no",
input.pointerPressed ? "yes" : "no",
input.pointerReleased ? "yes" : "no");
composition.input.pointerPosition.x,
composition.input.pointerPosition.y,
composition.input.pointerInside ? "yes" : "no",
composition.input.pointerDown ? "yes" : "no",
composition.input.pointerPressed ? "yes" : "no",
composition.input.pointerReleased ? "yes" : "no");
ImGui::Text(
"Focus %s | capture mouse %s | capture keyboard %s | text input %s | events %zu",
input.windowFocused ? "yes" : "no",
input.wantCaptureMouse ? "yes" : "no",
input.wantCaptureKeyboard ? "yes" : "no",
input.wantTextInput ? "yes" : "no",
input.events.size());
composition.input.windowFocused ? "yes" : "no",
composition.input.wantCaptureMouse ? "yes" : "no",
composition.input.wantCaptureKeyboard ? "yes" : "no",
composition.input.wantTextInput ? "yes" : "no",
composition.input.events.size());
ImGui::EndChild();
ImGui::End();

View File

@@ -7,11 +7,47 @@
#include "XCUIBackend/XCUIPanelCanvasHost.h"
#include "XCUIBackend/XCUIDemoRuntime.h"
#include <cstdint>
#include <memory>
#include <string>
namespace XCEngine {
namespace NewEditor {
struct XCUIDemoPanelFrameComposeOptions {
float canvasHeight = 0.0f;
float canvasTopInset = 0.0f;
bool hostedPreviewEnabled = true;
bool showCanvasHud = true;
bool showDebugRects = true;
std::uint64_t timestampNanoseconds = 0;
const char* canvasChildId = "XCUIDemoCanvasHost";
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot* inputSnapshot = nullptr;
bool useDefaultPlaceholder = true;
const char* placeholderTitle = nullptr;
const char* placeholderSubtitle = nullptr;
bool drawPreviewFrame = true;
const char* badgeTitle = nullptr;
const char* badgeSubtitle = nullptr;
};
struct XCUIDemoPanelFrameComposition {
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot inputSnapshot = {};
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta inputDelta = {};
::XCEngine::Editor::XCUIBackend::XCUIDemoInputState input = {};
const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult* frame = nullptr;
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats previewStats = {};
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
bool hostedPreviewEnabled = false;
bool nativeHostedPreview = false;
bool hasHostedSurfaceDescriptor = false;
bool showHostedSurfaceImage = false;
std::string previewPathLabel = {};
std::string previewStateLabel = {};
};
class XCUIDemoPanel : public Panel {
public:
explicit XCUIDemoPanel(
@@ -23,6 +59,8 @@ public:
~XCUIDemoPanel() override = default;
void Render() override;
const XCUIDemoPanelFrameComposition& ComposeFrame(
const XCUIDemoPanelFrameComposeOptions& options = XCUIDemoPanelFrameComposeOptions());
void SetHostedPreviewEnabled(bool enabled);
void SetHostedPreviewPresenter(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter);
@@ -32,6 +70,8 @@ public:
bool IsUsingNativeHostedPreview() const;
const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult& GetFrameResult() const;
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& GetLastPreviewStats() const;
const XCUIDemoPanelFrameComposition& GetLastFrameComposition() const;
bool GetLastReloadSucceeded() const { return m_lastReloadSucceeded; }
private:
bool m_lastReloadSucceeded = false;
@@ -44,6 +84,7 @@ private:
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter;
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost;
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats m_lastPreviewStats = {};
XCUIDemoPanelFrameComposition m_lastFrameComposition = {};
};
} // namespace NewEditor

View File

@@ -1,391 +0,0 @@
#include "XCUILayoutLabPanel.h"
#include "XCUIBackend/NullXCUIPanelCanvasHost.h"
#include <XCEngine/UI/Types.h>
#include <imgui.h>
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <utility>
namespace XCEngine {
namespace NewEditor {
namespace {
constexpr char kPreviewDebugName[] = "XCUI Layout Lab";
constexpr char kPreviewDebugSource[] = "new_editor.panels.xcui_layout_lab";
bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) {
return point.x >= rect.x &&
point.y >= rect.y &&
point.x <= rect.x + rect.width &&
point.y <= rect.y + rect.height;
}
const char* GetPreviewPathLabel(
const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter) {
if (previewPresenter == nullptr) {
return "not injected";
}
return previewPresenter->IsNativeQueued()
? "native queued offscreen surface"
: "hosted presenter";
}
const char* GetPreviewStateLabel(
const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter,
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats,
bool hasHostedSurfaceDescriptor,
bool showHostedSurfaceImage) {
if (previewPresenter == nullptr) {
return "disabled";
}
const bool nativeHostedPreview = previewPresenter->IsNativeQueued();
if (nativeHostedPreview) {
if (showHostedSurfaceImage) {
return "live";
}
if (previewStats.queuedToNativePass || hasHostedSurfaceDescriptor) {
return "warming";
}
return "awaiting submit";
}
return previewStats.presented ? "live" : "idle";
}
bool ContainsKeyTransition(
const std::vector<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) {
if (!canvasSession.validCanvas) {
return false;
}
return canvasSession.hovered ||
(canvasSession.windowFocused &&
!previousFrame.stats.selectedElementId.empty());
}
void PopulateKeyboardNavigationInput(
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState& input,
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta,
bool captureKeyboardNavigation) {
if (!captureKeyboardNavigation) {
return;
}
using ::XCEngine::Input::KeyCode;
const auto pressedThisFrame =
[&frameDelta](KeyCode keyCode) {
const std::int32_t code = static_cast<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::IXCUIInputSnapshotSource* inputSource)
: XCUILayoutLabPanel(inputSource, nullptr, ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost()) {}
XCUILayoutLabPanel::XCUILayoutLabPanel(
::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost)
: Panel("XCUI Layout Lab")
, m_inputSource(inputSource)
, m_previewPresenter(std::move(previewPresenter))
, m_canvasHost(std::move(canvasHost)) {
if (m_canvasHost == nullptr) {
m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
}
m_lastReloadSucceeded = m_runtime.ReloadDocuments();
}
void XCUILayoutLabPanel::SetHostedPreviewEnabled(bool enabled) {
m_hostedPreviewEnabled = enabled;
if (!m_hostedPreviewEnabled) {
m_lastPreviewStats = {};
}
}
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& XCUILayoutLabPanel::GetFrameResult() const {
return m_runtime.GetFrameResult();
}
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& XCUILayoutLabPanel::GetLastPreviewStats() const {
return m_lastPreviewStats;
}
bool XCUILayoutLabPanel::TryGetElementRect(const std::string& elementId, ::XCEngine::UI::UIRect& outRect) const {
return m_runtime.TryGetElementRect(elementId, outRect);
}
void XCUILayoutLabPanel::SetHostedPreviewPresenter(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) {
m_previewPresenter = std::move(previewPresenter);
m_lastPreviewStats = {};
}
void XCUILayoutLabPanel::SetCanvasHost(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost) {
m_canvasHost = std::move(canvasHost);
if (m_canvasHost == nullptr) {
m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
}
}
bool XCUILayoutLabPanel::IsUsingNativeHostedPreview() const {
return m_previewPresenter != nullptr && m_previewPresenter->IsNativeQueued();
}
void XCUILayoutLabPanel::Render() {
ImGui::SetNextWindowSize(ImVec2(960.0f, 720.0f), ImGuiCond_Appearing);
ImGui::SetNextWindowDockID(ImGui::GetID("XCNewEditorDockSpace"), ImGuiCond_Appearing);
bool open = true;
if (!ImGui::Begin(GetName().c_str(), &open)) {
ImGui::End();
if (!open) {
SetVisible(false);
}
return;
}
if (ImGui::Button("Reload Layout Lab")) {
m_lastReloadSucceeded = m_runtime.ReloadDocuments();
}
ImGui::SameLine();
ImGui::TextUnformatted(m_lastReloadSucceeded ? "Reload: OK" : "Reload: Failed");
ImGui::Separator();
const float diagnosticsHeight = 240.0f;
const ImVec2 hostRegion = ImGui::GetContentRegionAvail();
const float canvasHeight = (std::max)(140.0f, hostRegion.y - diagnosticsHeight);
if (m_canvasHost == nullptr) {
m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
}
const bool nativeHostedPreview = IsUsingNativeHostedPreview();
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
const bool hasHostedSurfaceDescriptor =
nativeHostedPreview &&
m_previewPresenter != nullptr &&
m_previewPresenter->TryGetSurfaceDescriptor(kPreviewDebugName, hostedSurfaceDescriptor);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
const bool showHostedSurfaceImage =
nativeHostedPreview &&
m_previewPresenter != nullptr &&
m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage);
const char* const previewPathLabel = GetPreviewPathLabel(m_previewPresenter.get());
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {};
canvasRequest.childId = "XCUILayoutLabCanvasHost";
canvasRequest.height = canvasHeight;
canvasRequest.showSurfaceImage = showHostedSurfaceImage;
canvasRequest.surfaceImage = hostedSurfaceImage;
canvasRequest.placeholderTitle =
nativeHostedPreview ? "Native layout preview pending" : "Injected layout canvas host";
canvasRequest.placeholderSubtitle =
nativeHostedPreview
? "Waiting for native queued render output to publish back into the layout sandbox."
: "Inject a concrete canvas host to render the layout sandbox inside this panel.";
canvasRequest.badgeTitle = "Layout Lab";
canvasRequest.badgeSubtitle = previewPathLabel;
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession =
m_canvasHost->BeginCanvas(canvasRequest);
const UI::UIRect canvasRect = canvasSession.canvasRect;
const bool validCanvas = canvasSession.validCanvas;
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions bridgeOptions = {};
bridgeOptions.timestampNanoseconds = static_cast<std::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;
input.pointerPosition = snapshot.pointerPosition;
input.pointerInside = snapshot.pointerInside;
input.pointerPressed = input.pointerInside && frameDelta.pointer.pressed[0];
PopulateKeyboardNavigationInput(
input,
frameDelta,
ShouldCaptureKeyboardNavigation(canvasSession, m_runtime.GetFrameResult()));
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& frame = m_runtime.Update(input);
if (m_hostedPreviewEnabled && m_previewPresenter != nullptr) {
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {};
previewFrame.drawData = &frame.drawData;
previewFrame.canvasRect = input.canvasRect;
previewFrame.logicalSize = UI::UISize(input.canvasRect.width, input.canvasRect.height);
previewFrame.debugName = kPreviewDebugName;
previewFrame.debugSource = kPreviewDebugSource;
m_previewPresenter->Present(previewFrame);
m_lastPreviewStats = m_previewPresenter->GetLastStats();
} else {
m_lastPreviewStats = {};
}
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameStats& stats = frame.stats;
const char* const previewStateLabel = GetPreviewStateLabel(
m_previewPresenter.get(),
m_lastPreviewStats,
hasHostedSurfaceDescriptor,
showHostedSurfaceImage);
m_canvasHost->EndCanvas();
ImGui::Separator();
ImGui::BeginChild("XCUILayoutLabDiagnostics", ImVec2(0.0f, 0.0f), false, ImGuiWindowFlags_NoScrollbar);
ImGui::SeparatorText("Preview");
ImGui::Text("Path: %s | state: %s", previewPathLabel, previewStateLabel);
ImGui::Text(
"Presenter: presented %s | submit->native %s",
m_lastPreviewStats.presented ? "yes" : "no",
m_lastPreviewStats.queuedToNativePass ? "yes" : "no");
ImGui::Text(
"Submitted: %zu lists / %zu cmds | Flushed: %zu lists / %zu cmds",
m_lastPreviewStats.submittedDrawListCount,
m_lastPreviewStats.submittedCommandCount,
m_lastPreviewStats.flushedDrawListCount,
m_lastPreviewStats.flushedCommandCount);
ImGui::TextWrapped(
"Source: %s",
hasHostedSurfaceDescriptor && !hostedSurfaceDescriptor.debugSource.empty()
? hostedSurfaceDescriptor.debugSource.c_str()
: kPreviewDebugSource);
if (nativeHostedPreview) {
ImGui::Text(
"Surface descriptor: %s | image published: %s | queued frame index: %zu",
hasHostedSurfaceDescriptor ? "yes" : "no",
showHostedSurfaceImage ? "yes" : "no",
hasHostedSurfaceDescriptor ? hostedSurfaceDescriptor.queuedFrameIndex : 0u);
if (hasHostedSurfaceDescriptor) {
ImGui::Text(
"Surface: %ux%u | logical: %.0f x %.0f | rendered rect: %.0f, %.0f %.0f x %.0f",
hostedSurfaceDescriptor.image.surfaceWidth,
hostedSurfaceDescriptor.image.surfaceHeight,
hostedSurfaceDescriptor.logicalSize.width,
hostedSurfaceDescriptor.logicalSize.height,
hostedSurfaceDescriptor.image.renderedCanvasRect.x,
hostedSurfaceDescriptor.image.renderedCanvasRect.y,
hostedSurfaceDescriptor.image.renderedCanvasRect.width,
hostedSurfaceDescriptor.image.renderedCanvasRect.height);
} else {
ImGui::TextDisabled("No native surface descriptor has been published back yet.");
}
} else {
ImGui::TextDisabled("No native surface descriptor is available without a native queued presenter.");
}
ImGui::SeparatorText("Runtime");
ImGui::Text("Status: %s", stats.statusMessage.c_str());
ImGui::Text(
"Rows: %zu | Columns: %zu | Overlays: %zu | Scroll views: %zu",
stats.rowCount,
stats.columnCount,
stats.overlayCount,
stats.scrollViewCount);
ImGui::Text(
"Tree items: %zu (%zu expanded) | Property sections: %zu (%zu expanded)",
stats.treeItemCount,
stats.expandedTreeItemCount,
stats.propertySectionCount,
stats.expandedPropertySectionCount);
ImGui::Text(
"Draw lists: %zu | Draw commands: %zu",
stats.drawListCount,
stats.commandCount);
ImGui::Text(
"Command types: fill %zu | outline %zu | text %zu | image %zu | clip %zu/%zu",
stats.filledRectCommandCount,
stats.rectOutlineCommandCount,
stats.textCommandCount,
stats.imageCommandCount,
stats.clipPushCommandCount,
stats.clipPopCommandCount);
ImGui::Text(
"Native overlay: %s | supported %zu | unsupported %zu",
stats.nativeOverlayReady ? "preflight OK" : "preflight issues",
stats.nativeSupportedCommandCount,
stats.nativeUnsupportedCommandCount);
ImGui::TextWrapped(
"Native note: %s",
stats.nativeOverlayStatusMessage.empty() ? "none" : stats.nativeOverlayStatusMessage.c_str());
ImGui::Text(
"Hovered: %s | Selected: %s | canvas: %.0f x %.0f",
stats.hoveredElementId.empty() ? "none" : stats.hoveredElementId.c_str(),
stats.selectedElementId.empty() ? "none" : stats.selectedElementId.c_str(),
input.canvasRect.width,
input.canvasRect.height);
ImGui::SeparatorText("Input");
ImGui::Text(
"Pointer: %.0f, %.0f | inside %s | pressed %s",
input.pointerPosition.x,
input.pointerPosition.y,
input.pointerInside ? "yes" : "no",
input.pointerPressed ? "yes" : "no");
ImGui::EndChild();
ImGui::End();
if (!open) {
SetVisible(false);
}
}
} // namespace NewEditor
} // namespace XCEngine

View File

@@ -13,6 +13,29 @@
namespace XCEngine {
namespace NewEditor {
struct XCUILayoutLabFrameCompositionRequest {
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot inputSnapshot = {};
};
struct XCUILayoutLabFrameComposition {
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot inputSnapshot = {};
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta inputDelta = {};
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState inputState = {};
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats previewStats = {};
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult* frameResult = nullptr;
std::string previewPathLabel = {};
std::string previewStateLabel = {};
std::string previewSourceLabel = {};
bool hostedPreviewEnabled = true;
bool nativeHostedPreview = false;
bool hasHostedSurfaceDescriptor = false;
bool showHostedSurfaceImage = false;
};
class XCUILayoutLabPanel : public Panel {
public:
explicit XCUILayoutLabPanel(
@@ -24,6 +47,8 @@ public:
~XCUILayoutLabPanel() override = default;
void Render() override;
const XCUILayoutLabFrameComposition& ComposeFrame(
const XCUILayoutLabFrameCompositionRequest& request);
void SetHostedPreviewEnabled(bool enabled);
void SetHostedPreviewPresenter(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter);
@@ -32,7 +57,9 @@ public:
bool IsHostedPreviewEnabled() const { return m_hostedPreviewEnabled; }
bool IsUsingNativeHostedPreview() const;
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& GetFrameResult() const;
const XCUILayoutLabFrameComposition& GetLastFrameComposition() const;
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& GetLastPreviewStats() const;
bool GetLastReloadSucceeded() const { return m_lastReloadSucceeded; }
bool TryGetElementRect(const std::string& elementId, ::XCEngine::UI::UIRect& outRect) const;
private:
@@ -44,6 +71,7 @@ private:
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter;
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost;
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats m_lastPreviewStats = {};
XCUILayoutLabFrameComposition m_lastFrameComposition = {};
};
} // namespace NewEditor

View File

@@ -202,4 +202,47 @@ TEST(NewEditorXCUIDemoPanelTest, ClearingHostedPreviewPresenterDoesNotRestoreImG
EXPECT_EQ(stats.flushedCommandCount, 0u);
}
TEST(NewEditorXCUIDemoPanelTest, ComposeFrameBuildsShellAgnosticFrameState) {
auto previewPresenter = std::make_unique<RecordingHostedPreviewPresenter>();
RecordingHostedPreviewPresenter* previewPresenterPtr = previewPresenter.get();
auto canvasHost = std::make_unique<StubCanvasHost>();
StubCanvasHost* canvasHostPtr = canvasHost.get();
XCUIDemoPanel panel(nullptr, std::move(previewPresenter));
panel.SetCanvasHost(std::move(canvasHost));
const XCEngine::NewEditor::XCUIDemoPanelFrameComposition& composition =
panel.ComposeFrame(XCEngine::NewEditor::XCUIDemoPanelFrameComposeOptions{
512.0f,
24.0f,
true,
false,
false,
123456u,
"XCUIDemoTestCanvas"});
EXPECT_EQ(&composition, &panel.GetLastFrameComposition());
ASSERT_NE(composition.frame, nullptr);
EXPECT_TRUE(composition.hostedPreviewEnabled);
EXPECT_FALSE(composition.nativeHostedPreview);
EXPECT_FALSE(composition.hasHostedSurfaceDescriptor);
EXPECT_FALSE(composition.showHostedSurfaceImage);
EXPECT_EQ(composition.previewPathLabel, "hosted presenter");
EXPECT_EQ(composition.previewStateLabel, "live");
EXPECT_EQ(composition.inputSnapshot.timestampNanoseconds, 123456u);
EXPECT_EQ(composition.canvasSession.canvasRect.width, canvasHostPtr->session.canvasRect.width);
EXPECT_EQ(composition.canvasSession.canvasRect.height, canvasHostPtr->session.canvasRect.height);
EXPECT_EQ(composition.input.canvasRect.width, canvasHostPtr->session.canvasRect.width);
EXPECT_EQ(composition.input.canvasRect.height, canvasHostPtr->session.canvasRect.height);
EXPECT_EQ(canvasHostPtr->beginCanvasCallCount, 1u);
EXPECT_EQ(canvasHostPtr->endCanvasCallCount, 1u);
EXPECT_FLOAT_EQ(canvasHostPtr->lastRequest.height, 512.0f);
EXPECT_FLOAT_EQ(canvasHostPtr->lastRequest.topInset, 24.0f);
EXPECT_STREQ(canvasHostPtr->lastRequest.childId, "XCUIDemoTestCanvas");
EXPECT_EQ(previewPresenterPtr->presentCallCount, 1u);
EXPECT_EQ(previewPresenterPtr->lastDebugName, "XCUI Demo");
EXPECT_EQ(composition.previewStats.submittedDrawListCount, panel.GetLastPreviewStats().submittedDrawListCount);
EXPECT_EQ(composition.previewStats.submittedCommandCount, panel.GetLastPreviewStats().submittedCommandCount);
}
} // namespace

View File

@@ -8,6 +8,7 @@
#include <imgui.h>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
@@ -18,9 +19,16 @@ using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::ImGuiXCUIInputSnapshotSource;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeKeyState;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession;
using XCEngine::Input::KeyCode;
using XCEngine::NewEditor::XCUILayoutLabFrameComposition;
using XCEngine::NewEditor::XCUILayoutLabFrameCompositionRequest;
using XCEngine::NewEditor::XCUILayoutLabPanel;
class ImGuiContextScope {
@@ -50,6 +58,7 @@ void PrepareImGui(float width = 1280.0f, float height = 900.0f) {
class StubHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter {
public:
bool Present(const XCUIHostedPreviewFrame& frame) override {
++m_presentCallCount;
m_lastStats = {};
m_lastStats.presented = frame.drawData != nullptr;
return m_lastStats.presented;
@@ -59,10 +68,79 @@ public:
return m_lastStats;
}
std::size_t GetPresentCallCount() const {
return m_presentCallCount;
}
private:
std::size_t m_presentCallCount = 0u;
XCUIHostedPreviewStats m_lastStats = {};
};
class StubNativeHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter {
public:
bool Present(const XCUIHostedPreviewFrame& frame) override {
++m_presentCallCount;
m_lastFrame = frame;
m_lastStats = {};
m_lastStats.presented = frame.drawData != nullptr;
m_lastStats.queuedToNativePass = frame.drawData != nullptr;
m_lastStats.submittedDrawListCount = frame.drawData != nullptr ? frame.drawData->GetDrawListCount() : 0u;
m_lastStats.submittedCommandCount = frame.drawData != nullptr ? frame.drawData->GetTotalCommandCount() : 0u;
return m_lastStats.presented;
}
const XCUIHostedPreviewStats& GetLastStats() const override {
return m_lastStats;
}
bool IsNativeQueued() const override {
return true;
}
bool TryGetSurfaceImage(
const char* debugName,
XCUIHostedPreviewSurfaceImage& outImage) const override {
outImage = {};
if (debugName == nullptr || m_descriptor.debugName != debugName) {
return false;
}
outImage = m_descriptor.image;
return outImage.IsValid();
}
bool TryGetSurfaceDescriptor(
const char* debugName,
XCUIHostedPreviewSurfaceDescriptor& outDescriptor) const override {
outDescriptor = {};
if (debugName == nullptr || m_descriptor.debugName != debugName) {
return false;
}
outDescriptor = m_descriptor;
return true;
}
void SetDescriptor(XCUIHostedPreviewSurfaceDescriptor descriptor) {
m_descriptor = std::move(descriptor);
}
std::size_t GetPresentCallCount() const {
return m_presentCallCount;
}
const XCUIHostedPreviewFrame& GetLastFrame() const {
return m_lastFrame;
}
private:
std::size_t m_presentCallCount = 0u;
XCUIHostedPreviewFrame m_lastFrame = {};
XCUIHostedPreviewStats m_lastStats = {};
XCUIHostedPreviewSurfaceDescriptor m_descriptor = {};
};
class StubCanvasHost final : public IXCUIPanelCanvasHost {
public:
const char* GetDebugName() const override {
@@ -119,6 +197,91 @@ private:
};
};
std::uint64_t NextTimestampNanoseconds() {
static std::uint64_t timestampNanoseconds = 1'000'000u;
timestampNanoseconds += 16'666'667u;
return timestampNanoseconds;
}
XCUIPanelCanvasSession MakeCanvasSession() {
XCUIPanelCanvasSession session = {};
session.hostRect = XCEngine::UI::UIRect(0.0f, 0.0f, 960.0f, 640.0f);
session.canvasRect = XCEngine::UI::UIRect(0.0f, 0.0f, 960.0f, 640.0f);
session.pointerPosition = XCEngine::UI::UIPoint(120.0f, 120.0f);
session.validCanvas = true;
session.hovered = true;
session.windowFocused = true;
return session;
}
XCEngine::UI::UITextureHandle MakeSurfaceTextureHandle(
std::uintptr_t nativeHandle,
std::uint32_t width,
std::uint32_t height) {
XCEngine::UI::UITextureHandle texture = {};
texture.nativeHandle = nativeHandle;
texture.width = width;
texture.height = height;
texture.kind = XCEngine::UI::UITextureHandleKind::ShaderResourceView;
return texture;
}
XCUIInputBridgeFrameSnapshot MakePointerSnapshot(
const XCEngine::UI::UIPoint& pointerPosition,
bool pointerInside,
bool pointerDown,
bool windowFocused) {
XCUIInputBridgeFrameSnapshot snapshot = {};
snapshot.pointerPosition = pointerPosition;
snapshot.pointerInside = pointerInside;
snapshot.pointerButtonsDown[0] = pointerDown;
snapshot.windowFocused = windowFocused;
snapshot.timestampNanoseconds = NextTimestampNanoseconds();
return snapshot;
}
XCUIInputBridgeFrameSnapshot MakeKeyboardSnapshot(
const XCEngine::UI::UIPoint& pointerPosition,
bool pointerInside,
bool windowFocused,
KeyCode keyCode) {
XCUIInputBridgeFrameSnapshot snapshot =
MakePointerSnapshot(pointerPosition, pointerInside, false, windowFocused);
snapshot.keys.push_back(XCUIInputBridgeKeyState {
static_cast<std::int32_t>(keyCode),
true,
false
});
return snapshot;
}
const XCUILayoutLabFrameComposition& ComposePanelFrame(
XCUILayoutLabPanel& panel,
const XCUIPanelCanvasSession& canvasSession,
const XCUIInputBridgeFrameSnapshot& snapshot) {
XCUILayoutLabFrameCompositionRequest request = {};
request.canvasSession = canvasSession;
request.inputSnapshot = snapshot;
return panel.ComposeFrame(request);
}
void ClickElementWithComposition(
XCUILayoutLabPanel& panel,
const XCUIPanelCanvasSession& baseSession,
const std::string& elementId) {
XCEngine::UI::UIRect elementRect = {};
ASSERT_TRUE(panel.TryGetElementRect(elementId, elementRect));
const XCEngine::UI::UIPoint clickPoint(
elementRect.x + elementRect.width * 0.5f,
elementRect.y + elementRect.height * 0.5f);
XCUIPanelCanvasSession clickSession = baseSession;
clickSession.pointerPosition = clickPoint;
clickSession.hovered = true;
ComposePanelFrame(panel, clickSession, MakePointerSnapshot(clickPoint, true, true, true));
ComposePanelFrame(panel, clickSession, MakePointerSnapshot(clickPoint, true, false, true));
}
void RenderPanelFrame(
XCUILayoutLabPanel& panel,
ImGuiContextScope&,
@@ -181,68 +344,92 @@ void PressKeyOnce(
}
TEST(NewEditorXCUILayoutLabPanelTest, MapsPreviousNextHomeAndEndIntoRuntimeNavigation) {
ImGuiContextScope contextScope;
auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>();
auto canvasHost = std::make_unique<StubCanvasHost>();
StubCanvasHost* canvasHostPtr = canvasHost.get();
ImGuiXCUIInputSnapshotSource inputSource(nullptr);
XCUILayoutLabPanel panel(&inputSource, std::move(previewPresenter), std::move(canvasHost));
XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::move(canvasHost));
panel.SetHostedPreviewEnabled(false);
RenderPanelFrame(panel, contextScope);
ClickElement(panel, *canvasHostPtr, "assetLighting", contextScope);
XCUIPanelCanvasSession session = MakeCanvasSession();
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true));
ClickElementWithComposition(panel, session, "assetLighting");
ASSERT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetLighting");
canvasHostPtr->SetHovered(false);
canvasHostPtr->SetWindowFocused(true);
session.hovered = false;
session.windowFocused = true;
PressKeyOnce(panel, contextScope, ImGuiKey_DownArrow);
const XCUILayoutLabFrameComposition& downComposition = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Down));
EXPECT_TRUE(downComposition.inputState.navigateNext);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetMaterials");
PressKeyOnce(panel, contextScope, ImGuiKey_UpArrow);
const XCUILayoutLabFrameComposition& upComposition = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Up));
EXPECT_TRUE(upComposition.inputState.navigatePrevious);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetLighting");
PressKeyOnce(panel, contextScope, ImGuiKey_End);
const XCUILayoutLabFrameComposition& endComposition = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::End));
EXPECT_TRUE(endComposition.inputState.navigateEnd);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
const std::string endSelection = panel.GetFrameResult().stats.selectedElementId;
ASSERT_FALSE(endSelection.empty());
EXPECT_NE(endSelection, "assetLighting");
PressKeyOnce(panel, contextScope, ImGuiKey_Home);
const XCUILayoutLabFrameComposition& homeComposition = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Home));
EXPECT_TRUE(homeComposition.inputState.navigateHome);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetLighting");
}
TEST(NewEditorXCUILayoutLabPanelTest, MapsCollapseAndExpandIntoRuntimeNavigation) {
ImGuiContextScope contextScope;
auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>();
auto canvasHost = std::make_unique<StubCanvasHost>();
StubCanvasHost* canvasHostPtr = canvasHost.get();
ImGuiXCUIInputSnapshotSource inputSource(nullptr);
XCUILayoutLabPanel panel(&inputSource, std::move(previewPresenter), std::move(canvasHost));
XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::move(canvasHost));
panel.SetHostedPreviewEnabled(false);
RenderPanelFrame(panel, contextScope);
ClickElement(panel, *canvasHostPtr, "treeScenes", contextScope);
XCUIPanelCanvasSession session = MakeCanvasSession();
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true));
ClickElementWithComposition(panel, session, "treeScenes");
ASSERT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeScenes");
canvasHostPtr->SetHovered(false);
canvasHostPtr->SetWindowFocused(true);
session.hovered = false;
session.windowFocused = true;
PressKeyOnce(panel, contextScope, ImGuiKey_LeftArrow);
const XCUILayoutLabFrameComposition& collapseSelection = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Left));
EXPECT_TRUE(collapseSelection.inputState.navigateCollapse);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeAssetsRoot");
PressKeyOnce(panel, contextScope, ImGuiKey_LeftArrow);
ComposePanelFrame(panel, session, MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Left));
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeAssetsRoot");
EXPECT_EQ(panel.GetFrameResult().stats.expandedTreeItemCount, 0u);
PressKeyOnce(panel, contextScope, ImGuiKey_RightArrow);
const XCUILayoutLabFrameComposition& expandRoot = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Right));
EXPECT_TRUE(expandRoot.inputState.navigateExpand);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeAssetsRoot");
EXPECT_EQ(panel.GetFrameResult().stats.expandedTreeItemCount, 1u);
PressKeyOnce(panel, contextScope, ImGuiKey_RightArrow);
ComposePanelFrame(panel, session, MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Right));
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeScenes");
}
@@ -259,4 +446,81 @@ TEST(NewEditorXCUILayoutLabPanelTest, DefaultFallbackDoesNotCreateImplicitHosted
EXPECT_EQ(panel.GetLastPreviewStats().submittedCommandCount, 0u);
}
TEST(NewEditorXCUILayoutLabPanelTest, ComposeFramePublishesShellAgnosticLastCompositionState) {
auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>();
auto canvasHost = std::make_unique<StubCanvasHost>();
XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::move(canvasHost));
XCUIPanelCanvasSession session = MakeCanvasSession();
const XCUILayoutLabFrameComposition& composition =
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true));
EXPECT_EQ(&composition, &panel.GetLastFrameComposition());
ASSERT_NE(composition.frameResult, nullptr);
EXPECT_TRUE(composition.previewStats.presented);
EXPECT_EQ(composition.previewPathLabel, "hosted presenter");
EXPECT_EQ(composition.previewStateLabel, "live");
EXPECT_EQ(composition.previewSourceLabel, "new_editor.panels.xcui_layout_lab");
EXPECT_TRUE(composition.hostedPreviewEnabled);
EXPECT_EQ(composition.inputState.canvasRect.width, session.canvasRect.width);
EXPECT_EQ(composition.inputState.canvasRect.height, session.canvasRect.height);
EXPECT_TRUE(composition.inputState.pointerInside);
}
TEST(NewEditorXCUILayoutLabPanelTest, ComposeFrameCarriesNativeQueuedPreviewMetadataIntoComposition) {
auto previewPresenter = std::make_unique<StubNativeHostedPreviewPresenter>();
XCUIHostedPreviewSurfaceDescriptor descriptor = {};
descriptor.debugName = "XCUI Layout Lab";
descriptor.debugSource = "tests.native_layout_preview";
descriptor.logicalSize = XCEngine::UI::UISize(512.0f, 320.0f);
descriptor.queuedFrameIndex = 7u;
descriptor.image.texture = MakeSurfaceTextureHandle(42u, 512u, 320u);
descriptor.image.surfaceWidth = 512u;
descriptor.image.surfaceHeight = 320u;
descriptor.image.renderedCanvasRect = XCEngine::UI::UIRect(0.0f, 0.0f, 512.0f, 320.0f);
previewPresenter->SetDescriptor(std::move(descriptor));
StubNativeHostedPreviewPresenter* previewPresenterRaw = previewPresenter.get();
XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::make_unique<StubCanvasHost>());
XCUIPanelCanvasSession session = MakeCanvasSession();
const XCUILayoutLabFrameComposition& composition =
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true));
EXPECT_TRUE(composition.hostedPreviewEnabled);
EXPECT_TRUE(composition.nativeHostedPreview);
EXPECT_TRUE(composition.hasHostedSurfaceDescriptor);
EXPECT_TRUE(composition.showHostedSurfaceImage);
EXPECT_EQ(composition.previewPathLabel, "native queued offscreen surface");
EXPECT_EQ(composition.previewStateLabel, "live");
EXPECT_EQ(composition.previewSourceLabel, "tests.native_layout_preview");
EXPECT_EQ(composition.hostedSurfaceDescriptor.queuedFrameIndex, 7u);
EXPECT_EQ(composition.hostedSurfaceImage.texture.nativeHandle, 42u);
EXPECT_TRUE(composition.previewStats.presented);
EXPECT_TRUE(composition.previewStats.queuedToNativePass);
EXPECT_EQ(previewPresenterRaw->GetPresentCallCount(), 1u);
EXPECT_EQ(previewPresenterRaw->GetLastFrame().debugName, std::string("XCUI Layout Lab"));
EXPECT_EQ(previewPresenterRaw->GetLastFrame().debugSource, std::string("new_editor.panels.xcui_layout_lab"));
EXPECT_FLOAT_EQ(previewPresenterRaw->GetLastFrame().logicalSize.width, session.canvasRect.width);
EXPECT_FLOAT_EQ(previewPresenterRaw->GetLastFrame().logicalSize.height, session.canvasRect.height);
}
TEST(NewEditorXCUILayoutLabPanelTest, ComposeFrameMarksPreviewDisabledWhenPresenterIsInjectedButDisabled) {
auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>();
StubHostedPreviewPresenter* previewPresenterRaw = previewPresenter.get();
XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::make_unique<StubCanvasHost>());
panel.SetHostedPreviewEnabled(false);
XCUIPanelCanvasSession session = MakeCanvasSession();
const XCUILayoutLabFrameComposition& composition =
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true));
EXPECT_FALSE(composition.hostedPreviewEnabled);
EXPECT_EQ(composition.previewStateLabel, "disabled");
EXPECT_FALSE(composition.previewStats.presented);
EXPECT_EQ(previewPresenterRaw->GetPresentCallCount(), 0u);
EXPECT_FALSE(panel.GetLastPreviewStats().presented);
}
} // namespace