Integrate XCUI shell state and runtime frame seams

This commit is contained in:
2026-04-05 12:50:55 +08:00
parent ec97445071
commit e5e9f348a3
29 changed files with 3183 additions and 102 deletions

View File

@@ -1,5 +1,4 @@
#include "Application.h"
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h"
#include "XCUIBackend/ImGuiWindowUICompositor.h"
@@ -91,16 +90,188 @@ Application::CreateHostedPreviewPresenter(bool nativePreview) {
return ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter();
}
void Application::ConfigureHostedPreviewPresenters() {
if (m_demoPanel != nullptr) {
m_demoPanel->SetHostedPreviewEnabled(true);
m_demoPanel->SetHostedPreviewPresenter(CreateHostedPreviewPresenter(m_showNativeDemoPanelPreview));
Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) {
const std::size_t index = GetShellPanelIndex(panelId);
if (index >= m_shellPanels.size()) {
return nullptr;
}
if (m_layoutLabPanel != nullptr) {
m_layoutLabPanel->SetHostedPreviewEnabled(true);
m_layoutLabPanel->SetHostedPreviewPresenter(CreateHostedPreviewPresenter(m_showNativeLayoutLabPreview));
return &m_shellPanels[index];
}
const Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const {
const std::size_t index = GetShellPanelIndex(panelId);
if (index >= m_shellPanels.size()) {
return nullptr;
}
return &m_shellPanels[index];
}
bool Application::IsShellViewToggleEnabled(ShellViewToggleId toggleId) const {
switch (toggleId) {
case ShellViewToggleId::ImGuiDemoWindow:
return m_shellViewToggles.imguiDemoWindowVisible;
case ShellViewToggleId::NativeBackdrop:
return m_shellViewToggles.nativeBackdropVisible;
case ShellViewToggleId::PulseAccent:
return m_shellViewToggles.pulseAccentEnabled;
case ShellViewToggleId::NativeXCUIOverlay:
return m_shellViewToggles.nativeXCUIOverlayVisible;
case ShellViewToggleId::HostedPreviewHud:
return m_shellViewToggles.hostedPreviewHudVisible;
case ShellViewToggleId::Count:
default:
return false;
}
}
void Application::SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled) {
switch (toggleId) {
case ShellViewToggleId::ImGuiDemoWindow:
m_shellViewToggles.imguiDemoWindowVisible = enabled;
return;
case ShellViewToggleId::NativeBackdrop:
m_shellViewToggles.nativeBackdropVisible = enabled;
return;
case ShellViewToggleId::PulseAccent:
m_shellViewToggles.pulseAccentEnabled = enabled;
return;
case ShellViewToggleId::NativeXCUIOverlay:
m_shellViewToggles.nativeXCUIOverlayVisible = enabled;
return;
case ShellViewToggleId::HostedPreviewHud:
m_shellViewToggles.hostedPreviewHudVisible = enabled;
return;
case ShellViewToggleId::Count:
default:
return;
}
}
bool Application::IsNativeHostedPreviewEnabled(ShellPanelId panelId) const {
const ShellPanelChromeState* panelState = TryGetShellPanelState(panelId);
return panelState != nullptr &&
panelState->hostedPreviewEnabled &&
panelState->previewMode == ShellHostedPreviewMode::NativeOffscreen;
}
void Application::ConfigureHostedPreviewPresenters() {
const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
if (m_demoPanel != nullptr) {
m_demoPanel->SetVisible(demoState != nullptr && demoState->visible);
m_demoPanel->SetHostedPreviewEnabled(demoState == nullptr || demoState->hostedPreviewEnabled);
m_demoPanel->SetHostedPreviewPresenter(CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)));
}
const ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
if (m_layoutLabPanel != nullptr) {
m_layoutLabPanel->SetVisible(layoutLabState != nullptr && layoutLabState->visible);
m_layoutLabPanel->SetHostedPreviewEnabled(layoutLabState == nullptr || layoutLabState->hostedPreviewEnabled);
m_layoutLabPanel->SetHostedPreviewPresenter(
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)));
}
}
void Application::ConfigureShellCommandRouter() {
m_shellCommandRouter.Clear();
ShellCommandBindings bindings = {};
bindings.getXCUIDemoPanelVisible = [this]() {
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
return panelState != nullptr && panelState->visible;
};
bindings.setXCUIDemoPanelVisible = [this](bool visible) {
if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo)) {
panelState->visible = visible;
}
if (m_demoPanel != nullptr) {
m_demoPanel->SetVisible(visible);
}
};
bindings.getXCUILayoutLabPanelVisible = [this]() {
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
return panelState != nullptr && panelState->visible;
};
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab)) {
panelState->visible = visible;
}
if (m_layoutLabPanel != nullptr) {
m_layoutLabPanel->SetVisible(visible);
}
};
bindings.getImGuiDemoWindowVisible = [this]() {
return IsShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow);
};
bindings.setImGuiDemoWindowVisible = [this](bool visible) {
SetShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow, visible);
};
bindings.getNativeBackdropVisible = [this]() {
return IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop);
};
bindings.setNativeBackdropVisible = [this](bool visible) {
SetShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop, visible);
};
bindings.getPulseAccentEnabled = [this]() {
return IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent);
};
bindings.setPulseAccentEnabled = [this](bool enabled) {
SetShellViewToggleEnabled(ShellViewToggleId::PulseAccent, enabled);
};
bindings.getNativeXCUIOverlayVisible = [this]() {
return IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay);
};
bindings.setNativeXCUIOverlayVisible = [this](bool visible) {
SetShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay, visible);
};
bindings.getHostedPreviewHudVisible = [this]() {
return IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud);
};
bindings.setHostedPreviewHudVisible = [this](bool visible) {
SetShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud, visible);
};
bindings.getNativeDemoPanelPreviewEnabled = [this]() {
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo);
};
bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) {
if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo)) {
panelState->previewMode =
enabled
? ShellHostedPreviewMode::NativeOffscreen
: ShellHostedPreviewMode::LegacyImGui;
}
};
bindings.getNativeLayoutLabPreviewEnabled = [this]() {
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
};
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
if (ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab)) {
panelState->previewMode =
enabled
? ShellHostedPreviewMode::NativeOffscreen
: ShellHostedPreviewMode::LegacyImGui;
}
};
bindings.onHostedPreviewModeChanged = [this]() { ConfigureHostedPreviewPresenters(); };
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;
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta =
m_shellInputBridge.Translate(snapshot);
const ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot commandSnapshot =
Application::BuildShellShortcutSnapshot(frameDelta);
m_shellCommandRouter.InvokeMatchingShortcut({ &commandSnapshot });
}
Application::HostedPreviewPanelDiagnostics Application::BuildHostedPreviewPanelDiagnostics(
@@ -185,10 +356,13 @@ int Application::Run(HINSTANCE instance, int nCmdShow) {
InitializeWindowCompositor();
m_demoPanel = std::make_unique<XCUIDemoPanel>(
&m_xcuiInputSource,
CreateHostedPreviewPresenter(m_showNativeDemoPanelPreview));
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)));
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
&m_xcuiInputSource,
CreateHostedPreviewPresenter(m_showNativeLayoutLabPreview));
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)));
ConfigureHostedPreviewPresenters();
m_shellInputBridge.Reset();
ConfigureShellCommandRouter();
m_running = true;
m_renderReady = true;
@@ -356,11 +530,29 @@ void Application::DestroyHostedPreviewSurfaces() {
m_hostedPreviewSurfaces.clear();
}
void Application::SyncShellChromePanelStateFromPanels() {
if (ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo)) {
demoState->visible = m_demoPanel != nullptr && m_demoPanel->IsVisible();
}
if (ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab)) {
layoutLabState->visible = m_layoutLabPanel != nullptr && m_layoutLabPanel->IsVisible();
}
}
void Application::SyncHostedPreviewSurfaces() {
const auto isNativePreviewEnabled = [this](const std::string& debugName) {
return
(debugName == "XCUI Demo" && m_showNativeDemoPanelPreview) ||
(debugName == "XCUI Layout Lab" && m_showNativeLayoutLabPreview);
const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
if (demoState != nullptr &&
debugName == demoState->previewDebugName &&
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)) {
return true;
}
const ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
return layoutLabState != nullptr &&
debugName == layoutLabState->previewDebugName &&
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
};
const auto syncSurfaceForNameAndSize =
@@ -523,6 +715,8 @@ bool Application::RenderHostedPreviewOffscreenSurface(
}
void Application::RenderShellChrome() {
SyncShellChromePanelStateFromPanels();
ImGuiViewport* viewport = ImGui::GetMainViewport();
if (viewport == nullptr) {
return;
@@ -551,38 +745,62 @@ void Application::RenderShellChrome() {
if (opened) {
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("View")) {
const bool demoVisible = m_demoPanel != nullptr ? m_demoPanel->IsVisible() : false;
bool demoToggle = demoVisible;
if (ImGui::MenuItem("XCUI Demo", nullptr, &demoToggle) && m_demoPanel != nullptr) {
m_demoPanel->SetVisible(demoToggle);
}
const auto drawCommandMenuItem =
[this](const char* label, const char* shortcut, bool selected, const char* commandId) {
const bool enabled = m_shellCommandRouter.IsCommandEnabled(commandId);
if (ImGui::MenuItem(label, shortcut, selected, enabled)) {
m_shellCommandRouter.InvokeCommand(commandId);
}
};
const bool layoutLabVisible =
m_layoutLabPanel != nullptr ? m_layoutLabPanel->IsVisible() : false;
bool layoutLabToggle = layoutLabVisible;
if (ImGui::MenuItem("XCUI Layout Lab", nullptr, &layoutLabToggle) &&
m_layoutLabPanel != nullptr) {
m_layoutLabPanel->SetVisible(layoutLabToggle);
}
ImGui::MenuItem("ImGui Demo", nullptr, &m_showImGuiDemoWindow);
drawCommandMenuItem(
"XCUI Demo",
"Ctrl+1",
TryGetShellPanelState(ShellPanelId::XCUIDemo) != nullptr &&
TryGetShellPanelState(ShellPanelId::XCUIDemo)->visible,
ShellCommandIds::ToggleXCUIDemoPanel);
drawCommandMenuItem(
"XCUI Layout Lab",
"Ctrl+2",
TryGetShellPanelState(ShellPanelId::XCUILayoutLab) != nullptr &&
TryGetShellPanelState(ShellPanelId::XCUILayoutLab)->visible,
ShellCommandIds::ToggleXCUILayoutLabPanel);
drawCommandMenuItem(
"ImGui Demo",
"Ctrl+3",
IsShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow),
ShellCommandIds::ToggleImGuiDemoWindow);
ImGui::Separator();
ImGui::MenuItem("Native Backdrop", nullptr, &m_showNativeBackdrop);
ImGui::MenuItem("Pulse Accent", nullptr, &m_pulseNativeBackdropAccent);
ImGui::MenuItem("Native XCUI Overlay", nullptr, &m_showNativeXCUIOverlay);
ImGui::MenuItem("Hosted Preview HUD", nullptr, &m_showHostedPreviewHud);
bool nativeDemoPanelPreview = m_showNativeDemoPanelPreview;
if (ImGui::MenuItem("Native Demo Panel Preview", nullptr, &nativeDemoPanelPreview) &&
nativeDemoPanelPreview != m_showNativeDemoPanelPreview) {
m_showNativeDemoPanelPreview = nativeDemoPanelPreview;
ConfigureHostedPreviewPresenters();
}
bool nativeLayoutLabPreview = m_showNativeLayoutLabPreview;
if (ImGui::MenuItem("Native Layout Lab Preview", nullptr, &nativeLayoutLabPreview) &&
nativeLayoutLabPreview != m_showNativeLayoutLabPreview) {
m_showNativeLayoutLabPreview = nativeLayoutLabPreview;
ConfigureHostedPreviewPresenters();
}
drawCommandMenuItem(
"Native Backdrop",
"Ctrl+Shift+B",
IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop),
ShellCommandIds::ToggleNativeBackdrop);
drawCommandMenuItem(
"Pulse Accent",
"Ctrl+Shift+P",
IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent),
ShellCommandIds::TogglePulseAccent);
drawCommandMenuItem(
"Native XCUI Overlay",
"Ctrl+Shift+O",
IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay),
ShellCommandIds::ToggleNativeXCUIOverlay);
drawCommandMenuItem(
"Hosted Preview HUD",
"Ctrl+Shift+H",
IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud),
ShellCommandIds::ToggleHostedPreviewHud);
drawCommandMenuItem(
"Native Demo Panel Preview",
"Ctrl+Alt+1",
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo),
ShellCommandIds::ToggleNativeDemoPanelPreview);
drawCommandMenuItem(
"Native Layout Lab Preview",
"Ctrl+Alt+2",
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab),
ShellCommandIds::ToggleNativeLayoutLabPreview);
ImGui::EndMenu();
}
@@ -593,7 +811,7 @@ void Application::RenderShellChrome() {
m_nativeOverlayRuntime.GetFrameResult().stats;
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewDrainStats& hostedPreviewStats =
m_hostedPreviewQueue.GetLastDrainStats();
if (m_showNativeXCUIOverlay) {
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
ImGui::TextDisabled(
"Native XCUI overlay: %s | runtime %zu cmds (%zu fill, %zu outline, %zu text, %zu image, clips %zu/%zu)",
overlayFrameStats.nativeOverlayReady ? "preflight OK" : "preflight issues",
@@ -616,7 +834,7 @@ void Application::RenderShellChrome() {
nativeOverlayStats.skippedCommandCount);
} else {
ImGui::TextDisabled(
m_showNativeBackdrop
IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop)
? "Transition backend + runtime diagnostics + native backbuffer pass"
: "Transition backend + runtime diagnostics");
}
@@ -644,12 +862,16 @@ void Application::RenderShellChrome() {
if (m_demoPanel != nullptr) {
ImGui::TextDisabled(
"XCUI Demo preview: %s",
m_showNativeDemoPanelPreview ? "native offscreen preview surface" : "ImGui hosted preview");
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)
? "native offscreen preview surface"
: "ImGui hosted preview");
}
if (m_layoutLabPanel != nullptr) {
ImGui::TextDisabled(
"Layout Lab preview: %s",
m_showNativeLayoutLabPreview ? "native offscreen preview surface" : "ImGui hosted preview");
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)
? "native offscreen preview surface"
: "ImGui hosted preview");
}
ImGui::EndMenuBar();
}
@@ -662,7 +884,7 @@ void Application::RenderShellChrome() {
ImGui::End();
if (m_showHostedPreviewHud) {
if (IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud)) {
RenderHostedPreviewHud();
}
}
@@ -673,22 +895,24 @@ void Application::RenderHostedPreviewHud() {
return;
}
const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
const ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
const HostedPreviewPanelDiagnostics demoDiagnostics = BuildHostedPreviewPanelDiagnostics(
"XCUI Demo",
"new_editor.panels.xcui_demo",
m_demoPanel != nullptr && m_demoPanel->IsVisible(),
m_demoPanel != nullptr && m_demoPanel->IsHostedPreviewEnabled(),
m_showNativeDemoPanelPreview,
demoState != nullptr ? demoState->previewDebugName.data() : "XCUI Demo",
demoState != nullptr ? demoState->previewDebugSource.data() : "new_editor.panels.xcui_demo",
demoState != nullptr && demoState->visible,
demoState != nullptr && demoState->hostedPreviewEnabled,
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo),
m_demoPanel != nullptr && m_demoPanel->IsUsingNativeHostedPreview(),
m_demoPanel != nullptr
? m_demoPanel->GetLastPreviewStats()
: ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats{});
const HostedPreviewPanelDiagnostics layoutLabDiagnostics = BuildHostedPreviewPanelDiagnostics(
"XCUI Layout Lab",
"new_editor.panels.xcui_layout_lab",
m_layoutLabPanel != nullptr && m_layoutLabPanel->IsVisible(),
m_layoutLabPanel != nullptr && m_layoutLabPanel->IsHostedPreviewEnabled(),
m_showNativeLayoutLabPreview,
layoutLabState != nullptr ? layoutLabState->previewDebugName.data() : "XCUI Layout Lab",
layoutLabState != nullptr ? layoutLabState->previewDebugSource.data() : "new_editor.panels.xcui_layout_lab",
layoutLabState != nullptr && layoutLabState->visible,
layoutLabState != nullptr && layoutLabState->hostedPreviewEnabled,
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab),
m_layoutLabPanel != nullptr && m_layoutLabPanel->IsUsingNativeHostedPreview(),
m_layoutLabPanel != nullptr
? m_layoutLabPanel->GetLastPreviewStats()
@@ -870,6 +1094,7 @@ void Application::Frame() {
m_windowCompositor->RenderFrame(
kClearColor,
[this]() {
DispatchShellShortcuts();
RenderShellChrome();
if (m_demoPanel) {
m_demoPanel->RenderIfVisible();
@@ -877,10 +1102,11 @@ void Application::Frame() {
if (m_layoutLabPanel) {
m_layoutLabPanel->RenderIfVisible();
}
if (m_showImGuiDemoWindow) {
ImGui::ShowDemoWindow(&m_showImGuiDemoWindow);
if (m_shellViewToggles.imguiDemoWindowVisible) {
ImGui::ShowDemoWindow(&m_shellViewToggles.imguiDemoWindowVisible);
}
SyncShellChromePanelStateFromPanels();
SyncHostedPreviewSurfaces();
},
[this](
@@ -888,16 +1114,17 @@ void Application::Frame() {
const ::XCEngine::Rendering::RenderSurface& surface) {
RenderQueuedHostedPreviews(renderContext, surface);
if (!m_showNativeBackdrop && !m_showNativeXCUIOverlay) {
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 = m_pulseNativeBackdropAccent;
frameState.drawBackdrop = m_showNativeBackdrop;
if (m_showNativeXCUIOverlay) {
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);