2026-04-05 04:55:25 +08:00
|
|
|
#include "Application.h"
|
2026-04-05 15:28:42 +08:00
|
|
|
#include "XCUIBackend/LegacyImGuiHostInterop.h"
|
2026-04-05 14:05:46 +08:00
|
|
|
#include "XCUIBackend/NativeWindowUICompositor.h"
|
2026-04-05 06:15:24 +08:00
|
|
|
|
2026-04-05 04:55:25 +08:00
|
|
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
2026-04-05 14:05:46 +08:00
|
|
|
#include <XCEngine/UI/DrawData.h>
|
2026-04-05 04:55:25 +08:00
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cmath>
|
2026-04-05 14:05:46 +08:00
|
|
|
#include <sstream>
|
2026-04-05 04:55:25 +08:00
|
|
|
|
|
|
|
|
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 };
|
2026-04-05 14:05:46 +08:00
|
|
|
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;
|
2026-04-05 04:55:25 +08:00
|
|
|
|
|
|
|
|
template <typename ResourceType>
|
|
|
|
|
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) {
|
2026-04-05 13:24:14 +08:00
|
|
|
return "native requested, hosted presenter bound";
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
if (nativePresenterBound) {
|
2026-04-05 13:24:14 +08:00
|
|
|
return "hosted presenter requested, native presenter still bound";
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
2026-04-05 13:24:14 +08:00
|
|
|
return "hosted presenter";
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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";
|
|
|
|
|
}
|
2026-04-05 14:05:46 +08:00
|
|
|
|
|
|
|
|
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::uint64_t>(
|
|
|
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
|
|
|
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<std::int32_t>& 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<std::int32_t>(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;
|
|
|
|
|
}
|
2026-04-05 04:55:25 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>
|
|
|
|
|
Application::CreateHostedPreviewPresenter(bool nativePreview) {
|
|
|
|
|
if (nativePreview) {
|
|
|
|
|
return ::XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter(
|
|
|
|
|
m_hostedPreviewQueue,
|
|
|
|
|
m_hostedPreviewSurfaceRegistry);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 15:28:42 +08:00
|
|
|
return ::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiHostedPreviewPresenter();
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
bool Application::IsNativeWindowHostEnabled() const {
|
|
|
|
|
return m_windowHostMode == WindowHostMode::NativeXCUI;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 15:28:42 +08:00
|
|
|
void Application::InitializePanelsForActiveWindowHost() {
|
|
|
|
|
if (IsNativeWindowHostEnabled()) {
|
|
|
|
|
InitializeNativeShell();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InitializeLegacyImGuiPanels();
|
|
|
|
|
ConfigureHostedPreviewPresenters();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::InitializeLegacyImGuiPanels() {
|
|
|
|
|
m_demoPanel = std::make_unique<XCUIDemoPanel>(
|
|
|
|
|
&m_xcuiInputSource,
|
|
|
|
|
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)),
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiPanelCanvasHost());
|
|
|
|
|
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
|
|
|
|
|
&m_xcuiInputSource,
|
|
|
|
|
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)),
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiPanelCanvasHost());
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 12:50:55 +08:00
|
|
|
const Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const {
|
2026-04-05 13:24:14 +08:00
|
|
|
return m_shellChromeState.TryGetPanelState(panelId);
|
2026-04-05 12:50:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Application::IsShellViewToggleEnabled(ShellViewToggleId toggleId) const {
|
2026-04-05 13:24:14 +08:00
|
|
|
return m_shellChromeState.GetViewToggle(toggleId);
|
2026-04-05 12:50:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled) {
|
2026-04-05 13:24:14 +08:00
|
|
|
m_shellChromeState.SetViewToggle(toggleId, enabled);
|
2026-04-05 12:50:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Application::IsNativeHostedPreviewEnabled(ShellPanelId panelId) const {
|
2026-04-05 13:24:14 +08:00
|
|
|
return m_shellChromeState.IsNativeHostedPreviewActive(panelId);
|
2026-04-05 12:50:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 04:55:25 +08:00
|
|
|
void Application::ConfigureHostedPreviewPresenters() {
|
2026-04-05 12:50:55 +08:00
|
|
|
const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
|
2026-04-05 04:55:25 +08:00
|
|
|
if (m_demoPanel != nullptr) {
|
2026-04-05 12:50:55 +08:00
|
|
|
m_demoPanel->SetVisible(demoState != nullptr && demoState->visible);
|
|
|
|
|
m_demoPanel->SetHostedPreviewEnabled(demoState == nullptr || demoState->hostedPreviewEnabled);
|
|
|
|
|
m_demoPanel->SetHostedPreviewPresenter(CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)));
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 12:50:55 +08:00
|
|
|
const ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
|
2026-04-05 04:55:25 +08:00
|
|
|
if (m_layoutLabPanel != nullptr) {
|
2026-04-05 12:50:55 +08:00
|
|
|
m_layoutLabPanel->SetVisible(layoutLabState != nullptr && layoutLabState->visible);
|
|
|
|
|
m_layoutLabPanel->SetHostedPreviewEnabled(layoutLabState == nullptr || layoutLabState->hostedPreviewEnabled);
|
|
|
|
|
m_layoutLabPanel->SetHostedPreviewPresenter(
|
|
|
|
|
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)));
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 12:50:55 +08:00
|
|
|
void Application::ConfigureShellCommandRouter() {
|
|
|
|
|
m_shellCommandRouter.Clear();
|
|
|
|
|
|
|
|
|
|
ShellCommandBindings bindings = {};
|
|
|
|
|
bindings.getXCUIDemoPanelVisible = [this]() {
|
2026-04-05 13:24:14 +08:00
|
|
|
return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo);
|
2026-04-05 12:50:55 +08:00
|
|
|
};
|
|
|
|
|
bindings.setXCUIDemoPanelVisible = [this](bool visible) {
|
2026-04-05 13:24:14 +08:00
|
|
|
m_shellChromeState.SetPanelVisible(ShellPanelId::XCUIDemo, visible);
|
2026-04-05 12:50:55 +08:00
|
|
|
if (m_demoPanel != nullptr) {
|
|
|
|
|
m_demoPanel->SetVisible(visible);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
bindings.getXCUILayoutLabPanelVisible = [this]() {
|
2026-04-05 13:24:14 +08:00
|
|
|
return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab);
|
2026-04-05 12:50:55 +08:00
|
|
|
};
|
|
|
|
|
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
|
2026-04-05 13:24:14 +08:00
|
|
|
m_shellChromeState.SetPanelVisible(ShellPanelId::XCUILayoutLab, visible);
|
2026-04-05 12:50:55 +08:00
|
|
|
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) {
|
2026-04-05 13:24:14 +08:00
|
|
|
m_shellChromeState.SetHostedPreviewMode(
|
|
|
|
|
ShellPanelId::XCUIDemo,
|
|
|
|
|
enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
|
2026-04-05 12:50:55 +08:00
|
|
|
};
|
|
|
|
|
bindings.getNativeLayoutLabPreviewEnabled = [this]() {
|
|
|
|
|
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
|
|
|
|
|
};
|
|
|
|
|
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
|
2026-04-05 13:24:14 +08:00
|
|
|
m_shellChromeState.SetHostedPreviewMode(
|
|
|
|
|
ShellPanelId::XCUILayoutLab,
|
|
|
|
|
enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
|
2026-04-05 12:50:55 +08:00
|
|
|
};
|
|
|
|
|
bindings.onHostedPreviewModeChanged = [this]() { ConfigureHostedPreviewPresenters(); };
|
|
|
|
|
|
|
|
|
|
Application::RegisterShellViewCommands(m_shellCommandRouter, bindings);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta
|
|
|
|
|
Application::DispatchShellShortcuts(
|
|
|
|
|
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& snapshot) {
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot shellSnapshot = snapshot;
|
|
|
|
|
if (!IsNativeWindowHostEnabled()) {
|
2026-04-05 15:28:42 +08:00
|
|
|
::XCEngine::Editor::XCUIBackend::ApplyLegacyImGuiHostInputCapture(shellSnapshot);
|
2026-04-05 14:05:46 +08:00
|
|
|
}
|
2026-04-05 12:50:55 +08:00
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
if (!m_shellInputBridge.HasBaseline()) {
|
|
|
|
|
m_shellInputBridge.Prime(shellSnapshot);
|
|
|
|
|
}
|
2026-04-05 12:50:55 +08:00
|
|
|
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta =
|
2026-04-05 14:05:46 +08:00
|
|
|
m_shellInputBridge.Translate(shellSnapshot);
|
2026-04-05 12:50:55 +08:00
|
|
|
const ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot commandSnapshot =
|
|
|
|
|
Application::BuildShellShortcutSnapshot(frameDelta);
|
|
|
|
|
m_shellCommandRouter.InvokeMatchingShortcut({ &commandSnapshot });
|
2026-04-05 14:05:46 +08:00
|
|
|
return frameDelta;
|
2026-04-05 12:50:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 04:55:25 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
InitializeWindowCompositor();
|
2026-04-05 15:28:42 +08:00
|
|
|
InitializePanelsForActiveWindowHost();
|
2026-04-05 12:50:55 +08:00
|
|
|
m_shellInputBridge.Reset();
|
|
|
|
|
ConfigureShellCommandRouter();
|
2026-04-05 04:55:25 +08:00
|
|
|
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();
|
2026-04-05 06:15:24 +08:00
|
|
|
ShutdownWindowCompositor();
|
2026-04-05 04:55:25 +08:00
|
|
|
ShutdownRenderer();
|
|
|
|
|
resourceManager.Shutdown();
|
|
|
|
|
return static_cast<int>(message.wParam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT CALLBACK Application::StaticWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
|
|
|
|
Application* app = reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
|
|
|
|
if (message == WM_NCCREATE) {
|
|
|
|
|
const CREATESTRUCTW* createStruct = reinterpret_cast<const CREATESTRUCTW*>(lParam);
|
|
|
|
|
app = reinterpret_cast<Application*>(createStruct->lpCreateParams);
|
|
|
|
|
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(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);
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor != nullptr &&
|
|
|
|
|
m_windowCompositor->HandleWindowMessage(hwnd, message, wParam, lParam)) {
|
2026-04-05 04:55:25 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (message) {
|
|
|
|
|
case WM_SIZE:
|
|
|
|
|
if (wParam != SIZE_MINIMIZED && m_renderReady) {
|
|
|
|
|
m_windowRenderer.Resize(static_cast<int>(LOWORD(lParam)), static_cast<int>(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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
void Application::InitializeWindowCompositor() {
|
2026-04-05 14:05:46 +08:00
|
|
|
m_windowCompositor = IsNativeWindowHostEnabled()
|
|
|
|
|
? ::XCEngine::Editor::XCUIBackend::CreateNativeWindowUICompositor()
|
2026-04-05 15:28:42 +08:00
|
|
|
: ::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiWindowUICompositor();
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor != nullptr) {
|
|
|
|
|
m_windowCompositor->Initialize(
|
|
|
|
|
m_hwnd,
|
|
|
|
|
m_windowRenderer,
|
2026-04-05 15:28:42 +08:00
|
|
|
[]() { (void)::XCEngine::Editor::XCUIBackend::ConfigureLegacyImGuiHostFonts(); });
|
2026-04-05 06:15:24 +08:00
|
|
|
}
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
void Application::ShutdownWindowCompositor() {
|
2026-04-05 04:55:25 +08:00
|
|
|
DestroyHostedPreviewSurfaces();
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor != nullptr) {
|
|
|
|
|
m_windowCompositor->Shutdown();
|
|
|
|
|
m_windowCompositor.reset();
|
|
|
|
|
}
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::ShutdownRenderer() {
|
|
|
|
|
m_renderReady = false;
|
|
|
|
|
m_hostedPreviewRenderBackend.Shutdown();
|
|
|
|
|
m_nativeBackdropRenderer.Shutdown();
|
|
|
|
|
m_windowRenderer.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::DestroyHostedPreviewSurfaces() {
|
|
|
|
|
for (HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) {
|
2026-04-05 14:36:02 +08:00
|
|
|
if (Application::HasHostedPreviewTextureRegistration(previewSurface.textureRegistration)) {
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor != nullptr) {
|
2026-04-05 06:41:32 +08:00
|
|
|
m_windowCompositor->FreeTextureDescriptor(previewSurface.textureRegistration);
|
2026-04-05 06:15:24 +08:00
|
|
|
}
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorView);
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorTexture);
|
|
|
|
|
previewSurface = {};
|
|
|
|
|
}
|
|
|
|
|
m_hostedPreviewSurfaces.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 12:50:55 +08:00
|
|
|
void Application::SyncShellChromePanelStateFromPanels() {
|
2026-04-05 13:24:14 +08:00
|
|
|
m_shellChromeState.SetPanelVisible(
|
|
|
|
|
ShellPanelId::XCUIDemo,
|
|
|
|
|
m_demoPanel != nullptr && m_demoPanel->IsVisible());
|
|
|
|
|
m_shellChromeState.SetPanelVisible(
|
|
|
|
|
ShellPanelId::XCUILayoutLab,
|
|
|
|
|
m_layoutLabPanel != nullptr && m_layoutLabPanel->IsVisible());
|
2026-04-05 12:50:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 04:55:25 +08:00
|
|
|
void Application::SyncHostedPreviewSurfaces() {
|
|
|
|
|
const auto isNativePreviewEnabled = [this](const std::string& debugName) {
|
2026-04-05 12:50:55 +08:00
|
|
|
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);
|
2026-04-05 04:55:25 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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::uint32_t>(std::ceil(logicalSize.width))
|
|
|
|
|
: 0u;
|
|
|
|
|
const std::uint32_t height = logicalSize.height > 1.0f
|
|
|
|
|
? static_cast<std::uint32_t>(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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:36:02 +08:00
|
|
|
if (Application::HasHostedPreviewTextureRegistration(previewSurface.textureRegistration)) {
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor != nullptr) {
|
2026-04-05 06:41:32 +08:00
|
|
|
m_windowCompositor->FreeTextureDescriptor(previewSurface.textureRegistration);
|
2026-04-05 06:15:24 +08:00
|
|
|
}
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorView);
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorTexture);
|
2026-04-05 06:41:32 +08:00
|
|
|
previewSurface.textureRegistration = {};
|
2026-04-05 04:55:25 +08:00
|
|
|
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<std::uint32_t>(::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
|
|
|
|
colorDesc.textureType = static_cast<std::uint32_t>(::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<std::uint32_t>(::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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor == nullptr ||
|
|
|
|
|
!m_windowCompositor->CreateTextureDescriptor(
|
2026-04-05 04:55:25 +08:00
|
|
|
m_windowRenderer.GetRHIDevice(),
|
|
|
|
|
previewSurface.colorTexture,
|
2026-04-05 06:41:32 +08:00
|
|
|
previewSurface.textureRegistration)) {
|
2026-04-05 04:55:25 +08:00
|
|
|
ShutdownAndDelete(previewSurface.colorView);
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorTexture);
|
2026-04-05 06:41:32 +08:00
|
|
|
previewSurface.textureRegistration = {};
|
2026-04-05 04:55:25 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
::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<float>(clientRect.right - clientRect.left);
|
|
|
|
|
const float windowHeight = static_cast<float>(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<NativeShellPanelLayout> 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<NativePanelFrameSummary> 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) {
|
2026-04-05 14:36:02 +08:00
|
|
|
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);
|
2026-04-05 15:28:42 +08:00
|
|
|
const Application::NativeHostedPreviewConsumption previewConsumption =
|
2026-04-05 14:36:02 +08:00
|
|
|
Application::ResolveNativeHostedPreviewConsumption(
|
|
|
|
|
nativeHostedPreview,
|
|
|
|
|
hasHostedSurfaceDescriptor,
|
|
|
|
|
showHostedSurfaceImage,
|
|
|
|
|
"Native XCUI preview pending",
|
|
|
|
|
"Waiting for queued native preview output to publish into the shell card.");
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
::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;
|
2026-04-05 14:36:02 +08:00
|
|
|
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();
|
2026-04-05 14:05:46 +08:00
|
|
|
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);
|
2026-04-05 14:36:02 +08:00
|
|
|
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);
|
|
|
|
|
}
|
2026-04-05 14:05:46 +08:00
|
|
|
|
|
|
|
|
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<unsigned long long>(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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:36:02 +08:00
|
|
|
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");
|
|
|
|
|
}
|
2026-04-05 14:05:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_nativeDemoCanvasHost.EndCanvas();
|
|
|
|
|
|
|
|
|
|
NativePanelFrameSummary summary = {};
|
|
|
|
|
summary.layout = panelLayout;
|
|
|
|
|
summary.lineA = m_nativeDemoReloadSucceeded
|
2026-04-05 14:36:02 +08:00
|
|
|
? Application::ComposeNativeHostedPreviewStatusLine(
|
|
|
|
|
previewConsumption,
|
|
|
|
|
frame.stats.statusMessage)
|
2026-04-05 14:05:46 +08:00
|
|
|
: "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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:36:02 +08:00
|
|
|
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);
|
2026-04-05 15:28:42 +08:00
|
|
|
const Application::NativeHostedPreviewConsumption previewConsumption =
|
2026-04-05 14:36:02 +08:00
|
|
|
Application::ResolveNativeHostedPreviewConsumption(
|
|
|
|
|
nativeHostedPreview,
|
|
|
|
|
hasHostedSurfaceDescriptor,
|
|
|
|
|
showHostedSurfaceImage,
|
|
|
|
|
"Native layout preview pending",
|
|
|
|
|
"Waiting for queued native preview output to publish into the layout card.");
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
::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;
|
2026-04-05 14:36:02 +08:00
|
|
|
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();
|
2026-04-05 14:05:46 +08:00
|
|
|
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);
|
2026-04-05 14:36:02 +08:00
|
|
|
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);
|
|
|
|
|
}
|
2026-04-05 14:05:46 +08:00
|
|
|
m_nativeLayoutCanvasHost.EndCanvas();
|
|
|
|
|
|
|
|
|
|
NativePanelFrameSummary summary = {};
|
|
|
|
|
summary.layout = panelLayout;
|
|
|
|
|
summary.lineA = m_nativeLayoutReloadSucceeded
|
2026-04-05 14:36:02 +08:00
|
|
|
? Application::ComposeNativeHostedPreviewStatusLine(
|
|
|
|
|
previewConsumption,
|
|
|
|
|
frame.stats.statusMessage)
|
2026-04-05 14:05:46 +08:00
|
|
|
: "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() {
|
2026-04-05 14:36:02 +08:00
|
|
|
Application::BeginHostedPreviewFrameLifecycle(
|
|
|
|
|
m_hostedPreviewQueue,
|
|
|
|
|
m_hostedPreviewSurfaceRegistry);
|
|
|
|
|
SyncHostedPreviewSurfaces();
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
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);
|
2026-04-05 14:36:02 +08:00
|
|
|
SyncHostedPreviewSurfaces();
|
2026-04-05 14:05:46 +08:00
|
|
|
|
|
|
|
|
m_windowCompositor->RenderFrame(
|
|
|
|
|
kClearColor,
|
|
|
|
|
{},
|
|
|
|
|
[this](
|
|
|
|
|
const ::XCEngine::Rendering::RenderContext& renderContext,
|
|
|
|
|
const ::XCEngine::Rendering::RenderSurface& surface) {
|
2026-04-05 14:36:02 +08:00
|
|
|
RenderQueuedHostedPreviews(renderContext, surface);
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
MainWindowNativeBackdropRenderer::FrameState frameState = {};
|
|
|
|
|
frameState.elapsedSeconds = static_cast<float>(
|
|
|
|
|
std::chrono::duration<double>(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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 04:55:25 +08:00
|
|
|
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::uint32_t>(std::ceil(queuedFrame.logicalSize.width))
|
|
|
|
|
: 0u;
|
|
|
|
|
const std::uint32_t expectedHeight = queuedFrame.logicalSize.height > 1.0f
|
|
|
|
|
? static_cast<std::uint32_t>(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,
|
2026-04-05 06:41:32 +08:00
|
|
|
previewSurface.textureRegistration.texture,
|
2026-04-05 04:55:25 +08:00
|
|
|
::XCEngine::UI::UIRect(
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
static_cast<float>(previewSurface.width),
|
|
|
|
|
static_cast<float>(previewSurface.height)));
|
|
|
|
|
++queuedFrameIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_hostedPreviewQueue.SetLastDrainStats(drainStats);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::Frame() {
|
|
|
|
|
if (!m_renderReady || !m_windowRenderer.BeginFrame()) {
|
|
|
|
|
m_xcuiInputSource.ClearFrameTransients();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor == nullptr) {
|
|
|
|
|
m_xcuiInputSource.ClearFrameTransients();
|
|
|
|
|
return;
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
if (IsNativeWindowHostEnabled()) {
|
|
|
|
|
FrameNativeXCUIHost();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-04-05 04:55:25 +08:00
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
FrameLegacyImGuiHost();
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace NewEditor
|
|
|
|
|
} // namespace XCEngine
|