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);

View File

@@ -1,5 +1,8 @@
#pragma once
#include <XCEngine/Input/InputTypes.h>
#include "XCUIBackend/XCUIEditorCommandRouter.h"
#include "panels/XCUIDemoPanel.h"
#include "panels/XCUILayoutLabPanel.h"
@@ -10,10 +13,14 @@
#include "XCUIBackend/XCUIInputBridge.h"
#include "XCUIBackend/XCUILayoutLabRuntime.h"
#include "XCUIBackend/XCUIRHIRenderBackend.h"
#include "XCUIBackend/XCUIShellChromeState.h"
#include "XCUIBackend/XCUIStandaloneTextAtlasProvider.h"
#include <array>
#include <chrono>
#include <cstdint>
#include <functional>
#include <initializer_list>
#include <memory>
#include <string>
#include <vector>
@@ -24,9 +31,252 @@ namespace NewEditor {
class Application {
public:
using ShellPanelId = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
using ShellViewToggleId = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId;
using ShellHostedPreviewMode = ::XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
using ShellPanelChromeState = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelChromeState;
using ShellViewToggleState = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleState;
struct ShellCommandIds {
static constexpr const char* ToggleXCUIDemoPanel =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleXCUIDemoPanel;
static constexpr const char* ToggleXCUILayoutLabPanel =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel;
static constexpr const char* ToggleImGuiDemoWindow =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleImGuiDemoWindow;
static constexpr const char* ToggleNativeBackdrop =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleNativeBackdrop;
static constexpr const char* TogglePulseAccent =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::TogglePulseAccent;
static constexpr const char* ToggleNativeXCUIOverlay =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay;
static constexpr const char* ToggleHostedPreviewHud =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleHostedPreviewHud;
static constexpr const char* ToggleNativeDemoPanelPreview =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview;
static constexpr const char* ToggleNativeLayoutLabPreview =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview;
};
struct ShellCommandBindings {
std::function<bool()> getXCUIDemoPanelVisible = {};
std::function<void(bool)> setXCUIDemoPanelVisible = {};
std::function<bool()> getXCUILayoutLabPanelVisible = {};
std::function<void(bool)> setXCUILayoutLabPanelVisible = {};
std::function<bool()> getImGuiDemoWindowVisible = {};
std::function<void(bool)> setImGuiDemoWindowVisible = {};
std::function<bool()> getNativeBackdropVisible = {};
std::function<void(bool)> setNativeBackdropVisible = {};
std::function<bool()> getPulseAccentEnabled = {};
std::function<void(bool)> setPulseAccentEnabled = {};
std::function<bool()> getNativeXCUIOverlayVisible = {};
std::function<void(bool)> setNativeXCUIOverlayVisible = {};
std::function<bool()> getHostedPreviewHudVisible = {};
std::function<void(bool)> setHostedPreviewHudVisible = {};
std::function<bool()> getNativeDemoPanelPreviewEnabled = {};
std::function<void(bool)> setNativeDemoPanelPreviewEnabled = {};
std::function<bool()> getNativeLayoutLabPreviewEnabled = {};
std::function<void(bool)> setNativeLayoutLabPreviewEnabled = {};
std::function<void()> onHostedPreviewModeChanged = {};
};
static ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot BuildShellShortcutSnapshot(
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta) {
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot snapshot = {};
snapshot.modifiers = frameDelta.state.modifiers;
snapshot.windowFocused = frameDelta.state.windowFocused;
snapshot.wantCaptureKeyboard = frameDelta.state.wantCaptureKeyboard;
snapshot.wantTextInput = frameDelta.state.wantTextInput;
snapshot.keys.reserve(
frameDelta.keyboard.pressedKeys.size() +
frameDelta.keyboard.repeatedKeys.size());
const auto appendKeyState =
[&snapshot](std::int32_t keyCode, bool repeat) {
for (auto& existing : snapshot.keys) {
if (existing.keyCode != keyCode) {
continue;
}
existing.down = true;
existing.repeat = existing.repeat || repeat;
return;
}
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandKeyState keyState = {};
keyState.keyCode = keyCode;
keyState.down = true;
keyState.repeat = repeat;
snapshot.keys.push_back(keyState);
};
for (std::int32_t keyCode : frameDelta.keyboard.pressedKeys) {
appendKeyState(keyCode, false);
}
for (std::int32_t keyCode : frameDelta.keyboard.repeatedKeys) {
appendKeyState(keyCode, true);
}
return snapshot;
}
static void RegisterShellViewCommands(
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter& router,
const ShellCommandBindings& bindings) {
using ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandAccelerator;
using ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandDefinition;
using ::XCEngine::Input::KeyCode;
using ModifierState = ::XCEngine::UI::UIInputModifiers;
const auto bindToggleCommand =
[&router](
const char* commandId,
const std::function<bool()>& getter,
const std::function<void(bool)>& setter,
std::initializer_list<XCUIEditorCommandAccelerator> accelerators,
const std::function<void()>& afterToggle = {}) {
if (!getter || !setter) {
return;
}
XCUIEditorCommandDefinition definition = {};
definition.commandId = commandId;
definition.isEnabled = [getter, setter]() {
return static_cast<bool>(getter) && static_cast<bool>(setter);
};
definition.invoke = [getter, setter, afterToggle]() {
const bool nextValue = !getter();
setter(nextValue);
if (afterToggle) {
afterToggle();
}
};
definition.accelerators.assign(accelerators.begin(), accelerators.end());
router.RegisterCommand(definition);
};
const ModifierState ctrlOnly = { false, true, false, false };
const ModifierState ctrlShift = { true, true, false, false };
const ModifierState ctrlAlt = { false, true, true, false };
bindToggleCommand(
ShellCommandIds::ToggleXCUIDemoPanel,
bindings.getXCUIDemoPanelVisible,
bindings.setXCUIDemoPanelVisible,
{ XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::One),
ctrlOnly,
true,
false } });
bindToggleCommand(
ShellCommandIds::ToggleXCUILayoutLabPanel,
bindings.getXCUILayoutLabPanelVisible,
bindings.setXCUILayoutLabPanelVisible,
{ XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::Two),
ctrlOnly,
true,
false } });
bindToggleCommand(
ShellCommandIds::ToggleImGuiDemoWindow,
bindings.getImGuiDemoWindowVisible,
bindings.setImGuiDemoWindowVisible,
{ XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::Three),
ctrlOnly,
true,
false } });
bindToggleCommand(
ShellCommandIds::ToggleNativeBackdrop,
bindings.getNativeBackdropVisible,
bindings.setNativeBackdropVisible,
{ XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::B),
ctrlShift,
true,
false } });
bindToggleCommand(
ShellCommandIds::TogglePulseAccent,
bindings.getPulseAccentEnabled,
bindings.setPulseAccentEnabled,
{ XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::P),
ctrlShift,
true,
false } });
bindToggleCommand(
ShellCommandIds::ToggleNativeXCUIOverlay,
bindings.getNativeXCUIOverlayVisible,
bindings.setNativeXCUIOverlayVisible,
{ XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::O),
ctrlShift,
true,
false } });
bindToggleCommand(
ShellCommandIds::ToggleHostedPreviewHud,
bindings.getHostedPreviewHudVisible,
bindings.setHostedPreviewHudVisible,
{ XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::H),
ctrlShift,
true,
false } });
bindToggleCommand(
ShellCommandIds::ToggleNativeDemoPanelPreview,
bindings.getNativeDemoPanelPreviewEnabled,
bindings.setNativeDemoPanelPreviewEnabled,
{ XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::One),
ctrlAlt,
true,
false } },
bindings.onHostedPreviewModeChanged);
bindToggleCommand(
ShellCommandIds::ToggleNativeLayoutLabPreview,
bindings.getNativeLayoutLabPreviewEnabled,
bindings.setNativeLayoutLabPreviewEnabled,
{ XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::Two),
ctrlAlt,
true,
false } },
bindings.onHostedPreviewModeChanged);
}
int Run(HINSTANCE instance, int nCmdShow);
private:
using ShellPanelStateArray = std::array<ShellPanelChromeState, static_cast<std::size_t>(ShellPanelId::Count)>;
static constexpr std::size_t GetShellPanelIndex(ShellPanelId panelId) {
return static_cast<std::size_t>(panelId);
}
static ShellPanelStateArray CreateDefaultShellPanelStates() {
ShellPanelStateArray panels = {};
panels[GetShellPanelIndex(ShellPanelId::XCUIDemo)] = {
ShellPanelId::XCUIDemo,
"XCUI Demo",
"XCUI Demo",
"new_editor.panels.xcui_demo",
true,
true,
ShellHostedPreviewMode::NativeOffscreen
};
panels[GetShellPanelIndex(ShellPanelId::XCUILayoutLab)] = {
ShellPanelId::XCUILayoutLab,
"XCUI Layout Lab",
"XCUI Layout Lab",
"new_editor.panels.xcui_layout_lab",
true,
true,
ShellHostedPreviewMode::LegacyImGui
};
return panels;
}
struct HostedPreviewPanelDiagnostics {
std::string debugName = {};
std::string debugSource = {};
@@ -79,10 +329,16 @@ private:
void ShutdownWindowCompositor();
void ShutdownRenderer();
void DestroyHostedPreviewSurfaces();
void SyncShellChromePanelStateFromPanels();
void SyncHostedPreviewSurfaces();
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> CreateHostedPreviewPresenter(
bool nativePreview);
void ConfigureHostedPreviewPresenters();
ShellPanelChromeState* TryGetShellPanelState(ShellPanelId panelId);
const ShellPanelChromeState* TryGetShellPanelState(ShellPanelId panelId) const;
bool IsShellViewToggleEnabled(ShellViewToggleId toggleId) const;
void SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled);
bool IsNativeHostedPreviewEnabled(ShellPanelId panelId) const;
HostedPreviewPanelDiagnostics BuildHostedPreviewPanelDiagnostics(
const char* debugName,
const char* fallbackDebugSource,
@@ -102,6 +358,8 @@ private:
HostedPreviewOffscreenSurface& previewSurface,
const ::XCEngine::Rendering::RenderContext& renderContext,
const ::XCEngine::UI::UIDrawData& drawData);
void ConfigureShellCommandRouter();
void DispatchShellShortcuts();
void RenderShellChrome();
void RenderHostedPreviewHud();
void RenderQueuedHostedPreviews(
@@ -115,20 +373,17 @@ private:
std::unique_ptr<XCUIDemoPanel> m_demoPanel;
std::unique_ptr<XCUILayoutLabPanel> m_layoutLabPanel;
::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource m_xcuiInputSource;
::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_shellInputBridge;
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter m_shellCommandRouter;
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewQueue m_hostedPreviewQueue;
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry m_hostedPreviewSurfaceRegistry;
::XCEngine::Editor::XCUIBackend::XCUIStandaloneTextAtlasProvider m_hostedPreviewTextAtlasProvider;
::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend m_hostedPreviewRenderBackend;
ShellViewToggleState m_shellViewToggles = {};
ShellPanelStateArray m_shellPanels = CreateDefaultShellPanelStates();
std::vector<HostedPreviewOffscreenSurface> m_hostedPreviewSurfaces = {};
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime;
MainWindowNativeBackdropRenderer m_nativeBackdropRenderer;
bool m_showImGuiDemoWindow = false;
bool m_showNativeBackdrop = true;
bool m_pulseNativeBackdropAccent = true;
bool m_showNativeXCUIOverlay = true;
bool m_showHostedPreviewHud = true;
bool m_showNativeDemoPanelPreview = true;
bool m_showNativeLayoutLabPreview = false;
bool m_running = false;
bool m_renderReady = false;
std::chrono::steady_clock::time_point m_startTime = {};

View File

@@ -11,8 +11,32 @@ namespace XCEngine {
namespace Editor {
namespace XCUIBackend {
class IImGuiXCUIHostedPreviewTargetBinding {
public:
virtual ~IImGuiXCUIHostedPreviewTargetBinding() = default;
virtual ImDrawList* ResolveTargetDrawList(const XCUIHostedPreviewFrame& frame) const = 0;
};
class ImGuiCurrentWindowXCUIHostedPreviewTargetBinding final
: public IImGuiXCUIHostedPreviewTargetBinding {
public:
ImDrawList* ResolveTargetDrawList(const XCUIHostedPreviewFrame& frame) const override {
(void)frame;
return ImGui::GetWindowDrawList();
}
};
class ImGuiXCUIHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter {
public:
explicit ImGuiXCUIHostedPreviewPresenter(
std::unique_ptr<IImGuiXCUIHostedPreviewTargetBinding> targetBinding = {})
: m_targetBinding(std::move(targetBinding)) {
if (m_targetBinding == nullptr) {
m_targetBinding = std::make_unique<ImGuiCurrentWindowXCUIHostedPreviewTargetBinding>();
}
}
bool Present(const XCUIHostedPreviewFrame& frame) override {
m_lastStats = {};
if (frame.drawData == nullptr) {
@@ -23,7 +47,14 @@ public:
m_backend.Submit(*frame.drawData);
m_lastStats.submittedDrawListCount = m_backend.GetPendingDrawListCount();
m_lastStats.submittedCommandCount = m_backend.GetPendingCommandCount();
m_lastStats.presented = m_backend.EndFrame(ImGui::GetWindowDrawList());
ImDrawList* targetDrawList =
m_targetBinding != nullptr ? m_targetBinding->ResolveTargetDrawList(frame) : nullptr;
if (targetDrawList == nullptr) {
m_backend.BeginFrame();
return false;
}
m_lastStats.presented = m_backend.EndFrame(targetDrawList);
m_lastStats.flushedDrawListCount = m_backend.GetLastFlushedDrawListCount();
m_lastStats.flushedCommandCount = m_backend.GetLastFlushedCommandCount();
return m_lastStats.presented;
@@ -35,11 +66,22 @@ public:
private:
ImGuiTransitionBackend m_backend = {};
std::unique_ptr<IImGuiXCUIHostedPreviewTargetBinding> m_targetBinding = {};
XCUIHostedPreviewStats m_lastStats = {};
};
inline std::unique_ptr<IImGuiXCUIHostedPreviewTargetBinding>
CreateImGuiCurrentWindowXCUIHostedPreviewTargetBinding() {
return std::make_unique<ImGuiCurrentWindowXCUIHostedPreviewTargetBinding>();
}
inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateImGuiXCUIHostedPreviewPresenter(
std::unique_ptr<IImGuiXCUIHostedPreviewTargetBinding> targetBinding) {
return std::make_unique<ImGuiXCUIHostedPreviewPresenter>(std::move(targetBinding));
}
inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateImGuiXCUIHostedPreviewPresenter() {
return std::make_unique<ImGuiXCUIHostedPreviewPresenter>();
return CreateImGuiXCUIHostedPreviewPresenter(CreateImGuiCurrentWindowXCUIHostedPreviewTargetBinding());
}
} // namespace XCUIBackend

View File

@@ -85,6 +85,22 @@ inline void DrawBadge(
class ImGuiXCUIPanelCanvasHost final : public IXCUIPanelCanvasHost {
public:
const char* GetDebugName() const override {
return "ImGuiXCUIPanelCanvasHost";
}
XCUIPanelCanvasHostBackend GetBackend() const override {
return XCUIPanelCanvasHostBackend::ImGui;
}
XCUIPanelCanvasHostCapabilities GetCapabilities() const override {
XCUIPanelCanvasHostCapabilities capabilities = {};
capabilities.supportsPointerHitTesting = true;
capabilities.supportsHostedSurfaceImages = true;
capabilities.supportsPrimitiveOverlays = true;
return capabilities;
}
XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override {
const char* childId =
request.childId != nullptr && request.childId[0] != '\0'

View File

@@ -0,0 +1,71 @@
#pragma once
#include "XCUIBackend/XCUIPanelCanvasHost.h"
#include <memory>
namespace XCEngine {
namespace Editor {
namespace XCUIBackend {
class NullXCUIPanelCanvasHost final : public IXCUIPanelCanvasHost {
public:
const char* GetDebugName() const override {
return "NullXCUIPanelCanvasHost";
}
XCUIPanelCanvasHostBackend GetBackend() const override {
return XCUIPanelCanvasHostBackend::Null;
}
XCUIPanelCanvasHostCapabilities GetCapabilities() const override {
return {};
}
XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override {
(void)request;
return {};
}
void DrawFilledRect(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIColor& color,
float rounding = 0.0f) override {
(void)rect;
(void)color;
(void)rounding;
}
void DrawOutlineRect(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIColor& color,
float thickness = 1.0f,
float rounding = 0.0f) override {
(void)rect;
(void)color;
(void)thickness;
(void)rounding;
}
void DrawText(
const ::XCEngine::UI::UIPoint& position,
std::string_view text,
const ::XCEngine::UI::UIColor& color,
float fontSize = 0.0f) override {
(void)position;
(void)text;
(void)color;
(void)fontSize;
}
void EndCanvas() override {
}
};
inline std::unique_ptr<IXCUIPanelCanvasHost> CreateNullXCUIPanelCanvasHost() {
return std::make_unique<NullXCUIPanelCanvasHost>();
}
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine

View File

@@ -992,6 +992,41 @@ DemoNode* TryGetNodeByElementId(RuntimeBuildContext& state, UIElementId elementI
return it != state.nodeIndexById.end() ? &state.nodes[it->second] : nullptr;
}
void ApplyActivationEffects(RuntimeBuildContext& state, const DemoNode& node) {
if (IsToggleNode(node)) {
const std::string stateKey = ResolveToggleStateKey(node);
state.toggleStates[stateKey] = !ResolveToggleState(state, node);
}
if (node.actionId == kToggleAccentCommandId || node.elementKey == "toggleAccent") {
state.accentEnabled = !state.accentEnabled;
}
}
bool BridgeCommand(
RuntimeBuildContext& state,
std::string commandId,
UIElementId sourceElementId = 0u) {
if (commandId.empty()) {
return false;
}
if (sourceElementId != 0u) {
if (DemoNode* sourceNode = TryGetNodeByElementId(state, sourceElementId)) {
ApplyActivationEffects(state, *sourceNode);
}
} else if (commandId == kToggleAccentCommandId) {
if (DemoNode* toggleNode = TryGetNodeByElementId(state, state.toggleButtonId)) {
ApplyActivationEffects(state, *toggleNode);
} else {
state.accentEnabled = !state.accentEnabled;
}
}
RecordCommand(state, std::move(commandId));
return true;
}
std::size_t FindCaretOffsetFromPoint(
RuntimeBuildContext& state,
const DemoNode& node,
@@ -1131,16 +1166,7 @@ void ActivateNode(RuntimeBuildContext& state, UIElementId elementId) {
}
const DemoNode& node = state.nodes[it->second];
if (IsToggleNode(node)) {
const std::string stateKey = ResolveToggleStateKey(node);
state.toggleStates[stateKey] = !ResolveToggleState(state, node);
}
if (node.actionId == kToggleAccentCommandId || node.elementKey == "toggleAccent") {
state.accentEnabled = !state.accentEnabled;
}
RecordCommand(state, BuildActivationCommandId(node));
BridgeCommand(state, BuildActivationCommandId(node), elementId);
}
void BuildDemoNodesRecursive(
@@ -2055,10 +2081,10 @@ const XCUIDemoFrameResult& XCUIDemoRuntime::Update(const XCUIDemoInputState& inp
if (event.type == UIInputEventType::KeyDown &&
!event.repeat &&
summary.shortcutHandled &&
summary.commandId == kToggleAccentCommandId) {
ActivateNode(state, state.toggleButtonId);
return;
summary.shortcutHandled) {
if (BridgeCommand(state, summary.commandId)) {
return;
}
}
const UIElementId focusedElementId =

View File

@@ -300,8 +300,6 @@ private:
XCUIHostedPreviewStats m_lastStats = {};
};
std::unique_ptr<IXCUIHostedPreviewPresenter> CreateImGuiXCUIHostedPreviewPresenter();
inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateQueuedNativeXCUIHostedPreviewPresenter(
XCUIHostedPreviewQueue& queue,
XCUIHostedPreviewSurfaceRegistry& surfaceRegistry) {

View File

@@ -10,6 +10,7 @@
#include <XCEngine/UI/Types.h>
#include <XCEngine/UI/Widgets/UIEditorCollectionPrimitives.h>
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
#include <XCEngine/UI/Widgets/UIKeyboardNavigationModel.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
#include <algorithm>
@@ -80,6 +81,9 @@ struct RuntimeBuildContext {
std::unordered_map<std::string, std::size_t> nodeIndexById = {};
std::unordered_map<std::string, UIRect> rectsById = {};
UIWidgets::UIExpansionModel expansionModel = {};
UIWidgets::UIKeyboardNavigationModel keyboardNavigationModel = {};
std::string keyboardNavigationScopeKey = {};
bool navigationOwnsSelection = false;
UIWidgets::UISelectionModel selectionModel = {};
bool documentsReady = false;
std::string statusMessage = {};
@@ -92,6 +96,11 @@ struct RuntimeBuildContext {
fs::file_time_type themeWriteTime = {};
};
struct KeyboardNavigationScope {
std::string key = {};
std::vector<std::size_t> itemIndices = {};
};
String ToContainersString(const std::string& value) {
return String(value.c_str());
}
@@ -660,6 +669,458 @@ std::vector<std::size_t> CollectVisibleChildren(
return visibleChildren;
}
UIWidgets::UIEditorCollectionPrimitiveKind GetPrimitiveKind(
const RuntimeBuildContext& state,
std::size_t nodeIndex) {
return UIWidgets::ClassifyUIEditorCollectionPrimitive(state.nodes[nodeIndex].tagName);
}
bool IsKeyboardNavigableKind(UIWidgets::UIEditorCollectionPrimitiveKind kind) {
return kind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem ||
kind == UIWidgets::UIEditorCollectionPrimitiveKind::ListItem ||
kind == UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection ||
kind == UIWidgets::UIEditorCollectionPrimitiveKind::FieldRow;
}
bool HasKeyboardNavigationInput(const XCUILayoutLabInputState& input) {
return input.navigatePrevious ||
input.navigateNext ||
input.navigateHome ||
input.navigateEnd ||
input.navigateCollapse ||
input.navigateExpand;
}
std::size_t FindNodeIndexById(
const RuntimeBuildContext& state,
const std::string& elementId) {
if (elementId.empty()) {
return kInvalidIndex;
}
const auto it = state.nodeIndexById.find(elementId);
return it != state.nodeIndexById.end() ? it->second : kInvalidIndex;
}
std::size_t FindAncestorByKind(
const RuntimeBuildContext& state,
std::size_t nodeIndex,
UIWidgets::UIEditorCollectionPrimitiveKind kind) {
std::size_t ancestorIndex = nodeIndex;
while (ancestorIndex != kInvalidIndex) {
if (GetPrimitiveKind(state, ancestorIndex) == kind) {
return ancestorIndex;
}
ancestorIndex = state.nodes[ancestorIndex].parentIndex;
}
return kInvalidIndex;
}
std::vector<std::size_t> CollectVisibleChildrenOfKind(
const RuntimeBuildContext& state,
std::size_t nodeIndex,
UIWidgets::UIEditorCollectionPrimitiveKind kind) {
std::vector<std::size_t> itemIndices = {};
if (nodeIndex == kInvalidIndex) {
return itemIndices;
}
const LayoutNode& node = state.nodes[nodeIndex];
itemIndices.reserve(node.children.size());
for (const std::size_t childIndex : node.children) {
if (GetPrimitiveKind(state, childIndex) != kind ||
!IsNodeVisible(state, childIndex)) {
continue;
}
itemIndices.push_back(childIndex);
}
return itemIndices;
}
std::size_t FindItemOffset(
const std::vector<std::size_t>& itemIndices,
std::size_t nodeIndex) {
for (std::size_t itemOffset = 0; itemOffset < itemIndices.size(); ++itemOffset) {
if (itemIndices[itemOffset] == nodeIndex) {
return itemOffset;
}
}
return kInvalidIndex;
}
KeyboardNavigationScope BuildKeyboardNavigationScopeForNode(
const RuntimeBuildContext& state,
std::size_t nodeIndex) {
KeyboardNavigationScope scope = {};
if (nodeIndex == kInvalidIndex) {
return scope;
}
const UIWidgets::UIEditorCollectionPrimitiveKind kind = GetPrimitiveKind(state, nodeIndex);
if (kind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem) {
const std::size_t treeViewIndex = FindAncestorByKind(
state,
state.nodes[nodeIndex].parentIndex,
UIWidgets::UIEditorCollectionPrimitiveKind::TreeView);
if (treeViewIndex == kInvalidIndex) {
return scope;
}
scope.key = "tree:" + state.nodes[treeViewIndex].id;
scope.itemIndices = CollectVisibleChildrenOfKind(
state,
treeViewIndex,
UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem);
return scope;
}
if (kind == UIWidgets::UIEditorCollectionPrimitiveKind::ListItem) {
const std::size_t listViewIndex = FindAncestorByKind(
state,
state.nodes[nodeIndex].parentIndex,
UIWidgets::UIEditorCollectionPrimitiveKind::ListView);
if (listViewIndex == kInvalidIndex) {
return scope;
}
scope.key = "list:" + state.nodes[listViewIndex].id;
scope.itemIndices = CollectVisibleChildrenOfKind(
state,
listViewIndex,
UIWidgets::UIEditorCollectionPrimitiveKind::ListItem);
return scope;
}
if (kind == UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection) {
const std::size_t propertyGroupIndex = state.nodes[nodeIndex].parentIndex;
if (propertyGroupIndex == kInvalidIndex) {
return scope;
}
scope.key = "property-sections:" + state.nodes[propertyGroupIndex].id;
scope.itemIndices = CollectVisibleChildrenOfKind(
state,
propertyGroupIndex,
UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection);
return scope;
}
if (kind == UIWidgets::UIEditorCollectionPrimitiveKind::FieldRow) {
const std::size_t propertySectionIndex = FindAncestorByKind(
state,
state.nodes[nodeIndex].parentIndex,
UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection);
if (propertySectionIndex == kInvalidIndex) {
return scope;
}
scope.key = "property-fields:" + state.nodes[propertySectionIndex].id;
scope.itemIndices.push_back(propertySectionIndex);
std::vector<std::size_t> fieldRows = CollectVisibleChildrenOfKind(
state,
propertySectionIndex,
UIWidgets::UIEditorCollectionPrimitiveKind::FieldRow);
scope.itemIndices.insert(
scope.itemIndices.end(),
fieldRows.begin(),
fieldRows.end());
}
return scope;
}
std::size_t FindFirstVisibleKeyboardNavigableNode(const RuntimeBuildContext& state) {
for (std::size_t nodeIndex = 0; nodeIndex < state.nodes.size(); ++nodeIndex) {
if (!IsNodeVisible(state, nodeIndex) ||
!IsKeyboardNavigableKind(GetPrimitiveKind(state, nodeIndex))) {
continue;
}
return nodeIndex;
}
return kInvalidIndex;
}
void ClearKeyboardNavigationState(RuntimeBuildContext& state) {
state.keyboardNavigationModel = UIWidgets::UIKeyboardNavigationModel();
state.keyboardNavigationScopeKey.clear();
}
KeyboardNavigationScope ResolveKeyboardNavigationScope(
const RuntimeBuildContext& state,
std::size_t hoveredIndex,
bool allowFallback) {
const std::size_t selectedIndex = FindNodeIndexById(
state,
state.selectionModel.GetSelectedId());
if (selectedIndex != kInvalidIndex &&
IsKeyboardNavigableKind(GetPrimitiveKind(state, selectedIndex))) {
return BuildKeyboardNavigationScopeForNode(state, selectedIndex);
}
if (hoveredIndex != kInvalidIndex &&
IsKeyboardNavigableKind(GetPrimitiveKind(state, hoveredIndex))) {
return BuildKeyboardNavigationScopeForNode(state, hoveredIndex);
}
if (allowFallback && !state.selectionModel.HasSelection()) {
return BuildKeyboardNavigationScopeForNode(
state,
FindFirstVisibleKeyboardNavigableNode(state));
}
return KeyboardNavigationScope();
}
bool ApplyKeyboardNavigationSelection(
RuntimeBuildContext& state,
const KeyboardNavigationScope& scope) {
if (!state.keyboardNavigationModel.HasCurrentIndex()) {
return false;
}
const std::size_t currentIndex = state.keyboardNavigationModel.GetCurrentIndex();
if (currentIndex >= scope.itemIndices.size()) {
return false;
}
state.selectionModel.SetSelection(state.nodes[scope.itemIndices[currentIndex]].id);
state.navigationOwnsSelection = true;
return true;
}
void SyncKeyboardNavigationScope(
RuntimeBuildContext& state,
const KeyboardNavigationScope& scope) {
if (scope.key.empty()) {
if (!state.navigationOwnsSelection) {
ClearKeyboardNavigationState(state);
}
return;
}
if (state.keyboardNavigationScopeKey != scope.key) {
ClearKeyboardNavigationState(state);
state.keyboardNavigationScopeKey = scope.key;
}
state.keyboardNavigationModel.SetItemCount(scope.itemIndices.size());
if (scope.itemIndices.empty()) {
if (state.navigationOwnsSelection) {
state.selectionModel.ClearSelection();
state.navigationOwnsSelection = false;
}
return;
}
const std::size_t selectedIndex = FindNodeIndexById(
state,
state.selectionModel.GetSelectedId());
const std::size_t selectedOffset = FindItemOffset(scope.itemIndices, selectedIndex);
if (selectedOffset != kInvalidIndex) {
state.keyboardNavigationModel.SetCurrentIndex(selectedOffset);
return;
}
if (state.navigationOwnsSelection &&
state.keyboardNavigationModel.HasCurrentIndex()) {
ApplyKeyboardNavigationSelection(state, scope);
}
}
std::size_t FindTreeParentItemIndex(
const RuntimeBuildContext& state,
std::size_t nodeIndex) {
if (nodeIndex == kInvalidIndex ||
GetPrimitiveKind(state, nodeIndex) != UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem) {
return kInvalidIndex;
}
const std::size_t treeViewIndex = FindAncestorByKind(
state,
state.nodes[nodeIndex].parentIndex,
UIWidgets::UIEditorCollectionPrimitiveKind::TreeView);
if (treeViewIndex == kInvalidIndex) {
return kInvalidIndex;
}
const float indentLevel = ResolveTreeIndentLevel(state.nodes[nodeIndex]);
if (indentLevel <= 0.0f) {
return kInvalidIndex;
}
const LayoutNode& treeView = state.nodes[treeViewIndex];
const auto siblingIt = std::find(treeView.children.begin(), treeView.children.end(), nodeIndex);
if (siblingIt == treeView.children.end()) {
return kInvalidIndex;
}
for (auto it = siblingIt; it != treeView.children.begin();) {
--it;
if (ResolveTreeIndentLevel(state.nodes[*it]) < indentLevel) {
return *it;
}
}
return kInvalidIndex;
}
std::size_t FindFirstTreeChildItemIndex(
const RuntimeBuildContext& state,
std::size_t nodeIndex) {
if (!HasTreeItemChildren(state, nodeIndex) ||
state.nodes[nodeIndex].parentIndex == kInvalidIndex) {
return kInvalidIndex;
}
const LayoutNode& node = state.nodes[nodeIndex];
const LayoutNode& treeView = state.nodes[node.parentIndex];
const float indentLevel = ResolveTreeIndentLevel(node);
const auto siblingIt = std::find(treeView.children.begin(), treeView.children.end(), nodeIndex);
if (siblingIt == treeView.children.end()) {
return kInvalidIndex;
}
for (auto it = siblingIt + 1; it != treeView.children.end(); ++it) {
const std::size_t candidateIndex = *it;
const float candidateIndent = ResolveTreeIndentLevel(state.nodes[candidateIndex]);
if (candidateIndent <= indentLevel) {
break;
}
if (IsNodeVisible(state, candidateIndex)) {
return candidateIndex;
}
}
return kInvalidIndex;
}
bool HandleKeyboardExpand(RuntimeBuildContext& state) {
const std::size_t selectedIndex = FindNodeIndexById(
state,
state.selectionModel.GetSelectedId());
if (selectedIndex == kInvalidIndex) {
return false;
}
const UIWidgets::UIEditorCollectionPrimitiveKind kind = GetPrimitiveKind(state, selectedIndex);
if (kind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem) {
if (!HasTreeItemChildren(state, selectedIndex)) {
return false;
}
if (!IsNodeExpanded(state, selectedIndex)) {
state.expansionModel.Expand(state.nodes[selectedIndex].id);
state.navigationOwnsSelection = true;
return true;
}
const std::size_t childIndex = FindFirstTreeChildItemIndex(state, selectedIndex);
if (childIndex == kInvalidIndex) {
return false;
}
state.selectionModel.SetSelection(state.nodes[childIndex].id);
state.navigationOwnsSelection = true;
return true;
}
if (kind == UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection) {
if (!IsNodeExpanded(state, selectedIndex)) {
state.expansionModel.Expand(state.nodes[selectedIndex].id);
state.navigationOwnsSelection = true;
return true;
}
const std::vector<std::size_t> fieldRows = CollectVisibleChildrenOfKind(
state,
selectedIndex,
UIWidgets::UIEditorCollectionPrimitiveKind::FieldRow);
if (fieldRows.empty()) {
return false;
}
state.selectionModel.SetSelection(state.nodes[fieldRows.front()].id);
state.navigationOwnsSelection = true;
return true;
}
return false;
}
bool HandleKeyboardCollapse(RuntimeBuildContext& state) {
const std::size_t selectedIndex = FindNodeIndexById(
state,
state.selectionModel.GetSelectedId());
if (selectedIndex == kInvalidIndex) {
return false;
}
const UIWidgets::UIEditorCollectionPrimitiveKind kind = GetPrimitiveKind(state, selectedIndex);
if (kind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem) {
if (HasTreeItemChildren(state, selectedIndex) &&
IsNodeExpanded(state, selectedIndex)) {
state.expansionModel.Collapse(state.nodes[selectedIndex].id);
state.navigationOwnsSelection = true;
return true;
}
const std::size_t parentIndex = FindTreeParentItemIndex(state, selectedIndex);
if (parentIndex == kInvalidIndex) {
return false;
}
state.selectionModel.SetSelection(state.nodes[parentIndex].id);
state.navigationOwnsSelection = true;
return true;
}
if (kind == UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection) {
if (!IsNodeExpanded(state, selectedIndex)) {
return false;
}
state.expansionModel.Collapse(state.nodes[selectedIndex].id);
state.navigationOwnsSelection = true;
return true;
}
if (kind == UIWidgets::UIEditorCollectionPrimitiveKind::FieldRow) {
const std::size_t propertySectionIndex = FindAncestorByKind(
state,
state.nodes[selectedIndex].parentIndex,
UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection);
if (propertySectionIndex == kInvalidIndex) {
return false;
}
state.selectionModel.SetSelection(state.nodes[propertySectionIndex].id);
state.navigationOwnsSelection = true;
return true;
}
return false;
}
bool MoveKeyboardNavigationSelection(
RuntimeBuildContext& state,
const KeyboardNavigationScope& scope,
bool (UIWidgets::UIKeyboardNavigationModel::*moveFn)()) {
if (scope.itemIndices.empty() ||
!(state.keyboardNavigationModel.*moveFn)()) {
return false;
}
return ApplyKeyboardNavigationSelection(state, scope);
}
void SeedDefaultExpansionState(RuntimeBuildContext& state) {
state.expansionModel.Clear();
for (std::size_t nodeIndex = 0; nodeIndex < state.nodes.size(); ++nodeIndex) {
@@ -1197,6 +1658,8 @@ bool XCUILayoutLabRuntime::ReloadDocuments() {
state.nodeIndexById.clear();
state.rectsById.clear();
state.expansionModel.Clear();
ClearKeyboardNavigationState(state);
state.navigationOwnsSelection = false;
state.selectionModel.ClearSelection();
state.documentSource.SetPathSet(XCUIAssetDocumentSource::MakeLayoutLabPathSet());
@@ -1285,8 +1748,55 @@ const XCUILayoutLabFrameResult& XCUILayoutLabRuntime::Update(const XCUILayoutLab
state.expansionModel.ToggleExpanded(state.nodes[hoveredIndex].id);
}
state.selectionModel.SetSelection(state.nodes[hoveredIndex].id);
state.navigationOwnsSelection =
IsKeyboardNavigableKind(GetPrimitiveKind(state, hoveredIndex));
} else {
state.selectionModel.ClearSelection();
state.navigationOwnsSelection = false;
}
}
KeyboardNavigationScope navigationScope = ResolveKeyboardNavigationScope(
state,
hoveredIndex,
HasKeyboardNavigationInput(input));
SyncKeyboardNavigationScope(state, navigationScope);
if (HasKeyboardNavigationInput(input) &&
!navigationScope.itemIndices.empty()) {
if (input.navigateCollapse) {
HandleKeyboardCollapse(state);
}
if (input.navigateExpand) {
HandleKeyboardExpand(state);
}
navigationScope = ResolveKeyboardNavigationScope(state, hoveredIndex, true);
SyncKeyboardNavigationScope(state, navigationScope);
if (input.navigateHome) {
MoveKeyboardNavigationSelection(
state,
navigationScope,
&UIWidgets::UIKeyboardNavigationModel::MoveHome);
}
if (input.navigateEnd) {
MoveKeyboardNavigationSelection(
state,
navigationScope,
&UIWidgets::UIKeyboardNavigationModel::MoveEnd);
}
if (input.navigatePrevious) {
MoveKeyboardNavigationSelection(
state,
navigationScope,
&UIWidgets::UIKeyboardNavigationModel::MovePrevious);
}
if (input.navigateNext) {
MoveKeyboardNavigationSelection(
state,
navigationScope,
&UIWidgets::UIKeyboardNavigationModel::MoveNext);
}
}

View File

@@ -18,6 +18,12 @@ struct XCUILayoutLabInputState {
UI::UIPoint pointerPosition = {};
bool pointerInside = false;
bool pointerPressed = false;
bool navigatePrevious = false;
bool navigateNext = false;
bool navigateHome = false;
bool navigateEnd = false;
bool navigateCollapse = false;
bool navigateExpand = false;
};
struct XCUILayoutLabFrameStats {

View File

@@ -4,6 +4,7 @@
#include <XCEngine/UI/DrawData.h>
#include <cstdint>
#include <memory>
#include <string_view>
@@ -11,6 +12,17 @@ namespace XCEngine {
namespace Editor {
namespace XCUIBackend {
enum class XCUIPanelCanvasHostBackend : std::uint8_t {
Null = 0,
ImGui
};
struct XCUIPanelCanvasHostCapabilities {
bool supportsPointerHitTesting = false;
bool supportsHostedSurfaceImages = false;
bool supportsPrimitiveOverlays = false;
};
struct XCUIPanelCanvasRequest {
const char* childId = nullptr;
float height = 0.0f;
@@ -38,6 +50,9 @@ class IXCUIPanelCanvasHost {
public:
virtual ~IXCUIPanelCanvasHost() = default;
virtual const char* GetDebugName() const = 0;
virtual XCUIPanelCanvasHostBackend GetBackend() const = 0;
virtual XCUIPanelCanvasHostCapabilities GetCapabilities() const = 0;
virtual XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) = 0;
virtual void DrawFilledRect(
const ::XCEngine::UI::UIRect& rect,
@@ -56,7 +71,7 @@ public:
virtual void EndCanvas() = 0;
};
std::unique_ptr<IXCUIPanelCanvasHost> CreateImGuiXCUIPanelCanvasHost();
std::unique_ptr<IXCUIPanelCanvasHost> CreateNullXCUIPanelCanvasHost();
} // namespace XCUIBackend
} // namespace Editor

View File

@@ -0,0 +1,295 @@
#include "XCUIBackend/XCUIShellChromeState.h"
namespace XCEngine {
namespace Editor {
namespace XCUIBackend {
namespace {
constexpr std::size_t ToIndex(XCUIShellPanelId panelId) {
return static_cast<std::size_t>(panelId);
}
} // namespace
XCUIShellChromeState::XCUIShellChromeState() {
m_panels[ToIndex(XCUIShellPanelId::XCUIDemo)] = {
XCUIShellPanelId::XCUIDemo,
"XCUI Demo",
"XCUI Demo",
"new_editor.panels.xcui_demo",
true,
true,
XCUIShellHostedPreviewMode::NativeOffscreen
};
m_panels[ToIndex(XCUIShellPanelId::XCUILayoutLab)] = {
XCUIShellPanelId::XCUILayoutLab,
"XCUI Layout Lab",
"XCUI Layout Lab",
"new_editor.panels.xcui_layout_lab",
true,
true,
XCUIShellHostedPreviewMode::LegacyImGui
};
}
const XCUIShellViewToggleState& XCUIShellChromeState::GetViewToggles() const {
return m_viewToggles;
}
const std::array<XCUIShellPanelChromeState, static_cast<std::size_t>(XCUIShellPanelId::Count)>&
XCUIShellChromeState::GetPanels() const {
return m_panels;
}
const XCUIShellPanelChromeState* XCUIShellChromeState::TryGetPanelState(XCUIShellPanelId panelId) const {
const std::size_t index = ToIndex(panelId);
if (index >= m_panels.size()) {
return nullptr;
}
return &m_panels[index];
}
XCUIShellPanelChromeState* XCUIShellChromeState::TryGetPanelStateMutable(XCUIShellPanelId panelId) {
const std::size_t index = ToIndex(panelId);
if (index >= m_panels.size()) {
return nullptr;
}
return &m_panels[index];
}
bool XCUIShellChromeState::IsPanelVisible(XCUIShellPanelId panelId) const {
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
return panelState != nullptr && panelState->visible;
}
bool XCUIShellChromeState::SetPanelVisible(XCUIShellPanelId panelId, bool visible) {
XCUIShellPanelChromeState* panelState = TryGetPanelStateMutable(panelId);
if (panelState == nullptr || panelState->visible == visible) {
return false;
}
panelState->visible = visible;
return true;
}
bool XCUIShellChromeState::TogglePanelVisible(XCUIShellPanelId panelId) {
XCUIShellPanelChromeState* panelState = TryGetPanelStateMutable(panelId);
if (panelState == nullptr) {
return false;
}
panelState->visible = !panelState->visible;
return true;
}
bool XCUIShellChromeState::IsHostedPreviewEnabled(XCUIShellPanelId panelId) const {
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
return panelState != nullptr && panelState->hostedPreviewEnabled;
}
bool XCUIShellChromeState::SetHostedPreviewEnabled(XCUIShellPanelId panelId, bool enabled) {
XCUIShellPanelChromeState* panelState = TryGetPanelStateMutable(panelId);
if (panelState == nullptr || panelState->hostedPreviewEnabled == enabled) {
return false;
}
panelState->hostedPreviewEnabled = enabled;
return true;
}
XCUIShellHostedPreviewMode XCUIShellChromeState::GetHostedPreviewMode(XCUIShellPanelId panelId) const {
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
return panelState != nullptr
? panelState->previewMode
: XCUIShellHostedPreviewMode::LegacyImGui;
}
XCUIShellHostedPreviewState XCUIShellChromeState::GetHostedPreviewState(XCUIShellPanelId panelId) const {
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
if (panelState == nullptr || !panelState->hostedPreviewEnabled) {
return XCUIShellHostedPreviewState::Disabled;
}
return panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen
? XCUIShellHostedPreviewState::NativeOffscreen
: XCUIShellHostedPreviewState::LegacyImGui;
}
bool XCUIShellChromeState::IsNativeHostedPreviewActive(XCUIShellPanelId panelId) const {
return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::NativeOffscreen;
}
bool XCUIShellChromeState::IsLegacyHostedPreviewActive(XCUIShellPanelId panelId) const {
return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::LegacyImGui;
}
bool XCUIShellChromeState::SetHostedPreviewMode(
XCUIShellPanelId panelId,
XCUIShellHostedPreviewMode mode) {
XCUIShellPanelChromeState* panelState = TryGetPanelStateMutable(panelId);
if (panelState == nullptr || panelState->previewMode == mode) {
return false;
}
panelState->previewMode = mode;
return true;
}
bool XCUIShellChromeState::ToggleHostedPreviewMode(XCUIShellPanelId panelId) {
XCUIShellPanelChromeState* panelState = TryGetPanelStateMutable(panelId);
if (panelState == nullptr) {
return false;
}
panelState->previewMode =
panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen
? XCUIShellHostedPreviewMode::LegacyImGui
: XCUIShellHostedPreviewMode::NativeOffscreen;
return true;
}
bool XCUIShellChromeState::GetViewToggle(XCUIShellViewToggleId toggleId) const {
switch (toggleId) {
case XCUIShellViewToggleId::ImGuiDemoWindow:
return m_viewToggles.imguiDemoWindowVisible;
case XCUIShellViewToggleId::NativeBackdrop:
return m_viewToggles.nativeBackdropVisible;
case XCUIShellViewToggleId::PulseAccent:
return m_viewToggles.pulseAccentEnabled;
case XCUIShellViewToggleId::NativeXCUIOverlay:
return m_viewToggles.nativeXCUIOverlayVisible;
case XCUIShellViewToggleId::HostedPreviewHud:
return m_viewToggles.hostedPreviewHudVisible;
case XCUIShellViewToggleId::Count:
default:
return false;
}
}
bool XCUIShellChromeState::SetViewToggle(XCUIShellViewToggleId toggleId, bool enabled) {
bool* target = nullptr;
switch (toggleId) {
case XCUIShellViewToggleId::ImGuiDemoWindow:
target = &m_viewToggles.imguiDemoWindowVisible;
break;
case XCUIShellViewToggleId::NativeBackdrop:
target = &m_viewToggles.nativeBackdropVisible;
break;
case XCUIShellViewToggleId::PulseAccent:
target = &m_viewToggles.pulseAccentEnabled;
break;
case XCUIShellViewToggleId::NativeXCUIOverlay:
target = &m_viewToggles.nativeXCUIOverlayVisible;
break;
case XCUIShellViewToggleId::HostedPreviewHud:
target = &m_viewToggles.hostedPreviewHudVisible;
break;
case XCUIShellViewToggleId::Count:
default:
return false;
}
if (*target == enabled) {
return false;
}
*target = enabled;
return true;
}
bool XCUIShellChromeState::ToggleViewToggle(XCUIShellViewToggleId toggleId) {
return SetViewToggle(toggleId, !GetViewToggle(toggleId));
}
bool XCUIShellChromeState::HasCommand(std::string_view commandId) const {
return commandId == GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo) ||
commandId == GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab) ||
commandId == GetViewToggleCommandId(XCUIShellViewToggleId::ImGuiDemoWindow) ||
commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop) ||
commandId == GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent) ||
commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay) ||
commandId == GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud) ||
commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo) ||
commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab);
}
bool XCUIShellChromeState::InvokeCommand(std::string_view commandId) {
if (commandId == XCUIShellChromeCommandIds::ToggleXCUIDemoPanel) {
return TogglePanelVisible(XCUIShellPanelId::XCUIDemo);
}
if (commandId == XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel) {
return TogglePanelVisible(XCUIShellPanelId::XCUILayoutLab);
}
if (commandId == XCUIShellChromeCommandIds::ToggleImGuiDemoWindow) {
return ToggleViewToggle(XCUIShellViewToggleId::ImGuiDemoWindow);
}
if (commandId == XCUIShellChromeCommandIds::ToggleNativeBackdrop) {
return ToggleViewToggle(XCUIShellViewToggleId::NativeBackdrop);
}
if (commandId == XCUIShellChromeCommandIds::TogglePulseAccent) {
return ToggleViewToggle(XCUIShellViewToggleId::PulseAccent);
}
if (commandId == XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay) {
return ToggleViewToggle(XCUIShellViewToggleId::NativeXCUIOverlay);
}
if (commandId == XCUIShellChromeCommandIds::ToggleHostedPreviewHud) {
return ToggleViewToggle(XCUIShellViewToggleId::HostedPreviewHud);
}
if (commandId == XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview) {
return ToggleHostedPreviewMode(XCUIShellPanelId::XCUIDemo);
}
if (commandId == XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview) {
return ToggleHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab);
}
return false;
}
std::string_view XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId panelId) {
switch (panelId) {
case XCUIShellPanelId::XCUIDemo:
return XCUIShellChromeCommandIds::ToggleXCUIDemoPanel;
case XCUIShellPanelId::XCUILayoutLab:
return XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel;
case XCUIShellPanelId::Count:
default:
return {};
}
}
std::string_view XCUIShellChromeState::GetPanelPreviewModeCommandId(XCUIShellPanelId panelId) {
switch (panelId) {
case XCUIShellPanelId::XCUIDemo:
return XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview;
case XCUIShellPanelId::XCUILayoutLab:
return XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview;
case XCUIShellPanelId::Count:
default:
return {};
}
}
std::string_view XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId toggleId) {
switch (toggleId) {
case XCUIShellViewToggleId::ImGuiDemoWindow:
return XCUIShellChromeCommandIds::ToggleImGuiDemoWindow;
case XCUIShellViewToggleId::NativeBackdrop:
return XCUIShellChromeCommandIds::ToggleNativeBackdrop;
case XCUIShellViewToggleId::PulseAccent:
return XCUIShellChromeCommandIds::TogglePulseAccent;
case XCUIShellViewToggleId::NativeXCUIOverlay:
return XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay;
case XCUIShellViewToggleId::HostedPreviewHud:
return XCUIShellChromeCommandIds::ToggleHostedPreviewHud;
case XCUIShellViewToggleId::Count:
default:
return {};
}
}
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine

View File

@@ -0,0 +1,109 @@
#pragma once
#include <array>
#include <cstdint>
#include <string_view>
namespace XCEngine {
namespace Editor {
namespace XCUIBackend {
enum class XCUIShellPanelId : std::uint8_t {
XCUIDemo = 0,
XCUILayoutLab,
Count
};
enum class XCUIShellViewToggleId : std::uint8_t {
ImGuiDemoWindow = 0,
NativeBackdrop,
PulseAccent,
NativeXCUIOverlay,
HostedPreviewHud,
Count
};
enum class XCUIShellHostedPreviewMode : std::uint8_t {
LegacyImGui = 0,
NativeOffscreen
};
enum class XCUIShellHostedPreviewState : std::uint8_t {
Disabled = 0,
LegacyImGui,
NativeOffscreen
};
struct XCUIShellPanelChromeState {
XCUIShellPanelId panelId = XCUIShellPanelId::XCUIDemo;
std::string_view panelTitle = {};
std::string_view previewDebugName = {};
std::string_view previewDebugSource = {};
bool visible = true;
bool hostedPreviewEnabled = true;
XCUIShellHostedPreviewMode previewMode = XCUIShellHostedPreviewMode::LegacyImGui;
};
struct XCUIShellViewToggleState {
bool imguiDemoWindowVisible = false;
bool nativeBackdropVisible = true;
bool pulseAccentEnabled = true;
bool nativeXCUIOverlayVisible = true;
bool hostedPreviewHudVisible = true;
};
struct XCUIShellChromeCommandIds {
static constexpr const char* ToggleXCUIDemoPanel = "new_editor.view.xcui_demo";
static constexpr const char* ToggleXCUILayoutLabPanel = "new_editor.view.xcui_layout_lab";
static constexpr const char* ToggleImGuiDemoWindow = "new_editor.view.imgui_demo";
static constexpr const char* ToggleNativeBackdrop = "new_editor.view.native_backdrop";
static constexpr const char* TogglePulseAccent = "new_editor.view.pulse_accent";
static constexpr const char* ToggleNativeXCUIOverlay = "new_editor.view.native_xcui_overlay";
static constexpr const char* ToggleHostedPreviewHud = "new_editor.view.hosted_preview_hud";
static constexpr const char* ToggleNativeDemoPanelPreview = "new_editor.view.native_demo_panel_preview";
static constexpr const char* ToggleNativeLayoutLabPreview = "new_editor.view.native_layout_lab_preview";
};
class XCUIShellChromeState {
public:
XCUIShellChromeState();
const XCUIShellViewToggleState& GetViewToggles() const;
const std::array<XCUIShellPanelChromeState, static_cast<std::size_t>(XCUIShellPanelId::Count)>& GetPanels() const;
const XCUIShellPanelChromeState* TryGetPanelState(XCUIShellPanelId panelId) const;
bool IsPanelVisible(XCUIShellPanelId panelId) const;
bool SetPanelVisible(XCUIShellPanelId panelId, bool visible);
bool TogglePanelVisible(XCUIShellPanelId panelId);
bool IsHostedPreviewEnabled(XCUIShellPanelId panelId) const;
bool SetHostedPreviewEnabled(XCUIShellPanelId panelId, bool enabled);
XCUIShellHostedPreviewMode GetHostedPreviewMode(XCUIShellPanelId panelId) const;
XCUIShellHostedPreviewState GetHostedPreviewState(XCUIShellPanelId panelId) const;
bool IsNativeHostedPreviewActive(XCUIShellPanelId panelId) const;
bool IsLegacyHostedPreviewActive(XCUIShellPanelId panelId) const;
bool SetHostedPreviewMode(XCUIShellPanelId panelId, XCUIShellHostedPreviewMode mode);
bool ToggleHostedPreviewMode(XCUIShellPanelId panelId);
bool GetViewToggle(XCUIShellViewToggleId toggleId) const;
bool SetViewToggle(XCUIShellViewToggleId toggleId, bool enabled);
bool ToggleViewToggle(XCUIShellViewToggleId toggleId);
bool HasCommand(std::string_view commandId) const;
bool InvokeCommand(std::string_view commandId);
static std::string_view GetPanelVisibilityCommandId(XCUIShellPanelId panelId);
static std::string_view GetPanelPreviewModeCommandId(XCUIShellPanelId panelId);
static std::string_view GetViewToggleCommandId(XCUIShellViewToggleId toggleId);
private:
XCUIShellPanelChromeState* TryGetPanelStateMutable(XCUIShellPanelId panelId);
XCUIShellViewToggleState m_viewToggles = {};
std::array<XCUIShellPanelChromeState, static_cast<std::size_t>(XCUIShellPanelId::Count)> m_panels = {};
};
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine