Integrate XCUI shell state and runtime frame seams
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
71
new_editor/src/XCUIBackend/NullXCUIPanelCanvasHost.h
Normal file
71
new_editor/src/XCUIBackend/NullXCUIPanelCanvasHost.h
Normal 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
|
||||
@@ -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 =
|
||||
|
||||
@@ -300,8 +300,6 @@ private:
|
||||
XCUIHostedPreviewStats m_lastStats = {};
|
||||
};
|
||||
|
||||
std::unique_ptr<IXCUIHostedPreviewPresenter> CreateImGuiXCUIHostedPreviewPresenter();
|
||||
|
||||
inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateQueuedNativeXCUIHostedPreviewPresenter(
|
||||
XCUIHostedPreviewQueue& queue,
|
||||
XCUIHostedPreviewSurfaceRegistry& surfaceRegistry) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
295
new_editor/src/XCUIBackend/XCUIShellChromeState.cpp
Normal file
295
new_editor/src/XCUIBackend/XCUIShellChromeState.cpp
Normal 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
|
||||
109
new_editor/src/XCUIBackend/XCUIShellChromeState.h
Normal file
109
new_editor/src/XCUIBackend/XCUIShellChromeState.h
Normal 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
|
||||
Reference in New Issue
Block a user