Add XCUI editor collection primitives and stack rollback
This commit is contained in:
@@ -31,13 +31,14 @@ Old `editor` replacement is explicitly out of scope for this phase.
|
||||
- Shared engine-side `UIDocumentScreenHost` now compiles `.xcui` / `.xctheme` screen documents into a runtime-facing document host path instead of leaving all document ownership in `new_editor`.
|
||||
- Shared text-editing primitives now live under `engine/include/XCEngine/UI/Text` and `engine/src/UI/Text`, so UTF-8 caret movement, line splitting, and multiline navigation are no longer trapped inside `XCUI Demo`.
|
||||
- Shared text-input controller/state now also lives under `engine/include/XCEngine/UI/Text` and `engine/src/UI/Text`, so character insertion, backspace/delete, submit, and multiline key handling no longer need to be reimplemented per host.
|
||||
- Shared editor collection primitive classification and metric helpers now also live under `engine/include/XCEngine/UI/Widgets` and `engine/src/UI/Widgets`, covering the current `ScrollView` / `TreeView` / `ListView` / `PropertySection` / `FieldRow` prototype taxonomy.
|
||||
- Core regression coverage now includes `UIContext`, layout, style, runtime screen player/system, and real document-host tests through `core_ui_tests`.
|
||||
|
||||
Current gap:
|
||||
|
||||
- Minimal schema self-definition support is landed, including consistency checks for enum/document-only schema metadata, but schema-driven validation for `.xcui` / `.xctheme` instances is still not implemented.
|
||||
- Shared widget/runtime instantiation is still thin and mostly editor-side.
|
||||
- Common widget primitives are still incomplete: shared text-input presentation/composition on top of the new text controller, tree/list virtualization, property-grid composition, and native image/source-rect level APIs.
|
||||
- Common widget primitives are still incomplete: shared text-input presentation/composition on top of the new text controller, real tree/list/property widget state on top of the new editor-primitive helpers, and native image/source-rect level APIs.
|
||||
|
||||
### 2. Runtime/Game Layer
|
||||
|
||||
@@ -45,6 +46,7 @@ Current gap:
|
||||
- The demo runtime has moved past single-line input: multiline `TextArea` behavior is now covered in the sandbox testbed.
|
||||
- Engine-side runtime ownership is no longer zero: `UIScreenPlayer`, `UIDocumentScreenHost`, and `UISystem` now define a shared runtime contract for loading a screen document, ticking it with input, and collecting `UI::UIDrawData`.
|
||||
- `UISystem` now supports layered screen composition semantics: stacked screen players, top-interactive input routing, and modal layers that block lower screens.
|
||||
- `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.
|
||||
- Runtime screen emission now also carries concrete button text in the shared document host path instead of silently dropping button labels.
|
||||
|
||||
Current gap:
|
||||
@@ -82,7 +84,7 @@ Current gap:
|
||||
- `new_editor_xcui_rhi_render_backend_tests`: `5/5`
|
||||
- `new_editor_xcui_hosted_preview_presenter_tests`: `12/12`
|
||||
- `XCNewEditor` Debug target builds successfully
|
||||
- `core_ui_tests`: `26/26`
|
||||
- `core_ui_tests`: `28/28`
|
||||
- `core_ui_style_tests`: `5/5`
|
||||
- `ui_resource_tests`: `11/11`
|
||||
- `editor_tests` targeted bridge smoke: `3/3`
|
||||
@@ -93,6 +95,7 @@ Current gap:
|
||||
- Demo runtime multiline `TextArea` path in the sandbox and test coverage for caret movement / multiline input.
|
||||
- Common-core `UITextEditing` extraction now owns UTF-8 offset stepping, codepoint counting, line splitting, and vertical caret motion with dedicated `core_ui_tests` coverage.
|
||||
- Common-core `UITextInputController` extraction now owns per-field text state, character insertion, enter-submit, and multiline keyboard editing behavior with dedicated `core_ui_tests` coverage.
|
||||
- Common-core `UIEditorCollectionPrimitives` extraction now owns the editor collection tag taxonomy and default metric resolution used by current `LayoutLab` widget prototypes, with dedicated `core_ui_tests` coverage.
|
||||
- Demo runtime text editing was extended with:
|
||||
- click-to-place caret
|
||||
- `Delete` support
|
||||
@@ -117,6 +120,7 @@ Current gap:
|
||||
- `UISystem`
|
||||
- layered screen composition and modal blocking semantics
|
||||
- Runtime/game integration scaffolding now includes reusable `HUD/menu/modal` stack helpers on top of `UISystem`.
|
||||
- `UIScreenStackController` replacement now rolls back safely on failure instead of popping the active top layer first.
|
||||
- Runtime document-host draw emission now preserves button labels for shared screen rendering.
|
||||
- RHI image path improvements:
|
||||
- clipped image UV adjustment
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Style/Theme.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Widgets {
|
||||
|
||||
enum class UIEditorCollectionPrimitiveKind : std::uint8_t {
|
||||
None = 0,
|
||||
ScrollView,
|
||||
TreeView,
|
||||
TreeItem,
|
||||
ListView,
|
||||
ListItem,
|
||||
PropertySection,
|
||||
FieldRow
|
||||
};
|
||||
|
||||
UIEditorCollectionPrimitiveKind ClassifyUIEditorCollectionPrimitive(std::string_view tagName);
|
||||
bool IsUIEditorCollectionPrimitiveContainer(UIEditorCollectionPrimitiveKind kind);
|
||||
bool UsesUIEditorCollectionPrimitiveColumnLayout(UIEditorCollectionPrimitiveKind kind);
|
||||
bool IsUIEditorCollectionPrimitiveHoverable(UIEditorCollectionPrimitiveKind kind);
|
||||
bool DoesUIEditorCollectionPrimitiveClipChildren(UIEditorCollectionPrimitiveKind kind);
|
||||
float ResolveUIEditorCollectionPrimitivePadding(
|
||||
UIEditorCollectionPrimitiveKind kind,
|
||||
const Style::UITheme& theme);
|
||||
float ResolveUIEditorCollectionPrimitiveDefaultHeight(
|
||||
UIEditorCollectionPrimitiveKind kind,
|
||||
const Style::UITheme& theme);
|
||||
float ResolveUIEditorCollectionPrimitiveIndent(
|
||||
UIEditorCollectionPrimitiveKind kind,
|
||||
const Style::UITheme& theme,
|
||||
float indentLevel);
|
||||
|
||||
} // namespace Widgets
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
@@ -82,11 +82,27 @@ bool UIScreenStackController::ReplaceTop(
|
||||
return PushScreen(asset, options) != 0;
|
||||
}
|
||||
|
||||
if (!Pop()) {
|
||||
if (m_system == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return PushScreen(asset, options) != 0;
|
||||
const UIScreenStackEntry previousTop = m_entries.back();
|
||||
const UIScreenLayerId replacementLayerId = m_system->PushScreen(asset, options);
|
||||
if (replacementLayerId == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_system->RemoveLayer(previousTop.layerId)) {
|
||||
m_system->RemoveLayer(replacementLayerId);
|
||||
return false;
|
||||
}
|
||||
|
||||
UIScreenStackEntry replacementEntry = {};
|
||||
replacementEntry.layerId = replacementLayerId;
|
||||
replacementEntry.asset = asset;
|
||||
replacementEntry.options = options;
|
||||
m_entries.back() = std::move(replacementEntry);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UIScreenStackController::Pop() {
|
||||
|
||||
114
engine/src/UI/Widgets/UIEditorCollectionPrimitives.cpp
Normal file
114
engine/src/UI/Widgets/UIEditorCollectionPrimitives.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include <XCEngine/UI/Widgets/UIEditorCollectionPrimitives.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Widgets {
|
||||
|
||||
namespace {
|
||||
|
||||
float ResolveFloatToken(
|
||||
const Style::UITheme& theme,
|
||||
const char* tokenName,
|
||||
float fallbackValue) {
|
||||
const Style::UITokenResolveResult result =
|
||||
theme.ResolveToken(tokenName, Style::UIStyleValueType::Float);
|
||||
if (result.status != Style::UITokenResolveStatus::Resolved) {
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
const float* value = result.value.TryGetFloat();
|
||||
return value != nullptr ? *value : fallbackValue;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UIEditorCollectionPrimitiveKind ClassifyUIEditorCollectionPrimitive(std::string_view tagName) {
|
||||
if (tagName == "ScrollView") {
|
||||
return UIEditorCollectionPrimitiveKind::ScrollView;
|
||||
}
|
||||
if (tagName == "TreeView") {
|
||||
return UIEditorCollectionPrimitiveKind::TreeView;
|
||||
}
|
||||
if (tagName == "TreeItem") {
|
||||
return UIEditorCollectionPrimitiveKind::TreeItem;
|
||||
}
|
||||
if (tagName == "ListView") {
|
||||
return UIEditorCollectionPrimitiveKind::ListView;
|
||||
}
|
||||
if (tagName == "ListItem") {
|
||||
return UIEditorCollectionPrimitiveKind::ListItem;
|
||||
}
|
||||
if (tagName == "PropertySection") {
|
||||
return UIEditorCollectionPrimitiveKind::PropertySection;
|
||||
}
|
||||
if (tagName == "FieldRow") {
|
||||
return UIEditorCollectionPrimitiveKind::FieldRow;
|
||||
}
|
||||
|
||||
return UIEditorCollectionPrimitiveKind::None;
|
||||
}
|
||||
|
||||
bool IsUIEditorCollectionPrimitiveContainer(UIEditorCollectionPrimitiveKind kind) {
|
||||
return kind == UIEditorCollectionPrimitiveKind::ScrollView ||
|
||||
kind == UIEditorCollectionPrimitiveKind::TreeView ||
|
||||
kind == UIEditorCollectionPrimitiveKind::ListView ||
|
||||
kind == UIEditorCollectionPrimitiveKind::PropertySection;
|
||||
}
|
||||
|
||||
bool UsesUIEditorCollectionPrimitiveColumnLayout(UIEditorCollectionPrimitiveKind kind) {
|
||||
return kind == UIEditorCollectionPrimitiveKind::TreeView ||
|
||||
kind == UIEditorCollectionPrimitiveKind::ListView ||
|
||||
kind == UIEditorCollectionPrimitiveKind::PropertySection;
|
||||
}
|
||||
|
||||
bool IsUIEditorCollectionPrimitiveHoverable(UIEditorCollectionPrimitiveKind kind) {
|
||||
return kind == UIEditorCollectionPrimitiveKind::TreeItem ||
|
||||
kind == UIEditorCollectionPrimitiveKind::ListItem ||
|
||||
kind == UIEditorCollectionPrimitiveKind::FieldRow;
|
||||
}
|
||||
|
||||
bool DoesUIEditorCollectionPrimitiveClipChildren(UIEditorCollectionPrimitiveKind kind) {
|
||||
return kind == UIEditorCollectionPrimitiveKind::ScrollView ||
|
||||
kind == UIEditorCollectionPrimitiveKind::TreeView ||
|
||||
kind == UIEditorCollectionPrimitiveKind::ListView;
|
||||
}
|
||||
|
||||
float ResolveUIEditorCollectionPrimitivePadding(
|
||||
UIEditorCollectionPrimitiveKind kind,
|
||||
const Style::UITheme& theme) {
|
||||
return kind == UIEditorCollectionPrimitiveKind::TreeView ||
|
||||
kind == UIEditorCollectionPrimitiveKind::ListView ||
|
||||
kind == UIEditorCollectionPrimitiveKind::PropertySection
|
||||
? ResolveFloatToken(theme, "space.cardInset", 12.0f)
|
||||
: 0.0f;
|
||||
}
|
||||
|
||||
float ResolveUIEditorCollectionPrimitiveDefaultHeight(
|
||||
UIEditorCollectionPrimitiveKind kind,
|
||||
const Style::UITheme& theme) {
|
||||
switch (kind) {
|
||||
case UIEditorCollectionPrimitiveKind::TreeItem:
|
||||
return ResolveFloatToken(theme, "size.treeItemHeight", 28.0f);
|
||||
case UIEditorCollectionPrimitiveKind::ListItem:
|
||||
return ResolveFloatToken(theme, "size.listItemHeight", 60.0f);
|
||||
case UIEditorCollectionPrimitiveKind::FieldRow:
|
||||
return ResolveFloatToken(theme, "size.fieldRowHeight", 32.0f);
|
||||
case UIEditorCollectionPrimitiveKind::PropertySection:
|
||||
return ResolveFloatToken(theme, "size.propertySectionHeight", 148.0f);
|
||||
default:
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
float ResolveUIEditorCollectionPrimitiveIndent(
|
||||
UIEditorCollectionPrimitiveKind kind,
|
||||
const Style::UITheme& theme,
|
||||
float indentLevel) {
|
||||
return kind == UIEditorCollectionPrimitiveKind::TreeItem
|
||||
? indentLevel * ResolveFloatToken(theme, "size.treeIndent", 18.0f)
|
||||
: 0.0f;
|
||||
}
|
||||
|
||||
} // namespace Widgets
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
set(UI_TEST_SOURCES
|
||||
test_ui_core.cpp
|
||||
test_ui_editor_collection_primitives.cpp
|
||||
test_layout_engine.cpp
|
||||
test_ui_runtime.cpp
|
||||
test_ui_text_editing.cpp
|
||||
|
||||
83
tests/Core/UI/test_ui_editor_collection_primitives.cpp
Normal file
83
tests/Core/UI/test_ui_editor_collection_primitives.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/Style/Theme.h>
|
||||
#include <XCEngine/UI/Style/StyleTypes.h>
|
||||
#include <XCEngine/UI/Widgets/UIEditorCollectionPrimitives.h>
|
||||
|
||||
namespace {
|
||||
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
namespace UIWidgets = XCEngine::UI::Widgets;
|
||||
|
||||
Style::UITheme BuildEditorPrimitiveTheme() {
|
||||
Style::UIThemeDefinition definition = {};
|
||||
definition.SetToken("space.cardInset", Style::UIStyleValue(14.0f));
|
||||
definition.SetToken("size.treeItemHeight", Style::UIStyleValue(30.0f));
|
||||
definition.SetToken("size.listItemHeight", Style::UIStyleValue(64.0f));
|
||||
definition.SetToken("size.fieldRowHeight", Style::UIStyleValue(36.0f));
|
||||
definition.SetToken("size.propertySectionHeight", Style::UIStyleValue(156.0f));
|
||||
definition.SetToken("size.treeIndent", Style::UIStyleValue(20.0f));
|
||||
return Style::BuildTheme(definition);
|
||||
}
|
||||
|
||||
TEST(UIEditorCollectionPrimitivesTest, ClassifyAndFlagsMatchEditorCollectionTags) {
|
||||
using Kind = UIWidgets::UIEditorCollectionPrimitiveKind;
|
||||
|
||||
EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("ScrollView"), Kind::ScrollView);
|
||||
EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("TreeView"), Kind::TreeView);
|
||||
EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("TreeItem"), Kind::TreeItem);
|
||||
EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("ListView"), Kind::ListView);
|
||||
EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("ListItem"), Kind::ListItem);
|
||||
EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("PropertySection"), Kind::PropertySection);
|
||||
EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("FieldRow"), Kind::FieldRow);
|
||||
EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("Column"), Kind::None);
|
||||
|
||||
EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveContainer(Kind::ScrollView));
|
||||
EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveContainer(Kind::TreeView));
|
||||
EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveContainer(Kind::ListView));
|
||||
EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveContainer(Kind::PropertySection));
|
||||
EXPECT_FALSE(UIWidgets::IsUIEditorCollectionPrimitiveContainer(Kind::TreeItem));
|
||||
|
||||
EXPECT_TRUE(UIWidgets::UsesUIEditorCollectionPrimitiveColumnLayout(Kind::TreeView));
|
||||
EXPECT_TRUE(UIWidgets::UsesUIEditorCollectionPrimitiveColumnLayout(Kind::ListView));
|
||||
EXPECT_TRUE(UIWidgets::UsesUIEditorCollectionPrimitiveColumnLayout(Kind::PropertySection));
|
||||
EXPECT_FALSE(UIWidgets::UsesUIEditorCollectionPrimitiveColumnLayout(Kind::ScrollView));
|
||||
|
||||
EXPECT_TRUE(UIWidgets::DoesUIEditorCollectionPrimitiveClipChildren(Kind::ScrollView));
|
||||
EXPECT_TRUE(UIWidgets::DoesUIEditorCollectionPrimitiveClipChildren(Kind::TreeView));
|
||||
EXPECT_TRUE(UIWidgets::DoesUIEditorCollectionPrimitiveClipChildren(Kind::ListView));
|
||||
EXPECT_FALSE(UIWidgets::DoesUIEditorCollectionPrimitiveClipChildren(Kind::PropertySection));
|
||||
|
||||
EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::TreeItem));
|
||||
EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::ListItem));
|
||||
EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::FieldRow));
|
||||
EXPECT_FALSE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::TreeView));
|
||||
}
|
||||
|
||||
TEST(UIEditorCollectionPrimitivesTest, ResolveMetricsUseThemeTokensAndFallbacks) {
|
||||
using Kind = UIWidgets::UIEditorCollectionPrimitiveKind;
|
||||
|
||||
const Style::UITheme themed = BuildEditorPrimitiveTheme();
|
||||
const Style::UITheme fallback = Style::UITheme();
|
||||
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::TreeView, themed), 14.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ListView, themed), 14.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::PropertySection, themed), 14.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ScrollView, themed), 0.0f);
|
||||
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::TreeItem, themed), 30.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::ListItem, themed), 64.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::FieldRow, themed), 36.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::PropertySection, themed), 156.0f);
|
||||
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::TreeItem, fallback), 28.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::ListItem, fallback), 60.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::FieldRow, fallback), 32.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::PropertySection, fallback), 148.0f);
|
||||
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::TreeItem, themed, 2.0f), 40.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::TreeItem, fallback, 2.0f), 36.0f);
|
||||
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::ListItem, themed, 2.0f), 0.0f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -205,3 +205,34 @@ TEST(UIRuntimeTest, ScreenStackControllerReplaceTopSwapsMenuContent) {
|
||||
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Settings Menu"));
|
||||
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Pause Menu"));
|
||||
}
|
||||
|
||||
TEST(UIRuntimeTest, ScreenStackControllerReplaceTopKeepsPreviousScreenWhenReplacementFails) {
|
||||
TempFileScope pauseView("xcui_runtime_pause", ".xcui", BuildViewMarkup("Pause Menu"));
|
||||
|
||||
UIDocumentScreenHost host = {};
|
||||
UISystem system(host);
|
||||
UIScreenStackController stack(system);
|
||||
|
||||
const auto pauseLayer = stack.PushMenu(BuildScreenAsset(pauseView.Path(), "runtime.pause"), "pause");
|
||||
ASSERT_NE(pauseLayer, 0u);
|
||||
|
||||
XCEngine::UI::Runtime::UIScreenLayerOptions replacementOptions = {};
|
||||
replacementOptions.debugName = "broken";
|
||||
replacementOptions.acceptsInput = true;
|
||||
replacementOptions.blocksLayersBelow = true;
|
||||
|
||||
UIScreenAsset invalidAsset = {};
|
||||
invalidAsset.screenId = "runtime.invalid";
|
||||
invalidAsset.documentPath = (fs::temp_directory_path() / "xcui_missing_runtime_screen.xcui").string();
|
||||
|
||||
EXPECT_FALSE(stack.ReplaceTop(invalidAsset, replacementOptions));
|
||||
ASSERT_EQ(stack.GetEntryCount(), 1u);
|
||||
ASSERT_NE(stack.GetTop(), nullptr);
|
||||
EXPECT_EQ(stack.GetTop()->layerId, pauseLayer);
|
||||
EXPECT_EQ(stack.GetTop()->asset.screenId, "runtime.pause");
|
||||
EXPECT_EQ(system.GetLayerCount(), 1u);
|
||||
|
||||
const auto& frame = system.Update(BuildInputState(6u));
|
||||
EXPECT_EQ(frame.presentedLayerCount, 1u);
|
||||
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Pause Menu"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user