Default new_editor to native XCUI shell host

This commit is contained in:
2026-04-05 14:05:46 +08:00
parent e22d7d7f3d
commit 551eefbaa1
11 changed files with 1772 additions and 98 deletions

View File

@@ -2,13 +2,16 @@
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h"
#include "XCUIBackend/ImGuiWindowUICompositor.h"
#include "XCUIBackend/NativeWindowUICompositor.h"
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/UI/DrawData.h>
#include <imgui.h>
#include <algorithm>
#include <cmath>
#include <sstream>
namespace XCEngine {
namespace NewEditor {
@@ -18,6 +21,14 @@ constexpr wchar_t kWindowClassName[] = L"XCNewEditorWindowClass";
constexpr wchar_t kWindowTitle[] = L"XCNewEditor";
constexpr float kClearColor[4] = { 0.08f, 0.09f, 0.11f, 1.0f };
constexpr float kHostedPreviewClearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
constexpr float kNativeShellOuterMargin = 22.0f;
constexpr float kNativeShellInnerGap = 18.0f;
constexpr float kNativeShellHeaderHeight = 58.0f;
constexpr float kNativeShellFooterHeight = 34.0f;
constexpr float kNativePanelHeaderHeight = 42.0f;
constexpr float kNativePanelPadding = 14.0f;
constexpr float kNativePanelMinWidth = 260.0f;
constexpr float kNativePanelMinHeight = 180.0f;
template <typename ResourceType>
void ShutdownAndDelete(ResourceType*& resource) {
@@ -78,6 +89,100 @@ const char* GetHostedPreviewStateLabel(
}
return "idle";
}
bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) {
return point.x >= rect.x &&
point.y >= rect.y &&
point.x <= rect.x + rect.width &&
point.y <= rect.y + rect.height;
}
std::uint64_t MakeFrameTimestampNanoseconds() {
return static_cast<std::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;
}
} // namespace
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>
@@ -91,6 +196,22 @@ Application::CreateHostedPreviewPresenter(bool nativePreview) {
return ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter();
}
bool Application::IsNativeWindowHostEnabled() const {
return m_windowHostMode == WindowHostMode::NativeXCUI;
}
void Application::InitializeNativeShell() {
m_nativeActivePanel = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo)
? ShellPanelId::XCUIDemo
: ShellPanelId::XCUILayoutLab;
m_nativeDemoInputBridge.Reset();
m_nativeLayoutInputBridge.Reset();
m_nativeDemoCanvasHost.ClearCanvasSession();
m_nativeLayoutCanvasHost.ClearCanvasSession();
m_nativeDemoReloadSucceeded = m_nativeDemoRuntime.ReloadDocuments();
m_nativeLayoutReloadSucceeded = m_nativeLayoutRuntime.ReloadDocuments();
}
const Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const {
return m_shellChromeState.TryGetPanelState(panelId);
}
@@ -197,20 +318,25 @@ void Application::ConfigureShellCommandRouter() {
Application::RegisterShellViewCommands(m_shellCommandRouter, bindings);
}
void Application::DispatchShellShortcuts() {
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {};
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot =
m_xcuiInputSource.CaptureSnapshot(options);
ImGuiIO& io = ImGui::GetIO();
snapshot.wantCaptureKeyboard = io.WantCaptureKeyboard;
snapshot.wantTextInput = io.WantTextInput;
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta
Application::DispatchShellShortcuts(
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& snapshot) {
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot shellSnapshot = snapshot;
if (!IsNativeWindowHostEnabled()) {
ImGuiIO& io = ImGui::GetIO();
shellSnapshot.wantCaptureKeyboard = io.WantCaptureKeyboard;
shellSnapshot.wantTextInput = io.WantTextInput;
}
if (!m_shellInputBridge.HasBaseline()) {
m_shellInputBridge.Prime(shellSnapshot);
}
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta =
m_shellInputBridge.Translate(snapshot);
m_shellInputBridge.Translate(shellSnapshot);
const ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot commandSnapshot =
Application::BuildShellShortcutSnapshot(frameDelta);
m_shellCommandRouter.InvokeMatchingShortcut({ &commandSnapshot });
return frameDelta;
}
Application::HostedPreviewPanelDiagnostics Application::BuildHostedPreviewPanelDiagnostics(
@@ -293,15 +419,19 @@ int Application::Run(HINSTANCE instance, int nCmdShow) {
}
InitializeWindowCompositor();
m_demoPanel = std::make_unique<XCUIDemoPanel>(
&m_xcuiInputSource,
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)),
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
&m_xcuiInputSource,
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)),
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
ConfigureHostedPreviewPresenters();
if (IsNativeWindowHostEnabled()) {
InitializeNativeShell();
} else {
m_demoPanel = std::make_unique<XCUIDemoPanel>(
&m_xcuiInputSource,
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)),
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
&m_xcuiInputSource,
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)),
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
ConfigureHostedPreviewPresenters();
}
m_shellInputBridge.Reset();
ConfigureShellCommandRouter();
m_running = true;
@@ -433,7 +563,9 @@ bool Application::InitializeRenderer() {
}
void Application::InitializeWindowCompositor() {
m_windowCompositor = ::XCEngine::Editor::XCUIBackend::CreateImGuiWindowUICompositor();
m_windowCompositor = IsNativeWindowHostEnabled()
? ::XCEngine::Editor::XCUIBackend::CreateNativeWindowUICompositor()
: ::XCEngine::Editor::XCUIBackend::CreateImGuiWindowUICompositor();
if (m_windowCompositor != nullptr) {
m_windowCompositor->Initialize(
m_hwnd,
@@ -654,6 +786,525 @@ bool Application::RenderHostedPreviewOffscreenSurface(
return true;
}
::XCEngine::UI::UIDrawData Application::BuildNativeShellDrawData(
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& shellSnapshot,
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& shellFrameDelta) {
::XCEngine::UI::UIDrawData composedDrawData = {};
RECT clientRect = {};
if (!GetClientRect(m_hwnd, &clientRect)) {
return composedDrawData;
}
const float windowWidth = static_cast<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) {
::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;
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);
AppendDrawData(composedDrawData, frame.drawData);
if (IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud)) {
const auto& stats = frame.stats;
const UI::UIRect hudRect(
resolvedSession.canvasRect.x + 10.0f,
resolvedSession.canvasRect.y + 10.0f,
(std::min)(resolvedSession.canvasRect.width - 20.0f, 360.0f),
62.0f);
if (hudRect.width > 40.0f && hudRect.height > 20.0f) {
m_nativeDemoCanvasHost.DrawFilledRect(
hudRect,
UI::UIColor(12.0f / 255.0f, 18.0f / 255.0f, 26.0f / 255.0f, 214.0f / 255.0f),
10.0f);
m_nativeDemoCanvasHost.DrawOutlineRect(hudRect, panelBorderColor, 1.0f, 10.0f);
m_nativeDemoCanvasHost.DrawText(
UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 8.0f),
"Direct native XCUI frame",
textPrimary);
m_nativeDemoCanvasHost.DrawText(
UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 28.0f),
std::string("Tree ") + std::to_string(static_cast<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);
}
const auto drawDebugRect =
[this, &stats](const std::string& elementId, const UI::UIColor& color, const char* label) {
if (elementId.empty()) {
return;
}
UI::UIRect rect = {};
if (!m_nativeDemoRuntime.TryGetElementRect(elementId, rect)) {
return;
}
m_nativeDemoCanvasHost.DrawOutlineRect(rect, color, 2.0f, 6.0f);
if (label != nullptr && label[0] != '\0') {
m_nativeDemoCanvasHost.DrawText(UI::UIPoint(rect.x + 4.0f, rect.y + 4.0f), label, color);
}
};
drawDebugRect(stats.hoveredElementId, UI::UIColor(1.0f, 195.0f / 255.0f, 64.0f / 255.0f, 1.0f), "hover");
drawDebugRect(stats.focusedElementId, UI::UIColor(64.0f / 255.0f, 214.0f / 255.0f, 1.0f, 1.0f), "focus");
}
m_nativeDemoCanvasHost.EndCanvas();
NativePanelFrameSummary summary = {};
summary.layout = panelLayout;
summary.lineA = m_nativeDemoReloadSucceeded
? frame.stats.statusMessage
: "Document reload failed; showing last retained runtime state.";
summary.lineB =
std::string(panelLayout.active ? "Active" : "Passive") +
" | " + std::to_string(frame.stats.elementCount) +
" elements | " + std::to_string(frame.stats.commandCount) +
" cmds";
summary.overlay = extractCanvasOverlay(m_nativeDemoCanvasHost);
panelSummaries.push_back(std::move(summary));
m_nativeDemoCanvasHost.ClearCanvasSession();
continue;
}
::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;
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);
AppendDrawData(composedDrawData, frame.drawData);
m_nativeLayoutCanvasHost.EndCanvas();
NativePanelFrameSummary summary = {};
summary.layout = panelLayout;
summary.lineA = m_nativeLayoutReloadSucceeded
? frame.stats.statusMessage
: "Layout lab reload failed; showing last retained runtime state.";
summary.lineB =
std::to_string(frame.stats.rowCount) + " rows | " +
std::to_string(frame.stats.columnCount) + " cols | " +
std::to_string(frame.stats.commandCount) + " cmds";
summary.overlay = extractCanvasOverlay(m_nativeLayoutCanvasHost);
panelSummaries.push_back(std::move(summary));
m_nativeLayoutCanvasHost.ClearCanvasSession();
}
::XCEngine::UI::UIDrawList& chromeForeground =
composedDrawData.EmplaceDrawList("XCUI.NativeShell.Foreground");
chromeForeground.AddText(
UI::UIPoint(topBarRect.x + 18.0f, topBarRect.y + 14.0f),
"XCUI Native Shell",
textPrimary);
chromeForeground.AddText(
UI::UIPoint(topBarRect.x + 18.0f, topBarRect.y + 34.0f),
"Default host path is now direct XCUI composition over the swapchain, with ImGui kept only as an explicit compatibility shell.",
textSecondary);
std::ostringstream footerStream = {};
footerStream
<< "Ctrl+1 Demo | Ctrl+2 Layout | Ctrl+Shift+B Backdrop "
<< "| Ctrl+Shift+H HUD | Active panel: "
<< (m_nativeActivePanel == ShellPanelId::XCUIDemo ? "XCUI Demo" : "XCUI Layout Lab");
chromeForeground.AddText(
UI::UIPoint(footerRect.x + 14.0f, footerRect.y + 10.0f),
footerStream.str(),
textSecondary);
for (const NativePanelFrameSummary& summary : panelSummaries) {
chromeForeground.AddText(
UI::UIPoint(summary.layout.panelRect.x + 16.0f, summary.layout.panelRect.y + 12.0f),
summary.layout.title,
textPrimary);
chromeForeground.AddText(
UI::UIPoint(summary.layout.panelRect.x + 16.0f, summary.layout.panelRect.y + 28.0f),
summary.lineA,
textSecondary);
chromeForeground.AddText(
UI::UIPoint(summary.layout.panelRect.x + 16.0f, summary.layout.panelRect.y + summary.layout.panelRect.height - 18.0f),
summary.lineB,
textMuted);
}
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
const UI::UIRect overlayRect(
topBarRect.x + topBarRect.width - 282.0f,
topBarRect.y + 10.0f,
266.0f,
38.0f);
chromeForeground.AddFilledRect(
overlayRect,
UI::UIColor(18.0f / 255.0f, 72.0f / 255.0f, 112.0f / 255.0f, 196.0f / 255.0f),
10.0f);
chromeForeground.AddRectOutline(overlayRect, panelAccentColor, 1.0f, 10.0f);
chromeForeground.AddText(
UI::UIPoint(overlayRect.x + 12.0f, overlayRect.y + 12.0f),
"Overlay: native compositor + direct XCUI packet",
textPrimary);
}
for (const NativePanelFrameSummary& summary : panelSummaries) {
AppendDrawData(composedDrawData, summary.overlay);
}
return composedDrawData;
}
void Application::FrameLegacyImGuiHost() {
m_hostedPreviewQueue.BeginFrame();
m_hostedPreviewSurfaceRegistry.BeginFrame();
SyncHostedPreviewSurfaces();
if (m_windowCompositor == nullptr) {
m_xcuiInputSource.ClearFrameTransients();
return;
}
m_windowCompositor->RenderFrame(
kClearColor,
[this]() {
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {};
options.timestampNanoseconds = MakeFrameTimestampNanoseconds();
options.windowFocused = m_xcuiInputSource.IsWindowFocused();
const auto shellSnapshot = m_xcuiInputSource.CaptureSnapshot(options);
DispatchShellShortcuts(shellSnapshot);
RenderShellChrome();
if (m_demoPanel) {
m_demoPanel->RenderIfVisible();
}
if (m_layoutLabPanel) {
m_layoutLabPanel->RenderIfVisible();
}
bool showImGuiDemoWindow = IsShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow);
if (showImGuiDemoWindow) {
ImGui::ShowDemoWindow(&showImGuiDemoWindow);
SetShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow, showImGuiDemoWindow);
}
SyncShellChromePanelStateFromPanels();
SyncHostedPreviewSurfaces();
},
[this](
const ::XCEngine::Rendering::RenderContext& renderContext,
const ::XCEngine::Rendering::RenderSurface& surface) {
RenderQueuedHostedPreviews(renderContext, surface);
if (!IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop) &&
!IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
return;
}
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);
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
const float width = static_cast<float>(surface.GetWidth());
const float height = static_cast<float>(surface.GetHeight());
const float horizontalMargin = (std::min)(width * 0.14f, 128.0f);
const float topMargin = (std::min)(height * 0.15f, 132.0f);
const float bottomMargin = (std::min)(height * 0.12f, 96.0f);
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState overlayInput = {};
overlayInput.canvasRect = ::XCEngine::UI::UIRect(
horizontalMargin,
topMargin,
(std::max)(0.0f, width - horizontalMargin * 2.0f),
(std::max)(0.0f, height - topMargin - bottomMargin));
overlayInput.pointerPosition = m_xcuiInputSource.GetPointerPosition();
overlayInput.pointerInside =
overlayInput.pointerPosition.x >= overlayInput.canvasRect.x &&
overlayInput.pointerPosition.y >= overlayInput.canvasRect.y &&
overlayInput.pointerPosition.x <= overlayInput.canvasRect.x + overlayInput.canvasRect.width &&
overlayInput.pointerPosition.y <= overlayInput.canvasRect.y + overlayInput.canvasRect.height;
const auto& overlayFrame = m_nativeOverlayRuntime.Update(overlayInput);
frameState.overlayDrawData = &overlayFrame.drawData;
}
m_nativeBackdropRenderer.Render(renderContext, surface, frameState);
});
m_xcuiInputSource.ClearFrameTransients();
}
void Application::FrameNativeXCUIHost() {
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);
m_windowCompositor->RenderFrame(
kClearColor,
{},
[this](
const ::XCEngine::Rendering::RenderContext& renderContext,
const ::XCEngine::Rendering::RenderSurface& 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();
}
void Application::RenderShellChrome() {
SyncShellChromePanelStateFromPanels();
@@ -1023,75 +1674,17 @@ void Application::Frame() {
return;
}
m_hostedPreviewQueue.BeginFrame();
m_hostedPreviewSurfaceRegistry.BeginFrame();
SyncHostedPreviewSurfaces();
if (m_windowCompositor == nullptr) {
m_xcuiInputSource.ClearFrameTransients();
return;
}
m_windowCompositor->RenderFrame(
kClearColor,
[this]() {
DispatchShellShortcuts();
RenderShellChrome();
if (m_demoPanel) {
m_demoPanel->RenderIfVisible();
}
if (m_layoutLabPanel) {
m_layoutLabPanel->RenderIfVisible();
}
bool showImGuiDemoWindow = IsShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow);
if (showImGuiDemoWindow) {
ImGui::ShowDemoWindow(&showImGuiDemoWindow);
SetShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow, showImGuiDemoWindow);
}
if (IsNativeWindowHostEnabled()) {
FrameNativeXCUIHost();
return;
}
SyncShellChromePanelStateFromPanels();
SyncHostedPreviewSurfaces();
},
[this](
const ::XCEngine::Rendering::RenderContext& renderContext,
const ::XCEngine::Rendering::RenderSurface& surface) {
RenderQueuedHostedPreviews(renderContext, surface);
if (!IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop) &&
!IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
return;
}
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);
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
const float width = static_cast<float>(surface.GetWidth());
const float height = static_cast<float>(surface.GetHeight());
const float horizontalMargin = (std::min)(width * 0.14f, 128.0f);
const float topMargin = (std::min)(height * 0.15f, 132.0f);
const float bottomMargin = (std::min)(height * 0.12f, 96.0f);
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState overlayInput = {};
overlayInput.canvasRect = ::XCEngine::UI::UIRect(
horizontalMargin,
topMargin,
(std::max)(0.0f, width - horizontalMargin * 2.0f),
(std::max)(0.0f, height - topMargin - bottomMargin));
overlayInput.pointerPosition = m_xcuiInputSource.GetPointerPosition();
overlayInput.pointerInside =
overlayInput.pointerPosition.x >= overlayInput.canvasRect.x &&
overlayInput.pointerPosition.y >= overlayInput.canvasRect.y &&
overlayInput.pointerPosition.x <= overlayInput.canvasRect.x + overlayInput.canvasRect.width &&
overlayInput.pointerPosition.y <= overlayInput.canvasRect.y + overlayInput.canvasRect.height;
const auto& overlayFrame = m_nativeOverlayRuntime.Update(overlayInput);
frameState.overlayDrawData = &overlayFrame.drawData;
}
m_nativeBackdropRenderer.Render(renderContext, surface, frameState);
});
m_xcuiInputSource.ClearFrameTransients();
FrameLegacyImGuiHost();
}
} // namespace NewEditor