Integrate XCUI shell state and runtime frame seams
This commit is contained in:
@@ -53,6 +53,8 @@ Current gap:
|
||||
- `UIScreenStackController::ReplaceTop` now preserves the previous top screen if the replacement screen fails to load, so runtime menu flows do not silently drop their active layer on bad assets.
|
||||
- `SceneRuntime` now owns a dedicated `UISceneRuntimeContext`, so game/runtime code has a first-class place to configure viewport/focus, queue `UIInputEvent`s, drive `UISystem` each `Update`, and inspect the latest UI frame result.
|
||||
- Runtime screen emission now also carries concrete button text in the shared document host path instead of silently dropping button labels.
|
||||
- `UISystemFrameResult` now also preserves viewport rect, submitted input count, frame delta, and focus state, and both `UISystem` and `UISceneRuntimeContext` now expose `ConsumeLastFrame()` so runtime/game hosts can drain the last retained frame packet without copying editor-side concepts into the shared layer.
|
||||
- `UIScreenPlayer` now also exposes `ConsumeLastFrame()`, so player/system/context all share the same consume-vs-borrow frame ownership semantics in the runtime layer.
|
||||
|
||||
Current gap:
|
||||
|
||||
@@ -75,32 +77,42 @@ Current gap:
|
||||
- `new_editor` now also has an isolated `XCUIEditorCommandRouter` model with shortcut matching, enable predicates, and direct command invocation semantics covered by dedicated tests, ready for shell-frame integration.
|
||||
- `XCUI Demo` now exports pending per-frame command ids through `DrainPendingCommandIds()`, so editor-side hosts have a clean seam for observing demo/runtime command traffic without parsing draw data.
|
||||
- `XCUI Demo` and `LayoutLab` panel canvases are now being pulled behind a dedicated `IXCUIPanelCanvasHost` seam, so canvas surface presentation, hover/focus fallback state, and overlay draw hooks no longer have to stay hard-coded inside each ImGui panel implementation.
|
||||
- The panel-canvas seam now also exposes explicit backend/capability metadata and a minimal `NullXCUIPanelCanvasHost`, so non-ImGui host paths have a concrete placeholder backend instead of relying on an implicit ImGui default.
|
||||
- Panel diagnostics were expanded to clearly separate preview/runtime/input state and native vs legacy paths.
|
||||
- The editor bridge layer now has smoke coverage for swapchain after-UI rendering hooks and SRV-backed ImGui texture descriptor registration.
|
||||
- `Application` no longer owns the ImGui backend directly; window presentation now routes through `IWindowUICompositor` with an `ImGuiWindowUICompositor` implementation, which currently delegates to `IEditorHostCompositor` / `ImGuiHostCompositor`.
|
||||
- Hosted preview offscreen surfaces now keep compositor-returned `UITextureRegistration` / `UITextureHandle` data inside `Application` instead of storing `ImTextureID` directly.
|
||||
- The generic hosted-preview presenter contract no longer owns `ImGuiTransitionBackend`; the ImGui presenter now sits in a separate `ImGuiXCUIHostedPreviewPresenter` header while the native queue/surface registry remains XCUI-generic.
|
||||
- The generic hosted-preview frame contract no longer carries an ImGui draw-list pointer; the legacy ImGui presenter resolves its inline draw target from the active ImGui window context instead of pushing that type through the XCUI contract.
|
||||
- The legacy ImGui hosted-preview presenter now also accepts an explicit draw-target binding object, so presenter-side `ImGui::GetWindowDrawList()` lookup is no longer hard-coded inside the generic presenter path and can stay isolated behind the ImGui adapter layer.
|
||||
- `Application` shell menu toggles and global shortcuts now route through `XCUIEditorCommandRouter` instead of directly mutating shell booleans from menu callbacks, giving the editor layer a real command-routing seam.
|
||||
- `LayoutLab` runtime now consumes the shared `UIKeyboardNavigationModel` for abstract list/tree/property navigation actions (`previous/next/home/end/collapse/expand`), so keyboard collection traversal rules are no longer trapped in sandbox-local state.
|
||||
- `XCUIDemoRuntime` now bridges pointer activation, text-edit commands, and shortcut-triggered commands through a unified command path, and `DrainPendingCommandIds()` now preserves mixed pointer/text/shortcut ordering.
|
||||
- `new_editor` now also has a pure `XCUIShellChromeState` model covering panel visibility, hosted-preview mode, and shell-level view toggles without depending on ImGui, `Application`, or the old editor.
|
||||
- `XCNewEditor` builds successfully to `build/new_editor/bin/Debug/XCNewEditor.exe`.
|
||||
|
||||
Current gap:
|
||||
|
||||
- The shell is still ImGui-hosted.
|
||||
- Legacy hosted preview still depends on an active ImGui window context for inline presentation.
|
||||
- Legacy hosted preview still depends on an ImGui-specific inline draw target binding for presentation.
|
||||
- The new panel-canvas seam still only has an ImGui adapter today; a native panel/shell host still needs to replace it before ImGui can stop being the default editor host path.
|
||||
- Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, and icon-atlas widgets are not yet extracted into reusable XCUI modules, and the new keyboard-navigation/property-edit/command-routing models are still only partially integrated.
|
||||
- Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, and icon-atlas widgets are not yet extracted into reusable XCUI modules, and panel-level keyboard mapping plus shell-state adoption are still only partially integrated.
|
||||
|
||||
## Validated This Phase
|
||||
|
||||
- `new_editor_xcui_demo_runtime_tests`: `7/7`
|
||||
- `new_editor_xcui_layout_lab_runtime_tests`: `9/9`
|
||||
- `new_editor_xcui_demo_runtime_tests`: `12/12`
|
||||
- `new_editor_xcui_layout_lab_runtime_tests`: `12/12`
|
||||
- `new_editor_xcui_rhi_command_compiler_tests`: `6/6`
|
||||
- `new_editor_xcui_rhi_render_backend_tests`: `5/5`
|
||||
- `new_editor_xcui_hosted_preview_presenter_tests`: `14/14`
|
||||
- `new_editor_xcui_hosted_preview_presenter_tests`: `17/17`
|
||||
- `new_editor_imgui_window_ui_compositor_tests`: `7/7`
|
||||
- `new_editor_xcui_editor_command_router_tests`: `5/5`
|
||||
- `new_editor_application_shell_command_bindings_tests`: `6/6`
|
||||
- `new_editor_xcui_shell_chrome_state_tests`: `8/8`
|
||||
- `new_editor_xcui_panel_canvas_host_tests`: `2/2`
|
||||
- `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1`
|
||||
- `XCNewEditor` Debug target builds successfully
|
||||
- `core_ui_tests`: `49 total` (`47` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`)
|
||||
- `core_ui_tests`: `52 total` (`50` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`)
|
||||
- `scene_tests`: `68/68`
|
||||
- `core_ui_style_tests`: `5/5`
|
||||
- `ui_resource_tests`: `11/11`
|
||||
@@ -117,6 +129,11 @@ Current gap:
|
||||
- Common-core `UIExpansionModel` extraction now owns reusable expansion/collapse state for tree/property-style widgets, with dedicated `core_ui_tests` coverage.
|
||||
- Common-core `UIKeyboardNavigationModel` extraction now owns reusable current-index/anchor navigation state for collection-style widgets, with dedicated `core_ui_tests` coverage.
|
||||
- Common-core `UIPropertyEditModel` extraction now owns reusable property-field edit session state, including staged values and commit/cancel behavior, with dedicated `core_ui_tests` coverage.
|
||||
- Runtime frame ownership was tightened again:
|
||||
- `UIScreenPlayer::ConsumeLastFrame()` now exposes consume-style packet ownership at the player layer
|
||||
- `UISystemFrameResult` now carries viewport rect, submitted input event count, frame delta, and focus state
|
||||
- `UISystem::ConsumeLastFrame()` moves the retained packet out of the runtime layer
|
||||
- `UISceneRuntimeContext::ConsumeLastFrame()` forwards the same shared runtime seam upward
|
||||
- Demo runtime text editing was extended with:
|
||||
- click-to-place caret
|
||||
- `Delete` support
|
||||
@@ -128,6 +145,7 @@ Current gap:
|
||||
- LayoutLab click-selection now persists through the shared `UISelectionModel`, including selected-state diagnostics and reusable visual selection feedback on cards, collection rows, and field rows.
|
||||
- LayoutLab tree expansion and property-section collapse now persist through the shared `UIExpansionModel`, including reserved property headers, disclosure glyphs, and runtime coverage for collapsed/expanded visibility.
|
||||
- `XCUIDemoRuntime` now exposes `DrainPendingCommandIds()` so hosts can observe emitted runtime commands in order across pointer/text interactions without scraping UI text or draw-command payloads.
|
||||
- `XCUIDemoRuntime` command recording was tightened so pointer activation, text editing, and shortcut-triggered commands now share one bridge path and preserve mixed ordering in `DrainPendingCommandIds()`.
|
||||
- Schema document support extended with:
|
||||
- retained `UISchemaDefinition` data on `UIDocumentModel`
|
||||
- artifact schema version bump for UI documents
|
||||
@@ -174,7 +192,14 @@ Current gap:
|
||||
- The window-level XCUI compositor seam now also has a dedicated regression target around `ImGuiWindowUICompositor`, covering initialization, render-frame ordering, Win32 message forwarding, texture registration forwarding, and shutdown safety.
|
||||
- The window compositor and hosted-preview seams gained more edge-case coverage around no-UI render passes, compositor re-initialization/rebinding, partial logical-size fallback, and descriptor reuse for repeated queued-frame keys.
|
||||
- `new_editor` now has a dedicated `XCUIEditorCommandRouter` test target covering command registration, replacement, enable predicates, accelerator matching, and policy gates around focus/keyboard capture/text input.
|
||||
- `Application` now integrates `XCUIEditorCommandRouter` into the shell itself:
|
||||
- `View` menu items invoke routed commands instead of directly mutating shell state
|
||||
- shell-level shortcuts now flow from `XCUIWin32InputSource` through `XCUIInputBridge` into command matching
|
||||
- hosted-preview mode toggles still trigger presenter reconfiguration through the routed command bindings
|
||||
- `new_editor` panel canvas ownership is now being split behind `IXCUIPanelCanvasHost`, with an `ImGuiXCUIPanelCanvasHost` adapter carrying the legacy path so panel code stops directly owning `ImGui::Image` / `ImGui::InvisibleButton` / draw-list preview plumbing.
|
||||
- `new_editor` now also has a pure `XCUIShellChromeState` model with dedicated tests, covering shell panel visibility, hosted-preview mode, and shell view toggles without depending on ImGui or `Application`.
|
||||
- `XCUIShellChromeState` now also exposes effective hosted-preview state helpers and shell view-toggle command-id helpers, so shell routing code no longer has to manually combine enablement and requested preview mode.
|
||||
- The panel-canvas seam now has dedicated null/imgui backend coverage, including explicit backend/capability reporting and a non-ImGui placeholder host path for future native shell adoption.
|
||||
- `SceneRuntime` layered XCUI routing now has dedicated regression coverage for:
|
||||
- top-interactive layer input ownership
|
||||
- blocking/modal layer suppression of lower layers
|
||||
@@ -185,8 +210,10 @@ Current gap:
|
||||
- generic preview surface metadata stays on XCUI-owned value types
|
||||
- `ImGuiTransitionBackend` moved behind `ImGuiXCUIHostedPreviewPresenter`
|
||||
- generic preview frame submission no longer carries an ImGui draw-list pointer
|
||||
- the ImGui presenter now resolves inline draw targets through an explicit ImGui-only binding seam
|
||||
- panel/runtime callers still preserve the same legacy and native-preview behavior
|
||||
- `LayoutLab` now resolves editor collection widget taxonomy and metrics through shared `UIEditorCollectionPrimitives` helpers instead of duplicating the same tag and metric rules inside the sandbox runtime.
|
||||
- `LayoutLab` runtime now consumes shared keyboard-navigation semantics for list/tree/property traversal, while the remaining panel-level key mapping is tracked as an editor-host integration gap rather than a runtime gap.
|
||||
- `new_editor` panel/shell diagnostics improvements for hosted preview state.
|
||||
- XCUI asset document loading changed to prefer direct source compilation before `ResourceManager` fallback for the sandbox path, fixing the LayoutLab crash.
|
||||
- `UIDocumentCompiler.cpp` repaired enough to restore full local builds after the duplicated schema-helper regression.
|
||||
@@ -198,12 +225,13 @@ Current gap:
|
||||
- `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet.
|
||||
- `Image` widgets still do not have source-rect/atlas-subregion level API in the high-level draw command model.
|
||||
- Editor shell still depends on ImGui as host chrome.
|
||||
- Legacy hosted preview still depends on an ImGui-only inline presenter path when not using the queued native surface path.
|
||||
- Editor widget coverage is still prototype-driven inside `LayoutLab`; it has not yet been promoted into a full reusable shared widget/runtime layer with command routing, virtualization, and property-edit transactions.
|
||||
|
||||
## Next Phase
|
||||
|
||||
1. Expand runtime/game-layer ownership from the current `SceneRuntime` UI context into scene-declared HUD/menu bootstrapping, draw submission, and higher-level runtime UI policies.
|
||||
2. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu and more native shell-owned chrome.
|
||||
2. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu chrome, shell-state adoption, and panel-level keyboard/navigation input plumbing.
|
||||
3. Add a native XCUI host compositor on the existing window-level compositor seam so `new_editor` can present without going through ImGui-owned draw data.
|
||||
4. Reduce remaining ImGui leakage in hosted preview surfaces and panel contracts now that the compositor seam is in place.
|
||||
4. Replace the remaining ImGui-only fallback seams in hosted preview and panel canvas hosting with native host implementations so ImGui can become compatibility-only instead of the default shell path.
|
||||
5. Continue phased validation, commit, push, and plan refresh after each stable batch.
|
||||
|
||||
@@ -19,6 +19,7 @@ public:
|
||||
const UIScreenStackController& GetStackController() const;
|
||||
|
||||
const UISystemFrameResult& GetLastFrame() const;
|
||||
UISystemFrameResult ConsumeLastFrame();
|
||||
|
||||
void Reset();
|
||||
void SetViewportRect(const UIRect& viewportRect);
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
const UIScreenAsset* GetAsset() const;
|
||||
const UIScreenDocument* GetDocument() const;
|
||||
const UIScreenFrameResult& GetLastFrame() const;
|
||||
UIScreenFrameResult ConsumeLastFrame();
|
||||
const std::string& GetLastError() const;
|
||||
std::uint64_t GetPresentedFrameCount() const;
|
||||
|
||||
|
||||
@@ -89,9 +89,13 @@ struct UISystemPresentedLayer {
|
||||
struct UISystemFrameResult {
|
||||
UIDrawData drawData = {};
|
||||
std::vector<UISystemPresentedLayer> layers = {};
|
||||
UIRect viewportRect = {};
|
||||
std::size_t presentedLayerCount = 0;
|
||||
std::size_t skippedLayerCount = 0;
|
||||
std::size_t submittedInputEventCount = 0;
|
||||
std::uint64_t frameIndex = 0;
|
||||
double deltaTimeSeconds = 0.0;
|
||||
bool focused = false;
|
||||
std::string errorMessage = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
const UISystemFrameResult& Update(const UIScreenFrameInput& input);
|
||||
void Tick(const UIScreenFrameInput& input);
|
||||
const UISystemFrameResult& GetLastFrame() const;
|
||||
UISystemFrameResult ConsumeLastFrame();
|
||||
|
||||
const std::vector<std::unique_ptr<UIScreenPlayer>>& GetPlayers() const;
|
||||
|
||||
|
||||
@@ -29,6 +29,10 @@ const UISystemFrameResult& UISceneRuntimeContext::GetLastFrame() const {
|
||||
return m_system.GetLastFrame();
|
||||
}
|
||||
|
||||
UISystemFrameResult UISceneRuntimeContext::ConsumeLastFrame() {
|
||||
return m_system.ConsumeLastFrame();
|
||||
}
|
||||
|
||||
void UISceneRuntimeContext::Reset() {
|
||||
m_stackController.Clear();
|
||||
m_system.DestroyAllPlayers();
|
||||
|
||||
@@ -75,6 +75,12 @@ const UIScreenFrameResult& UIScreenPlayer::GetLastFrame() const {
|
||||
return m_lastFrame;
|
||||
}
|
||||
|
||||
UIScreenFrameResult UIScreenPlayer::ConsumeLastFrame() {
|
||||
UIScreenFrameResult frame = std::move(m_lastFrame);
|
||||
m_lastFrame = {};
|
||||
return frame;
|
||||
}
|
||||
|
||||
const std::string& UIScreenPlayer::GetLastError() const {
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <XCEngine/UI/Runtime/UISystem.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Runtime {
|
||||
@@ -122,6 +124,10 @@ std::size_t UISystem::GetLayerCount() const {
|
||||
const UISystemFrameResult& UISystem::Update(const UIScreenFrameInput& input) {
|
||||
m_lastFrame = {};
|
||||
m_lastFrame.frameIndex = input.frameIndex;
|
||||
m_lastFrame.viewportRect = input.viewportRect;
|
||||
m_lastFrame.submittedInputEventCount = input.events.size();
|
||||
m_lastFrame.deltaTimeSeconds = input.deltaTimeSeconds;
|
||||
m_lastFrame.focused = input.focused;
|
||||
|
||||
if (m_players.empty()) {
|
||||
return m_lastFrame;
|
||||
@@ -177,6 +183,12 @@ const UISystemFrameResult& UISystem::GetLastFrame() const {
|
||||
return m_lastFrame;
|
||||
}
|
||||
|
||||
UISystemFrameResult UISystem::ConsumeLastFrame() {
|
||||
UISystemFrameResult frame = std::move(m_lastFrame);
|
||||
m_lastFrame = {};
|
||||
return frame;
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<UIScreenPlayer>>& UISystem::GetPlayers() const {
|
||||
return m_players;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <XCEngine/UI/Runtime/UIScreenDocumentHost.h>
|
||||
#include <XCEngine/UI/Runtime/UIScreenPlayer.h>
|
||||
#include <XCEngine/UI/Runtime/UISceneRuntimeContext.h>
|
||||
#include <XCEngine/UI/Runtime/UIScreenStackController.h>
|
||||
#include <XCEngine/UI/Runtime/UISystem.h>
|
||||
|
||||
@@ -9,12 +10,14 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::UI::Runtime::UIScreenAsset;
|
||||
using XCEngine::UI::Runtime::UIScreenFrameInput;
|
||||
using XCEngine::UI::Runtime::UIScreenPlayer;
|
||||
using XCEngine::UI::Runtime::UISceneRuntimeContext;
|
||||
using XCEngine::UI::Runtime::UIDocumentScreenHost;
|
||||
using XCEngine::UI::Runtime::UIScreenStackController;
|
||||
using XCEngine::UI::Runtime::UISystem;
|
||||
@@ -93,6 +96,69 @@ UIScreenFrameInput BuildInputState(std::uint64_t frameIndex = 1u) {
|
||||
return input;
|
||||
}
|
||||
|
||||
const XCEngine::UI::Runtime::UISystemPresentedLayer* FindPresentedLayerById(
|
||||
const XCEngine::UI::Runtime::UISystemFrameResult& frame,
|
||||
XCEngine::UI::Runtime::UIScreenLayerId layerId) {
|
||||
for (const XCEngine::UI::Runtime::UISystemPresentedLayer& layer : frame.layers) {
|
||||
if (layer.layerId == layerId) {
|
||||
return &layer;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class RecordingDocumentHost final : public XCEngine::UI::Runtime::IUIScreenDocumentHost {
|
||||
public:
|
||||
struct BuildCall {
|
||||
std::string displayName = {};
|
||||
UIScreenFrameInput input = {};
|
||||
};
|
||||
|
||||
XCEngine::UI::Runtime::UIScreenLoadResult LoadScreen(const UIScreenAsset& asset) override {
|
||||
XCEngine::UI::Runtime::UIScreenLoadResult result = {};
|
||||
result.succeeded = asset.IsValid();
|
||||
result.document.sourcePath = asset.documentPath;
|
||||
result.document.displayName = asset.screenId.empty() ? asset.documentPath : asset.screenId;
|
||||
return result;
|
||||
}
|
||||
|
||||
XCEngine::UI::Runtime::UIScreenFrameResult BuildFrame(
|
||||
const XCEngine::UI::Runtime::UIScreenDocument& document,
|
||||
const UIScreenFrameInput& input) override {
|
||||
m_buildCalls.push_back(BuildCall{ document.displayName, input });
|
||||
|
||||
XCEngine::UI::Runtime::UIScreenFrameResult result = {};
|
||||
result.stats.documentLoaded = true;
|
||||
result.stats.inputEventCount = input.events.size();
|
||||
result.stats.presentedFrameIndex = input.frameIndex;
|
||||
XCEngine::UI::UIDrawList& drawList = result.drawData.EmplaceDrawList(document.displayName);
|
||||
drawList.AddText(
|
||||
XCEngine::UI::UIPoint(input.viewportRect.x, input.viewportRect.y),
|
||||
document.displayName);
|
||||
result.stats.drawListCount = result.drawData.GetDrawListCount();
|
||||
result.stats.commandCount = result.drawData.GetTotalCommandCount();
|
||||
return result;
|
||||
}
|
||||
|
||||
const BuildCall* FindBuildCall(const std::string& displayName) const {
|
||||
for (const BuildCall& call : m_buildCalls) {
|
||||
if (call.displayName == displayName) {
|
||||
return &call;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::size_t GetBuildCallCount() const {
|
||||
return m_buildCalls.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<BuildCall> m_buildCalls = {};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UIRuntimeTest, ScreenPlayerBuildsDrawDataFromDocumentTree) {
|
||||
@@ -115,6 +181,52 @@ TEST(UIRuntimeTest, ScreenPlayerBuildsDrawDataFromDocumentTree) {
|
||||
EXPECT_EQ(player.GetPresentedFrameCount(), 1u);
|
||||
}
|
||||
|
||||
TEST(UIRuntimeTest, ScreenPlayerConsumeLastFrameReturnsDetachedPacketAndClearsBorrowedState) {
|
||||
TempFileScope viewFile("xcui_runtime_consume_player", ".xcui", BuildViewMarkup("Runtime Consume"));
|
||||
UIDocumentScreenHost host = {};
|
||||
UIScreenPlayer player(host);
|
||||
|
||||
ASSERT_TRUE(player.Load(BuildScreenAsset(viewFile.Path(), "runtime.consume.player")));
|
||||
|
||||
const auto& firstFrame = player.Update(BuildInputState(2u));
|
||||
ASSERT_TRUE(firstFrame.stats.documentLoaded);
|
||||
EXPECT_EQ(firstFrame.stats.presentedFrameIndex, 2u);
|
||||
EXPECT_TRUE(DrawDataContainsText(firstFrame.drawData, "Runtime Consume"));
|
||||
|
||||
XCEngine::UI::Runtime::UIScreenFrameResult consumedFrame = player.ConsumeLastFrame();
|
||||
EXPECT_TRUE(consumedFrame.stats.documentLoaded);
|
||||
EXPECT_EQ(consumedFrame.stats.presentedFrameIndex, 2u);
|
||||
EXPECT_EQ(consumedFrame.stats.drawListCount, consumedFrame.drawData.GetDrawListCount());
|
||||
EXPECT_EQ(consumedFrame.stats.commandCount, consumedFrame.drawData.GetTotalCommandCount());
|
||||
EXPECT_TRUE(DrawDataContainsText(consumedFrame.drawData, "Runtime Consume"));
|
||||
EXPECT_EQ(player.GetPresentedFrameCount(), 1u);
|
||||
|
||||
const auto& clearedFrame = player.GetLastFrame();
|
||||
EXPECT_FALSE(clearedFrame.stats.documentLoaded);
|
||||
EXPECT_EQ(clearedFrame.stats.presentedFrameIndex, 0u);
|
||||
EXPECT_EQ(clearedFrame.drawData.GetDrawListCount(), 0u);
|
||||
EXPECT_TRUE(clearedFrame.errorMessage.empty());
|
||||
|
||||
const auto& secondFrame = player.Update(BuildInputState(3u));
|
||||
EXPECT_TRUE(secondFrame.stats.documentLoaded);
|
||||
EXPECT_EQ(secondFrame.stats.presentedFrameIndex, 3u);
|
||||
EXPECT_TRUE(DrawDataContainsText(secondFrame.drawData, "Runtime Consume"));
|
||||
EXPECT_EQ(player.GetPresentedFrameCount(), 2u);
|
||||
|
||||
EXPECT_EQ(consumedFrame.stats.presentedFrameIndex, 2u);
|
||||
EXPECT_TRUE(DrawDataContainsText(consumedFrame.drawData, "Runtime Consume"));
|
||||
|
||||
const XCEngine::UI::Runtime::UIScreenFrameResult emptyFrame = player.ConsumeLastFrame();
|
||||
EXPECT_TRUE(emptyFrame.stats.documentLoaded);
|
||||
EXPECT_EQ(emptyFrame.stats.presentedFrameIndex, 3u);
|
||||
EXPECT_TRUE(DrawDataContainsText(emptyFrame.drawData, "Runtime Consume"));
|
||||
|
||||
const XCEngine::UI::Runtime::UIScreenFrameResult clearedAgain = player.ConsumeLastFrame();
|
||||
EXPECT_FALSE(clearedAgain.stats.documentLoaded);
|
||||
EXPECT_EQ(clearedAgain.stats.presentedFrameIndex, 0u);
|
||||
EXPECT_EQ(clearedAgain.drawData.GetDrawListCount(), 0u);
|
||||
}
|
||||
|
||||
TEST(UIRuntimeTest, UISystemForwardsActiveScreenToPlayer) {
|
||||
TempFileScope baseView("xcui_runtime_base", ".xcui", BuildViewMarkup("Base Screen"));
|
||||
TempFileScope overlayView("xcui_runtime_overlay", ".xcui", BuildViewMarkup("Overlay Screen", "Modal Dialog"));
|
||||
@@ -236,3 +348,224 @@ TEST(UIRuntimeTest, ScreenStackControllerReplaceTopKeepsPreviousScreenWhenReplac
|
||||
EXPECT_EQ(frame.presentedLayerCount, 1u);
|
||||
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Pause Menu"));
|
||||
}
|
||||
|
||||
TEST(UIRuntimeTest, RoutesViewportAndFocusOnlyToTopInteractiveVisibleLayer) {
|
||||
RecordingDocumentHost host = {};
|
||||
UISystem system(host);
|
||||
|
||||
XCEngine::UI::Runtime::UIScreenLayerOptions gameplayOptions = {};
|
||||
gameplayOptions.debugName = "gameplay";
|
||||
gameplayOptions.acceptsInput = true;
|
||||
gameplayOptions.blocksLayersBelow = false;
|
||||
|
||||
XCEngine::UI::Runtime::UIScreenLayerOptions overlayOptions = {};
|
||||
overlayOptions.debugName = "overlay";
|
||||
overlayOptions.acceptsInput = true;
|
||||
overlayOptions.blocksLayersBelow = false;
|
||||
|
||||
const auto gameplayLayerId = system.PushScreen(
|
||||
BuildScreenAsset(fs::path("gameplay_view.xcui"), "runtime.gameplay"),
|
||||
gameplayOptions);
|
||||
const auto overlayLayerId = system.PushScreen(
|
||||
BuildScreenAsset(fs::path("overlay_view.xcui"), "runtime.overlay"),
|
||||
overlayOptions);
|
||||
ASSERT_NE(gameplayLayerId, 0u);
|
||||
ASSERT_NE(overlayLayerId, 0u);
|
||||
|
||||
UIScreenFrameInput input = BuildInputState(8u);
|
||||
input.viewportRect = XCEngine::UI::UIRect(15.0f, 25.0f, 1024.0f, 576.0f);
|
||||
XCEngine::UI::UIInputEvent textEvent = {};
|
||||
textEvent.type = XCEngine::UI::UIInputEventType::Character;
|
||||
textEvent.character = 'I';
|
||||
input.events.push_back(textEvent);
|
||||
|
||||
const auto& frame = system.Update(input);
|
||||
ASSERT_EQ(frame.presentedLayerCount, 2u);
|
||||
ASSERT_EQ(frame.layers.size(), 2u);
|
||||
ASSERT_EQ(host.GetBuildCallCount(), 2u);
|
||||
|
||||
const auto* gameplayCall = host.FindBuildCall("runtime.gameplay");
|
||||
const auto* overlayCall = host.FindBuildCall("runtime.overlay");
|
||||
ASSERT_NE(gameplayCall, nullptr);
|
||||
ASSERT_NE(overlayCall, nullptr);
|
||||
|
||||
EXPECT_EQ(gameplayCall->input.viewportRect.x, input.viewportRect.x);
|
||||
EXPECT_EQ(gameplayCall->input.viewportRect.y, input.viewportRect.y);
|
||||
EXPECT_EQ(gameplayCall->input.viewportRect.width, input.viewportRect.width);
|
||||
EXPECT_EQ(gameplayCall->input.viewportRect.height, input.viewportRect.height);
|
||||
EXPECT_TRUE(gameplayCall->input.events.empty());
|
||||
EXPECT_FALSE(gameplayCall->input.focused);
|
||||
EXPECT_EQ(gameplayCall->input.frameIndex, input.frameIndex);
|
||||
|
||||
EXPECT_EQ(overlayCall->input.viewportRect.x, input.viewportRect.x);
|
||||
EXPECT_EQ(overlayCall->input.viewportRect.y, input.viewportRect.y);
|
||||
EXPECT_EQ(overlayCall->input.viewportRect.width, input.viewportRect.width);
|
||||
EXPECT_EQ(overlayCall->input.viewportRect.height, input.viewportRect.height);
|
||||
EXPECT_EQ(overlayCall->input.events.size(), 1u);
|
||||
EXPECT_TRUE(overlayCall->input.focused);
|
||||
EXPECT_EQ(overlayCall->input.frameIndex, input.frameIndex);
|
||||
|
||||
const auto* gameplayLayer = FindPresentedLayerById(frame, gameplayLayerId);
|
||||
const auto* overlayLayer = FindPresentedLayerById(frame, overlayLayerId);
|
||||
ASSERT_NE(gameplayLayer, nullptr);
|
||||
ASSERT_NE(overlayLayer, nullptr);
|
||||
EXPECT_EQ(gameplayLayer->stats.inputEventCount, 0u);
|
||||
EXPECT_EQ(overlayLayer->stats.inputEventCount, 1u);
|
||||
}
|
||||
|
||||
TEST(UIRuntimeTest, HiddenTopLayerLeavesUnderlyingLayerFocusedAndInteractive) {
|
||||
RecordingDocumentHost host = {};
|
||||
UISystem system(host);
|
||||
|
||||
XCEngine::UI::Runtime::UIScreenLayerOptions visibleOptions = {};
|
||||
visibleOptions.debugName = "visible";
|
||||
visibleOptions.acceptsInput = true;
|
||||
visibleOptions.blocksLayersBelow = false;
|
||||
|
||||
XCEngine::UI::Runtime::UIScreenLayerOptions hiddenOptions = {};
|
||||
hiddenOptions.debugName = "hidden";
|
||||
hiddenOptions.visible = false;
|
||||
hiddenOptions.acceptsInput = true;
|
||||
hiddenOptions.blocksLayersBelow = false;
|
||||
|
||||
const auto visibleLayerId = system.PushScreen(
|
||||
BuildScreenAsset(fs::path("visible_view.xcui"), "runtime.visible"),
|
||||
visibleOptions);
|
||||
const auto hiddenLayerId = system.PushScreen(
|
||||
BuildScreenAsset(fs::path("hidden_view.xcui"), "runtime.hidden"),
|
||||
hiddenOptions);
|
||||
ASSERT_NE(visibleLayerId, 0u);
|
||||
ASSERT_NE(hiddenLayerId, 0u);
|
||||
|
||||
UIScreenFrameInput input = BuildInputState(9u);
|
||||
input.viewportRect = XCEngine::UI::UIRect(40.0f, 60.0f, 700.0f, 420.0f);
|
||||
XCEngine::UI::UIInputEvent keyEvent = {};
|
||||
keyEvent.type = XCEngine::UI::UIInputEventType::KeyDown;
|
||||
keyEvent.keyCode = 32;
|
||||
input.events.push_back(keyEvent);
|
||||
|
||||
const auto& frame = system.Update(input);
|
||||
ASSERT_EQ(frame.presentedLayerCount, 1u);
|
||||
ASSERT_EQ(frame.skippedLayerCount, 1u);
|
||||
ASSERT_EQ(frame.layers.size(), 1u);
|
||||
ASSERT_EQ(host.GetBuildCallCount(), 1u);
|
||||
|
||||
const auto* visibleCall = host.FindBuildCall("runtime.visible");
|
||||
ASSERT_NE(visibleCall, nullptr);
|
||||
EXPECT_EQ(visibleCall->input.viewportRect.x, input.viewportRect.x);
|
||||
EXPECT_EQ(visibleCall->input.viewportRect.y, input.viewportRect.y);
|
||||
EXPECT_EQ(visibleCall->input.viewportRect.width, input.viewportRect.width);
|
||||
EXPECT_EQ(visibleCall->input.viewportRect.height, input.viewportRect.height);
|
||||
EXPECT_EQ(visibleCall->input.events.size(), 1u);
|
||||
EXPECT_TRUE(visibleCall->input.focused);
|
||||
EXPECT_EQ(host.FindBuildCall("runtime.hidden"), nullptr);
|
||||
|
||||
const auto* visibleLayer = FindPresentedLayerById(frame, visibleLayerId);
|
||||
ASSERT_NE(visibleLayer, nullptr);
|
||||
EXPECT_EQ(visibleLayer->stats.inputEventCount, 1u);
|
||||
EXPECT_EQ(FindPresentedLayerById(frame, hiddenLayerId), nullptr);
|
||||
}
|
||||
|
||||
TEST(UIRuntimeTest, UISystemConsumeLastFrameReturnsDetachedPresentationPacket) {
|
||||
RecordingDocumentHost host = {};
|
||||
UISystem system(host);
|
||||
|
||||
XCEngine::UI::Runtime::UIScreenLayerOptions options = {};
|
||||
options.debugName = "runtime";
|
||||
const auto layerId = system.PushScreen(
|
||||
BuildScreenAsset(fs::path("runtime_consume_view.xcui"), "runtime.consume"),
|
||||
options);
|
||||
ASSERT_NE(layerId, 0u);
|
||||
|
||||
UIScreenFrameInput input = BuildInputState(12u);
|
||||
input.viewportRect = XCEngine::UI::UIRect(48.0f, 72.0f, 1280.0f, 720.0f);
|
||||
input.deltaTimeSeconds = 1.0 / 30.0;
|
||||
XCEngine::UI::UIInputEvent textEvent = {};
|
||||
textEvent.type = XCEngine::UI::UIInputEventType::Character;
|
||||
textEvent.character = 'R';
|
||||
input.events.push_back(textEvent);
|
||||
|
||||
const auto& frame = system.Update(input);
|
||||
ASSERT_EQ(frame.presentedLayerCount, 1u);
|
||||
EXPECT_EQ(frame.viewportRect.x, input.viewportRect.x);
|
||||
EXPECT_EQ(frame.viewportRect.y, input.viewportRect.y);
|
||||
EXPECT_EQ(frame.viewportRect.width, input.viewportRect.width);
|
||||
EXPECT_EQ(frame.viewportRect.height, input.viewportRect.height);
|
||||
EXPECT_EQ(frame.submittedInputEventCount, 1u);
|
||||
EXPECT_DOUBLE_EQ(frame.deltaTimeSeconds, input.deltaTimeSeconds);
|
||||
EXPECT_TRUE(frame.focused);
|
||||
|
||||
XCEngine::UI::Runtime::UISystemFrameResult consumedFrame = system.ConsumeLastFrame();
|
||||
EXPECT_EQ(consumedFrame.frameIndex, input.frameIndex);
|
||||
EXPECT_EQ(consumedFrame.presentedLayerCount, 1u);
|
||||
EXPECT_EQ(consumedFrame.layers.size(), 1u);
|
||||
EXPECT_EQ(consumedFrame.layers.front().layerId, layerId);
|
||||
EXPECT_EQ(consumedFrame.viewportRect.x, input.viewportRect.x);
|
||||
EXPECT_EQ(consumedFrame.viewportRect.y, input.viewportRect.y);
|
||||
EXPECT_EQ(consumedFrame.viewportRect.width, input.viewportRect.width);
|
||||
EXPECT_EQ(consumedFrame.viewportRect.height, input.viewportRect.height);
|
||||
EXPECT_EQ(consumedFrame.submittedInputEventCount, 1u);
|
||||
EXPECT_DOUBLE_EQ(consumedFrame.deltaTimeSeconds, input.deltaTimeSeconds);
|
||||
EXPECT_TRUE(consumedFrame.focused);
|
||||
EXPECT_TRUE(DrawDataContainsText(consumedFrame.drawData, "runtime.consume"));
|
||||
|
||||
const auto& clearedFrame = system.GetLastFrame();
|
||||
EXPECT_EQ(clearedFrame.frameIndex, 0u);
|
||||
EXPECT_EQ(clearedFrame.presentedLayerCount, 0u);
|
||||
EXPECT_EQ(clearedFrame.submittedInputEventCount, 0u);
|
||||
EXPECT_TRUE(clearedFrame.layers.empty());
|
||||
EXPECT_EQ(clearedFrame.drawData.GetDrawListCount(), 0u);
|
||||
|
||||
const XCEngine::UI::Runtime::UISystemFrameResult emptyFrame = system.ConsumeLastFrame();
|
||||
EXPECT_EQ(emptyFrame.frameIndex, 0u);
|
||||
EXPECT_TRUE(emptyFrame.layers.empty());
|
||||
EXPECT_EQ(emptyFrame.drawData.GetDrawListCount(), 0u);
|
||||
}
|
||||
|
||||
TEST(UIRuntimeTest, SceneRuntimeContextConsumeLastFrameForwardsPresentationSnapshot) {
|
||||
TempFileScope viewFile("xcui_runtime_context", ".xcui", BuildViewMarkup("Runtime Context"));
|
||||
UISceneRuntimeContext runtimeContext = {};
|
||||
|
||||
const auto layerId = runtimeContext.GetStackController().PushMenu(
|
||||
BuildScreenAsset(viewFile.Path(), "runtime.context"),
|
||||
"context");
|
||||
ASSERT_NE(layerId, 0u);
|
||||
|
||||
const XCEngine::UI::UIRect viewportRect(24.0f, 32.0f, 960.0f, 540.0f);
|
||||
runtimeContext.SetViewportRect(viewportRect);
|
||||
runtimeContext.SetFocused(true);
|
||||
|
||||
XCEngine::UI::UIInputEvent textEvent = {};
|
||||
textEvent.type = XCEngine::UI::UIInputEventType::Character;
|
||||
textEvent.character = 'C';
|
||||
runtimeContext.QueueInputEvent(textEvent);
|
||||
|
||||
runtimeContext.Update(0.25);
|
||||
|
||||
XCEngine::UI::Runtime::UISystemFrameResult consumedFrame = runtimeContext.ConsumeLastFrame();
|
||||
EXPECT_EQ(consumedFrame.frameIndex, 1u);
|
||||
EXPECT_EQ(consumedFrame.presentedLayerCount, 1u);
|
||||
EXPECT_EQ(consumedFrame.layers.size(), 1u);
|
||||
EXPECT_EQ(consumedFrame.layers.front().layerId, layerId);
|
||||
EXPECT_EQ(consumedFrame.viewportRect.x, viewportRect.x);
|
||||
EXPECT_EQ(consumedFrame.viewportRect.y, viewportRect.y);
|
||||
EXPECT_EQ(consumedFrame.viewportRect.width, viewportRect.width);
|
||||
EXPECT_EQ(consumedFrame.viewportRect.height, viewportRect.height);
|
||||
EXPECT_EQ(consumedFrame.submittedInputEventCount, 1u);
|
||||
EXPECT_DOUBLE_EQ(consumedFrame.deltaTimeSeconds, 0.25);
|
||||
EXPECT_TRUE(consumedFrame.focused);
|
||||
EXPECT_TRUE(DrawDataContainsText(consumedFrame.drawData, "Runtime Context"));
|
||||
|
||||
const auto& clearedFrame = runtimeContext.GetLastFrame();
|
||||
EXPECT_EQ(clearedFrame.frameIndex, 0u);
|
||||
EXPECT_EQ(clearedFrame.presentedLayerCount, 0u);
|
||||
EXPECT_TRUE(clearedFrame.layers.empty());
|
||||
EXPECT_EQ(clearedFrame.drawData.GetDrawListCount(), 0u);
|
||||
|
||||
runtimeContext.Update(0.5);
|
||||
const auto& secondFrame = runtimeContext.GetLastFrame();
|
||||
EXPECT_EQ(secondFrame.frameIndex, 2u);
|
||||
EXPECT_EQ(secondFrame.submittedInputEventCount, 0u);
|
||||
EXPECT_DOUBLE_EQ(secondFrame.deltaTimeSeconds, 0.5);
|
||||
EXPECT_TRUE(secondFrame.focused);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,15 @@ set(NEW_EDITOR_LAYOUT_LAB_RUNTIME_HEADER
|
||||
set(NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUILayoutLabRuntime.cpp
|
||||
)
|
||||
set(NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUILayoutLabPanel.h
|
||||
)
|
||||
set(NEW_EDITOR_LAYOUT_LAB_PANEL_SOURCE
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUILayoutLabPanel.cpp
|
||||
)
|
||||
set(NEW_EDITOR_BASE_PANEL_SOURCE
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/panels/Panel.cpp
|
||||
)
|
||||
set(NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIAssetDocumentSource.h
|
||||
)
|
||||
@@ -64,6 +73,15 @@ set(NEW_EDITOR_COMMAND_ROUTER_HEADER
|
||||
set(NEW_EDITOR_COMMAND_ROUTER_SOURCE
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIEditorCommandRouter.cpp
|
||||
)
|
||||
set(NEW_EDITOR_SHELL_CHROME_STATE_HEADER
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIShellChromeState.h
|
||||
)
|
||||
set(NEW_EDITOR_SHELL_CHROME_STATE_SOURCE
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIShellChromeState.cpp
|
||||
)
|
||||
set(NEW_EDITOR_APPLICATION_HEADER
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/Application.h
|
||||
)
|
||||
set(NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiXCUIInputAdapter.h
|
||||
)
|
||||
@@ -213,6 +231,52 @@ else()
|
||||
message(STATUS "Skipping new_editor_xcui_layout_lab_runtime_tests because XCUILayoutLabRuntime files are missing.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER}" AND
|
||||
EXISTS "${NEW_EDITOR_LAYOUT_LAB_PANEL_SOURCE}" AND
|
||||
EXISTS "${NEW_EDITOR_LAYOUT_LAB_RUNTIME_HEADER}" AND
|
||||
EXISTS "${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE}" AND
|
||||
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND
|
||||
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}" AND
|
||||
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_layout_lab_panel.cpp" AND
|
||||
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
|
||||
add_executable(new_editor_xcui_layout_lab_panel_tests
|
||||
test_xcui_layout_lab_panel.cpp
|
||||
${NEW_EDITOR_LAYOUT_LAB_PANEL_SOURCE}
|
||||
${NEW_EDITOR_BASE_PANEL_SOURCE}
|
||||
${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE}
|
||||
${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_widgets.cpp
|
||||
)
|
||||
|
||||
xcengine_configure_new_editor_test_target(new_editor_xcui_layout_lab_panel_tests)
|
||||
|
||||
target_link_libraries(new_editor_xcui_layout_lab_panel_tests
|
||||
PRIVATE
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
user32
|
||||
comdlg32
|
||||
)
|
||||
|
||||
target_include_directories(new_editor_xcui_layout_lab_panel_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/backends
|
||||
)
|
||||
target_compile_definitions(new_editor_xcui_layout_lab_panel_tests PRIVATE
|
||||
XCENGINE_NEW_EDITOR_REPO_ROOT="${XCENGINE_TEST_REPO_ROOT_CMAKE}"
|
||||
)
|
||||
|
||||
xcengine_discover_new_editor_gtests(new_editor_xcui_layout_lab_panel_tests)
|
||||
else()
|
||||
message(STATUS "Skipping new_editor_xcui_layout_lab_panel_tests because panel, runtime, test, or ImGui sources are missing.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${NEW_EDITOR_BACKEND_HEADER}" AND EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
|
||||
add_executable(new_editor_imgui_transition_backend_tests
|
||||
test_new_editor_imgui_transition_backend.cpp
|
||||
@@ -388,6 +452,63 @@ else()
|
||||
message(STATUS "Skipping new_editor_xcui_editor_command_router_tests because command router files or the test source are missing.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${NEW_EDITOR_SHELL_CHROME_STATE_HEADER}" AND
|
||||
EXISTS "${NEW_EDITOR_SHELL_CHROME_STATE_SOURCE}" AND
|
||||
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_shell_chrome_state.cpp")
|
||||
add_executable(new_editor_xcui_shell_chrome_state_tests
|
||||
test_xcui_shell_chrome_state.cpp
|
||||
${NEW_EDITOR_SHELL_CHROME_STATE_SOURCE}
|
||||
)
|
||||
|
||||
xcengine_configure_new_editor_test_target(new_editor_xcui_shell_chrome_state_tests)
|
||||
|
||||
target_link_libraries(new_editor_xcui_shell_chrome_state_tests
|
||||
PRIVATE
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_include_directories(new_editor_xcui_shell_chrome_state_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src
|
||||
)
|
||||
|
||||
xcengine_discover_new_editor_gtests(new_editor_xcui_shell_chrome_state_tests)
|
||||
else()
|
||||
message(STATUS "Skipping new_editor_xcui_shell_chrome_state_tests because shell chrome state files or the test source are missing.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${NEW_EDITOR_APPLICATION_HEADER}" AND
|
||||
EXISTS "${NEW_EDITOR_COMMAND_ROUTER_SOURCE}" AND
|
||||
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_application_shell_command_bindings.cpp")
|
||||
add_executable(new_editor_application_shell_command_bindings_tests
|
||||
test_application_shell_command_bindings.cpp
|
||||
${NEW_EDITOR_COMMAND_ROUTER_SOURCE}
|
||||
)
|
||||
|
||||
xcengine_configure_new_editor_test_target(new_editor_application_shell_command_bindings_tests)
|
||||
|
||||
target_link_libraries(new_editor_application_shell_command_bindings_tests
|
||||
PRIVATE
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_include_directories(new_editor_application_shell_command_bindings_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src
|
||||
${CMAKE_SOURCE_DIR}/editor/src
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/backends
|
||||
)
|
||||
|
||||
xcengine_discover_new_editor_gtests(new_editor_application_shell_command_bindings_tests)
|
||||
else()
|
||||
message(STATUS "Skipping new_editor_application_shell_command_bindings_tests because Application header, command router source, or the test source are missing.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND
|
||||
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}" AND
|
||||
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_imgui_xcui_input_adapter.cpp" AND
|
||||
@@ -576,3 +697,63 @@ if(EXISTS "${NEW_EDITOR_RHI_COMMAND_COMPILER_HEADER}" AND
|
||||
else()
|
||||
message(STATUS "Skipping new_editor_xcui_rhi_command_compiler_tests because compiler files are missing.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIPanelCanvasHost.h" AND
|
||||
EXISTS "${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/NullXCUIPanelCanvasHost.h" AND
|
||||
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_panel_canvas_host.cpp")
|
||||
add_executable(new_editor_xcui_panel_canvas_host_tests
|
||||
test_xcui_panel_canvas_host.cpp
|
||||
)
|
||||
|
||||
xcengine_configure_new_editor_test_target(new_editor_xcui_panel_canvas_host_tests)
|
||||
|
||||
target_link_libraries(new_editor_xcui_panel_canvas_host_tests
|
||||
PRIVATE
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_include_directories(new_editor_xcui_panel_canvas_host_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src
|
||||
)
|
||||
|
||||
xcengine_discover_new_editor_gtests(new_editor_xcui_panel_canvas_host_tests)
|
||||
else()
|
||||
message(STATUS "Skipping new_editor_xcui_panel_canvas_host_tests because panel canvas host headers or the test source are missing.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiXCUIPanelCanvasHost.h" AND
|
||||
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_imgui_xcui_panel_canvas_host.cpp" AND
|
||||
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
|
||||
add_executable(new_editor_imgui_xcui_panel_canvas_host_tests
|
||||
test_imgui_xcui_panel_canvas_host.cpp
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_widgets.cpp
|
||||
)
|
||||
|
||||
xcengine_configure_new_editor_test_target(new_editor_imgui_xcui_panel_canvas_host_tests)
|
||||
|
||||
target_link_libraries(new_editor_imgui_xcui_panel_canvas_host_tests
|
||||
PRIVATE
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
user32
|
||||
comdlg32
|
||||
)
|
||||
|
||||
target_include_directories(new_editor_imgui_xcui_panel_canvas_host_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/backends
|
||||
)
|
||||
|
||||
xcengine_discover_new_editor_gtests(new_editor_imgui_xcui_panel_canvas_host_tests)
|
||||
else()
|
||||
message(STATUS "Skipping new_editor_imgui_xcui_panel_canvas_host_tests because the ImGui host header, test source, or ImGui sources are missing.")
|
||||
endif()
|
||||
|
||||
250
tests/NewEditor/test_application_shell_command_bindings.cpp
Normal file
250
tests/NewEditor/test_application_shell_command_bindings.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
#include "Application.h"
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta;
|
||||
using XCEngine::Input::KeyCode;
|
||||
using XCEngine::NewEditor::Application;
|
||||
|
||||
constexpr std::size_t ToPanelIndex(Application::ShellPanelId panelId) {
|
||||
return static_cast<std::size_t>(panelId);
|
||||
}
|
||||
|
||||
struct ShellCommandHarness {
|
||||
Application::ShellViewToggleState viewToggles = {};
|
||||
std::array<Application::ShellPanelChromeState, static_cast<std::size_t>(Application::ShellPanelId::Count)>
|
||||
panels = {};
|
||||
int hostedPreviewReconfigureCount = 0;
|
||||
|
||||
ShellCommandHarness() {
|
||||
panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)] = {
|
||||
Application::ShellPanelId::XCUIDemo,
|
||||
"XCUI Demo",
|
||||
"XCUI Demo",
|
||||
"new_editor.panels.xcui_demo",
|
||||
true,
|
||||
true,
|
||||
Application::ShellHostedPreviewMode::NativeOffscreen
|
||||
};
|
||||
panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)] = {
|
||||
Application::ShellPanelId::XCUILayoutLab,
|
||||
"XCUI Layout Lab",
|
||||
"XCUI Layout Lab",
|
||||
"new_editor.panels.xcui_layout_lab",
|
||||
true,
|
||||
true,
|
||||
Application::ShellHostedPreviewMode::LegacyImGui
|
||||
};
|
||||
}
|
||||
|
||||
Application::ShellPanelChromeState& Panel(Application::ShellPanelId panelId) {
|
||||
return panels[ToPanelIndex(panelId)];
|
||||
}
|
||||
|
||||
const Application::ShellPanelChromeState& Panel(Application::ShellPanelId panelId) const {
|
||||
return panels[ToPanelIndex(panelId)];
|
||||
}
|
||||
|
||||
Application::ShellCommandBindings BuildBindings() {
|
||||
Application::ShellCommandBindings bindings = {};
|
||||
bindings.getXCUIDemoPanelVisible = [this]() { return Panel(Application::ShellPanelId::XCUIDemo).visible; };
|
||||
bindings.setXCUIDemoPanelVisible = [this](bool visible) {
|
||||
Panel(Application::ShellPanelId::XCUIDemo).visible = visible;
|
||||
};
|
||||
bindings.getXCUILayoutLabPanelVisible = [this]() {
|
||||
return Panel(Application::ShellPanelId::XCUILayoutLab).visible;
|
||||
};
|
||||
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
|
||||
Panel(Application::ShellPanelId::XCUILayoutLab).visible = visible;
|
||||
};
|
||||
bindings.getImGuiDemoWindowVisible = [this]() { return viewToggles.imguiDemoWindowVisible; };
|
||||
bindings.setImGuiDemoWindowVisible = [this](bool visible) { viewToggles.imguiDemoWindowVisible = visible; };
|
||||
bindings.getNativeBackdropVisible = [this]() { return viewToggles.nativeBackdropVisible; };
|
||||
bindings.setNativeBackdropVisible = [this](bool visible) { viewToggles.nativeBackdropVisible = visible; };
|
||||
bindings.getPulseAccentEnabled = [this]() { return viewToggles.pulseAccentEnabled; };
|
||||
bindings.setPulseAccentEnabled = [this](bool enabled) { viewToggles.pulseAccentEnabled = enabled; };
|
||||
bindings.getNativeXCUIOverlayVisible = [this]() { return viewToggles.nativeXCUIOverlayVisible; };
|
||||
bindings.setNativeXCUIOverlayVisible = [this](bool visible) { viewToggles.nativeXCUIOverlayVisible = visible; };
|
||||
bindings.getHostedPreviewHudVisible = [this]() { return viewToggles.hostedPreviewHudVisible; };
|
||||
bindings.setHostedPreviewHudVisible = [this](bool visible) { viewToggles.hostedPreviewHudVisible = visible; };
|
||||
bindings.getNativeDemoPanelPreviewEnabled = [this]() {
|
||||
return Panel(Application::ShellPanelId::XCUIDemo).previewMode ==
|
||||
Application::ShellHostedPreviewMode::NativeOffscreen;
|
||||
};
|
||||
bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) {
|
||||
Panel(Application::ShellPanelId::XCUIDemo).previewMode =
|
||||
enabled
|
||||
? Application::ShellHostedPreviewMode::NativeOffscreen
|
||||
: Application::ShellHostedPreviewMode::LegacyImGui;
|
||||
};
|
||||
bindings.getNativeLayoutLabPreviewEnabled = [this]() {
|
||||
return Panel(Application::ShellPanelId::XCUILayoutLab).previewMode ==
|
||||
Application::ShellHostedPreviewMode::NativeOffscreen;
|
||||
};
|
||||
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
|
||||
Panel(Application::ShellPanelId::XCUILayoutLab).previewMode =
|
||||
enabled
|
||||
? Application::ShellHostedPreviewMode::NativeOffscreen
|
||||
: Application::ShellHostedPreviewMode::LegacyImGui;
|
||||
};
|
||||
bindings.onHostedPreviewModeChanged = [this]() { ++hostedPreviewReconfigureCount; };
|
||||
return bindings;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(ApplicationShellCommandBindingsTest, RegisterShellViewCommandsInvokesBoundToggleHandlers) {
|
||||
ShellCommandHarness harness = {};
|
||||
XCUIEditorCommandRouter router = {};
|
||||
|
||||
Application::RegisterShellViewCommands(router, harness.BuildBindings());
|
||||
|
||||
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleXCUIDemoPanel));
|
||||
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleXCUILayoutLabPanel));
|
||||
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleImGuiDemoWindow));
|
||||
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeBackdrop));
|
||||
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::TogglePulseAccent));
|
||||
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeXCUIOverlay));
|
||||
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleHostedPreviewHud));
|
||||
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeDemoPanelPreview));
|
||||
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeLayoutLabPreview));
|
||||
|
||||
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleXCUIDemoPanel));
|
||||
EXPECT_FALSE(harness.Panel(Application::ShellPanelId::XCUIDemo).visible);
|
||||
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleImGuiDemoWindow));
|
||||
EXPECT_TRUE(harness.viewToggles.imguiDemoWindowVisible);
|
||||
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeBackdrop));
|
||||
EXPECT_FALSE(harness.viewToggles.nativeBackdropVisible);
|
||||
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeXCUIOverlay));
|
||||
EXPECT_FALSE(harness.viewToggles.nativeXCUIOverlayVisible);
|
||||
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleHostedPreviewHud));
|
||||
EXPECT_FALSE(harness.viewToggles.hostedPreviewHudVisible);
|
||||
}
|
||||
|
||||
TEST(ApplicationShellCommandBindingsTest, PreviewModeCommandsTriggerHostedPreviewReconfigureCallback) {
|
||||
ShellCommandHarness harness = {};
|
||||
XCUIEditorCommandRouter router = {};
|
||||
|
||||
Application::RegisterShellViewCommands(router, harness.BuildBindings());
|
||||
|
||||
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeDemoPanelPreview));
|
||||
EXPECT_EQ(
|
||||
harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode,
|
||||
Application::ShellHostedPreviewMode::LegacyImGui);
|
||||
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1);
|
||||
|
||||
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeLayoutLabPreview));
|
||||
EXPECT_EQ(
|
||||
harness.Panel(Application::ShellPanelId::XCUILayoutLab).previewMode,
|
||||
Application::ShellHostedPreviewMode::NativeOffscreen);
|
||||
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 2);
|
||||
}
|
||||
|
||||
TEST(ApplicationShellCommandBindingsTest, BuildShellShortcutSnapshotCarriesBridgeStateAndKeyboardEdges) {
|
||||
XCUIInputBridgeFrameDelta frameDelta = {};
|
||||
frameDelta.state.windowFocused = true;
|
||||
frameDelta.state.wantCaptureKeyboard = true;
|
||||
frameDelta.state.wantTextInput = true;
|
||||
frameDelta.state.modifiers.control = true;
|
||||
frameDelta.state.modifiers.shift = true;
|
||||
frameDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
|
||||
frameDelta.keyboard.repeatedKeys.push_back(static_cast<std::int32_t>(KeyCode::Two));
|
||||
|
||||
const auto snapshot = Application::BuildShellShortcutSnapshot(frameDelta);
|
||||
|
||||
EXPECT_TRUE(snapshot.windowFocused);
|
||||
EXPECT_TRUE(snapshot.wantCaptureKeyboard);
|
||||
EXPECT_TRUE(snapshot.wantTextInput);
|
||||
EXPECT_TRUE(snapshot.modifiers.control);
|
||||
EXPECT_TRUE(snapshot.modifiers.shift);
|
||||
EXPECT_TRUE(snapshot.IsKeyDown(static_cast<std::int32_t>(KeyCode::One)));
|
||||
EXPECT_TRUE(snapshot.IsKeyDown(static_cast<std::int32_t>(KeyCode::Two)));
|
||||
|
||||
const auto* repeatedKey = snapshot.FindKeyState(static_cast<std::int32_t>(KeyCode::Two));
|
||||
ASSERT_NE(repeatedKey, nullptr);
|
||||
EXPECT_TRUE(repeatedKey->repeat);
|
||||
}
|
||||
|
||||
TEST(ApplicationShellCommandBindingsTest, RegisteredShellShortcutsMatchExpectedViewCommands) {
|
||||
ShellCommandHarness harness = {};
|
||||
XCUIEditorCommandRouter router = {};
|
||||
Application::RegisterShellViewCommands(router, harness.BuildBindings());
|
||||
|
||||
XCUIInputBridgeFrameDelta frameDelta = {};
|
||||
frameDelta.state.windowFocused = true;
|
||||
frameDelta.state.modifiers.control = true;
|
||||
frameDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
|
||||
|
||||
const auto demoSnapshot = Application::BuildShellShortcutSnapshot(frameDelta);
|
||||
const auto demoMatch = router.MatchShortcut({ &demoSnapshot });
|
||||
ASSERT_TRUE(demoMatch.matched);
|
||||
EXPECT_EQ(demoMatch.commandId, Application::ShellCommandIds::ToggleXCUIDemoPanel);
|
||||
|
||||
XCUIInputBridgeFrameDelta previewDelta = {};
|
||||
previewDelta.state.windowFocused = true;
|
||||
previewDelta.state.modifiers.control = true;
|
||||
previewDelta.state.modifiers.alt = true;
|
||||
previewDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::Two));
|
||||
|
||||
const auto previewSnapshot = Application::BuildShellShortcutSnapshot(previewDelta);
|
||||
const auto previewMatch = router.MatchShortcut({ &previewSnapshot });
|
||||
ASSERT_TRUE(previewMatch.matched);
|
||||
EXPECT_EQ(previewMatch.commandId, Application::ShellCommandIds::ToggleNativeLayoutLabPreview);
|
||||
}
|
||||
|
||||
TEST(ApplicationShellCommandBindingsTest, RegisteredShellShortcutsRespectCaptureAndRepeatGuards) {
|
||||
ShellCommandHarness harness = {};
|
||||
XCUIEditorCommandRouter router = {};
|
||||
Application::RegisterShellViewCommands(router, harness.BuildBindings());
|
||||
|
||||
XCUIInputBridgeFrameDelta capturedDelta = {};
|
||||
capturedDelta.state.windowFocused = true;
|
||||
capturedDelta.state.wantCaptureKeyboard = true;
|
||||
capturedDelta.state.modifiers.control = true;
|
||||
capturedDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
|
||||
|
||||
const auto capturedSnapshot = Application::BuildShellShortcutSnapshot(capturedDelta);
|
||||
EXPECT_FALSE(router.MatchShortcut({ &capturedSnapshot }).matched);
|
||||
|
||||
XCUIInputBridgeFrameDelta textInputDelta = {};
|
||||
textInputDelta.state.windowFocused = true;
|
||||
textInputDelta.state.wantTextInput = true;
|
||||
textInputDelta.state.modifiers.control = true;
|
||||
textInputDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
|
||||
|
||||
const auto textInputSnapshot = Application::BuildShellShortcutSnapshot(textInputDelta);
|
||||
EXPECT_FALSE(router.MatchShortcut({ &textInputSnapshot }).matched);
|
||||
|
||||
XCUIInputBridgeFrameDelta repeatedDelta = {};
|
||||
repeatedDelta.state.windowFocused = true;
|
||||
repeatedDelta.state.modifiers.control = true;
|
||||
repeatedDelta.keyboard.repeatedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
|
||||
|
||||
const auto repeatedSnapshot = Application::BuildShellShortcutSnapshot(repeatedDelta);
|
||||
EXPECT_FALSE(router.MatchShortcut({ &repeatedSnapshot }).matched);
|
||||
}
|
||||
|
||||
TEST(ApplicationShellCommandBindingsTest, PreviewShortcutInvokesCommandHandlerAndReconfigureCallback) {
|
||||
ShellCommandHarness harness = {};
|
||||
XCUIEditorCommandRouter router = {};
|
||||
Application::RegisterShellViewCommands(router, harness.BuildBindings());
|
||||
|
||||
XCUIInputBridgeFrameDelta previewDelta = {};
|
||||
previewDelta.state.windowFocused = true;
|
||||
previewDelta.state.modifiers.control = true;
|
||||
previewDelta.state.modifiers.alt = true;
|
||||
previewDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
|
||||
|
||||
const auto previewSnapshot = Application::BuildShellShortcutSnapshot(previewDelta);
|
||||
EXPECT_TRUE(router.InvokeMatchingShortcut({ &previewSnapshot }));
|
||||
EXPECT_EQ(
|
||||
harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode,
|
||||
Application::ShellHostedPreviewMode::LegacyImGui);
|
||||
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
25
tests/NewEditor/test_imgui_xcui_panel_canvas_host.cpp
Normal file
25
tests/NewEditor/test_imgui_xcui_panel_canvas_host.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost;
|
||||
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostCapabilities;
|
||||
|
||||
TEST(NewEditorImGuiXCUIPanelCanvasHostTest, ReportsExplicitBackendAndCapabilities) {
|
||||
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateImGuiXCUIPanelCanvasHost();
|
||||
ASSERT_NE(host, nullptr);
|
||||
|
||||
EXPECT_STREQ(host->GetDebugName(), "ImGuiXCUIPanelCanvasHost");
|
||||
EXPECT_EQ(host->GetBackend(), XCUIPanelCanvasHostBackend::ImGui);
|
||||
|
||||
const XCUIPanelCanvasHostCapabilities capabilities = host->GetCapabilities();
|
||||
EXPECT_TRUE(capabilities.supportsPointerHitTesting);
|
||||
EXPECT_TRUE(capabilities.supportsHostedSurfaceImages);
|
||||
EXPECT_TRUE(capabilities.supportsPrimitiveOverlays);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -56,6 +56,17 @@ UIInputEvent MakeKeyDownEvent(
|
||||
return event;
|
||||
}
|
||||
|
||||
UIInputEvent MakePointerButtonEvent(
|
||||
UIInputEventType type,
|
||||
const XCEngine::UI::UIPoint& position,
|
||||
XCEngine::UI::UIPointerButton button = XCEngine::UI::UIPointerButton::Left) {
|
||||
UIInputEvent event = {};
|
||||
event.type = type;
|
||||
event.pointerButton = button;
|
||||
event.position = position;
|
||||
return event;
|
||||
}
|
||||
|
||||
fs::path FindDemoResourcePath() {
|
||||
fs::path probe = fs::current_path();
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
@@ -263,6 +274,25 @@ TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsCapturesPointerActivati
|
||||
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
|
||||
}
|
||||
|
||||
TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsCapturesShortcutCommands) {
|
||||
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
|
||||
ASSERT_TRUE(runtime.ReloadDocuments());
|
||||
|
||||
const auto& baselineFrame = runtime.Update(BuildInputState());
|
||||
ASSERT_TRUE(baselineFrame.stats.documentsReady);
|
||||
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUIDemoInputState shortcutInput = BuildInputState();
|
||||
shortcutInput.events.push_back(MakeKeyDownEvent(XCEngine::Input::KeyCode::P, false, false, true));
|
||||
const auto& shortcutFrame = runtime.Update(shortcutInput);
|
||||
|
||||
ASSERT_TRUE(shortcutFrame.stats.documentsReady);
|
||||
EXPECT_TRUE(shortcutFrame.stats.accentEnabled);
|
||||
EXPECT_EQ(shortcutFrame.stats.lastCommandId, "demo.toggleAccent");
|
||||
EXPECT_EQ(runtime.DrainPendingCommandIds(), std::vector<std::string>({ "demo.toggleAccent" }));
|
||||
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
|
||||
}
|
||||
|
||||
TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsPreservesMultipleTextEditCommandsPerFrame) {
|
||||
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
|
||||
ASSERT_TRUE(runtime.ReloadDocuments());
|
||||
@@ -313,6 +343,43 @@ TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsPreservesMultipleTextEd
|
||||
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
|
||||
}
|
||||
|
||||
TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsPreserveMixedPointerTextAndShortcutOrder) {
|
||||
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
|
||||
ASSERT_TRUE(runtime.ReloadDocuments());
|
||||
|
||||
const auto& baselineFrame = runtime.Update(BuildInputState());
|
||||
ASSERT_TRUE(baselineFrame.stats.documentsReady);
|
||||
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
|
||||
|
||||
XCEngine::UI::UIRect promptRect = {};
|
||||
ASSERT_TRUE(runtime.TryGetElementRect("agentPrompt", promptRect));
|
||||
|
||||
const XCEngine::UI::UIPoint promptCenter(
|
||||
promptRect.x + promptRect.width * 0.5f,
|
||||
promptRect.y + promptRect.height * 0.5f);
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUIDemoInputState mixedInput = BuildInputState();
|
||||
mixedInput.pointerPosition = promptCenter;
|
||||
mixedInput.events.push_back(MakePointerButtonEvent(UIInputEventType::PointerButtonDown, promptCenter));
|
||||
mixedInput.events.push_back(MakePointerButtonEvent(UIInputEventType::PointerButtonUp, promptCenter));
|
||||
mixedInput.events.push_back(MakeCharacterEvent('A'));
|
||||
mixedInput.events.push_back(MakeKeyDownEvent(XCEngine::Input::KeyCode::P, false, false, true));
|
||||
const auto& mixedFrame = runtime.Update(mixedInput);
|
||||
|
||||
ASSERT_TRUE(mixedFrame.stats.documentsReady);
|
||||
EXPECT_EQ(mixedFrame.stats.focusedElementId, "agentPrompt");
|
||||
EXPECT_TRUE(mixedFrame.stats.accentEnabled);
|
||||
EXPECT_EQ(mixedFrame.stats.lastCommandId, "demo.toggleAccent");
|
||||
EXPECT_NE(FindTextCommand(mixedFrame.drawData, "A"), nullptr);
|
||||
EXPECT_EQ(
|
||||
runtime.DrainPendingCommandIds(),
|
||||
std::vector<std::string>({
|
||||
"demo.activate.agentPrompt",
|
||||
"demo.text.edit.agentPrompt",
|
||||
"demo.toggleAccent" }));
|
||||
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
|
||||
}
|
||||
|
||||
TEST(NewEditorXCUIDemoRuntimeTest, PointerToggleUpdatesFocusStatusTextAndAccentState) {
|
||||
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
|
||||
ASSERT_TRUE(runtime.ReloadDocuments());
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace {
|
||||
|
||||
using XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter;
|
||||
using XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter;
|
||||
using XCEngine::Editor::XCUIBackend::IImGuiXCUIHostedPreviewTargetBinding;
|
||||
using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewDrainStats;
|
||||
@@ -36,6 +37,19 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class RecordingImGuiHostedPreviewTargetBinding final : public IImGuiXCUIHostedPreviewTargetBinding {
|
||||
public:
|
||||
ImDrawList* ResolveTargetDrawList(const XCUIHostedPreviewFrame& frame) const override {
|
||||
++resolveCallCount;
|
||||
lastFrame = &frame;
|
||||
return resolvedDrawList;
|
||||
}
|
||||
|
||||
mutable std::size_t resolveCallCount = 0u;
|
||||
mutable const XCUIHostedPreviewFrame* lastFrame = nullptr;
|
||||
ImDrawList* resolvedDrawList = nullptr;
|
||||
};
|
||||
|
||||
void PrepareImGui(float width = 1024.0f, float height = 768.0f) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.DisplaySize = ImVec2(width, height);
|
||||
@@ -78,13 +92,10 @@ TEST(XCUIHostedPreviewPresenterTest, PresentReturnsFalseAndClearsStatsWhenFrameH
|
||||
EXPECT_EQ(stats.flushedCommandCount, 0u);
|
||||
}
|
||||
|
||||
TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoProvidedImGuiDrawList) {
|
||||
TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoExplicitBindingResolvedImGuiDrawList) {
|
||||
ImGuiContextScope contextScope;
|
||||
PrepareImGui(800.0f, 600.0f);
|
||||
|
||||
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter = CreateImGuiXCUIHostedPreviewPresenter();
|
||||
ASSERT_NE(presenter, nullptr);
|
||||
|
||||
XCEngine::UI::UIDrawData drawData = {};
|
||||
XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreview");
|
||||
drawList.AddFilledRect(
|
||||
@@ -101,6 +112,15 @@ TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoProvidedImGuiDraw
|
||||
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
|
||||
ASSERT_NE(targetDrawList, nullptr);
|
||||
|
||||
std::unique_ptr<RecordingImGuiHostedPreviewTargetBinding> targetBinding =
|
||||
std::make_unique<RecordingImGuiHostedPreviewTargetBinding>();
|
||||
RecordingImGuiHostedPreviewTargetBinding* targetBindingPtr = targetBinding.get();
|
||||
targetBindingPtr->resolvedDrawList = targetDrawList;
|
||||
|
||||
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter =
|
||||
CreateImGuiXCUIHostedPreviewPresenter(std::move(targetBinding));
|
||||
ASSERT_NE(presenter, nullptr);
|
||||
|
||||
XCUIHostedPreviewFrame frame = {};
|
||||
frame.drawData = &drawData;
|
||||
|
||||
@@ -116,6 +136,129 @@ TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoProvidedImGuiDraw
|
||||
EXPECT_EQ(stats.submittedCommandCount, 2u);
|
||||
EXPECT_EQ(stats.flushedDrawListCount, 1u);
|
||||
EXPECT_EQ(stats.flushedCommandCount, 2u);
|
||||
EXPECT_EQ(targetBindingPtr->resolveCallCount, 1u);
|
||||
EXPECT_EQ(targetBindingPtr->lastFrame, &frame);
|
||||
EXPECT_GT(targetDrawList->VtxBuffer.Size, 0);
|
||||
EXPECT_GT(targetDrawList->CmdBuffer.Size, 0);
|
||||
}
|
||||
|
||||
TEST(XCUIHostedPreviewPresenterTest, PresentReturnsFalseWhenExplicitBindingDoesNotResolveTargetDrawList) {
|
||||
ImGuiContextScope contextScope;
|
||||
PrepareImGui(800.0f, 600.0f);
|
||||
|
||||
XCEngine::UI::UIDrawData drawData = {};
|
||||
drawData.EmplaceDrawList("HostedPreviewMissingTarget").AddFilledRect(
|
||||
XCEngine::UI::UIRect(10.0f, 12.0f, 44.0f, 28.0f),
|
||||
XCEngine::UI::UIColor(0.9f, 0.3f, 0.2f, 1.0f));
|
||||
|
||||
std::unique_ptr<RecordingImGuiHostedPreviewTargetBinding> targetBinding =
|
||||
std::make_unique<RecordingImGuiHostedPreviewTargetBinding>();
|
||||
RecordingImGuiHostedPreviewTargetBinding* targetBindingPtr = targetBinding.get();
|
||||
|
||||
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter =
|
||||
CreateImGuiXCUIHostedPreviewPresenter(std::move(targetBinding));
|
||||
ASSERT_NE(presenter, nullptr);
|
||||
|
||||
XCUIHostedPreviewFrame frame = {};
|
||||
frame.drawData = &drawData;
|
||||
frame.debugName = "HostedPreviewMissingTarget";
|
||||
|
||||
const bool presented = presenter->Present(frame);
|
||||
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
|
||||
|
||||
EXPECT_FALSE(presented);
|
||||
EXPECT_FALSE(stats.presented);
|
||||
EXPECT_EQ(stats.submittedDrawListCount, 1u);
|
||||
EXPECT_EQ(stats.submittedCommandCount, 1u);
|
||||
EXPECT_EQ(stats.flushedDrawListCount, 0u);
|
||||
EXPECT_EQ(stats.flushedCommandCount, 0u);
|
||||
EXPECT_EQ(targetBindingPtr->resolveCallCount, 1u);
|
||||
EXPECT_EQ(targetBindingPtr->lastFrame, &frame);
|
||||
}
|
||||
|
||||
TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoExplicitBindingResolvedForegroundDrawListWithoutWindow) {
|
||||
ImGuiContextScope contextScope;
|
||||
PrepareImGui(800.0f, 600.0f);
|
||||
|
||||
XCEngine::UI::UIDrawData drawData = {};
|
||||
XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreviewForegroundTarget");
|
||||
drawList.AddFilledRect(
|
||||
XCEngine::UI::UIRect(16.0f, 18.0f, 52.0f, 34.0f),
|
||||
XCEngine::UI::UIColor(0.15f, 0.75f, 0.45f, 1.0f));
|
||||
drawList.AddText(
|
||||
XCEngine::UI::UIPoint(24.0f, 28.0f),
|
||||
"foreground",
|
||||
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
15.0f);
|
||||
|
||||
ImGui::NewFrame();
|
||||
ImDrawList* targetDrawList = ImGui::GetForegroundDrawList();
|
||||
ASSERT_NE(targetDrawList, nullptr);
|
||||
const int baselineVertexCount = targetDrawList->VtxBuffer.Size;
|
||||
const int baselineIndexCount = targetDrawList->IdxBuffer.Size;
|
||||
const int baselineCommandCount = targetDrawList->CmdBuffer.Size;
|
||||
|
||||
std::unique_ptr<RecordingImGuiHostedPreviewTargetBinding> targetBinding =
|
||||
std::make_unique<RecordingImGuiHostedPreviewTargetBinding>();
|
||||
RecordingImGuiHostedPreviewTargetBinding* targetBindingPtr = targetBinding.get();
|
||||
targetBindingPtr->resolvedDrawList = targetDrawList;
|
||||
|
||||
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter =
|
||||
CreateImGuiXCUIHostedPreviewPresenter(std::move(targetBinding));
|
||||
ASSERT_NE(presenter, nullptr);
|
||||
|
||||
XCUIHostedPreviewFrame frame = {};
|
||||
frame.drawData = &drawData;
|
||||
frame.debugName = "HostedPreviewForegroundTarget";
|
||||
|
||||
const bool presented = presenter->Present(frame);
|
||||
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
|
||||
|
||||
ImGui::EndFrame();
|
||||
|
||||
EXPECT_TRUE(presented);
|
||||
EXPECT_TRUE(stats.presented);
|
||||
EXPECT_EQ(stats.submittedDrawListCount, 1u);
|
||||
EXPECT_EQ(stats.submittedCommandCount, 2u);
|
||||
EXPECT_EQ(stats.flushedDrawListCount, 1u);
|
||||
EXPECT_EQ(stats.flushedCommandCount, 2u);
|
||||
EXPECT_EQ(targetBindingPtr->resolveCallCount, 1u);
|
||||
EXPECT_EQ(targetBindingPtr->lastFrame, &frame);
|
||||
EXPECT_GT(targetDrawList->VtxBuffer.Size, baselineVertexCount);
|
||||
EXPECT_GT(targetDrawList->IdxBuffer.Size, baselineIndexCount);
|
||||
EXPECT_GE(targetDrawList->CmdBuffer.Size, baselineCommandCount);
|
||||
}
|
||||
|
||||
TEST(XCUIHostedPreviewPresenterTest, DefaultFactoryStillUsesCurrentWindowBindingForLegacyImGuiPath) {
|
||||
ImGuiContextScope contextScope;
|
||||
PrepareImGui(800.0f, 600.0f);
|
||||
|
||||
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter = CreateImGuiXCUIHostedPreviewPresenter();
|
||||
ASSERT_NE(presenter, nullptr);
|
||||
|
||||
XCEngine::UI::UIDrawData drawData = {};
|
||||
drawData.EmplaceDrawList("HostedPreviewDefaultBinding").AddFilledRect(
|
||||
XCEngine::UI::UIRect(8.0f, 10.0f, 36.0f, 22.0f),
|
||||
XCEngine::UI::UIColor(0.2f, 0.6f, 0.9f, 1.0f));
|
||||
|
||||
ImGui::NewFrame();
|
||||
ASSERT_TRUE(ImGui::Begin("HostedPreviewPresenterDefaultBindingWindow"));
|
||||
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
|
||||
ASSERT_NE(targetDrawList, nullptr);
|
||||
|
||||
XCUIHostedPreviewFrame frame = {};
|
||||
frame.drawData = &drawData;
|
||||
|
||||
const bool presented = presenter->Present(frame);
|
||||
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
|
||||
|
||||
ImGui::End();
|
||||
ImGui::EndFrame();
|
||||
|
||||
EXPECT_TRUE(presented);
|
||||
EXPECT_TRUE(stats.presented);
|
||||
EXPECT_EQ(stats.flushedDrawListCount, 1u);
|
||||
EXPECT_EQ(stats.flushedCommandCount, 1u);
|
||||
EXPECT_GT(targetDrawList->VtxBuffer.Size, 0);
|
||||
EXPECT_GT(targetDrawList->CmdBuffer.Size, 0);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,20 @@ XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState BuildInputState(
|
||||
return input;
|
||||
}
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState BuildKeyboardInputState(
|
||||
float width = 960.0f,
|
||||
float height = 640.0f) {
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = BuildInputState(width, height);
|
||||
input.pointerInside = false;
|
||||
return input;
|
||||
}
|
||||
|
||||
XCEngine::UI::UIPoint RectCenter(const XCEngine::UI::UIRect& rect) {
|
||||
return XCEngine::UI::UIPoint(
|
||||
rect.x + rect.width * 0.5f,
|
||||
rect.y + rect.height * 0.5f);
|
||||
}
|
||||
|
||||
std::vector<const UIDrawCommand*> CollectTextCommands(const XCEngine::UI::UIDrawData& drawData) {
|
||||
std::vector<const UIDrawCommand*> textCommands = {};
|
||||
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
|
||||
@@ -363,3 +377,156 @@ TEST(NewEditorXCUILayoutLabRuntimeTest, ClickingPropertySectionHeaderTogglesFiel
|
||||
EXPECT_TRUE(runtime.TryGetElementRect("fieldPosition", fieldRect));
|
||||
EXPECT_GT(fieldRect.height, 0.0f);
|
||||
}
|
||||
|
||||
TEST(NewEditorXCUILayoutLabRuntimeTest, KeyboardNavigationMovesSelectionAcrossListItems) {
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
|
||||
ASSERT_TRUE(runtime.ReloadDocuments());
|
||||
|
||||
const auto& baseline = runtime.Update(BuildInputState());
|
||||
ASSERT_TRUE(baseline.stats.documentsReady);
|
||||
|
||||
XCEngine::UI::UIRect listItemRect = {};
|
||||
ASSERT_TRUE(runtime.TryGetElementRect("assetLighting", listItemRect));
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState selectInput = BuildInputState();
|
||||
selectInput.pointerPosition = RectCenter(listItemRect);
|
||||
selectInput.pointerPressed = true;
|
||||
const auto& selectedFrame = runtime.Update(selectInput);
|
||||
|
||||
ASSERT_TRUE(selectedFrame.stats.documentsReady);
|
||||
EXPECT_EQ(selectedFrame.stats.selectedElementId, "assetLighting");
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState nextInput = BuildKeyboardInputState();
|
||||
nextInput.navigateNext = true;
|
||||
const auto& nextFrame = runtime.Update(nextInput);
|
||||
ASSERT_TRUE(nextFrame.stats.documentsReady);
|
||||
EXPECT_EQ(nextFrame.stats.selectedElementId, "assetMaterials");
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState endInput = BuildKeyboardInputState();
|
||||
endInput.navigateEnd = true;
|
||||
const auto& endFrame = runtime.Update(endInput);
|
||||
ASSERT_TRUE(endFrame.stats.documentsReady);
|
||||
ASSERT_FALSE(endFrame.stats.selectedElementId.empty());
|
||||
EXPECT_NE(endFrame.stats.selectedElementId, "assetLighting");
|
||||
|
||||
const std::string lastListSelection = endFrame.stats.selectedElementId;
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState nextAtEndInput = BuildKeyboardInputState();
|
||||
nextAtEndInput.navigateNext = true;
|
||||
const auto& nextAtEndFrame = runtime.Update(nextAtEndInput);
|
||||
ASSERT_TRUE(nextAtEndFrame.stats.documentsReady);
|
||||
EXPECT_EQ(nextAtEndFrame.stats.selectedElementId, lastListSelection);
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState homeInput = BuildKeyboardInputState();
|
||||
homeInput.navigateHome = true;
|
||||
const auto& homeFrame = runtime.Update(homeInput);
|
||||
ASSERT_TRUE(homeFrame.stats.documentsReady);
|
||||
EXPECT_EQ(homeFrame.stats.selectedElementId, "assetLighting");
|
||||
}
|
||||
|
||||
TEST(NewEditorXCUILayoutLabRuntimeTest, KeyboardCollapseAndExpandFollowTreeHierarchy) {
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
|
||||
ASSERT_TRUE(runtime.ReloadDocuments());
|
||||
|
||||
const auto& baseline = runtime.Update(BuildInputState());
|
||||
ASSERT_TRUE(baseline.stats.documentsReady);
|
||||
|
||||
XCEngine::UI::UIRect treeChildRect = {};
|
||||
ASSERT_TRUE(runtime.TryGetElementRect("treeScenes", treeChildRect));
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState selectInput = BuildInputState();
|
||||
selectInput.pointerPosition = RectCenter(treeChildRect);
|
||||
selectInput.pointerPressed = true;
|
||||
const auto& selectedFrame = runtime.Update(selectInput);
|
||||
|
||||
ASSERT_TRUE(selectedFrame.stats.documentsReady);
|
||||
EXPECT_EQ(selectedFrame.stats.selectedElementId, "treeScenes");
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState collapseToParent = BuildKeyboardInputState();
|
||||
collapseToParent.navigateCollapse = true;
|
||||
const auto& parentFrame = runtime.Update(collapseToParent);
|
||||
ASSERT_TRUE(parentFrame.stats.documentsReady);
|
||||
EXPECT_EQ(parentFrame.stats.selectedElementId, "treeAssetsRoot");
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState collapseRoot = BuildKeyboardInputState();
|
||||
collapseRoot.navigateCollapse = true;
|
||||
const auto& collapsedFrame = runtime.Update(collapseRoot);
|
||||
ASSERT_TRUE(collapsedFrame.stats.documentsReady);
|
||||
EXPECT_EQ(collapsedFrame.stats.selectedElementId, "treeAssetsRoot");
|
||||
|
||||
const auto& collapsedPersistedFrame = runtime.Update(BuildKeyboardInputState());
|
||||
ASSERT_TRUE(collapsedPersistedFrame.stats.documentsReady);
|
||||
EXPECT_EQ(collapsedPersistedFrame.stats.expandedTreeItemCount, 0u);
|
||||
EXPECT_FALSE(runtime.TryGetElementRect("treeScenes", treeChildRect));
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState expandRoot = BuildKeyboardInputState();
|
||||
expandRoot.navigateExpand = true;
|
||||
const auto& expandedRootFrame = runtime.Update(expandRoot);
|
||||
ASSERT_TRUE(expandedRootFrame.stats.documentsReady);
|
||||
EXPECT_EQ(expandedRootFrame.stats.selectedElementId, "treeAssetsRoot");
|
||||
|
||||
const auto& expandedPersistedFrame = runtime.Update(BuildKeyboardInputState());
|
||||
ASSERT_TRUE(expandedPersistedFrame.stats.documentsReady);
|
||||
EXPECT_EQ(expandedPersistedFrame.stats.expandedTreeItemCount, 1u);
|
||||
ASSERT_TRUE(runtime.TryGetElementRect("treeScenes", treeChildRect));
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState moveIntoChild = BuildKeyboardInputState();
|
||||
moveIntoChild.navigateExpand = true;
|
||||
const auto& childFrame = runtime.Update(moveIntoChild);
|
||||
ASSERT_TRUE(childFrame.stats.documentsReady);
|
||||
EXPECT_EQ(childFrame.stats.selectedElementId, "treeScenes");
|
||||
}
|
||||
|
||||
TEST(NewEditorXCUILayoutLabRuntimeTest, KeyboardNavigationTraversesPropertySectionsAndFields) {
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
|
||||
ASSERT_TRUE(runtime.ReloadDocuments());
|
||||
|
||||
const auto& baseline = runtime.Update(BuildInputState());
|
||||
ASSERT_TRUE(baseline.stats.documentsReady);
|
||||
|
||||
XCEngine::UI::UIRect sectionRect = {};
|
||||
ASSERT_TRUE(runtime.TryGetElementRect("inspectorTransform", sectionRect));
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState selectInput = BuildInputState();
|
||||
selectInput.pointerPosition = XCEngine::UI::UIPoint(sectionRect.x + 18.0f, sectionRect.y + 10.0f);
|
||||
selectInput.pointerPressed = true;
|
||||
const auto& selectedFrame = runtime.Update(selectInput);
|
||||
|
||||
ASSERT_TRUE(selectedFrame.stats.documentsReady);
|
||||
EXPECT_EQ(selectedFrame.stats.selectedElementId, "inspectorTransform");
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState nextSectionInput = BuildKeyboardInputState();
|
||||
nextSectionInput.navigateNext = true;
|
||||
const auto& nextSectionFrame = runtime.Update(nextSectionInput);
|
||||
ASSERT_TRUE(nextSectionFrame.stats.documentsReady);
|
||||
EXPECT_EQ(nextSectionFrame.stats.selectedElementId, "inspectorMesh");
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState previousSectionInput = BuildKeyboardInputState();
|
||||
previousSectionInput.navigatePrevious = true;
|
||||
const auto& previousSectionFrame = runtime.Update(previousSectionInput);
|
||||
ASSERT_TRUE(previousSectionFrame.stats.documentsReady);
|
||||
EXPECT_EQ(previousSectionFrame.stats.selectedElementId, "inspectorTransform");
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState expandIntoFieldsInput = BuildKeyboardInputState();
|
||||
expandIntoFieldsInput.navigateExpand = true;
|
||||
const auto& expandedSectionFrame = runtime.Update(expandIntoFieldsInput);
|
||||
ASSERT_TRUE(expandedSectionFrame.stats.documentsReady);
|
||||
EXPECT_EQ(expandedSectionFrame.stats.selectedElementId, "inspectorTransform");
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState enterFieldsInput = BuildKeyboardInputState();
|
||||
enterFieldsInput.navigateExpand = true;
|
||||
const auto& firstFieldFrame = runtime.Update(enterFieldsInput);
|
||||
ASSERT_TRUE(firstFieldFrame.stats.documentsReady);
|
||||
EXPECT_EQ(firstFieldFrame.stats.selectedElementId, "fieldPosition");
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState nextFieldInput = BuildKeyboardInputState();
|
||||
nextFieldInput.navigateNext = true;
|
||||
const auto& nextFieldFrame = runtime.Update(nextFieldInput);
|
||||
ASSERT_TRUE(nextFieldFrame.stats.documentsReady);
|
||||
EXPECT_EQ(nextFieldFrame.stats.selectedElementId, "fieldRotation");
|
||||
|
||||
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState collapseToSectionInput = BuildKeyboardInputState();
|
||||
collapseToSectionInput.navigateCollapse = true;
|
||||
const auto& collapseToSectionFrame = runtime.Update(collapseToSectionInput);
|
||||
ASSERT_TRUE(collapseToSectionFrame.stats.documentsReady);
|
||||
EXPECT_EQ(collapseToSectionFrame.stats.selectedElementId, "inspectorTransform");
|
||||
}
|
||||
|
||||
67
tests/NewEditor/test_xcui_panel_canvas_host.cpp
Normal file
67
tests/NewEditor/test_xcui_panel_canvas_host.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "XCUIBackend/NullXCUIPanelCanvasHost.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost;
|
||||
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostCapabilities;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession;
|
||||
|
||||
TEST(NewEditorXCUIPanelCanvasHostTest, NullHostReportsExplicitBackendAndCapabilities) {
|
||||
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateNullXCUIPanelCanvasHost();
|
||||
ASSERT_NE(host, nullptr);
|
||||
|
||||
EXPECT_STREQ(host->GetDebugName(), "NullXCUIPanelCanvasHost");
|
||||
EXPECT_EQ(host->GetBackend(), XCUIPanelCanvasHostBackend::Null);
|
||||
|
||||
const XCUIPanelCanvasHostCapabilities capabilities = host->GetCapabilities();
|
||||
EXPECT_FALSE(capabilities.supportsPointerHitTesting);
|
||||
EXPECT_FALSE(capabilities.supportsHostedSurfaceImages);
|
||||
EXPECT_FALSE(capabilities.supportsPrimitiveOverlays);
|
||||
}
|
||||
|
||||
TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsEmptySessionAndDrawCallsAreNoops) {
|
||||
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateNullXCUIPanelCanvasHost();
|
||||
ASSERT_NE(host, nullptr);
|
||||
|
||||
XCUIPanelCanvasRequest request = {};
|
||||
request.childId = "NullCanvas";
|
||||
request.height = 280.0f;
|
||||
request.topInset = 24.0f;
|
||||
request.bordered = true;
|
||||
request.showSurfaceImage = true;
|
||||
request.drawPreviewFrame = true;
|
||||
request.placeholderTitle = "Placeholder";
|
||||
request.badgeTitle = "Badge";
|
||||
|
||||
const XCUIPanelCanvasSession session = host->BeginCanvas(request);
|
||||
EXPECT_FALSE(session.validCanvas);
|
||||
EXPECT_FALSE(session.hovered);
|
||||
EXPECT_FALSE(session.windowFocused);
|
||||
EXPECT_FLOAT_EQ(session.hostRect.width, 0.0f);
|
||||
EXPECT_FLOAT_EQ(session.hostRect.height, 0.0f);
|
||||
EXPECT_FLOAT_EQ(session.canvasRect.width, 0.0f);
|
||||
EXPECT_FLOAT_EQ(session.canvasRect.height, 0.0f);
|
||||
|
||||
host->DrawFilledRect(
|
||||
XCEngine::UI::UIRect(10.0f, 12.0f, 48.0f, 64.0f),
|
||||
XCEngine::UI::UIColor(1.0f, 0.0f, 0.0f, 1.0f),
|
||||
6.0f);
|
||||
host->DrawOutlineRect(
|
||||
XCEngine::UI::UIRect(5.0f, 6.0f, 100.0f, 40.0f),
|
||||
XCEngine::UI::UIColor(0.0f, 1.0f, 0.0f, 1.0f),
|
||||
2.0f,
|
||||
8.0f);
|
||||
host->DrawText(
|
||||
XCEngine::UI::UIPoint(8.0f, 14.0f),
|
||||
"Null host should ignore text draws",
|
||||
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
16.0f);
|
||||
host->EndCanvas();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
221
tests/NewEditor/test_xcui_shell_chrome_state.cpp
Normal file
221
tests/NewEditor/test_xcui_shell_chrome_state.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
#include "XCUIBackend/XCUIShellChromeState.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIShellChromeState;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewState;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId;
|
||||
|
||||
TEST(XCUIShellChromeStateTest, DefaultsMatchCurrentShellChromeConfiguration) {
|
||||
XCUIShellChromeState state = {};
|
||||
|
||||
const auto& viewToggles = state.GetViewToggles();
|
||||
EXPECT_FALSE(viewToggles.imguiDemoWindowVisible);
|
||||
EXPECT_TRUE(viewToggles.nativeBackdropVisible);
|
||||
EXPECT_TRUE(viewToggles.pulseAccentEnabled);
|
||||
EXPECT_TRUE(viewToggles.nativeXCUIOverlayVisible);
|
||||
EXPECT_TRUE(viewToggles.hostedPreviewHudVisible);
|
||||
|
||||
const auto* demoPanel = state.TryGetPanelState(XCUIShellPanelId::XCUIDemo);
|
||||
ASSERT_NE(demoPanel, nullptr);
|
||||
EXPECT_EQ(demoPanel->panelTitle, "XCUI Demo");
|
||||
EXPECT_EQ(demoPanel->previewDebugName, "XCUI Demo");
|
||||
EXPECT_EQ(demoPanel->previewDebugSource, "new_editor.panels.xcui_demo");
|
||||
EXPECT_TRUE(demoPanel->visible);
|
||||
EXPECT_TRUE(demoPanel->hostedPreviewEnabled);
|
||||
EXPECT_EQ(demoPanel->previewMode, XCUIShellHostedPreviewMode::NativeOffscreen);
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
|
||||
XCUIShellHostedPreviewState::NativeOffscreen);
|
||||
EXPECT_TRUE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
|
||||
EXPECT_FALSE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
|
||||
|
||||
const auto* layoutLabPanel = state.TryGetPanelState(XCUIShellPanelId::XCUILayoutLab);
|
||||
ASSERT_NE(layoutLabPanel, nullptr);
|
||||
EXPECT_EQ(layoutLabPanel->panelTitle, "XCUI Layout Lab");
|
||||
EXPECT_EQ(layoutLabPanel->previewDebugName, "XCUI Layout Lab");
|
||||
EXPECT_EQ(layoutLabPanel->previewDebugSource, "new_editor.panels.xcui_layout_lab");
|
||||
EXPECT_TRUE(layoutLabPanel->visible);
|
||||
EXPECT_TRUE(layoutLabPanel->hostedPreviewEnabled);
|
||||
EXPECT_EQ(layoutLabPanel->previewMode, XCUIShellHostedPreviewMode::LegacyImGui);
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewState(XCUIShellPanelId::XCUILayoutLab),
|
||||
XCUIShellHostedPreviewState::LegacyImGui);
|
||||
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab));
|
||||
EXPECT_TRUE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab));
|
||||
}
|
||||
|
||||
TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateChanges) {
|
||||
XCUIShellChromeState state = {};
|
||||
|
||||
EXPECT_TRUE(state.SetPanelVisible(XCUIShellPanelId::XCUIDemo, false));
|
||||
EXPECT_FALSE(state.IsPanelVisible(XCUIShellPanelId::XCUIDemo));
|
||||
EXPECT_FALSE(state.SetPanelVisible(XCUIShellPanelId::XCUIDemo, false));
|
||||
EXPECT_TRUE(state.TogglePanelVisible(XCUIShellPanelId::XCUIDemo));
|
||||
EXPECT_TRUE(state.IsPanelVisible(XCUIShellPanelId::XCUIDemo));
|
||||
|
||||
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUILayoutLab, false));
|
||||
EXPECT_FALSE(state.IsHostedPreviewEnabled(XCUIShellPanelId::XCUILayoutLab));
|
||||
EXPECT_FALSE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUILayoutLab, false));
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
|
||||
XCUIShellHostedPreviewMode::LegacyImGui);
|
||||
|
||||
EXPECT_TRUE(state.ToggleHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab));
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
|
||||
XCUIShellHostedPreviewMode::NativeOffscreen);
|
||||
EXPECT_TRUE(state.SetHostedPreviewMode(
|
||||
XCUIShellPanelId::XCUILayoutLab,
|
||||
XCUIShellHostedPreviewMode::LegacyImGui));
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
|
||||
XCUIShellHostedPreviewMode::LegacyImGui);
|
||||
EXPECT_FALSE(state.SetHostedPreviewMode(
|
||||
XCUIShellPanelId::XCUILayoutLab,
|
||||
XCUIShellHostedPreviewMode::LegacyImGui));
|
||||
}
|
||||
|
||||
TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequestedMode) {
|
||||
XCUIShellChromeState state = {};
|
||||
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
|
||||
XCUIShellHostedPreviewState::NativeOffscreen);
|
||||
|
||||
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, false));
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
|
||||
XCUIShellHostedPreviewState::Disabled);
|
||||
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
|
||||
EXPECT_FALSE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
|
||||
|
||||
EXPECT_TRUE(state.SetHostedPreviewMode(
|
||||
XCUIShellPanelId::XCUIDemo,
|
||||
XCUIShellHostedPreviewMode::LegacyImGui));
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewMode(XCUIShellPanelId::XCUIDemo),
|
||||
XCUIShellHostedPreviewMode::LegacyImGui);
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
|
||||
XCUIShellHostedPreviewState::Disabled);
|
||||
|
||||
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, true));
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
|
||||
XCUIShellHostedPreviewState::LegacyImGui);
|
||||
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
|
||||
EXPECT_TRUE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
|
||||
}
|
||||
|
||||
TEST(XCUIShellChromeStateTest, ViewToggleMutatorsOnlyFlipRequestedFlags) {
|
||||
XCUIShellChromeState state = {};
|
||||
|
||||
EXPECT_TRUE(state.SetViewToggle(XCUIShellViewToggleId::ImGuiDemoWindow, true));
|
||||
EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::ImGuiDemoWindow));
|
||||
EXPECT_FALSE(state.SetViewToggle(XCUIShellViewToggleId::ImGuiDemoWindow, true));
|
||||
|
||||
EXPECT_TRUE(state.ToggleViewToggle(XCUIShellViewToggleId::HostedPreviewHud));
|
||||
EXPECT_FALSE(state.GetViewToggle(XCUIShellViewToggleId::HostedPreviewHud));
|
||||
EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::NativeBackdrop));
|
||||
EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::PulseAccent));
|
||||
EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::NativeXCUIOverlay));
|
||||
}
|
||||
|
||||
TEST(XCUIShellChromeStateTest, CommandInterfaceTogglesShellViewAndPreviewStates) {
|
||||
XCUIShellChromeState state = {};
|
||||
|
||||
EXPECT_TRUE(state.HasCommand(XCUIShellChromeCommandIds::ToggleXCUIDemoPanel));
|
||||
EXPECT_TRUE(state.HasCommand(XCUIShellChromeCommandIds::ToggleHostedPreviewHud));
|
||||
EXPECT_TRUE(state.HasCommand(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview));
|
||||
EXPECT_FALSE(state.HasCommand("new_editor.view.unknown"));
|
||||
|
||||
EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleXCUIDemoPanel));
|
||||
EXPECT_FALSE(state.IsPanelVisible(XCUIShellPanelId::XCUIDemo));
|
||||
|
||||
EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleHostedPreviewHud));
|
||||
EXPECT_FALSE(state.GetViewToggle(XCUIShellViewToggleId::HostedPreviewHud));
|
||||
|
||||
EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview));
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
|
||||
XCUIShellHostedPreviewMode::NativeOffscreen);
|
||||
EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview));
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
|
||||
XCUIShellHostedPreviewMode::LegacyImGui);
|
||||
|
||||
EXPECT_FALSE(state.InvokeCommand("new_editor.view.unknown"));
|
||||
}
|
||||
|
||||
TEST(XCUIShellChromeStateTest, PanelCommandIdHelpersMatchCurrentShellCommands) {
|
||||
EXPECT_EQ(
|
||||
XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo),
|
||||
XCUIShellChromeCommandIds::ToggleXCUIDemoPanel);
|
||||
EXPECT_EQ(
|
||||
XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab),
|
||||
XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel);
|
||||
EXPECT_EQ(
|
||||
XCUIShellChromeState::GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo),
|
||||
XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview);
|
||||
EXPECT_EQ(
|
||||
XCUIShellChromeState::GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab),
|
||||
XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview);
|
||||
}
|
||||
|
||||
TEST(XCUIShellChromeStateTest, ViewToggleCommandIdHelpersMatchCurrentShellCommands) {
|
||||
EXPECT_EQ(
|
||||
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::ImGuiDemoWindow),
|
||||
XCUIShellChromeCommandIds::ToggleImGuiDemoWindow);
|
||||
EXPECT_EQ(
|
||||
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop),
|
||||
XCUIShellChromeCommandIds::ToggleNativeBackdrop);
|
||||
EXPECT_EQ(
|
||||
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent),
|
||||
XCUIShellChromeCommandIds::TogglePulseAccent);
|
||||
EXPECT_EQ(
|
||||
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay),
|
||||
XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay);
|
||||
EXPECT_EQ(
|
||||
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud),
|
||||
XCUIShellChromeCommandIds::ToggleHostedPreviewHud);
|
||||
}
|
||||
|
||||
TEST(XCUIShellChromeStateTest, InvalidPanelAndToggleIdsFailGracefully) {
|
||||
XCUIShellChromeState state = {};
|
||||
|
||||
const XCUIShellPanelId invalidPanelId = static_cast<XCUIShellPanelId>(255);
|
||||
const XCUIShellViewToggleId invalidToggleId = static_cast<XCUIShellViewToggleId>(255);
|
||||
|
||||
EXPECT_EQ(state.TryGetPanelState(invalidPanelId), nullptr);
|
||||
EXPECT_FALSE(state.IsPanelVisible(invalidPanelId));
|
||||
EXPECT_FALSE(state.SetPanelVisible(invalidPanelId, true));
|
||||
EXPECT_FALSE(state.TogglePanelVisible(invalidPanelId));
|
||||
EXPECT_FALSE(state.IsHostedPreviewEnabled(invalidPanelId));
|
||||
EXPECT_FALSE(state.SetHostedPreviewEnabled(invalidPanelId, false));
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewMode(invalidPanelId),
|
||||
XCUIShellHostedPreviewMode::LegacyImGui);
|
||||
EXPECT_EQ(
|
||||
state.GetHostedPreviewState(invalidPanelId),
|
||||
XCUIShellHostedPreviewState::Disabled);
|
||||
EXPECT_FALSE(state.IsNativeHostedPreviewActive(invalidPanelId));
|
||||
EXPECT_FALSE(state.IsLegacyHostedPreviewActive(invalidPanelId));
|
||||
EXPECT_FALSE(state.SetHostedPreviewMode(invalidPanelId, XCUIShellHostedPreviewMode::NativeOffscreen));
|
||||
EXPECT_FALSE(state.ToggleHostedPreviewMode(invalidPanelId));
|
||||
|
||||
EXPECT_FALSE(state.GetViewToggle(invalidToggleId));
|
||||
EXPECT_FALSE(state.SetViewToggle(invalidToggleId, true));
|
||||
EXPECT_FALSE(state.ToggleViewToggle(invalidToggleId));
|
||||
|
||||
EXPECT_TRUE(XCUIShellChromeState::GetPanelVisibilityCommandId(invalidPanelId).empty());
|
||||
EXPECT_TRUE(XCUIShellChromeState::GetPanelPreviewModeCommandId(invalidPanelId).empty());
|
||||
EXPECT_TRUE(XCUIShellChromeState::GetViewToggleCommandId(invalidToggleId).empty());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user