Wire LayoutLab to XCUI editor collection primitives

This commit is contained in:
2026-04-05 06:39:10 +08:00
parent c6e0973fe7
commit 9d994dcdb1
2 changed files with 54 additions and 84 deletions

View File

@@ -64,6 +64,7 @@ Current gap:
- `XCUI Demo` now consumes the shared `UITextInputController` path for text editing instead of carrying a private key-handling state machine.
- `LayoutLab` now includes a `ScrollView` prototype and a more editor-like three-column authored layout.
- `LayoutLab` now also covers editor-facing widget prototypes: `TreeView`, `TreeItem`, `ListView`, `ListItem`, `PropertySection`, and `FieldRow`.
- `LayoutLab` now consumes the shared `UIEditorCollectionPrimitives` helper layer for collection-widget tag classification, clipping flags, and default metric resolution instead of keeping that taxonomy private to the sandbox runtime.
- 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`.
@@ -143,6 +144,7 @@ Current gap:
- `ImGuiTransitionBackend` moved behind `ImGuiXCUIHostedPreviewPresenter`
- generic preview frame submission no longer carries an ImGui draw-list pointer
- 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.
- `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.

View File

@@ -8,6 +8,7 @@
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
#include <XCEngine/UI/Style/Theme.h>
#include <XCEngine/UI/Types.h>
#include <XCEngine/UI/Widgets/UIEditorCollectionPrimitives.h>
#include <algorithm>
#include <cstdlib>
@@ -42,6 +43,7 @@ using XCEngine::UI::UIDrawList;
using XCEngine::UI::UIPoint;
using XCEngine::UI::UIRect;
namespace Style = XCEngine::UI::Style;
namespace UIWidgets = XCEngine::UI::Widgets;
constexpr std::size_t kInvalidIndex = static_cast<std::size_t>(-1);
constexpr char kViewRelativePath[] = "new_editor/resources/xcui_layout_lab_view.xcui";
@@ -284,34 +286,6 @@ bool IsStretch(const std::string& value) {
return value.empty() || value == "stretch";
}
bool IsScrollViewTag(const std::string& tagName) {
return tagName == "ScrollView";
}
bool IsTreeViewTag(const std::string& tagName) {
return tagName == "TreeView";
}
bool IsTreeItemTag(const std::string& tagName) {
return tagName == "TreeItem";
}
bool IsListViewTag(const std::string& tagName) {
return tagName == "ListView";
}
bool IsListItemTag(const std::string& tagName) {
return tagName == "ListItem";
}
bool IsPropertySectionTag(const std::string& tagName) {
return tagName == "PropertySection";
}
bool IsFieldRowTag(const std::string& tagName) {
return tagName == "FieldRow";
}
float ResolveScalar(
const std::string& text,
float referenceValue,
@@ -484,10 +458,11 @@ float ResolvePadding(const LayoutNode& node, const Style::UITheme& theme) {
ResolveFloatToken(theme, "space.outer", 18.0f));
}
if (IsTreeViewTag(node.tagName) ||
IsListViewTag(node.tagName) ||
IsPropertySectionTag(node.tagName)) {
return ResolveFloatToken(theme, "space.cardInset", 12.0f);
const float primitivePadding = UIWidgets::ResolveUIEditorCollectionPrimitivePadding(
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName),
theme);
if (primitivePadding > 0.0f) {
return primitivePadding;
}
return node.tagName == "View"
@@ -509,7 +484,9 @@ float ResolveScrollOffset(const LayoutNode& node, const Style::UITheme& theme) {
}
float ResolveListItemHeight(const Style::UITheme& theme) {
return ResolveFloatToken(theme, "size.listItemHeight", 60.0f);
return UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(
UIWidgets::UIEditorCollectionPrimitiveKind::ListItem,
theme);
}
float ResolveTreeIndent(const LayoutNode& node, const Style::UITheme& theme) {
@@ -518,12 +495,16 @@ float ResolveTreeIndent(const LayoutNode& node, const Style::UITheme& theme) {
TryParseFloat(node.indentAttr, indentLevel);
}
return (std::max)(0.0f, indentLevel) *
ResolveFloatToken(theme, "size.treeIndent", 18.0f);
return UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(
UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem,
theme,
(std::max)(0.0f, indentLevel));
}
float ResolveFieldRowHeight(const Style::UITheme& theme) {
return ResolveFloatToken(theme, "size.fieldRowHeight", 32.0f);
return UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(
UIWidgets::UIEditorCollectionPrimitiveKind::FieldRow,
theme);
}
UIRect GetContentRect(const LayoutNode& node, const Style::UITheme& theme) {
@@ -531,23 +512,9 @@ UIRect GetContentRect(const LayoutNode& node, const Style::UITheme& theme) {
}
float ResolveDefaultHeight(const LayoutNode& node, const Style::UITheme& theme) {
if (IsTreeItemTag(node.tagName)) {
return ResolveFloatToken(theme, "size.treeItemHeight", 28.0f);
}
if (IsListItemTag(node.tagName)) {
return ResolveListItemHeight(theme);
}
if (IsFieldRowTag(node.tagName)) {
return ResolveFieldRowHeight(theme);
}
if (IsPropertySectionTag(node.tagName)) {
return ResolveFloatToken(theme, "size.propertySectionHeight", 148.0f);
}
return 0.0f;
return UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName),
theme);
}
void LayoutNodeTree(RuntimeBuildContext& state, std::size_t nodeIndex);
@@ -701,18 +668,18 @@ void LayoutScrollViewChildren(RuntimeBuildContext& state, std::size_t nodeIndex)
void LayoutNodeTree(RuntimeBuildContext& state, std::size_t nodeIndex) {
const LayoutNode& node = state.nodes[nodeIndex];
state.rectsById[node.id] = node.rect;
const UIWidgets::UIEditorCollectionPrimitiveKind primitiveKind =
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName);
if (node.tagName == "View" ||
node.tagName == "Column" ||
IsTreeViewTag(node.tagName) ||
IsListViewTag(node.tagName) ||
IsPropertySectionTag(node.tagName)) {
UIWidgets::UsesUIEditorCollectionPrimitiveColumnLayout(primitiveKind)) {
LayoutColumnChildren(state, nodeIndex);
} else if (node.tagName == "Row") {
LayoutRowChildren(state, nodeIndex);
} else if (node.tagName == "Overlay") {
LayoutOverlayChildren(state, nodeIndex);
} else if (IsScrollViewTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ScrollView) {
LayoutScrollViewChildren(state, nodeIndex);
}
}
@@ -723,6 +690,8 @@ void DrawNode(
UIDrawList& drawList,
const std::string& hoveredId) {
const LayoutNode& node = state.nodes[nodeIndex];
const UIWidgets::UIEditorCollectionPrimitiveKind primitiveKind =
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName);
if (node.tagName == "View") {
const Color panelColor = ResolveColorToken(
@@ -730,9 +699,9 @@ void DrawNode(
"color.panel",
Color(0.07f, 0.10f, 0.14f, 1.0f));
drawList.AddFilledRect(node.rect, ToUIColor(panelColor), 0.0f);
} else if (IsScrollViewTag(node.tagName) ||
IsTreeViewTag(node.tagName) ||
IsListViewTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ScrollView ||
primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeView ||
primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ListView) {
const Color surfaceColor = ResolveColorToken(
state.theme,
"color.scroll.surface",
@@ -744,7 +713,7 @@ void DrawNode(
const float rounding = ResolveFloatToken(state.theme, "radius.card", 10.0f);
drawList.AddFilledRect(node.rect, ToUIColor(surfaceColor), rounding);
drawList.AddRectOutline(node.rect, ToUIColor(borderColor), 1.0f, rounding);
} else if (IsPropertySectionTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection) {
const Color sectionColor = ResolveColorToken(
state.theme,
"color.card",
@@ -831,7 +800,8 @@ void DrawNode(
2.0f,
rounding);
}
} else if (IsTreeItemTag(node.tagName) || IsListItemTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem ||
primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ListItem) {
const bool hovered = node.id == hoveredId;
const Color rowColor = hovered
? ResolveColorToken(state.theme, "color.card.alt", Color(0.20f, 0.27f, 0.34f, 1.0f))
@@ -852,11 +822,13 @@ void DrawNode(
const float titleFont = ResolveFloatToken(state.theme, "font.body", 13.0f);
const float bodyFont = ResolveFloatToken(state.theme, "font.body", 13.0f);
const float rounding = ResolveFloatToken(state.theme, "radius.card", 10.0f);
const float indent = IsTreeItemTag(node.tagName) ? ResolveTreeIndent(node, state.theme) : 0.0f;
const float indent = primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem
? ResolveTreeIndent(node, state.theme)
: 0.0f;
drawList.AddFilledRect(node.rect, ToUIColor(rowColor), rounding);
drawList.AddRectOutline(node.rect, ToUIColor(borderColor), 1.0f, rounding);
if (IsTreeItemTag(node.tagName)) {
if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem) {
drawList.AddFilledRect(
UIRect(node.rect.x + inset + indent - 8.0f, node.rect.y + 11.0f, 4.0f, 4.0f),
ToUIColor(textColor),
@@ -874,7 +846,7 @@ void DrawNode(
ToUIColor(mutedColor),
bodyFont);
}
} else if (IsFieldRowTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::FieldRow) {
const bool hovered = node.id == hoveredId;
const Color textColor = ResolveColorToken(
state.theme,
@@ -903,10 +875,7 @@ void DrawNode(
fontSize);
}
const bool clipsChildren =
IsScrollViewTag(node.tagName) ||
IsTreeViewTag(node.tagName) ||
IsListViewTag(node.tagName);
const bool clipsChildren = UIWidgets::DoesUIEditorCollectionPrimitiveClipChildren(primitiveKind);
if (clipsChildren) {
drawList.PushClipRect(GetContentRect(node, state.theme));
}
@@ -925,9 +894,8 @@ bool IsPointInsideNodeClipping(
std::size_t currentIndex = nodeIndex;
while (currentIndex != kInvalidIndex) {
const LayoutNode& currentNode = state.nodes[currentIndex];
if ((IsScrollViewTag(currentNode.tagName) ||
IsTreeViewTag(currentNode.tagName) ||
IsListViewTag(currentNode.tagName)) &&
if (UIWidgets::DoesUIEditorCollectionPrimitiveClipChildren(
UIWidgets::ClassifyUIEditorCollectionPrimitive(currentNode.tagName)) &&
!ContainsPoint(GetContentRect(currentNode, state.theme), point)) {
return false;
}
@@ -944,11 +912,9 @@ std::size_t HitTest(
int bestDepth = -1;
for (std::size_t index = 0; index < state.nodes.size(); ++index) {
const LayoutNode& node = state.nodes[index];
const bool hoverable =
node.tagName == "Card" ||
IsTreeItemTag(node.tagName) ||
IsListItemTag(node.tagName) ||
IsFieldRowTag(node.tagName);
const bool hoverable = node.tagName == "Card" ||
UIWidgets::IsUIEditorCollectionPrimitiveHoverable(
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName));
if (!hoverable ||
!ContainsPoint(node.rect, point) ||
!IsPointInsideNodeClipping(state, index, point)) {
@@ -1080,25 +1046,27 @@ const XCUILayoutLabFrameResult& XCUILayoutLabRuntime::Update(const XCUILayoutLab
state.frameResult.stats.commandCount = state.frameResult.drawData.GetTotalCommandCount();
AnalyzeNativeOverlayCompatibility(state.frameResult.drawData, state.frameResult.stats);
for (const LayoutNode& node : state.nodes) {
const UIWidgets::UIEditorCollectionPrimitiveKind primitiveKind =
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName);
if (node.tagName == "Row") {
++state.frameResult.stats.rowCount;
} else if (node.tagName == "Column") {
++state.frameResult.stats.columnCount;
} else if (node.tagName == "Overlay") {
++state.frameResult.stats.overlayCount;
} else if (IsScrollViewTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ScrollView) {
++state.frameResult.stats.scrollViewCount;
} else if (IsTreeViewTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeView) {
++state.frameResult.stats.treeViewCount;
} else if (IsTreeItemTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem) {
++state.frameResult.stats.treeItemCount;
} else if (IsListViewTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ListView) {
++state.frameResult.stats.listViewCount;
} else if (IsListItemTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ListItem) {
++state.frameResult.stats.listItemCount;
} else if (IsPropertySectionTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection) {
++state.frameResult.stats.propertySectionCount;
} else if (IsFieldRowTag(node.tagName)) {
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::FieldRow) {
++state.frameResult.stats.fieldRowCount;
}
}