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

@@ -5,7 +5,7 @@
#include <XCEngine/Rendering/ObjectIdEncoding.h>
#include <XCEngine/Rendering/RenderPass.h>
#include <XCEngine/Rendering/RenderResourceCache.h>
#include <XCEngine/Core/Asset/ResourceHandle.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/RHI/RHIPipelineState.h>
#include <XCEngine/Resources/Shader/Shader.h>

View File

@@ -32,7 +32,10 @@ namespace Layout = XCEngine::UI::Layout;
constexpr float kDefaultFontSize = 16.0f;
constexpr float kSmallFontSize = 13.0f;
constexpr float kButtonFontSize = 14.0f;
constexpr float kApproximateGlyphWidth = 0.56f;
constexpr float kHeaderTextInset = 12.0f;
constexpr float kHeaderTextGap = 2.0f;
struct RuntimeLayoutNode {
const UIDocumentNode* source = nullptr;
@@ -71,6 +74,10 @@ float MeasureTextHeight(float fontSize) {
return fontSize + 6.0f;
}
float ComputeCenteredTextTop(const UIRect& rect, float fontSize) {
return rect.y + (std::max)(0.0f, std::floor((rect.height - fontSize) * 0.5f));
}
const UIDocumentAttribute* FindAttribute(const UIDocumentNode& node, const char* name) {
for (const UIDocumentAttribute& attribute : node.attributes) {
if (attribute.name == name) {
@@ -89,6 +96,43 @@ std::string GetAttribute(
return attribute != nullptr ? ToStdString(attribute->value) : fallback;
}
float MeasureHeaderTextWidth(const UIDocumentNode& node) {
float width = 0.0f;
const std::string title = GetAttribute(node, "title");
if (!title.empty()) {
width = (std::max)(width, MeasureTextWidth(title, kDefaultFontSize));
}
const std::string subtitle = GetAttribute(node, "subtitle");
if (!subtitle.empty()) {
width = (std::max)(width, MeasureTextWidth(subtitle, kSmallFontSize));
}
return width;
}
float MeasureHeaderHeight(const UIDocumentNode& node) {
const std::string title = GetAttribute(node, "title");
const std::string subtitle = GetAttribute(node, "subtitle");
if (title.empty() && subtitle.empty()) {
return 0.0f;
}
float headerHeight = kHeaderTextInset;
if (!title.empty()) {
headerHeight += MeasureTextHeight(kDefaultFontSize);
}
if (!subtitle.empty()) {
if (!title.empty()) {
headerHeight += kHeaderTextGap;
}
headerHeight += MeasureTextHeight(kSmallFontSize);
}
return headerHeight;
}
bool TryParseFloat(const std::string& text, float& outValue) {
if (text.empty()) {
return false;
@@ -139,6 +183,38 @@ Layout::UILayoutThickness ParsePadding(
return Layout::UILayoutThickness::Uniform(ParseFloatAttribute(node, "padding", fallback));
}
Layout::UILayoutItem BuildLayoutItem(
const RuntimeLayoutNode& child,
Layout::UILayoutAxis parentAxis) {
Layout::UILayoutItem item = {};
item.desiredContentSize = child.desiredSize;
item.width = ParseLengthAttribute(*child.source, "width");
item.height = ParseLengthAttribute(*child.source, "height");
// Pixel-authored lengths act as requested extents, but never below the measured content floor.
if (item.width.unit == Layout::UILayoutLengthUnit::Pixels &&
item.width.value < child.desiredSize.width) {
item.minSize.width = child.desiredSize.width;
}
if (item.height.unit == Layout::UILayoutLengthUnit::Pixels &&
item.height.value < child.desiredSize.height) {
item.minSize.height = child.desiredSize.height;
}
if (parentAxis == Layout::UILayoutAxis::Vertical &&
item.width.unit == Layout::UILayoutLengthUnit::Auto) {
item.horizontalAlignment = Layout::UILayoutAlignment::Stretch;
}
if (parentAxis == Layout::UILayoutAxis::Horizontal &&
item.height.unit == Layout::UILayoutLengthUnit::Auto) {
item.verticalAlignment = Layout::UILayoutAlignment::Stretch;
}
return item;
}
std::string ResolveNodeText(const UIDocumentNode& node) {
const std::string text = GetAttribute(node, "text");
if (!text.empty()) {
@@ -175,31 +251,31 @@ Color ResolveBackgroundColor(const UIDocumentNode& node) {
const std::string tagName = ToStdString(node.tagName);
if (tagName == "View") {
return Color(0.08f, 0.09f, 0.11f, 1.0f);
return Color(0.11f, 0.11f, 0.11f, 1.0f);
}
if (tone == "accent") {
return Color(0.19f, 0.31f, 0.52f, 1.0f);
return Color(0.25f, 0.25f, 0.25f, 1.0f);
}
if (tone == "accent-alt") {
return Color(0.24f, 0.26f, 0.39f, 1.0f);
return Color(0.22f, 0.22f, 0.22f, 1.0f);
}
if (tagName == "Button") {
return Color(0.20f, 0.23f, 0.29f, 1.0f);
return Color(0.24f, 0.24f, 0.24f, 1.0f);
}
return Color(0.13f, 0.15f, 0.18f, 1.0f);
return Color(0.16f, 0.16f, 0.16f, 1.0f);
}
Color ResolveBorderColor(const UIDocumentNode& node) {
const std::string tone = GetAttribute(node, "tone");
if (tone == "accent") {
return Color(0.31f, 0.56f, 0.90f, 1.0f);
return Color(0.42f, 0.42f, 0.42f, 1.0f);
}
if (tone == "accent-alt") {
return Color(0.47f, 0.51f, 0.76f, 1.0f);
return Color(0.34f, 0.34f, 0.34f, 1.0f);
}
return Color(0.25f, 0.28f, 0.33f, 1.0f);
return Color(0.30f, 0.30f, 0.30f, 1.0f);
}
RuntimeLayoutNode BuildLayoutTree(const UIDocumentNode& source) {
@@ -246,42 +322,32 @@ UISize MeasureNode(RuntimeLayoutNode& node) {
std::vector<Layout::UILayoutItem> items = {};
items.reserve(node.children.size());
for (RuntimeLayoutNode& child : node.children) {
Layout::UILayoutItem item = {};
item.desiredContentSize = MeasureNode(child);
item.width = ParseLengthAttribute(*child.source, "width");
item.height = ParseLengthAttribute(*child.source, "height");
MeasureNode(child);
Layout::UILayoutItem item = BuildLayoutItem(child, options.axis);
items.push_back(item);
}
const auto measured = Layout::MeasureStackLayout(options, items);
node.desiredSize = measured.desiredSize;
const std::string title = GetAttribute(source, "title");
const std::string subtitle = GetAttribute(source, "subtitle");
float headerHeight = 0.0f;
if (!title.empty()) {
headerHeight += MeasureTextHeight(kDefaultFontSize);
}
if (!subtitle.empty()) {
if (headerHeight > 0.0f) {
headerHeight += 4.0f;
}
headerHeight += MeasureTextHeight(kSmallFontSize);
}
const float headerHeight = MeasureHeaderHeight(source);
const float headerTextWidth = MeasureHeaderTextWidth(source);
node.desiredSize.width = (std::max)(
node.desiredSize.width,
MeasureTextWidth(ResolveNodeText(source), kDefaultFontSize) + options.padding.Horizontal());
headerTextWidth > 0.0f
? headerTextWidth + options.padding.Horizontal()
: MeasureTextWidth(ResolveNodeText(source), kDefaultFontSize) + options.padding.Horizontal());
node.desiredSize.height += headerHeight;
float explicitWidth = 0.0f;
if (TryParseFloat(GetAttribute(source, "width"), explicitWidth)) {
node.desiredSize.width = explicitWidth;
node.desiredSize.width = (std::max)(node.desiredSize.width, explicitWidth);
}
float explicitHeight = 0.0f;
if (TryParseFloat(GetAttribute(source, "height"), explicitHeight)) {
node.desiredSize.height = explicitHeight;
node.desiredSize.height = (std::max)(node.desiredSize.height, explicitHeight);
}
return node.desiredSize;
@@ -308,16 +374,7 @@ void ArrangeNode(RuntimeLayoutNode& node, const UIRect& rect) {
source,
tagName == "View" ? 16.0f : 12.0f);
float headerHeight = 0.0f;
if (!GetAttribute(source, "title").empty()) {
headerHeight += MeasureTextHeight(kDefaultFontSize);
}
if (!GetAttribute(source, "subtitle").empty()) {
if (headerHeight > 0.0f) {
headerHeight += 4.0f;
}
headerHeight += MeasureTextHeight(kSmallFontSize);
}
const float headerHeight = MeasureHeaderHeight(source);
UIRect contentRect = rect;
contentRect.y += headerHeight;
@@ -326,10 +383,7 @@ void ArrangeNode(RuntimeLayoutNode& node, const UIRect& rect) {
std::vector<Layout::UILayoutItem> items = {};
items.reserve(node.children.size());
for (RuntimeLayoutNode& child : node.children) {
Layout::UILayoutItem item = {};
item.desiredContentSize = child.desiredSize;
item.width = ParseLengthAttribute(*child.source, "width");
item.height = ParseLengthAttribute(*child.source, "height");
Layout::UILayoutItem item = BuildLayoutItem(child, options.axis);
items.push_back(item);
}
@@ -359,7 +413,7 @@ void EmitNode(
const std::string title = GetAttribute(source, "title");
const std::string subtitle = GetAttribute(source, "subtitle");
float textY = node.rect.y + 12.0f;
float textY = node.rect.y + kHeaderTextInset;
if (!title.empty()) {
drawList.AddText(
UIPoint(node.rect.x + 12.0f, textY),
@@ -367,7 +421,7 @@ void EmitNode(
ToUIColor(Color(0.94f, 0.95f, 0.97f, 1.0f)),
kDefaultFontSize);
++stats.textCommandCount;
textY += MeasureTextHeight(kDefaultFontSize) + 2.0f;
textY += MeasureTextHeight(kDefaultFontSize) + kHeaderTextGap;
}
if (!subtitle.empty()) {
@@ -390,10 +444,10 @@ void EmitNode(
if (tagName == "Button" && title.empty() && subtitle.empty()) {
drawList.AddText(
UIPoint(node.rect.x + 12.0f, node.rect.y + 12.0f),
UIPoint(node.rect.x + 12.0f, ComputeCenteredTextTop(node.rect, kButtonFontSize)),
ResolveNodeText(source),
ToUIColor(Color(0.95f, 0.97f, 1.0f, 1.0f)),
kDefaultFontSize);
kButtonFontSize);
++stats.textCommandCount;
}