Restore split layout panel render wrapper

This commit is contained in:
2026-04-05 17:48:12 +08:00
parent 3db09ea5d0
commit a600e73fb2

View File

@@ -0,0 +1,240 @@
#include "XCUILayoutLabPanel.h"
#include "XCUIBackend/NullXCUIPanelCanvasHost.h"
#include <XCEngine/UI/Types.h>
#include <imgui.h>
#include <algorithm>
#include <chrono>
#include <cstdint>
namespace XCEngine {
namespace NewEditor {
namespace {
constexpr char kPreviewDebugName[] = "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";
}
::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;
}
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot CapturePanelSnapshot(
::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource,
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession) {
::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());
if (inputSource != nullptr) {
bridgeOptions.hasPointerInsideOverride = true;
bridgeOptions.windowFocused = canvasSession.windowFocused;
const UI::UIPoint pointerPosition = inputSource->GetPointerPosition();
bridgeOptions.pointerInsideOverride =
canvasSession.validCanvas && ContainsPoint(canvasSession.canvasRect, pointerPosition);
return inputSource->CaptureSnapshot(bridgeOptions);
}
bridgeOptions.hasPointerInsideOverride = true;
bridgeOptions.pointerInsideOverride = canvasSession.validCanvas && canvasSession.hovered;
bridgeOptions.windowFocused = canvasSession.windowFocused;
return BuildPassiveSnapshot(canvasSession, bridgeOptions);
}
} // namespace
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::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 ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot =
CapturePanelSnapshot(m_inputSource, canvasSession);
const XCUILayoutLabFrameCompositionRequest compositionRequest = {
canvasSession,
snapshot
};
const XCUILayoutLabFrameComposition& composition = ComposeFrame(compositionRequest);
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",
composition.previewPathLabel.c_str(),
composition.previewStateLabel.c_str());
ImGui::Text(
"Presenter: presented %s | submit->native %s",
composition.previewStats.presented ? "yes" : "no",
composition.previewStats.queuedToNativePass ? "yes" : "no");
ImGui::Text(
"Submitted: %zu lists / %zu cmds | Flushed: %zu lists / %zu cmds",
composition.previewStats.submittedDrawListCount,
composition.previewStats.submittedCommandCount,
composition.previewStats.flushedDrawListCount,
composition.previewStats.flushedCommandCount);
ImGui::TextWrapped("Source: %s", composition.previewSourceLabel.c_str());
if (nativeHostedPreview) {
ImGui::Text(
"Surface descriptor: %s | image published: %s | queued frame index: %zu",
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",
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.");
}
} else {
ImGui::TextDisabled("No native surface descriptor is available without a native queued presenter.");
}
ImGui::SeparatorText("Runtime");
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameStats& stats =
composition.frameResult != nullptr
? composition.frameResult->stats
: m_runtime.GetFrameResult().stats;
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(),
composition.inputState.canvasRect.width,
composition.inputState.canvasRect.height);
ImGui::SeparatorText("Input");
ImGui::Text(
"Pointer: %.0f, %.0f | inside %s | pressed %s",
composition.inputState.pointerPosition.x,
composition.inputState.pointerPosition.y,
composition.inputState.pointerInside ? "yes" : "no",
composition.inputState.pointerPressed ? "yes" : "no");
ImGui::EndChild();
ImGui::End();
if (!open) {
SetVisible(false);
}
}
} // namespace NewEditor
} // namespace XCEngine