#include "XCUIDemoPanel.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 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 Demo"; constexpr char kPreviewDebugSource[] = "new_editor.panels.xcui_demo"; UI::UIRect ToUIRect(const ImVec2& minPoint, const ImVec2& size) { return UI::UIRect(minPoint.x, minPoint.y, size.x, size.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 DrawRectOverlay( ImDrawList* drawList, ::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime& runtime, const std::string& elementId, ImU32 color, const char* label) { if (drawList == nullptr || elementId.empty()) { return; } UI::UIRect rect = {}; if (!runtime.TryGetElementRect(elementId, rect)) { return; } const ImVec2 minPoint(rect.x, rect.y); const ImVec2 maxPoint(rect.x + rect.width, rect.y + rect.height); drawList->AddRect(minPoint, maxPoint, color, 6.0f, 0, 2.0f); if (label != nullptr && label[0] != '\0') { drawList->AddText(ImVec2(minPoint.x + 4.0f, minPoint.y + 4.0f), color, label); } } 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)) { 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 = {}; } 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); ImGui::BeginChild("XCUIDemoCanvasHost", ImVec2(0.0f, canvasHeight), true, ImGuiWindowFlags_NoScrollWithMouse); const ImVec2 canvasHostMin = ImGui::GetCursorScreenPos(); const ImVec2 availableSize = ImGui::GetContentRegionAvail(); const float topInset = m_showCanvasHud ? (kCanvasHudHeight + kCanvasHudPadding) : 0.0f; const ImVec2 canvasMin(canvasHostMin.x, canvasHostMin.y + topInset); const ImVec2 canvasSize( availableSize.x, (std::max)(0.0f, availableSize.y - topInset)); 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(hostedSurfaceImage.textureId, canvasSize, hostedSurfaceImage.uvMin, hostedSurfaceImage.uvMax); DrawHostedPreviewFrame(drawList, canvasMin, canvasSize); } else { ImGui::InvisibleButton("##XCUIDemoCanvas", canvasSize); const char* 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."; DrawHostedPreviewPlaceholder( drawList, canvasMin, canvasSize, nativeHostedPreview ? "Native XCUI preview pending" : "Legacy XCUI canvas host", placeholderSubtitle); } } else { ImGui::Dummy(ImVec2(0.0f, 0.0f)); } ::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()); const UI::UIRect canvasRect = ToUIRect(canvasMin, canvasSize); ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot = {}; if (m_inputSource != nullptr) { bridgeOptions.hasPointerInsideOverride = true; bridgeOptions.windowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); 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 && ImGui::IsItemHovered(); bridgeOptions.windowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); snapshot = ::XCEngine::Editor::XCUIBackend::ImGuiXCUIInputAdapter::CaptureSnapshot( ImGui::GetIO(), 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.targetDrawList = drawList; 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 ImVec2 hudMin(canvasHostMin.x + 8.0f, canvasHostMin.y + 8.0f); const ImVec2 hudMax( canvasHostMin.x + (std::min)(availableSize.x - 8.0f, 430.0f), canvasHostMin.y + kCanvasHudHeight); drawList->AddRectFilled( hudMin, hudMax, IM_COL32(16, 22, 30, 220), 8.0f); drawList->AddRect( hudMin, hudMax, IM_COL32(52, 72, 96, 255), 8.0f, 0, 1.0f); ImGui::SetCursorScreenPos(ImVec2(hudMin.x + 10.0f, hudMin.y + 8.0f)); ImGui::BeginGroup(); ImGui::TextUnformatted("XCUI Runtime"); ImGui::Text("%s | %s", previewPathLabel, previewStateLabel); ImGui::TextUnformatted(stats.statusMessage.c_str()); ImGui::Text( "Tree %llu | Elements %zu | Commands %zu", static_cast(stats.treeGeneration), stats.elementCount, stats.commandCount); ImGui::Text( "Submit %zu/%zu | Flush %zu/%zu", m_lastPreviewStats.submittedDrawListCount, m_lastPreviewStats.submittedCommandCount, m_lastPreviewStats.flushedDrawListCount, m_lastPreviewStats.flushedCommandCount); ImGui::EndGroup(); } if (m_showDebugRects && validCanvas && (!nativeHostedPreview || showHostedSurfaceImage)) { DrawRectOverlay(drawList, m_runtime, stats.hoveredElementId, IM_COL32(255, 195, 64, 255), "hover"); DrawRectOverlay(drawList, m_runtime, stats.focusedElementId, IM_COL32(64, 214, 255, 255), "focus"); DrawRectOverlay(drawList, m_runtime, "toggleAccent", IM_COL32(150, 255, 150, 160), "toggle"); } ImGui::EndChild(); 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