Files
XCEngine/new_editor/src/panels/XCUILayoutLabPanel.cpp

371 lines
14 KiB
C++

#include "XCUILayoutLabPanel.h"
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h"
#include <XCEngine/UI/Types.h>
#include <imgui.h>
#include <algorithm>
#include <utility>
namespace XCEngine {
namespace NewEditor {
namespace {
constexpr ImU32 kPreviewFrameColor = IM_COL32(54, 72, 94, 255);
constexpr ImU32 kPreviewPlaceholderFill = IM_COL32(18, 24, 32, 255);
constexpr ImU32 kPreviewPlaceholderText = IM_COL32(191, 205, 224, 255);
constexpr ImU32 kPreviewPlaceholderSubtleText = IM_COL32(132, 147, 170, 255);
constexpr char kPreviewDebugName[] = "XCUI Layout Lab";
constexpr char kPreviewDebugSource[] = "new_editor.panels.xcui_layout_lab";
UI::UIRect ToUIRect(const ImVec2& minPoint, const ImVec2& size) {
return UI::UIRect(minPoint.x, minPoint.y, size.x, size.y);
}
ImTextureID ToImTextureId(const UI::UITextureHandle& texture) {
return texture.IsValid()
? static_cast<ImTextureID>(texture.nativeHandle)
: ImTextureID{};
}
ImVec2 ToImVec2(const UI::UIPoint& point) {
return ImVec2(point.x, point.y);
}
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 DrawHostedPreviewFrame(
ImDrawList* drawList,
const ImVec2& minPoint,
const ImVec2& size) {
if (drawList == nullptr || size.x <= 1.0f || size.y <= 1.0f) {
return;
}
const ImVec2 maxPoint(minPoint.x + size.x, minPoint.y + size.y);
drawList->AddRect(minPoint, maxPoint, kPreviewFrameColor, 8.0f, 0, 1.0f);
}
void DrawHostedPreviewPlaceholder(
ImDrawList* drawList,
const ImVec2& minPoint,
const ImVec2& size,
const char* title,
const char* subtitle) {
if (drawList == nullptr || size.x <= 1.0f || size.y <= 1.0f) {
return;
}
const ImVec2 maxPoint(minPoint.x + size.x, minPoint.y + size.y);
drawList->AddRectFilled(minPoint, maxPoint, kPreviewPlaceholderFill, 8.0f);
drawList->AddRect(minPoint, maxPoint, kPreviewFrameColor, 8.0f, 0, 1.0f);
drawList->AddText(ImVec2(minPoint.x + 14.0f, minPoint.y + 14.0f), kPreviewPlaceholderText, title);
if (subtitle != nullptr && subtitle[0] != '\0') {
drawList->AddText(
ImVec2(minPoint.x + 14.0f, minPoint.y + 36.0f),
kPreviewPlaceholderSubtleText,
subtitle);
}
}
void DrawCanvasBadge(
ImDrawList* drawList,
const ImVec2& minPoint,
const char* title,
const char* subtitle) {
if (drawList == nullptr) {
return;
}
const ImVec2 badgeMin(minPoint.x + 10.0f, minPoint.y + 10.0f);
const ImVec2 badgeMax(minPoint.x + 300.0f, minPoint.y + 52.0f);
drawList->AddRectFilled(badgeMin, badgeMax, IM_COL32(16, 22, 30, 216), 8.0f);
drawList->AddRect(badgeMin, badgeMax, IM_COL32(52, 72, 96, 255), 8.0f, 0, 1.0f);
drawList->AddText(ImVec2(badgeMin.x + 10.0f, badgeMin.y + 8.0f), kPreviewPlaceholderText, title);
drawList->AddText(
ImVec2(badgeMin.x + 10.0f, badgeMin.y + 28.0f),
kPreviewPlaceholderSubtleText,
subtitle);
}
const char* GetPreviewPathLabel(bool nativeHostedPreview) {
return nativeHostedPreview ? "native queued offscreen surface" : "legacy imgui transition";
}
const char* GetPreviewStateLabel(
bool nativeHostedPreview,
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats,
bool hasHostedSurfaceDescriptor,
bool showHostedSurfaceImage) {
if (nativeHostedPreview) {
if (showHostedSurfaceImage) {
return "live";
}
if (previewStats.queuedToNativePass || hasHostedSurfaceDescriptor) {
return "warming";
}
return "awaiting submit";
}
return previewStats.presented ? "live" : "idle";
}
} // namespace
XCUILayoutLabPanel::XCUILayoutLabPanel(::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource)
: XCUILayoutLabPanel(inputSource, ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter()) {}
XCUILayoutLabPanel::XCUILayoutLabPanel(
::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource,
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter)
: Panel("XCUI Layout Lab")
, m_inputSource(inputSource)
, m_previewPresenter(std::move(previewPresenter)) {
if (m_previewPresenter == nullptr) {
m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter();
}
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;
}
void XCUILayoutLabPanel::SetHostedPreviewPresenter(
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) {
m_previewPresenter = std::move(previewPresenter);
if (m_previewPresenter == nullptr) {
m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter();
}
m_lastPreviewStats = {};
}
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);
ImGui::BeginChild("XCUILayoutLabCanvasHost", ImVec2(0.0f, canvasHeight), true, ImGuiWindowFlags_NoScrollWithMouse);
const ImVec2 canvasMin = ImGui::GetCursorScreenPos();
const ImVec2 canvasSize = ImGui::GetContentRegionAvail();
const bool validCanvas = canvasSize.x > 1.0f && canvasSize.y > 1.0f;
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 =
validCanvas &&
nativeHostedPreview &&
m_previewPresenter != nullptr &&
m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage);
const char* const previewPathLabel = GetPreviewPathLabel(nativeHostedPreview);
ImDrawList* drawList = ImGui::GetWindowDrawList();
if (validCanvas) {
ImGui::SetCursorScreenPos(canvasMin);
if (showHostedSurfaceImage) {
ImGui::Image(
ToImTextureId(hostedSurfaceImage.texture),
canvasSize,
ToImVec2(hostedSurfaceImage.uvMin),
ToImVec2(hostedSurfaceImage.uvMax));
DrawHostedPreviewFrame(drawList, canvasMin, canvasSize);
} else {
ImGui::InvisibleButton("##XCUILayoutLabCanvas", canvasSize);
const char* placeholderSubtitle =
nativeHostedPreview
? "Waiting for native queued render output to publish back into the layout sandbox."
: "Legacy ImGui transition path remains active until native offscreen preview is enabled.";
DrawHostedPreviewPlaceholder(
drawList,
canvasMin,
canvasSize,
nativeHostedPreview ? "Native layout preview pending" : "Legacy layout canvas host",
placeholderSubtitle);
}
DrawCanvasBadge(drawList, canvasMin, "Layout Lab", previewPathLabel);
} else {
ImGui::Dummy(ImVec2(0.0f, 0.0f));
}
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = {};
input.canvasRect = ToUIRect(canvasMin, canvasSize);
if (m_inputSource != nullptr) {
input.pointerPosition = m_inputSource->GetPointerPosition();
input.pointerInside = validCanvas && ContainsPoint(input.canvasRect, input.pointerPosition);
} else {
input.pointerPosition = UI::UIPoint(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y);
input.pointerInside = validCanvas && ImGui::IsItemHovered();
}
input.pointerPressed = input.pointerInside && ImGui::IsMouseClicked(ImGuiMouseButton_Left);
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(
nativeHostedPreview,
m_lastPreviewStats,
hasHostedSurfaceDescriptor,
showHostedSurfaceImage);
ImGui::EndChild();
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("Legacy path renders directly into the panel draw list. No native surface descriptor exists.");
}
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