From 9d994dcdb1f42d954fe66a3783e5cc61cce6ac07 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 5 Apr 2026 06:39:10 +0800 Subject: [PATCH] Wire LayoutLab to XCUI editor collection primitives --- docs/plan/XCUI_Phase_Status_2026-04-05.md | 2 + .../src/XCUIBackend/XCUILayoutLabRuntime.cpp | 136 +++++++----------- 2 files changed, 54 insertions(+), 84 deletions(-) diff --git a/docs/plan/XCUI_Phase_Status_2026-04-05.md b/docs/plan/XCUI_Phase_Status_2026-04-05.md index b89288e2..e8f150f5 100644 --- a/docs/plan/XCUI_Phase_Status_2026-04-05.md +++ b/docs/plan/XCUI_Phase_Status_2026-04-05.md @@ -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. diff --git a/new_editor/src/XCUIBackend/XCUILayoutLabRuntime.cpp b/new_editor/src/XCUIBackend/XCUILayoutLabRuntime.cpp index af871c12..11218b8f 100644 --- a/new_editor/src/XCUIBackend/XCUILayoutLabRuntime.cpp +++ b/new_editor/src/XCUIBackend/XCUILayoutLabRuntime.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -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(-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; } }