Reuse panel frame composition in native XCUI shell
This commit is contained in:
@@ -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`)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user