#include "XCUILayoutLabPanel.h" #include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" #include #include #include #include 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(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