Replace new_editor with native XCUI shell sandbox

This commit is contained in:
2026-04-05 20:46:24 +08:00
parent 18f3f9f227
commit ba6c8eaae5
108 changed files with 2041 additions and 24403 deletions

View File

@@ -9,6 +9,7 @@
#include <chrono>
#include <filesystem>
#include <fstream>
#include <limits>
#include <string>
#include <vector>
@@ -88,6 +89,70 @@ bool DrawDataContainsText(
return false;
}
std::vector<XCEngine::UI::UIRect> CollectFilledRects(
const XCEngine::UI::UIDrawData& drawData) {
std::vector<XCEngine::UI::UIRect> rects = {};
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == XCEngine::UI::UIDrawCommandType::FilledRect) {
rects.push_back(command.rect);
}
}
}
return rects;
}
const XCEngine::UI::UIDrawCommand* FindTextCommand(
const XCEngine::UI::UIDrawData& drawData,
const std::string& text) {
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == XCEngine::UI::UIDrawCommandType::Text &&
command.text == text) {
return &command;
}
}
}
return nullptr;
}
bool RectContainsPoint(
const XCEngine::UI::UIRect& rect,
const XCEngine::UI::UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
bool TryFindSmallestFilledRectContainingPoint(
const XCEngine::UI::UIDrawData& drawData,
const XCEngine::UI::UIPoint& point,
XCEngine::UI::UIRect& outRect) {
bool found = false;
float bestArea = (std::numeric_limits<float>::max)();
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
if (command.type != XCEngine::UI::UIDrawCommandType::FilledRect ||
!RectContainsPoint(command.rect, point)) {
continue;
}
const float area = command.rect.width * command.rect.height;
if (!found || area < bestArea) {
outRect = command.rect;
bestArea = area;
found = true;
}
}
}
return found;
}
UIScreenFrameInput BuildInputState(std::uint64_t frameIndex = 1u) {
UIScreenFrameInput input = {};
input.viewportRect = XCEngine::UI::UIRect(0.0f, 0.0f, 800.0f, 480.0f);
@@ -181,6 +246,68 @@ TEST(UIRuntimeTest, ScreenPlayerBuildsDrawDataFromDocumentTree) {
EXPECT_EQ(player.GetPresentedFrameCount(), 1u);
}
TEST(UIRuntimeTest, DocumentHostStretchesColumnChildrenAcrossCrossAxis) {
TempFileScope viewFile(
"xcui_runtime_stretch_column",
".xcui",
"<View name=\"Stretch Column\">\n"
" <Column padding=\"18\" gap=\"10\">\n"
" <Button text=\"Wide Button\" />\n"
" </Column>\n"
"</View>\n");
UIDocumentScreenHost host = {};
UIScreenPlayer player(host);
ASSERT_TRUE(player.Load(BuildScreenAsset(viewFile.Path(), "runtime.stretch.column")));
UIScreenFrameInput input = BuildInputState();
input.viewportRect = XCEngine::UI::UIRect(0.0f, 0.0f, 400.0f, 220.0f);
const auto& frame = player.Update(input);
const auto* buttonText = FindTextCommand(frame.drawData, "Wide Button");
ASSERT_NE(buttonText, nullptr);
XCEngine::UI::UIRect buttonRect = {};
ASSERT_TRUE(TryFindSmallestFilledRectContainingPoint(frame.drawData, buttonText->position, buttonRect));
EXPECT_FLOAT_EQ(buttonRect.x, 34.0f);
EXPECT_FLOAT_EQ(buttonRect.width, 332.0f);
}
TEST(UIRuntimeTest, DocumentHostDoesNotLetExplicitHeightCrushCardContent) {
TempFileScope viewFile(
"xcui_runtime_card_height_floor",
".xcui",
"<View name=\"Card Height Floor\">\n"
" <Column padding=\"18\" gap=\"10\">\n"
" <Card title=\"Hero\" subtitle=\"Subtitle\" height=\"32\">\n"
" <Row gap=\"10\">\n"
" <Button text=\"Action\" />\n"
" </Row>\n"
" </Card>\n"
" </Column>\n"
"</View>\n");
UIDocumentScreenHost host = {};
UIScreenPlayer player(host);
ASSERT_TRUE(player.Load(BuildScreenAsset(viewFile.Path(), "runtime.card.height.floor")));
UIScreenFrameInput input = BuildInputState();
input.viewportRect = XCEngine::UI::UIRect(0.0f, 0.0f, 500.0f, 300.0f);
const auto& frame = player.Update(input);
const auto* heroTitle = FindTextCommand(frame.drawData, "Hero");
const auto* actionText = FindTextCommand(frame.drawData, "Action");
ASSERT_NE(heroTitle, nullptr);
ASSERT_NE(actionText, nullptr);
XCEngine::UI::UIRect cardRect = {};
XCEngine::UI::UIRect buttonRect = {};
ASSERT_TRUE(TryFindSmallestFilledRectContainingPoint(frame.drawData, heroTitle->position, cardRect));
ASSERT_TRUE(TryFindSmallestFilledRectContainingPoint(frame.drawData, actionText->position, buttonRect));
EXPECT_GT(cardRect.height, 32.0f);
EXPECT_GE(buttonRect.y, cardRect.y + 50.0f);
EXPECT_LE(buttonRect.y + buttonRect.height, cardRect.y + cardRect.height - 8.0f);
}
TEST(UIRuntimeTest, ScreenPlayerConsumeLastFrameReturnsDetachedPacketAndClearsBorrowedState) {
TempFileScope viewFile("xcui_runtime_consume_player", ".xcui", BuildViewMarkup("Runtime Consume"));
UIDocumentScreenHost host = {};