#include "XCUIDemoPanel.h" #include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" #include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h" #include "XCUIBackend/ImGuiXCUIInputAdapter.h" #include #include #include #include #include #include #include namespace XCEngine { namespace NewEditor { 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(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 XCUIDemoPanel::XCUIDemoPanel(::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource) : XCUIDemoPanel(inputSource, ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter()) {} XCUIDemoPanel::XCUIDemoPanel( ::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* inputSource, std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) : Panel("XCUI Demo") , m_inputSource(inputSource) , m_previewPresenter(std::move(previewPresenter)) , m_canvasHost(::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost()) { if (m_previewPresenter == nullptr) { m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); } 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); if (m_previewPresenter == nullptr) { m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); } 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::CreateImGuiXCUIPanelCanvasHost(); } } 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); bool open = true; if (!ImGui::Begin(GetName().c_str(), &open)) { ImGui::End(); if (!open) { SetVisible(false); } return; } if (ImGui::Button("Reload Documents")) { m_lastReloadSucceeded = m_runtime.ReloadDocuments(); } ImGui::SameLine(); ImGui::Checkbox("Canvas HUD", &m_showCanvasHud); ImGui::SameLine(); ImGui::Checkbox("Debug Rects", &m_showDebugRects); ImGui::SameLine(); ImGui::TextUnformatted(m_lastReloadSucceeded ? "Reload: OK" : "Reload: Failed"); ImGui::Separator(); 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::CreateImGuiXCUIPanelCanvasHost(); } 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(nativeHostedPreview); 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" : "Legacy XCUI canvas host"; canvasRequest.placeholderSubtitle = nativeHostedPreview ? "Waiting for native queued render output to publish back into the sandbox panel." : "Legacy ImGui transition path stays active until native offscreen preview is enabled."; 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::chrono::duration_cast( 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 = ::XCEngine::Editor::XCUIBackend::ImGuiXCUIInputAdapter::CaptureSnapshot( ImGui::GetIO(), bridgeOptions); snapshot.pointerPosition = canvasSession.pointerPosition; } 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( nativeHostedPreview, 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(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(); 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( "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( "Tree gen: %llu | elements: %zu | dirty roots: %zu", static_cast(stats.treeGeneration), stats.elementCount, stats.dirtyRootCount); ImGui::Text( "Draw lists: %zu | draw cmds: %zu | dependencies: %zu", stats.drawListCount, stats.commandCount, stats.dependencyCount); ImGui::Text( "Hovered: %s | Focused: %s", stats.hoveredElementId.empty() ? "none" : stats.hoveredElementId.c_str(), stats.focusedElementId.empty() ? "none" : stats.focusedElementId.c_str()); ImGui::Text( "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::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"); 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()); ImGui::EndChild(); ImGui::End(); if (!open) { SetVisible(false); } } } // namespace NewEditor } // namespace XCEngine