From bc6722e5ab78f9eb42f57ac2f56f9b9e97131063 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 5 Apr 2026 12:30:03 +0800 Subject: [PATCH] Extract XCUI panel canvas host seam --- docs/plan/XCUI_Phase_Status_2026-04-05.md | 3 + .../XCUIBackend/ImGuiXCUIPanelCanvasHost.h | 244 ++++++++++++++++++ .../src/XCUIBackend/XCUIPanelCanvasHost.h | 63 +++++ new_editor/src/panels/XCUIDemoPanel.cpp | 233 +++++++---------- new_editor/src/panels/XCUIDemoPanel.h | 4 + new_editor/src/panels/XCUILayoutLabPanel.cpp | 146 +++-------- new_editor/src/panels/XCUILayoutLabPanel.h | 4 + 7 files changed, 450 insertions(+), 247 deletions(-) create mode 100644 new_editor/src/XCUIBackend/ImGuiXCUIPanelCanvasHost.h create mode 100644 new_editor/src/XCUIBackend/XCUIPanelCanvasHost.h diff --git a/docs/plan/XCUI_Phase_Status_2026-04-05.md b/docs/plan/XCUI_Phase_Status_2026-04-05.md index 732f5d60..27f261c1 100644 --- a/docs/plan/XCUI_Phase_Status_2026-04-05.md +++ b/docs/plan/XCUI_Phase_Status_2026-04-05.md @@ -74,6 +74,7 @@ Current gap: - `LayoutLab` now also consumes the shared `UIExpansionModel` for tree expansion and property-section collapse, with reserved property headers, disclosure glyphs, and persisted click-toggle behavior in the sandbox runtime. - `new_editor` now also has an isolated `XCUIEditorCommandRouter` model with shortcut matching, enable predicates, and direct command invocation semantics covered by dedicated tests, ready for shell-frame integration. - `XCUI Demo` now exports pending per-frame command ids through `DrainPendingCommandIds()`, so editor-side hosts have a clean seam for observing demo/runtime command traffic without parsing draw data. +- `XCUI Demo` and `LayoutLab` panel canvases are now being pulled behind a dedicated `IXCUIPanelCanvasHost` seam, so canvas surface presentation, hover/focus fallback state, and overlay draw hooks no longer have to stay hard-coded inside each ImGui panel implementation. - Panel diagnostics were expanded to clearly separate preview/runtime/input state and native vs legacy paths. - The editor bridge layer now has smoke coverage for swapchain after-UI rendering hooks and SRV-backed ImGui texture descriptor registration. - `Application` no longer owns the ImGui backend directly; window presentation now routes through `IWindowUICompositor` with an `ImGuiWindowUICompositor` implementation, which currently delegates to `IEditorHostCompositor` / `ImGuiHostCompositor`. @@ -86,6 +87,7 @@ Current gap: - The shell is still ImGui-hosted. - Legacy hosted preview still depends on an active ImGui window context for inline presentation. +- The new panel-canvas seam still only has an ImGui adapter today; a native panel/shell host still needs to replace it before ImGui can stop being the default editor host path. - Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, and icon-atlas widgets are not yet extracted into reusable XCUI modules, and the new keyboard-navigation/property-edit/command-routing models are still only partially integrated. ## Validated This Phase @@ -172,6 +174,7 @@ Current gap: - The window-level XCUI compositor seam now also has a dedicated regression target around `ImGuiWindowUICompositor`, covering initialization, render-frame ordering, Win32 message forwarding, texture registration forwarding, and shutdown safety. - The window compositor and hosted-preview seams gained more edge-case coverage around no-UI render passes, compositor re-initialization/rebinding, partial logical-size fallback, and descriptor reuse for repeated queued-frame keys. - `new_editor` now has a dedicated `XCUIEditorCommandRouter` test target covering command registration, replacement, enable predicates, accelerator matching, and policy gates around focus/keyboard capture/text input. +- `new_editor` panel canvas ownership is now being split behind `IXCUIPanelCanvasHost`, with an `ImGuiXCUIPanelCanvasHost` adapter carrying the legacy path so panel code stops directly owning `ImGui::Image` / `ImGui::InvisibleButton` / draw-list preview plumbing. - `SceneRuntime` layered XCUI routing now has dedicated regression coverage for: - top-interactive layer input ownership - blocking/modal layer suppression of lower layers diff --git a/new_editor/src/XCUIBackend/ImGuiXCUIPanelCanvasHost.h b/new_editor/src/XCUIBackend/ImGuiXCUIPanelCanvasHost.h new file mode 100644 index 00000000..7a3532f0 --- /dev/null +++ b/new_editor/src/XCUIBackend/ImGuiXCUIPanelCanvasHost.h @@ -0,0 +1,244 @@ +#pragma once + +#include "XCUIBackend/XCUIPanelCanvasHost.h" + +#include + +#include + +namespace XCEngine { +namespace Editor { +namespace XCUIBackend { + +namespace detail { + +inline ImVec2 ToImVec2(const ::XCEngine::UI::UIPoint& point) { + return ImVec2(point.x, point.y); +} + +inline ImU32 ToImU32(const ::XCEngine::UI::UIColor& color) { + return ImGui::ColorConvertFloat4ToU32(ImVec4(color.r, color.g, color.b, color.a)); +} + +inline ImTextureID ToImTextureId(const ::XCEngine::UI::UITextureHandle& texture) { + return texture.IsValid() + ? static_cast(texture.nativeHandle) + : ImTextureID{}; +} + +inline void DrawPlaceholder( + ImDrawList* drawList, + const ::XCEngine::UI::UIRect& rect, + const char* title, + const char* subtitle) { + if (drawList == nullptr || rect.width <= 1.0f || rect.height <= 1.0f) { + return; + } + + const ImVec2 minPoint(rect.x, rect.y); + const ImVec2 maxPoint(rect.x + rect.width, rect.y + rect.height); + drawList->AddRectFilled(minPoint, maxPoint, IM_COL32(18, 24, 32, 255), 8.0f); + drawList->AddRect(minPoint, maxPoint, IM_COL32(54, 72, 94, 255), 8.0f, 0, 1.0f); + + if (title != nullptr && title[0] != '\0') { + drawList->AddText(ImVec2(rect.x + 14.0f, rect.y + 14.0f), IM_COL32(191, 205, 224, 255), title); + } + if (subtitle != nullptr && subtitle[0] != '\0') { + drawList->AddText( + ImVec2(rect.x + 14.0f, rect.y + 36.0f), + IM_COL32(132, 147, 170, 255), + subtitle); + } +} + +inline void DrawBadge( + ImDrawList* drawList, + const ::XCEngine::UI::UIRect& canvasRect, + const char* title, + const char* subtitle) { + if (drawList == nullptr || title == nullptr || title[0] == '\0') { + return; + } + + const ::XCEngine::UI::UIRect badgeRect( + canvasRect.x + 10.0f, + canvasRect.y + 10.0f, + 290.0f, + 42.0f); + const ImVec2 minPoint(badgeRect.x, badgeRect.y); + const ImVec2 maxPoint(badgeRect.x + badgeRect.width, badgeRect.y + badgeRect.height); + drawList->AddRectFilled(minPoint, maxPoint, IM_COL32(16, 22, 30, 216), 8.0f); + drawList->AddRect(minPoint, maxPoint, IM_COL32(52, 72, 96, 255), 8.0f, 0, 1.0f); + drawList->AddText( + ImVec2(badgeRect.x + 10.0f, badgeRect.y + 8.0f), + IM_COL32(191, 205, 224, 255), + title); + if (subtitle != nullptr && subtitle[0] != '\0') { + drawList->AddText( + ImVec2(badgeRect.x + 10.0f, badgeRect.y + 24.0f), + IM_COL32(132, 147, 170, 255), + subtitle); + } +} + +} // namespace detail + +class ImGuiXCUIPanelCanvasHost final : public IXCUIPanelCanvasHost { +public: + XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override { + const char* childId = + request.childId != nullptr && request.childId[0] != '\0' + ? request.childId + : "XCUIPanelCanvasHost"; + ImGui::BeginChild( + childId, + ImVec2(0.0f, request.height), + request.bordered, + ImGuiWindowFlags_NoScrollWithMouse); + + m_drawList = ImGui::GetWindowDrawList(); + m_canvasSession = {}; + + const ImVec2 canvasMin = ImGui::GetCursorScreenPos(); + const ImVec2 canvasSize = ImGui::GetContentRegionAvail(); + m_canvasSession.hostRect = ::XCEngine::UI::UIRect( + canvasMin.x, + canvasMin.y, + canvasSize.x, + canvasSize.y); + const float topInset = request.topInset > 0.0f ? request.topInset : 0.0f; + m_canvasSession.canvasRect = ::XCEngine::UI::UIRect( + m_canvasSession.hostRect.x, + m_canvasSession.hostRect.y + topInset, + m_canvasSession.hostRect.width, + (m_canvasSession.hostRect.height > topInset) + ? (m_canvasSession.hostRect.height - topInset) + : 0.0f); + m_canvasSession.validCanvas = + m_canvasSession.canvasRect.width > 1.0f && + m_canvasSession.canvasRect.height > 1.0f; + m_canvasSession.windowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); + m_canvasSession.pointerPosition = + ::XCEngine::UI::UIPoint(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); + + if (m_canvasSession.validCanvas) { + ImGui::SetCursorScreenPos(ImVec2(m_canvasSession.canvasRect.x, m_canvasSession.canvasRect.y)); + if (request.showSurfaceImage && request.surfaceImage.IsValid()) { + ImGui::Image( + detail::ToImTextureId(request.surfaceImage.texture), + ImVec2(m_canvasSession.canvasRect.width, m_canvasSession.canvasRect.height), + detail::ToImVec2(request.surfaceImage.uvMin), + detail::ToImVec2(request.surfaceImage.uvMax)); + } else { + std::string buttonId = "##"; + buttonId += childId; + buttonId += ".canvas"; + ImGui::InvisibleButton( + buttonId.c_str(), + ImVec2(m_canvasSession.canvasRect.width, m_canvasSession.canvasRect.height)); + detail::DrawPlaceholder( + m_drawList, + m_canvasSession.canvasRect, + request.placeholderTitle, + request.placeholderSubtitle); + } + + m_canvasSession.hovered = ImGui::IsItemHovered(); + if (request.showSurfaceImage && request.drawPreviewFrame) { + DrawOutlineRect( + m_canvasSession.canvasRect, + ::XCEngine::UI::UIColor(54.0f / 255.0f, 72.0f / 255.0f, 94.0f / 255.0f, 1.0f), + 1.0f, + 8.0f); + } + detail::DrawBadge( + m_drawList, + m_canvasSession.canvasRect, + request.badgeTitle, + request.badgeSubtitle); + } else { + ImGui::Dummy(ImVec2(0.0f, 0.0f)); + } + + return m_canvasSession; + } + + void DrawFilledRect( + const ::XCEngine::UI::UIRect& rect, + const ::XCEngine::UI::UIColor& color, + float rounding = 0.0f) override { + if (m_drawList == nullptr || rect.width <= 0.0f || rect.height <= 0.0f) { + return; + } + + m_drawList->AddRectFilled( + ImVec2(rect.x, rect.y), + ImVec2(rect.x + rect.width, rect.y + rect.height), + detail::ToImU32(color), + rounding); + } + + void DrawOutlineRect( + const ::XCEngine::UI::UIRect& rect, + const ::XCEngine::UI::UIColor& color, + float thickness = 1.0f, + float rounding = 0.0f) override { + if (m_drawList == nullptr || rect.width <= 0.0f || rect.height <= 0.0f) { + return; + } + + m_drawList->AddRect( + ImVec2(rect.x, rect.y), + ImVec2(rect.x + rect.width, rect.y + rect.height), + detail::ToImU32(color), + rounding, + 0, + thickness); + } + + void DrawText( + const ::XCEngine::UI::UIPoint& position, + std::string_view text, + const ::XCEngine::UI::UIColor& color, + float fontSize = 0.0f) override { + if (m_drawList == nullptr || text.empty()) { + return; + } + + const ImVec2 textPosition(position.x, position.y); + if (fontSize > 0.0f) { + m_drawList->AddText( + ImGui::GetFont(), + fontSize, + textPosition, + detail::ToImU32(color), + text.data(), + text.data() + text.size()); + return; + } + + m_drawList->AddText( + textPosition, + detail::ToImU32(color), + text.data(), + text.data() + text.size()); + } + + void EndCanvas() override { + ImGui::EndChild(); + m_drawList = nullptr; + m_canvasSession = {}; + } + +private: + ImDrawList* m_drawList = nullptr; + XCUIPanelCanvasSession m_canvasSession = {}; +}; + +inline std::unique_ptr CreateImGuiXCUIPanelCanvasHost() { + return std::make_unique(); +} + +} // namespace XCUIBackend +} // namespace Editor +} // namespace XCEngine diff --git a/new_editor/src/XCUIBackend/XCUIPanelCanvasHost.h b/new_editor/src/XCUIBackend/XCUIPanelCanvasHost.h new file mode 100644 index 00000000..973123fa --- /dev/null +++ b/new_editor/src/XCUIBackend/XCUIPanelCanvasHost.h @@ -0,0 +1,63 @@ +#pragma once + +#include "XCUIBackend/XCUIHostedPreviewPresenter.h" + +#include + +#include +#include + +namespace XCEngine { +namespace Editor { +namespace XCUIBackend { + +struct XCUIPanelCanvasRequest { + const char* childId = nullptr; + float height = 0.0f; + float topInset = 0.0f; + bool bordered = true; + bool showSurfaceImage = false; + bool drawPreviewFrame = true; + const char* placeholderTitle = nullptr; + const char* placeholderSubtitle = nullptr; + const char* badgeTitle = nullptr; + const char* badgeSubtitle = nullptr; + XCUIHostedPreviewSurfaceImage surfaceImage = {}; +}; + +struct XCUIPanelCanvasSession { + ::XCEngine::UI::UIRect hostRect = {}; + ::XCEngine::UI::UIRect canvasRect = {}; + ::XCEngine::UI::UIPoint pointerPosition = {}; + bool validCanvas = false; + bool hovered = false; + bool windowFocused = false; +}; + +class IXCUIPanelCanvasHost { +public: + virtual ~IXCUIPanelCanvasHost() = default; + + virtual XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) = 0; + virtual void DrawFilledRect( + const ::XCEngine::UI::UIRect& rect, + const ::XCEngine::UI::UIColor& color, + float rounding = 0.0f) = 0; + virtual void DrawOutlineRect( + const ::XCEngine::UI::UIRect& rect, + const ::XCEngine::UI::UIColor& color, + float thickness = 1.0f, + float rounding = 0.0f) = 0; + virtual void DrawText( + const ::XCEngine::UI::UIPoint& position, + std::string_view text, + const ::XCEngine::UI::UIColor& color, + float fontSize = 0.0f) = 0; + virtual void EndCanvas() = 0; +}; + +std::unique_ptr CreateImGuiXCUIPanelCanvasHost(); + +} // namespace XCUIBackend +} // namespace Editor +} // namespace XCEngine diff --git a/new_editor/src/panels/XCUIDemoPanel.cpp b/new_editor/src/panels/XCUIDemoPanel.cpp index da32f26a..71b4ae06 100644 --- a/new_editor/src/panels/XCUIDemoPanel.cpp +++ b/new_editor/src/panels/XCUIDemoPanel.cpp @@ -1,6 +1,7 @@ #include "XCUIDemoPanel.h" #include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" +#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h" #include "XCUIBackend/ImGuiXCUIInputAdapter.h" #include @@ -20,27 +21,9 @@ 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); -} - -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 && @@ -48,47 +31,13 @@ bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) { 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::IXCUIPanelCanvasHost& canvasHost, ::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime& runtime, const std::string& elementId, - ImU32 color, + const UI::UIColor& color, const char* label) { - if (drawList == nullptr || elementId.empty()) { + if (elementId.empty()) { return; } @@ -97,11 +46,12 @@ void DrawRectOverlay( 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); + canvasHost.DrawOutlineRect(rect, color, 2.0f, 6.0f); if (label != nullptr && label[0] != '\0') { - drawList->AddText(ImVec2(minPoint.x + 4.0f, minPoint.y + 4.0f), color, label); + canvasHost.DrawText( + UI::UIPoint(rect.x + 4.0f, rect.y + 4.0f), + label, + color); } } @@ -137,7 +87,8 @@ XCUIDemoPanel::XCUIDemoPanel( std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) : Panel("XCUI Demo") , m_inputSource(inputSource) - , m_previewPresenter(std::move(previewPresenter)) { + , m_previewPresenter(std::move(previewPresenter)) + , m_canvasHost(::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost()) { if (m_previewPresenter == nullptr) { m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); } @@ -168,6 +119,14 @@ void XCUIDemoPanel::SetHostedPreviewPresenter( 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(); } @@ -200,15 +159,10 @@ void XCUIDemoPanel::Render() { 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; + if (m_canvasHost == nullptr) { + m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost(); + } + const bool nativeHostedPreview = IsUsingNativeHostedPreview(); ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {}; const bool hasHostedSurfaceDescriptor = @@ -217,38 +171,27 @@ void XCUIDemoPanel::Render() { 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("##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)); - } + 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 = {}; @@ -256,22 +199,22 @@ void XCUIDemoPanel::Render() { 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); + 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 && ImGui::IsItemHovered(); - bridgeOptions.windowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); + 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()) { @@ -315,48 +258,64 @@ void XCUIDemoPanel::Render() { 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), + 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); - 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(); + 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(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"); + 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"); } - ImGui::EndChild(); + m_canvasHost->EndCanvas(); ImGui::Separator(); ImGui::BeginChild("XCUIDemoDiagnostics", ImVec2(0.0f, 0.0f), false, ImGuiWindowFlags_NoScrollbar); diff --git a/new_editor/src/panels/XCUIDemoPanel.h b/new_editor/src/panels/XCUIDemoPanel.h index 1f983688..43c91e8c 100644 --- a/new_editor/src/panels/XCUIDemoPanel.h +++ b/new_editor/src/panels/XCUIDemoPanel.h @@ -4,6 +4,7 @@ #include "XCUIBackend/XCUIHostedPreviewPresenter.h" #include "XCUIBackend/XCUIInputBridge.h" +#include "XCUIBackend/XCUIPanelCanvasHost.h" #include "XCUIBackend/XCUIDemoRuntime.h" #include @@ -24,6 +25,8 @@ public: void SetHostedPreviewEnabled(bool enabled); void SetHostedPreviewPresenter( std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter); + void SetCanvasHost( + std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost); bool IsHostedPreviewEnabled() const { return m_hostedPreviewEnabled; } bool IsUsingNativeHostedPreview() const; const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult& GetFrameResult() const; @@ -38,6 +41,7 @@ private: ::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_inputBridge; ::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime m_runtime; std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter; + std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost; ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats m_lastPreviewStats = {}; }; diff --git a/new_editor/src/panels/XCUILayoutLabPanel.cpp b/new_editor/src/panels/XCUILayoutLabPanel.cpp index 1e2c9a8b..2d9c33e3 100644 --- a/new_editor/src/panels/XCUILayoutLabPanel.cpp +++ b/new_editor/src/panels/XCUILayoutLabPanel.cpp @@ -1,6 +1,7 @@ #include "XCUILayoutLabPanel.h" #include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" +#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h" #include #include @@ -13,27 +14,9 @@ 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 && @@ -41,60 +24,6 @@ bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) { 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"; } @@ -127,7 +56,8 @@ XCUILayoutLabPanel::XCUILayoutLabPanel( std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter) : Panel("XCUI Layout Lab") , m_inputSource(inputSource) - , m_previewPresenter(std::move(previewPresenter)) { + , m_previewPresenter(std::move(previewPresenter)) + , m_canvasHost(::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost()) { if (m_previewPresenter == nullptr) { m_previewPresenter = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter(); } @@ -158,6 +88,14 @@ void XCUILayoutLabPanel::SetHostedPreviewPresenter( m_lastPreviewStats = {}; } +void XCUILayoutLabPanel::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 XCUILayoutLabPanel::IsUsingNativeHostedPreview() const { return m_previewPresenter != nullptr && m_previewPresenter->IsNativeQueued(); } @@ -186,10 +124,10 @@ void XCUILayoutLabPanel::Render() { 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; + if (m_canvasHost == nullptr) { + m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost(); + } + const bool nativeHostedPreview = IsUsingNativeHostedPreview(); ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {}; const bool hasHostedSurfaceDescriptor = @@ -198,48 +136,36 @@ void XCUILayoutLabPanel::Render() { 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::XCUIPanelCanvasRequest canvasRequest = {}; + canvasRequest.childId = "XCUILayoutLabCanvasHost"; + canvasRequest.height = canvasHeight; + canvasRequest.showSurfaceImage = showHostedSurfaceImage; + canvasRequest.surfaceImage = hostedSurfaceImage; + canvasRequest.placeholderTitle = + nativeHostedPreview ? "Native layout preview pending" : "Legacy layout canvas host"; + canvasRequest.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."; + canvasRequest.badgeTitle = "Layout Lab"; + canvasRequest.badgeSubtitle = previewPathLabel; + const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = + m_canvasHost->BeginCanvas(canvasRequest); + const UI::UIRect canvasRect = canvasSession.canvasRect; + const bool validCanvas = canvasSession.validCanvas; ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = {}; - input.canvasRect = ToUIRect(canvasMin, canvasSize); + input.canvasRect = canvasRect; 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.pointerPosition = canvasSession.pointerPosition; + input.pointerInside = validCanvas && canvasSession.hovered; } input.pointerPressed = input.pointerInside && ImGui::IsMouseClicked(ImGuiMouseButton_Left); @@ -264,7 +190,7 @@ void XCUILayoutLabPanel::Render() { m_lastPreviewStats, hasHostedSurfaceDescriptor, showHostedSurfaceImage); - ImGui::EndChild(); + m_canvasHost->EndCanvas(); ImGui::Separator(); ImGui::BeginChild("XCUILayoutLabDiagnostics", ImVec2(0.0f, 0.0f), false, ImGuiWindowFlags_NoScrollbar); diff --git a/new_editor/src/panels/XCUILayoutLabPanel.h b/new_editor/src/panels/XCUILayoutLabPanel.h index 3e1fc975..152d0279 100644 --- a/new_editor/src/panels/XCUILayoutLabPanel.h +++ b/new_editor/src/panels/XCUILayoutLabPanel.h @@ -4,6 +4,7 @@ #include "XCUIBackend/XCUIHostedPreviewPresenter.h" #include "XCUIBackend/XCUIInputBridge.h" +#include "XCUIBackend/XCUIPanelCanvasHost.h" #include "XCUIBackend/XCUILayoutLabRuntime.h" #include @@ -24,6 +25,8 @@ public: void SetHostedPreviewEnabled(bool enabled); void SetHostedPreviewPresenter( std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter); + void SetCanvasHost( + std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost); bool IsHostedPreviewEnabled() const { return m_hostedPreviewEnabled; } bool IsUsingNativeHostedPreview() const; const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& GetFrameResult() const; @@ -35,6 +38,7 @@ private: ::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource* m_inputSource = nullptr; ::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_runtime; std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter; + std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost; ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats m_lastPreviewStats = {}; };