Apply shared native shell layout in new editor

This commit is contained in:
2026-04-05 16:38:00 +08:00
parent cbd888e745
commit cda4a57756
2 changed files with 67 additions and 159 deletions

View File

@@ -128,6 +128,7 @@ Current gap:
- routes shell shortcuts through the same command router without reading ImGui capture state in the default host path
- drives both sandbox runtimes directly from Win32/XCUI input snapshots instead of routing through ImGui panel hosts
- composes one native `UIDrawData` packet and submits it through `NativeWindowUICompositor`
- The native shell layout policy now also lives behind `XCUINativeShellLayout`, so top-bar/footer/workspace geometry, panel split rules, and active-panel transfer on pointer press are no longer hard-coded inline inside `Application.cpp`.
- `NativeXCUIPanelCanvasHost` now backs that direct shell path as an externally driven canvas/session host for native cards instead of assuming an ImGui child-window model, and it now emits native `Image` draw commands for hosted surface-image previews while preserving per-surface UVs.
- `NativeWindowUICompositor` now creates and frees SRV-backed texture registrations for hosted preview surfaces, so native publication no longer depends on ImGui descriptor handles.
- `Application` now runs the hosted-preview lifecycle in both legacy and native frame paths, treats published textures as XCUI-owned `UITextureHandle` state, queues native preview frames from `BuildNativeShellDrawData(...)`, and drains them during native rendering before shell chrome overlays.
@@ -138,6 +139,7 @@ Current gap:
- The default shell host is now native, and the legacy ImGui shell/panel path has been split out of the default executable into the standalone `XCNewEditorImGuiCompat` compatibility slice.
- The default native shell path is now split away from direct `ImGui::*` calls at the main-target header/include level and no longer links the compatibility slice by default.
- The default native shell now also consumes shared `XCUINativeShellLayout` and `UIEditorPanelChrome` helpers for panel split/chrome policy instead of duplicating that card layout logic entirely inside `Application.cpp`.
- The native shell currently proves direct runtime composition, but its shell chrome is still a bespoke `Application`-side layout rather than a fully shared XCUI-authored editor shell document.
- Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, menu interaction widgets, and icon-atlas widgets are not yet extracted into reusable XCUI modules.
- The default native text path now uses a standalone Windows/GDI atlas through `XCUIStandaloneTextAtlasProvider`, but that provider still lives inside `new_editor` and is not yet promoted into a shared/cross-platform text subsystem.

View File

@@ -1,8 +1,10 @@
#include "Application.h"
#include "XCUIBackend/XCUINativeShellLayout.h"
#include "XCUIBackend/NativeWindowUICompositor.h"
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Widgets/UIEditorPanelChrome.h>
#include <algorithm>
#include <cmath>
@@ -20,14 +22,6 @@ 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) {
@@ -40,13 +34,6 @@ void ShutdownAndDelete(ResourceType*& resource) {
resource = nullptr;
}
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>(
@@ -102,37 +89,6 @@ void PopulateKeyboardNavigationInput(
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
bool Application::IsNativeWindowHostEnabled() const {
@@ -583,6 +539,15 @@ bool Application::RenderHostedPreviewOffscreenSurface(
::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 = {};
@@ -597,96 +562,42 @@ bool Application::RenderHostedPreviewOffscreenSurface(
}
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 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 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 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 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 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;
}
}
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");
@@ -702,8 +613,12 @@ bool Application::RenderHostedPreviewOffscreenSurface(
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::Widgets::AppendUIEditorPanelChromeBackground(
chromeBackground,
emptyStateRect,
{},
panelChromePalette,
panelChromeMetrics);
::XCEngine::UI::UIDrawList& emptyForeground =
composedDrawData.EmplaceDrawList("XCUI.NativeShell.EmptyState");
@@ -719,19 +634,12 @@ bool Application::RenderHostedPreviewOffscreenSurface(
}
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);
::XCEngine::UI::Widgets::AppendUIEditorPanelChromeBackground(
chromeBackground,
panelLayout.panelRect,
PanelChromeState{ panelLayout.active, panelLayout.hovered },
panelChromePalette,
panelChromeMetrics);
}
struct NativePanelFrameSummary {
@@ -1065,18 +973,16 @@ bool Application::RenderHostedPreviewOffscreenSurface(
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);
::XCEngine::UI::Widgets::AppendUIEditorPanelChromeForeground(
chromeForeground,
summary.layout.panelRect,
PanelChromeText{
summary.layout.title,
summary.lineA,
summary.lineB
},
panelChromePalette,
panelChromeMetrics);
}
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {