Files
XCEngine/new_editor/src/Application.cpp

1127 lines
46 KiB
C++
Raw Normal View History

2026-04-05 04:55:25 +08:00
#include "Application.h"
#include "XCUIBackend/XCUINativeShellLayout.h"
#include "XCUIBackend/NativeWindowUICompositor.h"
#include "XCUIBackend/XCUIPanelCanvasHost.h"
#include "panels/XCUIDemoPanel.h"
#include "panels/XCUILayoutLabPanel.h"
2026-04-05 06:15:24 +08:00
2026-04-05 04:55:25 +08:00
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Widgets/UIEditorPanelChrome.h>
2026-04-05 04:55:25 +08:00
#include <algorithm>
#include <cmath>
#include <sstream>
2026-04-05 04:55:25 +08:00
#ifdef DrawText
#undef DrawText
#endif
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 };
template <typename ResourceType>
void ShutdownAndDelete(ResourceType*& resource) {
if (resource == nullptr) {
return;
}
resource->Shutdown();
delete resource;
resource = nullptr;
}
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());
}
class ForwardingNativePanelCanvasHost final : public ::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost {
public:
explicit ForwardingNativePanelCanvasHost(::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& host)
: m_host(host) {}
const char* GetDebugName() const override {
return m_host.GetDebugName();
}
auto BeginCanvas(
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest& request)
-> decltype(std::declval<::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost&>().BeginCanvas(request))
override {
return m_host.BeginCanvas(request);
}
void DrawFilledRect(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIColor& color,
float rounding) override {
m_host.DrawFilledRect(rect, color, rounding);
}
void DrawOutlineRect(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIColor& color,
float thickness,
float rounding) override {
m_host.DrawOutlineRect(rect, color, thickness, rounding);
}
void DrawText(
const ::XCEngine::UI::UIPoint& position,
std::string_view text,
const ::XCEngine::UI::UIColor& color,
float fontSize) override {
m_host.DrawText(position, text, color, fontSize);
}
void EndCanvas() override {
m_host.EndCanvas();
}
bool TryGetLatestFrameSnapshot(
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasFrameSnapshot& outSnapshot) const override {
return m_host.TryGetLatestFrameSnapshot(outSnapshot);
}
private:
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& m_host;
};
void AppendDrawData(::XCEngine::UI::UIDrawData& destination, const ::XCEngine::UI::UIDrawData& source) {
for (const ::XCEngine::UI::UIDrawList& drawList : source.GetDrawLists()) {
destination.AddDrawList(drawList);
}
}
2026-04-05 04:55:25 +08:00
} // namespace
bool Application::IsNativeWindowHostEnabled() const {
return m_windowHostMode == WindowHostMode::NativeXCUI;
}
void Application::InitializeNativeShell() {
m_nativeActivePanel = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo)
? ShellPanelId::XCUIDemo
: ShellPanelId::XCUILayoutLab;
m_nativeDemoCanvasHost.ClearCanvasSession();
m_nativeLayoutCanvasHost.ClearCanvasSession();
const auto buildNativePreviewPresenter =
[this](ShellPanelId panelId, bool hostedPreviewEnabled) {
if (!hostedPreviewEnabled || !IsNativeHostedPreviewEnabled(panelId)) {
return std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>();
}
return CreateHostedPreviewPresenter(true);
};
const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
const bool demoHostedPreviewEnabled = demoState == nullptr || demoState->hostedPreviewEnabled;
m_demoPanel = std::make_unique<XCUIDemoPanel>(
&m_xcuiInputSource,
buildNativePreviewPresenter(ShellPanelId::XCUIDemo, demoHostedPreviewEnabled),
std::make_unique<ForwardingNativePanelCanvasHost>(m_nativeDemoCanvasHost));
m_demoPanel->SetVisible(demoState != nullptr && demoState->visible);
m_demoPanel->SetHostedPreviewEnabled(demoHostedPreviewEnabled);
const ShellPanelChromeState* layoutState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
const bool layoutHostedPreviewEnabled = layoutState == nullptr || layoutState->hostedPreviewEnabled;
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
&m_xcuiInputSource,
buildNativePreviewPresenter(ShellPanelId::XCUILayoutLab, layoutHostedPreviewEnabled),
std::make_unique<ForwardingNativePanelCanvasHost>(m_nativeLayoutCanvasHost));
m_layoutLabPanel->SetVisible(layoutState != nullptr && layoutState->visible);
m_layoutLabPanel->SetHostedPreviewEnabled(layoutHostedPreviewEnabled);
}
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);
}
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();
InitializePanelsForActiveWindowHost();
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();
}
ResetCompatibilityHostPanels();
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::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) {
if (Application::HasHostedPreviewTextureRegistration(previewSurface.textureRegistration)) {
2026-04-05 06:15:24 +08:00
if (m_windowCompositor != nullptr) {
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();
}
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);
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;
}
if (Application::HasHostedPreviewTextureRegistration(previewSurface.textureRegistration)) {
2026-04-05 06:15:24 +08:00
if (m_windowCompositor != nullptr) {
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.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,
previewSurface.textureRegistration)) {
2026-04-05 04:55:25 +08:00
ShutdownAndDelete(previewSurface.colorView);
ShutdownAndDelete(previewSurface.colorTexture);
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;
}
::XCEngine::UI::UIDrawData Application::BuildNativeShellDrawData(
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& shellSnapshot,
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& shellFrameDelta) {
using NativeShellLayout = ::XCEngine::Editor::XCUIBackend::XCUINativeShellLayoutResult;
using NativeShellMetrics = ::XCEngine::Editor::XCUIBackend::XCUINativeShellMetrics;
using NativeShellPanelLayout = ::XCEngine::Editor::XCUIBackend::XCUINativeShellPanelLayout;
using NativeShellPanelSpec = ::XCEngine::Editor::XCUIBackend::XCUINativeShellPanelSpec;
using PanelChromeMetrics = ::XCEngine::UI::Widgets::UIEditorPanelChromeMetrics;
using PanelChromePalette = ::XCEngine::UI::Widgets::UIEditorPanelChromePalette;
using PanelChromeState = ::XCEngine::UI::Widgets::UIEditorPanelChromeState;
using PanelChromeText = ::XCEngine::UI::Widgets::UIEditorPanelChromeText;
::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 NativeShellMetrics nativeShellMetrics = {};
const PanelChromeMetrics panelChromeMetrics = {};
const PanelChromePalette panelChromePalette = {};
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& panelBorderColor = panelChromePalette.borderColor;
const UI::UIColor& panelAccentColor = panelChromePalette.accentColor;
const UI::UIColor& textPrimary = panelChromePalette.textPrimary;
const UI::UIColor& textSecondary = panelChromePalette.textSecondary;
const UI::UIColor& textMuted = panelChromePalette.textMuted;
const NativeShellLayout shellLayout = ::XCEngine::Editor::XCUIBackend::BuildXCUINativeShellLayout(
shellRect,
{
NativeShellPanelSpec{
ShellPanelId::XCUIDemo,
"XCUI Demo",
m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo),
},
NativeShellPanelSpec{
ShellPanelId::XCUILayoutLab,
"XCUI Layout Lab",
m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab),
},
},
m_nativeActivePanel,
shellSnapshot.pointerPosition,
shellSnapshot.windowFocused,
shellFrameDelta.pointer.pressed[0],
nativeShellMetrics);
m_nativeActivePanel = shellLayout.activePanel;
const UI::UIRect& topBarRect = shellLayout.topBarRect;
const UI::UIRect& footerRect = shellLayout.footerRect;
const UI::UIRect& workspaceRect = shellLayout.workspaceRect;
const std::vector<NativeShellPanelLayout>& panelLayouts = shellLayout.panelLayouts;
::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));
::XCEngine::UI::Widgets::AppendUIEditorPanelChromeBackground(
chromeBackground,
emptyStateRect,
{},
panelChromePalette,
panelChromeMetrics);
::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) {
::XCEngine::UI::Widgets::AppendUIEditorPanelChromeBackground(
chromeBackground,
panelLayout.panelRect,
PanelChromeState{ panelLayout.active, panelLayout.hovered },
panelChromePalette,
panelChromeMetrics);
}
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) {
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
const bool nativeHostedPreviewRequested =
panelState != nullptr &&
panelState->hostedPreviewEnabled &&
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo);
::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);
const bool wantsKeyboard = panelLayout.active;
const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard);
const bool showPanelHud = IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud);
const XCUIDemoPanelFrameComposition* composition = nullptr;
if (m_demoPanel != nullptr) {
composition = &m_demoPanel->ComposeFrame(
XCUIDemoPanelFrameComposeOptions{
panelLayout.panelRect.height,
panelLayout.canvasRect.y - panelLayout.panelRect.y,
panelState == nullptr || panelState->hostedPreviewEnabled,
showPanelHud,
showPanelHud,
panelSnapshot.timestampNanoseconds,
"XCUIDemo.NativeCanvas",
&panelSnapshot,
false,
nativeHostedPreviewRequested ? "Native XCUI preview pending" : nullptr,
nativeHostedPreviewRequested
? "Waiting for queued native preview output to publish into the shell card."
: nullptr,
false,
nullptr,
nullptr,
});
}
const Application::NativeHostedPreviewConsumption previewConsumption =
composition != nullptr
? Application::ResolveNativeHostedPreviewConsumption(
composition->nativeHostedPreview,
composition->hasHostedSurfaceDescriptor,
composition->showHostedSurfaceImage,
"Native XCUI preview pending",
"Waiting for queued native preview output to publish into the shell card.")
: Application::NativeHostedPreviewConsumption{};
if (composition != nullptr &&
composition->frame != nullptr &&
previewConsumption.appendRuntimeDrawDataToShell) {
AppendDrawData(composedDrawData, composition->frame->drawData);
}
if (showPanelHud && composition != nullptr && composition->frame != nullptr) {
const auto& stats = composition->frame->stats;
const UI::UIRect hudRect(
composition->canvasSession.canvasRect.x + 10.0f,
composition->canvasSession.canvasRect.y + 10.0f,
(std::min)(composition->canvasSession.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);
}
}
NativePanelFrameSummary summary = {};
summary.layout = panelLayout;
summary.lineA =
m_demoPanel != nullptr && m_demoPanel->GetLastReloadSucceeded()
? Application::ComposeNativeHostedPreviewStatusLine(
previewConsumption,
composition != nullptr && composition->frame != nullptr
? composition->frame->stats.statusMessage
: std::string_view("XCUI demo frame unavailable"))
: "Document reload failed; showing last retained runtime state.";
summary.lineB =
std::string(panelLayout.active ? "Active" : "Passive") +
" | " + std::to_string(
composition != nullptr && composition->frame != nullptr
? composition->frame->stats.elementCount
: 0u) +
" elements | " + std::to_string(
composition != nullptr && composition->frame != nullptr
? composition->frame->stats.commandCount
: 0u) +
" cmds";
summary.overlay = extractCanvasOverlay(m_nativeDemoCanvasHost);
panelSummaries.push_back(std::move(summary));
m_nativeDemoCanvasHost.ClearCanvasSession();
continue;
}
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
const bool nativeHostedPreviewRequested =
panelState != nullptr &&
panelState->hostedPreviewEnabled &&
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
const bool hasHostedSurfaceDescriptor =
nativeHostedPreviewRequested &&
panelState != nullptr &&
!panelState->previewDebugName.empty() &&
m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor(
panelState->previewDebugName.data(),
hostedSurfaceDescriptor);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
const bool showHostedSurfaceImage =
nativeHostedPreviewRequested &&
panelState != nullptr &&
!panelState->previewDebugName.empty() &&
m_hostedPreviewSurfaceRegistry.TryGetSurfaceImage(
panelState->previewDebugName.data(),
hostedSurfaceImage);
const Application::NativeHostedPreviewConsumption previewConsumption =
Application::ResolveNativeHostedPreviewConsumption(
nativeHostedPreviewRequested,
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);
const XCUILayoutLabFrameComposition* composition = nullptr;
if (m_layoutLabPanel != nullptr) {
composition = &m_layoutLabPanel->ComposeFrame(
XCUILayoutLabFrameCompositionRequest{
resolvedSession,
panelSnapshot });
}
m_nativeLayoutCanvasHost.EndCanvas();
const Application::NativeHostedPreviewConsumption composedPreviewConsumption =
composition != nullptr
? Application::ResolveNativeHostedPreviewConsumption(
composition->nativeHostedPreview,
composition->hasHostedSurfaceDescriptor,
composition->showHostedSurfaceImage,
"Native layout preview pending",
"Waiting for queued native preview output to publish into the layout card.")
: previewConsumption;
if (composition != nullptr &&
composition->frameResult != nullptr &&
composedPreviewConsumption.appendRuntimeDrawDataToShell) {
AppendDrawData(composedDrawData, composition->frameResult->drawData);
}
NativePanelFrameSummary summary = {};
summary.layout = panelLayout;
summary.lineA =
m_layoutLabPanel != nullptr && m_layoutLabPanel->GetLastReloadSucceeded()
? Application::ComposeNativeHostedPreviewStatusLine(
composedPreviewConsumption,
composition != nullptr && composition->frameResult != nullptr
? composition->frameResult->stats.statusMessage
: std::string_view("XCUI layout frame unavailable"))
: "Layout lab reload failed; showing last retained runtime state.";
summary.lineB =
std::to_string(
composition != nullptr && composition->frameResult != nullptr
? composition->frameResult->stats.rowCount
: 0u) + " rows | " +
std::to_string(
composition != nullptr && composition->frameResult != nullptr
? composition->frameResult->stats.columnCount
: 0u) + " cols | " +
std::to_string(
composition != nullptr && composition->frameResult != nullptr
? composition->frameResult->stats.commandCount
: 0u) + " 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) {
::XCEngine::UI::Widgets::AppendUIEditorPanelChromeForeground(
chromeForeground,
summary.layout.panelRect,
PanelChromeText{
summary.layout.title,
summary.lineA,
summary.lineB
},
panelChromePalette,
panelChromeMetrics);
}
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<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,
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
}
if (IsNativeWindowHostEnabled()) {
FrameNativeXCUIHost();
return;
}
2026-04-05 04:55:25 +08:00
FrameCompatibilityHost();
2026-04-05 04:55:25 +08:00
}
} // namespace NewEditor
} // namespace XCEngine