#include "Application.h" #include "XCUIBackend/LegacyImGuiHostInterop.h" #include "XCUIBackend/NativeWindowUICompositor.h" #include #include #include #include #include namespace XCEngine { namespace NewEditor { namespace { constexpr wchar_t kWindowClassName[] = L"XCNewEditorWindowClass"; constexpr wchar_t kWindowTitle[] = L"XCNewEditor"; constexpr float kClearColor[4] = { 0.08f, 0.09f, 0.11f, 1.0f }; constexpr float kHostedPreviewClearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; constexpr float kNativeShellOuterMargin = 22.0f; constexpr float kNativeShellInnerGap = 18.0f; constexpr float kNativeShellHeaderHeight = 58.0f; constexpr float kNativeShellFooterHeight = 34.0f; constexpr float kNativePanelHeaderHeight = 42.0f; constexpr float kNativePanelPadding = 14.0f; constexpr float kNativePanelMinWidth = 260.0f; constexpr float kNativePanelMinHeight = 180.0f; template void ShutdownAndDelete(ResourceType*& resource) { if (resource == nullptr) { return; } resource->Shutdown(); delete resource; resource = nullptr; } const char* GetHostedPreviewPathLabel(bool nativeRequested, bool nativePresenterBound) { if (nativeRequested && nativePresenterBound) { return "native queued offscreen surface"; } if (nativeRequested) { return "native requested, hosted presenter bound"; } if (nativePresenterBound) { return "hosted presenter requested, native presenter still bound"; } return "hosted presenter"; } const char* GetHostedPreviewStateLabel( bool hostedPreviewEnabled, bool nativePresenterBound, bool presentedThisFrame, bool queuedToNativePassThisFrame, bool surfaceImageAvailable, bool surfaceAllocated, bool surfaceReady, bool descriptorAvailable) { if (!hostedPreviewEnabled) { return "disabled"; } if (nativePresenterBound) { if (surfaceImageAvailable && surfaceReady) { return "live"; } if (queuedToNativePassThisFrame || surfaceAllocated || descriptorAvailable) { return "warming"; } return "awaiting submit"; } if (presentedThisFrame) { return "live"; } return "idle"; } 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; } std::uint64_t MakeFrameTimestampNanoseconds() { return static_cast( std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()) .count()); } void AppendDrawData(::XCEngine::UI::UIDrawData& destination, const ::XCEngine::UI::UIDrawData& source) { for (const ::XCEngine::UI::UIDrawList& drawList : source.GetDrawLists()) { destination.AddDrawList(drawList); } } bool ContainsKeyTransition( const std::vector& keys, std::int32_t keyCode) { return std::find(keys.begin(), keys.end(), keyCode) != keys.end(); } bool ShouldCaptureKeyboardNavigation( const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession, const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& previousFrame) { if (!canvasSession.validCanvas) { return false; } return canvasSession.hovered || (canvasSession.windowFocused && !previousFrame.stats.selectedElementId.empty()); } void PopulateKeyboardNavigationInput( ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState& input, const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta, bool captureKeyboardNavigation) { if (!captureKeyboardNavigation) { return; } using ::XCEngine::Input::KeyCode; const auto pressedThisFrame = [&frameDelta](KeyCode keyCode) { const std::int32_t code = static_cast(keyCode); return ContainsKeyTransition(frameDelta.keyboard.pressedKeys, code) || ContainsKeyTransition(frameDelta.keyboard.repeatedKeys, code); }; input.navigatePrevious = pressedThisFrame(KeyCode::Up); input.navigateNext = pressedThisFrame(KeyCode::Down); input.navigateHome = pressedThisFrame(KeyCode::Home); input.navigateEnd = pressedThisFrame(KeyCode::End); input.navigateCollapse = pressedThisFrame(KeyCode::Left); input.navigateExpand = pressedThisFrame(KeyCode::Right); } struct NativeShellPanelLayout { Application::ShellPanelId panelId = Application::ShellPanelId::XCUIDemo; std::string title = {}; UI::UIRect panelRect = {}; UI::UIRect canvasRect = {}; bool visible = false; bool hovered = false; bool active = false; }; NativeShellPanelLayout MakePanelLayout( Application::ShellPanelId panelId, std::string title, const UI::UIRect& panelRect, const UI::UIPoint& pointerPosition, bool windowFocused, bool active) { NativeShellPanelLayout layout = {}; layout.panelId = panelId; layout.title = std::move(title); layout.panelRect = panelRect; layout.canvasRect = UI::UIRect( panelRect.x + kNativePanelPadding, panelRect.y + kNativePanelHeaderHeight, (std::max)(0.0f, panelRect.width - kNativePanelPadding * 2.0f), (std::max)(0.0f, panelRect.height - kNativePanelHeaderHeight - kNativePanelPadding)); layout.visible = panelRect.width >= kNativePanelMinWidth && panelRect.height >= kNativePanelMinHeight; layout.hovered = windowFocused && ContainsPoint(layout.canvasRect, pointerPosition); layout.active = active; return layout; } } // namespace std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> Application::CreateHostedPreviewPresenter(bool nativePreview) { if (nativePreview) { return ::XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter( m_hostedPreviewQueue, m_hostedPreviewSurfaceRegistry); } return ::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiHostedPreviewPresenter(); } bool Application::IsNativeWindowHostEnabled() const { return m_windowHostMode == WindowHostMode::NativeXCUI; } void Application::InitializePanelsForActiveWindowHost() { if (IsNativeWindowHostEnabled()) { InitializeNativeShell(); return; } InitializeLegacyImGuiPanels(); ConfigureHostedPreviewPresenters(); } void Application::InitializeLegacyImGuiPanels() { m_demoPanel = std::make_unique( &m_xcuiInputSource, CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)), ::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiPanelCanvasHost()); m_layoutLabPanel = std::make_unique( &m_xcuiInputSource, CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)), ::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiPanelCanvasHost()); } void Application::InitializeNativeShell() { m_nativeActivePanel = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo) ? ShellPanelId::XCUIDemo : ShellPanelId::XCUILayoutLab; m_nativeDemoInputBridge.Reset(); m_nativeLayoutInputBridge.Reset(); m_nativeDemoCanvasHost.ClearCanvasSession(); m_nativeLayoutCanvasHost.ClearCanvasSession(); m_nativeDemoReloadSucceeded = m_nativeDemoRuntime.ReloadDocuments(); m_nativeLayoutReloadSucceeded = m_nativeLayoutRuntime.ReloadDocuments(); } const Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const { return m_shellChromeState.TryGetPanelState(panelId); } bool Application::IsShellViewToggleEnabled(ShellViewToggleId toggleId) const { return m_shellChromeState.GetViewToggle(toggleId); } void Application::SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled) { m_shellChromeState.SetViewToggle(toggleId, enabled); } bool Application::IsNativeHostedPreviewEnabled(ShellPanelId panelId) const { return m_shellChromeState.IsNativeHostedPreviewActive(panelId); } void Application::ConfigureHostedPreviewPresenters() { const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo); if (m_demoPanel != nullptr) { m_demoPanel->SetVisible(demoState != nullptr && demoState->visible); m_demoPanel->SetHostedPreviewEnabled(demoState == nullptr || demoState->hostedPreviewEnabled); m_demoPanel->SetHostedPreviewPresenter(CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo))); } const ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab); if (m_layoutLabPanel != nullptr) { m_layoutLabPanel->SetVisible(layoutLabState != nullptr && layoutLabState->visible); m_layoutLabPanel->SetHostedPreviewEnabled(layoutLabState == nullptr || layoutLabState->hostedPreviewEnabled); m_layoutLabPanel->SetHostedPreviewPresenter( CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab))); } } void Application::ConfigureShellCommandRouter() { m_shellCommandRouter.Clear(); ShellCommandBindings bindings = {}; bindings.getXCUIDemoPanelVisible = [this]() { return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo); }; bindings.setXCUIDemoPanelVisible = [this](bool visible) { m_shellChromeState.SetPanelVisible(ShellPanelId::XCUIDemo, visible); if (m_demoPanel != nullptr) { m_demoPanel->SetVisible(visible); } }; bindings.getXCUILayoutLabPanelVisible = [this]() { return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab); }; bindings.setXCUILayoutLabPanelVisible = [this](bool visible) { m_shellChromeState.SetPanelVisible(ShellPanelId::XCUILayoutLab, visible); if (m_layoutLabPanel != nullptr) { m_layoutLabPanel->SetVisible(visible); } }; bindings.getImGuiDemoWindowVisible = [this]() { return IsShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow); }; bindings.setImGuiDemoWindowVisible = [this](bool visible) { SetShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow, visible); }; bindings.getNativeBackdropVisible = [this]() { return IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop); }; bindings.setNativeBackdropVisible = [this](bool visible) { SetShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop, visible); }; bindings.getPulseAccentEnabled = [this]() { return IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent); }; bindings.setPulseAccentEnabled = [this](bool enabled) { SetShellViewToggleEnabled(ShellViewToggleId::PulseAccent, enabled); }; bindings.getNativeXCUIOverlayVisible = [this]() { return IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay); }; bindings.setNativeXCUIOverlayVisible = [this](bool visible) { SetShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay, visible); }; bindings.getHostedPreviewHudVisible = [this]() { return IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud); }; bindings.setHostedPreviewHudVisible = [this](bool visible) { SetShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud, visible); }; bindings.getNativeDemoPanelPreviewEnabled = [this]() { return IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo); }; bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) { m_shellChromeState.SetHostedPreviewMode( ShellPanelId::XCUIDemo, enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter); }; bindings.getNativeLayoutLabPreviewEnabled = [this]() { return IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab); }; bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) { m_shellChromeState.SetHostedPreviewMode( ShellPanelId::XCUILayoutLab, enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter); }; bindings.onHostedPreviewModeChanged = [this]() { ConfigureHostedPreviewPresenters(); }; Application::RegisterShellViewCommands(m_shellCommandRouter, bindings); } ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta Application::DispatchShellShortcuts( const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& snapshot) { ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot shellSnapshot = snapshot; if (!IsNativeWindowHostEnabled()) { ::XCEngine::Editor::XCUIBackend::ApplyLegacyImGuiHostInputCapture(shellSnapshot); } if (!m_shellInputBridge.HasBaseline()) { m_shellInputBridge.Prime(shellSnapshot); } const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta = m_shellInputBridge.Translate(shellSnapshot); const ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot commandSnapshot = Application::BuildShellShortcutSnapshot(frameDelta); m_shellCommandRouter.InvokeMatchingShortcut({ &commandSnapshot }); return frameDelta; } Application::HostedPreviewPanelDiagnostics Application::BuildHostedPreviewPanelDiagnostics( const char* debugName, const char* fallbackDebugSource, bool visible, bool hostedPreviewEnabled, bool nativeRequested, bool nativePresenterBound, const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats) const { HostedPreviewPanelDiagnostics diagnostics = {}; if (debugName != nullptr) { diagnostics.debugName = debugName; } if (fallbackDebugSource != nullptr) { diagnostics.debugSource = fallbackDebugSource; } diagnostics.visible = visible; diagnostics.hostedPreviewEnabled = hostedPreviewEnabled; diagnostics.nativeRequested = nativeRequested; diagnostics.nativePresenterBound = nativePresenterBound; diagnostics.presentedThisFrame = previewStats.presented; diagnostics.queuedToNativePassThisFrame = previewStats.queuedToNativePass; diagnostics.submittedDrawListCount = previewStats.submittedDrawListCount; diagnostics.submittedCommandCount = previewStats.submittedCommandCount; diagnostics.flushedDrawListCount = previewStats.flushedDrawListCount; diagnostics.flushedCommandCount = previewStats.flushedCommandCount; ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor descriptor = {}; if (!diagnostics.debugName.empty() && m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor(diagnostics.debugName.c_str(), descriptor)) { diagnostics.descriptorAvailable = true; diagnostics.surfaceImageAvailable = descriptor.image.IsValid(); diagnostics.surfaceWidth = descriptor.image.surfaceWidth; diagnostics.surfaceHeight = descriptor.image.surfaceHeight; diagnostics.logicalWidth = descriptor.logicalSize.width; diagnostics.logicalHeight = descriptor.logicalSize.height; diagnostics.queuedFrameIndex = descriptor.queuedFrameIndex; if (!descriptor.debugSource.empty()) { diagnostics.debugSource = descriptor.debugSource; } } if (!diagnostics.debugName.empty()) { const HostedPreviewOffscreenSurface* previewSurface = FindHostedPreviewSurface(diagnostics.debugName); if (previewSurface != nullptr) { diagnostics.surfaceAllocated = previewSurface->colorTexture != nullptr || previewSurface->colorView != nullptr || previewSurface->width > 0u || previewSurface->height > 0u; diagnostics.surfaceReady = previewSurface->IsReady(); if (diagnostics.surfaceWidth == 0u) { diagnostics.surfaceWidth = previewSurface->width; } if (diagnostics.surfaceHeight == 0u) { diagnostics.surfaceHeight = previewSurface->height; } } } return diagnostics; } int Application::Run(HINSTANCE instance, int nCmdShow) { if (!CreateMainWindow(instance, nCmdShow)) { return -1; } m_xcuiInputSource.Reset(); auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get(); resourceManager.Initialize(); resourceManager.SetResourceRoot(XCENGINE_NEW_EDITOR_REPO_ROOT); if (!InitializeRenderer()) { resourceManager.Shutdown(); return -1; } InitializeWindowCompositor(); InitializePanelsForActiveWindowHost(); m_shellInputBridge.Reset(); ConfigureShellCommandRouter(); m_running = true; m_renderReady = true; MSG message = {}; while (m_running) { while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) { if (message.message == WM_QUIT) { m_running = false; break; } TranslateMessage(&message); DispatchMessageW(&message); } if (!m_running) { break; } Frame(); } m_demoPanel.reset(); m_layoutLabPanel.reset(); ShutdownWindowCompositor(); ShutdownRenderer(); resourceManager.Shutdown(); return static_cast(message.wParam); } LRESULT CALLBACK Application::StaticWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { Application* app = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); if (message == WM_NCCREATE) { const CREATESTRUCTW* createStruct = reinterpret_cast(lParam); app = reinterpret_cast(createStruct->lpCreateParams); SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(app)); } return app != nullptr ? app->WndProc(hwnd, message, wParam, lParam) : DefWindowProcW(hwnd, message, wParam, lParam); } LRESULT Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { m_xcuiInputSource.HandleWindowMessage(hwnd, message, wParam, lParam); if (m_windowCompositor != nullptr && m_windowCompositor->HandleWindowMessage(hwnd, message, wParam, lParam)) { return true; } switch (message) { case WM_SIZE: if (wParam != SIZE_MINIMIZED && m_renderReady) { m_windowRenderer.Resize(static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))); } return 0; case WM_CLOSE: DestroyWindow(hwnd); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; case WM_PAINT: { PAINTSTRUCT paintStruct = {}; BeginPaint(hwnd, &paintStruct); if (m_renderReady) { Frame(); } EndPaint(hwnd, &paintStruct); return 0; } default: return DefWindowProcW(hwnd, message, wParam, lParam); } } bool Application::CreateMainWindow(HINSTANCE instance, int nCmdShow) { WNDCLASSEXW windowClass = {}; windowClass.cbSize = sizeof(windowClass); windowClass.style = CS_HREDRAW | CS_VREDRAW; windowClass.lpfnWndProc = &Application::StaticWndProc; windowClass.hInstance = instance; windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); windowClass.lpszClassName = kWindowClassName; if (!RegisterClassExW(&windowClass)) { return false; } m_hwnd = CreateWindowExW( 0, kWindowClassName, kWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 1360, 840, nullptr, nullptr, instance, this); if (m_hwnd == nullptr) { return false; } ShowWindow(m_hwnd, nCmdShow); UpdateWindow(m_hwnd); return true; } bool Application::InitializeRenderer() { RECT clientRect = {}; if (!GetClientRect(m_hwnd, &clientRect)) { return false; } const int width = clientRect.right - clientRect.left; const int height = clientRect.bottom - clientRect.top; const bool initialized = width > 0 && height > 0 && m_windowRenderer.Initialize(m_hwnd, width, height); if (initialized) { m_startTime = std::chrono::steady_clock::now(); } return initialized; } void Application::InitializeWindowCompositor() { m_windowCompositor = IsNativeWindowHostEnabled() ? ::XCEngine::Editor::XCUIBackend::CreateNativeWindowUICompositor() : ::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiWindowUICompositor(); if (m_windowCompositor != nullptr) { m_windowCompositor->Initialize( m_hwnd, m_windowRenderer, []() { (void)::XCEngine::Editor::XCUIBackend::ConfigureLegacyImGuiHostFonts(); }); } } void Application::ShutdownWindowCompositor() { DestroyHostedPreviewSurfaces(); if (m_windowCompositor != nullptr) { m_windowCompositor->Shutdown(); m_windowCompositor.reset(); } } void Application::ShutdownRenderer() { m_renderReady = false; m_hostedPreviewRenderBackend.Shutdown(); m_nativeBackdropRenderer.Shutdown(); m_windowRenderer.Shutdown(); } void Application::DestroyHostedPreviewSurfaces() { for (HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) { if (Application::HasHostedPreviewTextureRegistration(previewSurface.textureRegistration)) { if (m_windowCompositor != nullptr) { m_windowCompositor->FreeTextureDescriptor(previewSurface.textureRegistration); } } ShutdownAndDelete(previewSurface.colorView); ShutdownAndDelete(previewSurface.colorTexture); previewSurface = {}; } m_hostedPreviewSurfaces.clear(); } void Application::SyncShellChromePanelStateFromPanels() { m_shellChromeState.SetPanelVisible( ShellPanelId::XCUIDemo, m_demoPanel != nullptr && m_demoPanel->IsVisible()); m_shellChromeState.SetPanelVisible( ShellPanelId::XCUILayoutLab, m_layoutLabPanel != nullptr && m_layoutLabPanel->IsVisible()); } void Application::SyncHostedPreviewSurfaces() { const auto isNativePreviewEnabled = [this](const std::string& debugName) { const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo); if (demoState != nullptr && debugName == demoState->previewDebugName && IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)) { return true; } const ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab); return layoutLabState != nullptr && debugName == layoutLabState->previewDebugName && IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab); }; const auto syncSurfaceForNameAndSize = [this, &isNativePreviewEnabled]( const std::string& debugName, const ::XCEngine::UI::UISize& logicalSize) { if (!isNativePreviewEnabled(debugName)) { return; } const std::uint32_t width = logicalSize.width > 1.0f ? static_cast(std::ceil(logicalSize.width)) : 0u; const std::uint32_t height = logicalSize.height > 1.0f ? static_cast(std::ceil(logicalSize.height)) : 0u; if (width == 0u || height == 0u) { return; } HostedPreviewOffscreenSurface& previewSurface = FindOrAddHostedPreviewSurface(debugName); EnsureHostedPreviewSurface(previewSurface, width, height); }; for (const auto& descriptor : m_hostedPreviewSurfaceRegistry.GetDescriptors()) { syncSurfaceForNameAndSize(descriptor.debugName, descriptor.logicalSize); } for (const auto& queuedFrame : m_hostedPreviewQueue.GetQueuedFrames()) { syncSurfaceForNameAndSize(queuedFrame.debugName, queuedFrame.logicalSize); } } Application::HostedPreviewOffscreenSurface* Application::FindHostedPreviewSurface(const std::string& debugName) { for (HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) { if (previewSurface.debugName == debugName) { return &previewSurface; } } return nullptr; } const Application::HostedPreviewOffscreenSurface* Application::FindHostedPreviewSurface(const std::string& debugName) const { for (const HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) { if (previewSurface.debugName == debugName) { return &previewSurface; } } return nullptr; } Application::HostedPreviewOffscreenSurface& Application::FindOrAddHostedPreviewSurface(const std::string& debugName) { HostedPreviewOffscreenSurface* existingSurface = FindHostedPreviewSurface(debugName); if (existingSurface != nullptr) { return *existingSurface; } HostedPreviewOffscreenSurface previewSurface = {}; previewSurface.debugName = debugName; m_hostedPreviewSurfaces.push_back(std::move(previewSurface)); return m_hostedPreviewSurfaces.back(); } bool Application::EnsureHostedPreviewSurface( HostedPreviewOffscreenSurface& previewSurface, std::uint32_t width, std::uint32_t height) { if (previewSurface.IsReady() && previewSurface.width == width && previewSurface.height == height) { return true; } if (Application::HasHostedPreviewTextureRegistration(previewSurface.textureRegistration)) { if (m_windowCompositor != nullptr) { m_windowCompositor->FreeTextureDescriptor(previewSurface.textureRegistration); } } ShutdownAndDelete(previewSurface.colorView); ShutdownAndDelete(previewSurface.colorTexture); previewSurface.textureRegistration = {}; previewSurface.width = width; previewSurface.height = height; previewSurface.colorState = ::XCEngine::RHI::ResourceStates::Common; if (width == 0u || height == 0u) { return false; } ::XCEngine::RHI::TextureDesc colorDesc = {}; colorDesc.width = width; colorDesc.height = height; colorDesc.depth = 1; colorDesc.mipLevels = 1; colorDesc.arraySize = 1; colorDesc.format = static_cast(::XCEngine::RHI::Format::R8G8B8A8_UNorm); colorDesc.textureType = static_cast(::XCEngine::RHI::TextureType::Texture2D); colorDesc.sampleCount = 1; previewSurface.colorTexture = m_windowRenderer.GetRHIDevice()->CreateTexture(colorDesc); if (previewSurface.colorTexture == nullptr) { return false; } ::XCEngine::RHI::ResourceViewDesc colorViewDesc = {}; colorViewDesc.format = static_cast(::XCEngine::RHI::Format::R8G8B8A8_UNorm); colorViewDesc.dimension = ::XCEngine::RHI::ResourceViewDimension::Texture2D; previewSurface.colorView = m_windowRenderer.GetRHIDevice()->CreateRenderTargetView(previewSurface.colorTexture, colorViewDesc); if (previewSurface.colorView == nullptr) { ShutdownAndDelete(previewSurface.colorTexture); return false; } if (m_windowCompositor == nullptr || !m_windowCompositor->CreateTextureDescriptor( m_windowRenderer.GetRHIDevice(), previewSurface.colorTexture, previewSurface.textureRegistration)) { ShutdownAndDelete(previewSurface.colorView); ShutdownAndDelete(previewSurface.colorTexture); previewSurface.textureRegistration = {}; return false; } return true; } bool Application::RenderHostedPreviewOffscreenSurface( HostedPreviewOffscreenSurface& previewSurface, const ::XCEngine::Rendering::RenderContext& renderContext, const ::XCEngine::UI::UIDrawData& drawData) { if (!previewSurface.IsReady() || renderContext.commandList == nullptr) { return false; } ::XCEngine::Rendering::RenderSurface renderSurface(previewSurface.width, previewSurface.height); renderSurface.SetColorAttachment(previewSurface.colorView); renderSurface.SetAutoTransitionEnabled(false); renderSurface.SetColorStateBefore(::XCEngine::RHI::ResourceStates::RenderTarget); renderSurface.SetColorStateAfter(::XCEngine::RHI::ResourceStates::RenderTarget); renderContext.commandList->TransitionBarrier( previewSurface.colorView, previewSurface.colorState, ::XCEngine::RHI::ResourceStates::RenderTarget); renderContext.commandList->SetRenderTargets(1, &previewSurface.colorView, nullptr); renderContext.commandList->ClearRenderTarget(previewSurface.colorView, kHostedPreviewClearColor); if (!m_hostedPreviewRenderBackend.Render(renderContext, renderSurface, drawData)) { previewSurface.colorState = ::XCEngine::RHI::ResourceStates::RenderTarget; return false; } renderContext.commandList->TransitionBarrier( previewSurface.colorView, ::XCEngine::RHI::ResourceStates::RenderTarget, ::XCEngine::RHI::ResourceStates::PixelShaderResource); previewSurface.colorState = ::XCEngine::RHI::ResourceStates::PixelShaderResource; return true; } ::XCEngine::UI::UIDrawData Application::BuildNativeShellDrawData( const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& shellSnapshot, const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& shellFrameDelta) { ::XCEngine::UI::UIDrawData composedDrawData = {}; RECT clientRect = {}; if (!GetClientRect(m_hwnd, &clientRect)) { return composedDrawData; } const float windowWidth = static_cast(clientRect.right - clientRect.left); const float windowHeight = static_cast(clientRect.bottom - clientRect.top); if (windowWidth <= 1.0f || windowHeight <= 1.0f) { return composedDrawData; } const UI::UIRect shellRect(0.0f, 0.0f, windowWidth, windowHeight); const UI::UIColor shellBorderColor(40.0f / 255.0f, 54.0f / 255.0f, 74.0f / 255.0f, 1.0f); const UI::UIColor shellSurfaceColor(11.0f / 255.0f, 15.0f / 255.0f, 22.0f / 255.0f, 180.0f / 255.0f); const UI::UIColor panelSurfaceColor(9.0f / 255.0f, 13.0f / 255.0f, 18.0f / 255.0f, 212.0f / 255.0f); const UI::UIColor panelBorderColor(53.0f / 255.0f, 72.0f / 255.0f, 96.0f / 255.0f, 1.0f); const UI::UIColor panelAccentColor(84.0f / 255.0f, 176.0f / 255.0f, 244.0f / 255.0f, 1.0f); const UI::UIColor hoveredAccentColor(1.0f, 206.0f / 255.0f, 112.0f / 255.0f, 1.0f); const UI::UIColor textPrimary(232.0f / 255.0f, 238.0f / 255.0f, 246.0f / 255.0f, 1.0f); const UI::UIColor textSecondary(150.0f / 255.0f, 164.0f / 255.0f, 184.0f / 255.0f, 1.0f); const UI::UIColor textMuted(108.0f / 255.0f, 123.0f / 255.0f, 145.0f / 255.0f, 1.0f); const float topBarY = kNativeShellOuterMargin; const UI::UIRect topBarRect( kNativeShellOuterMargin, topBarY, (std::max)(0.0f, windowWidth - kNativeShellOuterMargin * 2.0f), kNativeShellHeaderHeight); const UI::UIRect footerRect( kNativeShellOuterMargin, (std::max)(topBarRect.y + topBarRect.height + kNativeShellInnerGap, windowHeight - kNativeShellOuterMargin - kNativeShellFooterHeight), (std::max)(0.0f, windowWidth - kNativeShellOuterMargin * 2.0f), kNativeShellFooterHeight); const float workspaceTop = topBarRect.y + topBarRect.height + kNativeShellInnerGap; const float workspaceBottom = footerRect.y - kNativeShellInnerGap; const UI::UIRect workspaceRect( kNativeShellOuterMargin, workspaceTop, (std::max)(0.0f, windowWidth - kNativeShellOuterMargin * 2.0f), (std::max)(0.0f, workspaceBottom - workspaceTop)); bool demoVisible = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo); bool layoutVisible = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab); if (m_nativeActivePanel == ShellPanelId::XCUIDemo && !demoVisible && layoutVisible) { m_nativeActivePanel = ShellPanelId::XCUILayoutLab; } else if (m_nativeActivePanel == ShellPanelId::XCUILayoutLab && !layoutVisible && demoVisible) { m_nativeActivePanel = ShellPanelId::XCUIDemo; } std::vector panelLayouts = {}; panelLayouts.reserve(2u); if (demoVisible && layoutVisible) { const float leftWidth = (std::max)( kNativePanelMinWidth, (std::min)(workspaceRect.width * 0.60f, workspaceRect.width - kNativePanelMinWidth - kNativeShellInnerGap)); const float rightWidth = (std::max)(0.0f, workspaceRect.width - leftWidth - kNativeShellInnerGap); panelLayouts.push_back(MakePanelLayout( ShellPanelId::XCUIDemo, "XCUI Demo", UI::UIRect(workspaceRect.x, workspaceRect.y, leftWidth, workspaceRect.height), shellSnapshot.pointerPosition, shellSnapshot.windowFocused, m_nativeActivePanel == ShellPanelId::XCUIDemo)); panelLayouts.push_back(MakePanelLayout( ShellPanelId::XCUILayoutLab, "XCUI Layout Lab", UI::UIRect(workspaceRect.x + leftWidth + kNativeShellInnerGap, workspaceRect.y, rightWidth, workspaceRect.height), shellSnapshot.pointerPosition, shellSnapshot.windowFocused, m_nativeActivePanel == ShellPanelId::XCUILayoutLab)); } else if (demoVisible) { panelLayouts.push_back(MakePanelLayout( ShellPanelId::XCUIDemo, "XCUI Demo", workspaceRect, shellSnapshot.pointerPosition, shellSnapshot.windowFocused, true)); m_nativeActivePanel = ShellPanelId::XCUIDemo; } else if (layoutVisible) { panelLayouts.push_back(MakePanelLayout( ShellPanelId::XCUILayoutLab, "XCUI Layout Lab", workspaceRect, shellSnapshot.pointerPosition, shellSnapshot.windowFocused, true)); m_nativeActivePanel = ShellPanelId::XCUILayoutLab; } if (shellFrameDelta.pointer.pressed[0]) { for (const NativeShellPanelLayout& panelLayout : panelLayouts) { if (panelLayout.hovered) { m_nativeActivePanel = panelLayout.panelId; break; } } for (NativeShellPanelLayout& panelLayout : panelLayouts) { panelLayout.active = panelLayout.panelId == m_nativeActivePanel; } } ::XCEngine::UI::UIDrawList& chromeBackground = composedDrawData.EmplaceDrawList("XCUI.NativeShell.Background"); chromeBackground.AddFilledRect(shellRect, shellSurfaceColor, 0.0f); chromeBackground.AddFilledRect(topBarRect, UI::UIColor(12.0f / 255.0f, 18.0f / 255.0f, 26.0f / 255.0f, 230.0f / 255.0f), 14.0f); chromeBackground.AddRectOutline(topBarRect, shellBorderColor, 1.0f, 14.0f); chromeBackground.AddFilledRect(footerRect, UI::UIColor(12.0f / 255.0f, 18.0f / 255.0f, 26.0f / 255.0f, 214.0f / 255.0f), 12.0f); chromeBackground.AddRectOutline(footerRect, shellBorderColor, 1.0f, 12.0f); if (panelLayouts.empty()) { const UI::UIRect emptyStateRect( workspaceRect.x, workspaceRect.y, workspaceRect.width, (std::max)(0.0f, workspaceRect.height)); chromeBackground.AddFilledRect(emptyStateRect, panelSurfaceColor, 18.0f); chromeBackground.AddRectOutline(emptyStateRect, panelBorderColor, 1.0f, 18.0f); ::XCEngine::UI::UIDrawList& emptyForeground = composedDrawData.EmplaceDrawList("XCUI.NativeShell.EmptyState"); emptyForeground.AddText( UI::UIPoint(emptyStateRect.x + 24.0f, emptyStateRect.y + 28.0f), "XCUI native shell is active, but both sandbox panels are hidden.", textPrimary); emptyForeground.AddText( UI::UIPoint(emptyStateRect.x + 24.0f, emptyStateRect.y + 50.0f), "Use Ctrl+1 or Ctrl+2 to bring a panel back.", textSecondary); return composedDrawData; } for (const NativeShellPanelLayout& panelLayout : panelLayouts) { const UI::UIColor borderColor = panelLayout.active ? panelAccentColor : (panelLayout.hovered ? hoveredAccentColor : panelBorderColor); chromeBackground.AddFilledRect(panelLayout.panelRect, panelSurfaceColor, 18.0f); chromeBackground.AddRectOutline(panelLayout.panelRect, borderColor, panelLayout.active ? 2.0f : 1.0f, 18.0f); chromeBackground.AddFilledRect( UI::UIRect( panelLayout.panelRect.x, panelLayout.panelRect.y, panelLayout.panelRect.width, kNativePanelHeaderHeight), UI::UIColor(13.0f / 255.0f, 20.0f / 255.0f, 28.0f / 255.0f, 242.0f / 255.0f), 18.0f); } struct NativePanelFrameSummary { NativeShellPanelLayout layout = {}; std::string lineA = {}; std::string lineB = {}; ::XCEngine::UI::UIDrawData overlay = {}; }; std::vector panelSummaries = {}; panelSummaries.reserve(panelLayouts.size()); const auto capturePanelSnapshot = [this, &shellSnapshot](const NativeShellPanelLayout& panelLayout, bool wantsKeyboard) { ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {}; options.timestampNanoseconds = shellSnapshot.timestampNanoseconds; options.windowFocused = shellSnapshot.windowFocused; options.hasPointerInsideOverride = true; options.pointerInsideOverride = panelLayout.hovered; ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot panelSnapshot = m_xcuiInputSource.CaptureSnapshot(options); if (!wantsKeyboard) { panelSnapshot.keys.clear(); panelSnapshot.characters.clear(); panelSnapshot.wantCaptureKeyboard = false; panelSnapshot.wantTextInput = false; } return panelSnapshot; }; const auto extractCanvasOverlay = [](const ::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& canvasHost) { ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasFrameSnapshot snapshot = {}; if (!canvasHost.TryGetLatestFrameSnapshot(snapshot)) { return ::XCEngine::UI::UIDrawData(); } return snapshot.overlayDrawData; }; for (const NativeShellPanelLayout& panelLayout : panelLayouts) { if (panelLayout.panelId == ShellPanelId::XCUIDemo) { const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo); const bool nativeHostedPreview = panelState != nullptr && panelState->hostedPreviewEnabled && IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo); ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {}; const bool hasHostedSurfaceDescriptor = nativeHostedPreview && panelState != nullptr && !panelState->previewDebugName.empty() && m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor( panelState->previewDebugName.data(), hostedSurfaceDescriptor); ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {}; const bool showHostedSurfaceImage = nativeHostedPreview && panelState != nullptr && !panelState->previewDebugName.empty() && m_hostedPreviewSurfaceRegistry.TryGetSurfaceImage( panelState->previewDebugName.data(), hostedSurfaceImage); const Application::NativeHostedPreviewConsumption previewConsumption = Application::ResolveNativeHostedPreviewConsumption( nativeHostedPreview, hasHostedSurfaceDescriptor, showHostedSurfaceImage, "Native XCUI preview pending", "Waiting for queued native preview output to publish into the shell card."); ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {}; canvasSession.hostRect = panelLayout.panelRect; canvasSession.canvasRect = panelLayout.canvasRect; canvasSession.pointerPosition = shellSnapshot.pointerPosition; canvasSession.validCanvas = panelLayout.canvasRect.width > 1.0f && panelLayout.canvasRect.height > 1.0f; canvasSession.hovered = panelLayout.hovered; canvasSession.windowFocused = shellSnapshot.windowFocused; m_nativeDemoCanvasHost.SetCanvasSession(canvasSession); ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {}; canvasRequest.childId = "XCUIDemo.NativeCanvas"; canvasRequest.height = panelLayout.panelRect.height; canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y; canvasRequest.drawPreviewFrame = false; canvasRequest.showSurfaceImage = previewConsumption.showSurfaceImage; canvasRequest.surfaceImage = hostedSurfaceImage; canvasRequest.placeholderTitle = previewConsumption.placeholderTitle.empty() ? nullptr : previewConsumption.placeholderTitle.data(); canvasRequest.placeholderSubtitle = previewConsumption.placeholderSubtitle.empty() ? nullptr : previewConsumption.placeholderSubtitle.data(); const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession resolvedSession = m_nativeDemoCanvasHost.BeginCanvas(canvasRequest); const bool wantsKeyboard = panelLayout.active; const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard); if (!m_nativeDemoInputBridge.HasBaseline()) { m_nativeDemoInputBridge.Prime(panelSnapshot); } const auto panelFrameDelta = m_nativeDemoInputBridge.Translate(panelSnapshot); ::XCEngine::Editor::XCUIBackend::XCUIDemoInputState input = {}; input.canvasRect = resolvedSession.canvasRect; input.pointerPosition = panelSnapshot.pointerPosition; input.pointerInside = panelSnapshot.pointerInside; input.pointerPressed = panelFrameDelta.pointer.pressed[0]; input.pointerReleased = panelFrameDelta.pointer.released[0]; input.pointerDown = panelSnapshot.pointerButtonsDown[0]; input.windowFocused = panelSnapshot.windowFocused; input.wantCaptureMouse = panelSnapshot.wantCaptureMouse; input.wantCaptureKeyboard = panelSnapshot.wantCaptureKeyboard; input.wantTextInput = panelSnapshot.wantTextInput; input.events = panelFrameDelta.events; const auto& frame = m_nativeDemoRuntime.Update(input); if (previewConsumption.queueRuntimeFrame) { ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {}; previewFrame.drawData = &frame.drawData; previewFrame.canvasRect = resolvedSession.canvasRect; previewFrame.logicalSize = UI::UISize( resolvedSession.canvasRect.width, resolvedSession.canvasRect.height); previewFrame.debugName = panelState != nullptr ? panelState->previewDebugName.data() : nullptr; previewFrame.debugSource = panelState != nullptr ? panelState->previewDebugSource.data() : nullptr; m_hostedPreviewQueue.Submit(previewFrame); } else if (previewConsumption.appendRuntimeDrawDataToShell) { AppendDrawData(composedDrawData, frame.drawData); } if (IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud)) { const auto& stats = frame.stats; const UI::UIRect hudRect( resolvedSession.canvasRect.x + 10.0f, resolvedSession.canvasRect.y + 10.0f, (std::min)(resolvedSession.canvasRect.width - 20.0f, 360.0f), 62.0f); if (hudRect.width > 40.0f && hudRect.height > 20.0f) { m_nativeDemoCanvasHost.DrawFilledRect( hudRect, UI::UIColor(12.0f / 255.0f, 18.0f / 255.0f, 26.0f / 255.0f, 214.0f / 255.0f), 10.0f); m_nativeDemoCanvasHost.DrawOutlineRect(hudRect, panelBorderColor, 1.0f, 10.0f); m_nativeDemoCanvasHost.DrawText( UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 8.0f), "Direct native XCUI frame", textPrimary); m_nativeDemoCanvasHost.DrawText( UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 28.0f), std::string("Tree ") + std::to_string(static_cast(stats.treeGeneration)) + " | Elements " + std::to_string(stats.elementCount) + " | Commands " + std::to_string(stats.commandCount), textSecondary); m_nativeDemoCanvasHost.DrawText( UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 46.0f), stats.statusMessage, textMuted); } if (previewConsumption.drawRuntimeDebugRects) { const auto drawDebugRect = [this](const std::string& elementId, const UI::UIColor& color, const char* label) { if (elementId.empty()) { return; } UI::UIRect rect = {}; if (!m_nativeDemoRuntime.TryGetElementRect(elementId, rect)) { return; } m_nativeDemoCanvasHost.DrawOutlineRect(rect, color, 2.0f, 6.0f); if (label != nullptr && label[0] != '\0') { m_nativeDemoCanvasHost.DrawText( UI::UIPoint(rect.x + 4.0f, rect.y + 4.0f), label, color); } }; drawDebugRect( stats.hoveredElementId, UI::UIColor(1.0f, 195.0f / 255.0f, 64.0f / 255.0f, 1.0f), "hover"); drawDebugRect( stats.focusedElementId, UI::UIColor(64.0f / 255.0f, 214.0f / 255.0f, 1.0f, 1.0f), "focus"); } } m_nativeDemoCanvasHost.EndCanvas(); NativePanelFrameSummary summary = {}; summary.layout = panelLayout; summary.lineA = m_nativeDemoReloadSucceeded ? Application::ComposeNativeHostedPreviewStatusLine( previewConsumption, frame.stats.statusMessage) : "Document reload failed; showing last retained runtime state."; summary.lineB = std::string(panelLayout.active ? "Active" : "Passive") + " | " + std::to_string(frame.stats.elementCount) + " elements | " + std::to_string(frame.stats.commandCount) + " cmds"; summary.overlay = extractCanvasOverlay(m_nativeDemoCanvasHost); panelSummaries.push_back(std::move(summary)); m_nativeDemoCanvasHost.ClearCanvasSession(); continue; } const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab); const bool nativeHostedPreview = panelState != nullptr && panelState->hostedPreviewEnabled && IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab); ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {}; const bool hasHostedSurfaceDescriptor = nativeHostedPreview && panelState != nullptr && !panelState->previewDebugName.empty() && m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor( panelState->previewDebugName.data(), hostedSurfaceDescriptor); ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {}; const bool showHostedSurfaceImage = nativeHostedPreview && panelState != nullptr && !panelState->previewDebugName.empty() && m_hostedPreviewSurfaceRegistry.TryGetSurfaceImage( panelState->previewDebugName.data(), hostedSurfaceImage); const Application::NativeHostedPreviewConsumption previewConsumption = Application::ResolveNativeHostedPreviewConsumption( nativeHostedPreview, hasHostedSurfaceDescriptor, showHostedSurfaceImage, "Native layout preview pending", "Waiting for queued native preview output to publish into the layout card."); ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {}; canvasSession.hostRect = panelLayout.panelRect; canvasSession.canvasRect = panelLayout.canvasRect; canvasSession.pointerPosition = shellSnapshot.pointerPosition; canvasSession.validCanvas = panelLayout.canvasRect.width > 1.0f && panelLayout.canvasRect.height > 1.0f; canvasSession.hovered = panelLayout.hovered; canvasSession.windowFocused = shellSnapshot.windowFocused; m_nativeLayoutCanvasHost.SetCanvasSession(canvasSession); ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {}; canvasRequest.childId = "XCUILayoutLab.NativeCanvas"; canvasRequest.height = panelLayout.panelRect.height; canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y; canvasRequest.drawPreviewFrame = false; canvasRequest.showSurfaceImage = previewConsumption.showSurfaceImage; canvasRequest.surfaceImage = hostedSurfaceImage; canvasRequest.placeholderTitle = previewConsumption.placeholderTitle.empty() ? nullptr : previewConsumption.placeholderTitle.data(); canvasRequest.placeholderSubtitle = previewConsumption.placeholderSubtitle.empty() ? nullptr : previewConsumption.placeholderSubtitle.data(); const auto resolvedSession = m_nativeLayoutCanvasHost.BeginCanvas(canvasRequest); const bool wantsKeyboard = panelLayout.active; const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard); if (!m_nativeLayoutInputBridge.HasBaseline()) { m_nativeLayoutInputBridge.Prime(panelSnapshot); } const auto panelFrameDelta = m_nativeLayoutInputBridge.Translate(panelSnapshot); ::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = {}; input.canvasRect = resolvedSession.canvasRect; input.pointerPosition = panelSnapshot.pointerPosition; input.pointerInside = panelSnapshot.pointerInside; input.pointerPressed = input.pointerInside && panelFrameDelta.pointer.pressed[0]; PopulateKeyboardNavigationInput( input, panelFrameDelta, panelLayout.active && ShouldCaptureKeyboardNavigation(resolvedSession, m_nativeLayoutRuntime.GetFrameResult())); const auto& frame = m_nativeLayoutRuntime.Update(input); if (previewConsumption.queueRuntimeFrame) { ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {}; previewFrame.drawData = &frame.drawData; previewFrame.canvasRect = resolvedSession.canvasRect; previewFrame.logicalSize = UI::UISize( resolvedSession.canvasRect.width, resolvedSession.canvasRect.height); previewFrame.debugName = panelState != nullptr ? panelState->previewDebugName.data() : nullptr; previewFrame.debugSource = panelState != nullptr ? panelState->previewDebugSource.data() : nullptr; m_hostedPreviewQueue.Submit(previewFrame); } else if (previewConsumption.appendRuntimeDrawDataToShell) { AppendDrawData(composedDrawData, frame.drawData); } m_nativeLayoutCanvasHost.EndCanvas(); NativePanelFrameSummary summary = {}; summary.layout = panelLayout; summary.lineA = m_nativeLayoutReloadSucceeded ? Application::ComposeNativeHostedPreviewStatusLine( previewConsumption, frame.stats.statusMessage) : "Layout lab reload failed; showing last retained runtime state."; summary.lineB = std::to_string(frame.stats.rowCount) + " rows | " + std::to_string(frame.stats.columnCount) + " cols | " + std::to_string(frame.stats.commandCount) + " cmds"; summary.overlay = extractCanvasOverlay(m_nativeLayoutCanvasHost); panelSummaries.push_back(std::move(summary)); m_nativeLayoutCanvasHost.ClearCanvasSession(); } ::XCEngine::UI::UIDrawList& chromeForeground = composedDrawData.EmplaceDrawList("XCUI.NativeShell.Foreground"); chromeForeground.AddText( UI::UIPoint(topBarRect.x + 18.0f, topBarRect.y + 14.0f), "XCUI Native Shell", textPrimary); chromeForeground.AddText( UI::UIPoint(topBarRect.x + 18.0f, topBarRect.y + 34.0f), "Default host path is now direct XCUI composition over the swapchain, with ImGui kept only as an explicit compatibility shell.", textSecondary); std::ostringstream footerStream = {}; footerStream << "Ctrl+1 Demo | Ctrl+2 Layout | Ctrl+Shift+B Backdrop " << "| Ctrl+Shift+H HUD | Active panel: " << (m_nativeActivePanel == ShellPanelId::XCUIDemo ? "XCUI Demo" : "XCUI Layout Lab"); chromeForeground.AddText( UI::UIPoint(footerRect.x + 14.0f, footerRect.y + 10.0f), footerStream.str(), textSecondary); for (const NativePanelFrameSummary& summary : panelSummaries) { chromeForeground.AddText( UI::UIPoint(summary.layout.panelRect.x + 16.0f, summary.layout.panelRect.y + 12.0f), summary.layout.title, textPrimary); chromeForeground.AddText( UI::UIPoint(summary.layout.panelRect.x + 16.0f, summary.layout.panelRect.y + 28.0f), summary.lineA, textSecondary); chromeForeground.AddText( UI::UIPoint(summary.layout.panelRect.x + 16.0f, summary.layout.panelRect.y + summary.layout.panelRect.height - 18.0f), summary.lineB, textMuted); } if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) { const UI::UIRect overlayRect( topBarRect.x + topBarRect.width - 282.0f, topBarRect.y + 10.0f, 266.0f, 38.0f); chromeForeground.AddFilledRect( overlayRect, UI::UIColor(18.0f / 255.0f, 72.0f / 255.0f, 112.0f / 255.0f, 196.0f / 255.0f), 10.0f); chromeForeground.AddRectOutline(overlayRect, panelAccentColor, 1.0f, 10.0f); chromeForeground.AddText( UI::UIPoint(overlayRect.x + 12.0f, overlayRect.y + 12.0f), "Overlay: native compositor + direct XCUI packet", textPrimary); } for (const NativePanelFrameSummary& summary : panelSummaries) { AppendDrawData(composedDrawData, summary.overlay); } return composedDrawData; } void Application::FrameNativeXCUIHost() { Application::BeginHostedPreviewFrameLifecycle( m_hostedPreviewQueue, m_hostedPreviewSurfaceRegistry); SyncHostedPreviewSurfaces(); auto* nativeCompositor = dynamic_cast<::XCEngine::Editor::XCUIBackend::NativeWindowUICompositor*>(m_windowCompositor.get()); if (nativeCompositor == nullptr) { m_xcuiInputSource.ClearFrameTransients(); return; } ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {}; options.timestampNanoseconds = MakeFrameTimestampNanoseconds(); options.windowFocused = m_xcuiInputSource.IsWindowFocused(); const auto shellSnapshot = m_xcuiInputSource.CaptureSnapshot(options); const auto shellFrameDelta = DispatchShellShortcuts(shellSnapshot); ::XCEngine::UI::UIDrawData nativeShellDrawData = BuildNativeShellDrawData(shellSnapshot, shellFrameDelta); nativeCompositor->SubmitRenderPacket(nativeShellDrawData, &m_hostedPreviewTextAtlasProvider); SyncHostedPreviewSurfaces(); m_windowCompositor->RenderFrame( kClearColor, {}, [this]( const ::XCEngine::Rendering::RenderContext& renderContext, const ::XCEngine::Rendering::RenderSurface& surface) { RenderQueuedHostedPreviews(renderContext, surface); MainWindowNativeBackdropRenderer::FrameState frameState = {}; frameState.elapsedSeconds = static_cast( std::chrono::duration(std::chrono::steady_clock::now() - m_startTime).count()); frameState.pulseAccent = IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent); frameState.drawBackdrop = IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop); m_nativeBackdropRenderer.Render(renderContext, surface, frameState); }); m_xcuiInputSource.ClearFrameTransients(); } void Application::RenderQueuedHostedPreviews( const ::XCEngine::Rendering::RenderContext& renderContext, const ::XCEngine::Rendering::RenderSurface& surface) { (void)surface; ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewDrainStats drainStats = {}; const auto& queuedFrames = m_hostedPreviewQueue.GetQueuedFrames(); drainStats.queuedFrameCount = queuedFrames.size(); for (const auto& queuedFrame : queuedFrames) { drainStats.queuedDrawListCount += queuedFrame.drawData.GetDrawListCount(); drainStats.queuedCommandCount += queuedFrame.drawData.GetTotalCommandCount(); } if (queuedFrames.empty()) { m_hostedPreviewQueue.SetLastDrainStats(drainStats); return; } m_hostedPreviewRenderBackend.SetTextAtlasProvider(&m_hostedPreviewTextAtlasProvider); std::size_t queuedFrameIndex = 0u; for (const auto& queuedFrame : queuedFrames) { m_hostedPreviewSurfaceRegistry.RecordQueuedFrame(queuedFrame, queuedFrameIndex); if (queuedFrame.debugName.empty()) { ++drainStats.skippedFrameCount; ++queuedFrameIndex; continue; } const std::uint32_t expectedWidth = queuedFrame.logicalSize.width > 1.0f ? static_cast(std::ceil(queuedFrame.logicalSize.width)) : 0u; const std::uint32_t expectedHeight = queuedFrame.logicalSize.height > 1.0f ? static_cast(std::ceil(queuedFrame.logicalSize.height)) : 0u; if (expectedWidth == 0u || expectedHeight == 0u) { ++drainStats.skippedFrameCount; ++queuedFrameIndex; continue; } HostedPreviewOffscreenSurface& previewSurface = FindOrAddHostedPreviewSurface(queuedFrame.debugName); if (!EnsureHostedPreviewSurface(previewSurface, expectedWidth, expectedHeight) || !RenderHostedPreviewOffscreenSurface(previewSurface, renderContext, queuedFrame.drawData)) { ++drainStats.skippedFrameCount; ++queuedFrameIndex; continue; } ++drainStats.renderedFrameCount; const auto& overlayStats = m_hostedPreviewRenderBackend.GetLastOverlayStats(); drainStats.renderedDrawListCount += overlayStats.drawListCount; drainStats.renderedCommandCount += overlayStats.renderedCommandCount; drainStats.skippedCommandCount += overlayStats.skippedCommandCount; m_hostedPreviewSurfaceRegistry.UpdateSurface( queuedFrame.debugName, previewSurface.textureRegistration.texture, ::XCEngine::UI::UIRect( 0.0f, 0.0f, static_cast(previewSurface.width), static_cast(previewSurface.height))); ++queuedFrameIndex; } m_hostedPreviewQueue.SetLastDrainStats(drainStats); } void Application::Frame() { if (!m_renderReady || !m_windowRenderer.BeginFrame()) { m_xcuiInputSource.ClearFrameTransients(); return; } if (m_windowCompositor == nullptr) { m_xcuiInputSource.ClearFrameTransients(); return; } if (IsNativeWindowHostEnabled()) { FrameNativeXCUIHost(); return; } FrameLegacyImGuiHost(); } } // namespace NewEditor } // namespace XCEngine