Add XCUI editor collection primitives and stack rollback

This commit is contained in:
2026-04-05 06:36:50 +08:00
parent 9525053624
commit 585575a738
7 changed files with 294 additions and 4 deletions

View File

@@ -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

View 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

View File

@@ -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"));
}