Add XCUI expansion state and coverage tests
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include <XCEngine/UI/Style/Theme.h>
|
||||
#include <XCEngine/UI/Types.h>
|
||||
#include <XCEngine/UI/Widgets/UIEditorCollectionPrimitives.h>
|
||||
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
|
||||
#include <XCEngine/UI/Widgets/UISelectionModel.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -78,6 +79,7 @@ struct RuntimeBuildContext {
|
||||
std::vector<LayoutNode> nodes = {};
|
||||
std::unordered_map<std::string, std::size_t> nodeIndexById = {};
|
||||
std::unordered_map<std::string, UIRect> rectsById = {};
|
||||
UIWidgets::UIExpansionModel expansionModel = {};
|
||||
UIWidgets::UISelectionModel selectionModel = {};
|
||||
bool documentsReady = false;
|
||||
std::string statusMessage = {};
|
||||
@@ -491,16 +493,20 @@ float ResolveListItemHeight(const Style::UITheme& theme) {
|
||||
theme);
|
||||
}
|
||||
|
||||
float ResolveTreeIndent(const LayoutNode& node, const Style::UITheme& theme) {
|
||||
float ResolveTreeIndentLevel(const LayoutNode& node) {
|
||||
float indentLevel = 0.0f;
|
||||
if (!node.indentAttr.empty()) {
|
||||
TryParseFloat(node.indentAttr, indentLevel);
|
||||
}
|
||||
|
||||
return (std::max)(0.0f, indentLevel);
|
||||
}
|
||||
|
||||
float ResolveTreeIndent(const LayoutNode& node, const Style::UITheme& theme) {
|
||||
return UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(
|
||||
UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem,
|
||||
theme,
|
||||
(std::max)(0.0f, indentLevel));
|
||||
ResolveTreeIndentLevel(node));
|
||||
}
|
||||
|
||||
float ResolveFieldRowHeight(const Style::UITheme& theme) {
|
||||
@@ -519,23 +525,195 @@ float ResolveDefaultHeight(const LayoutNode& node, const Style::UITheme& theme)
|
||||
theme);
|
||||
}
|
||||
|
||||
float ResolvePropertySectionHeaderHeight(
|
||||
const RuntimeBuildContext& state,
|
||||
const LayoutNode& node) {
|
||||
const float titleFont = ResolveFloatToken(state.theme, "font.title", 16.0f);
|
||||
const float bodyFont = ResolveFloatToken(state.theme, "font.body", 13.0f);
|
||||
return titleFont + 8.0f + (node.subtitle.empty() ? 0.0f : bodyFont + 8.0f);
|
||||
}
|
||||
|
||||
bool HasTreeItemChildren(const RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
const LayoutNode& node = state.nodes[nodeIndex];
|
||||
if (UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName) !=
|
||||
UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem ||
|
||||
node.parentIndex == kInvalidIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const LayoutNode& parent = state.nodes[node.parentIndex];
|
||||
if (UIWidgets::ClassifyUIEditorCollectionPrimitive(parent.tagName) !=
|
||||
UIWidgets::UIEditorCollectionPrimitiveKind::TreeView) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float indentLevel = ResolveTreeIndentLevel(node);
|
||||
const auto siblingIt = std::find(parent.children.begin(), parent.children.end(), nodeIndex);
|
||||
if (siblingIt == parent.children.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto it = siblingIt + 1; it != parent.children.end(); ++it) {
|
||||
const LayoutNode& sibling = state.nodes[*it];
|
||||
const float siblingIndentLevel = ResolveTreeIndentLevel(sibling);
|
||||
if (siblingIndentLevel <= indentLevel) {
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsNodeCollapsible(const RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
const LayoutNode& node = state.nodes[nodeIndex];
|
||||
const UIWidgets::UIEditorCollectionPrimitiveKind primitiveKind =
|
||||
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName);
|
||||
if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection) {
|
||||
return !node.children.empty();
|
||||
}
|
||||
|
||||
return HasTreeItemChildren(state, nodeIndex);
|
||||
}
|
||||
|
||||
bool IsNodeExpanded(const RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
return !IsNodeCollapsible(state, nodeIndex) ||
|
||||
state.expansionModel.IsExpanded(state.nodes[nodeIndex].id);
|
||||
}
|
||||
|
||||
float ResolvePropertySectionCollapsedHeight(
|
||||
const RuntimeBuildContext& state,
|
||||
const LayoutNode& node) {
|
||||
const float inset = ResolveFloatToken(state.theme, "space.cardInset", 12.0f);
|
||||
return inset * 2.0f + ResolvePropertySectionHeaderHeight(state, node);
|
||||
}
|
||||
|
||||
bool IsNodeVisible(const RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
const LayoutNode& node = state.nodes[nodeIndex];
|
||||
const UIWidgets::UIEditorCollectionPrimitiveKind primitiveKind =
|
||||
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName);
|
||||
|
||||
std::size_t ancestorIndex = node.parentIndex;
|
||||
while (ancestorIndex != kInvalidIndex) {
|
||||
const LayoutNode& ancestor = state.nodes[ancestorIndex];
|
||||
if (UIWidgets::ClassifyUIEditorCollectionPrimitive(ancestor.tagName) ==
|
||||
UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection &&
|
||||
!IsNodeExpanded(state, ancestorIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ancestorIndex = ancestor.parentIndex;
|
||||
}
|
||||
|
||||
if (primitiveKind != UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem ||
|
||||
node.parentIndex == kInvalidIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const LayoutNode& parent = state.nodes[node.parentIndex];
|
||||
if (UIWidgets::ClassifyUIEditorCollectionPrimitive(parent.tagName) !=
|
||||
UIWidgets::UIEditorCollectionPrimitiveKind::TreeView) {
|
||||
return true;
|
||||
}
|
||||
|
||||
float requiredAncestorIndent = ResolveTreeIndentLevel(node);
|
||||
if (requiredAncestorIndent <= 0.0f) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto siblingIt = std::find(parent.children.begin(), parent.children.end(), nodeIndex);
|
||||
if (siblingIt == parent.children.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::size_t siblingOffset =
|
||||
static_cast<std::size_t>(siblingIt - parent.children.begin());
|
||||
for (std::size_t offset = siblingOffset; offset > 0u && requiredAncestorIndent > 0.0f; --offset) {
|
||||
const std::size_t siblingIndex = parent.children[offset - 1u];
|
||||
const LayoutNode& sibling = state.nodes[siblingIndex];
|
||||
const float siblingIndent = ResolveTreeIndentLevel(sibling);
|
||||
if (siblingIndent < requiredAncestorIndent) {
|
||||
if (!IsNodeExpanded(state, siblingIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
requiredAncestorIndent = siblingIndent;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::size_t> CollectVisibleChildren(
|
||||
const RuntimeBuildContext& state,
|
||||
std::size_t nodeIndex) {
|
||||
std::vector<std::size_t> visibleChildren = {};
|
||||
const LayoutNode& node = state.nodes[nodeIndex];
|
||||
visibleChildren.reserve(node.children.size());
|
||||
for (const std::size_t childIndex : node.children) {
|
||||
if (IsNodeVisible(state, childIndex)) {
|
||||
visibleChildren.push_back(childIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return visibleChildren;
|
||||
}
|
||||
|
||||
void SeedDefaultExpansionState(RuntimeBuildContext& state) {
|
||||
state.expansionModel.Clear();
|
||||
for (std::size_t nodeIndex = 0; nodeIndex < state.nodes.size(); ++nodeIndex) {
|
||||
const LayoutNode& node = state.nodes[nodeIndex];
|
||||
const UIWidgets::UIEditorCollectionPrimitiveKind primitiveKind =
|
||||
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName);
|
||||
if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection ||
|
||||
HasTreeItemChildren(state, nodeIndex)) {
|
||||
state.expansionModel.Expand(node.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LayoutNodeTree(RuntimeBuildContext& state, std::size_t nodeIndex);
|
||||
|
||||
void LayoutColumnChildren(RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
LayoutNode& node = state.nodes[nodeIndex];
|
||||
const float padding = ResolvePadding(node, state.theme);
|
||||
const float gap = ResolveGap(node, state.theme);
|
||||
const UIRect contentRect = InsetRect(node.rect, padding);
|
||||
if (node.children.empty()) {
|
||||
UIRect contentRect = InsetRect(node.rect, padding);
|
||||
if (UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName) ==
|
||||
UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection) {
|
||||
const float headerHeight = ResolvePropertySectionHeaderHeight(state, node);
|
||||
contentRect.y += headerHeight;
|
||||
contentRect.height = (std::max)(0.0f, contentRect.height - headerHeight);
|
||||
}
|
||||
const std::vector<std::size_t> visibleChildren = CollectVisibleChildren(state, nodeIndex);
|
||||
if (visibleChildren.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float fixedHeight = 0.0f;
|
||||
std::size_t stretchCount = 0;
|
||||
std::vector<float> resolvedHeights(node.children.size(), 0.0f);
|
||||
for (std::size_t childOffset = 0; childOffset < node.children.size(); ++childOffset) {
|
||||
const LayoutNode& child = state.nodes[node.children[childOffset]];
|
||||
std::vector<float> resolvedHeights(visibleChildren.size(), 0.0f);
|
||||
std::vector<bool> childUsesStretchHeight(visibleChildren.size(), false);
|
||||
for (std::size_t childOffset = 0; childOffset < visibleChildren.size(); ++childOffset) {
|
||||
const LayoutNode& child = state.nodes[visibleChildren[childOffset]];
|
||||
const float defaultHeight = ResolveDefaultHeight(child, state.theme);
|
||||
if (UIWidgets::ClassifyUIEditorCollectionPrimitive(child.tagName) ==
|
||||
UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection &&
|
||||
!IsNodeExpanded(state, visibleChildren[childOffset])) {
|
||||
resolvedHeights[childOffset] = ResolvePropertySectionCollapsedHeight(state, child);
|
||||
fixedHeight += resolvedHeights[childOffset];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.heightAttr.empty() && defaultHeight > 0.0f) {
|
||||
resolvedHeights[childOffset] = defaultHeight;
|
||||
fixedHeight += resolvedHeights[childOffset];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsStretch(child.heightAttr)) {
|
||||
childUsesStretchHeight[childOffset] = true;
|
||||
++stretchCount;
|
||||
continue;
|
||||
}
|
||||
@@ -543,27 +721,28 @@ void LayoutColumnChildren(RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
resolvedHeights[childOffset] = ResolveScalar(
|
||||
child.heightAttr,
|
||||
contentRect.height,
|
||||
ResolveDefaultHeight(child, state.theme));
|
||||
defaultHeight);
|
||||
fixedHeight += resolvedHeights[childOffset];
|
||||
}
|
||||
|
||||
const float totalGap = gap * static_cast<float>((std::max<std::size_t>)(1u, node.children.size()) - 1u);
|
||||
const float totalGap =
|
||||
gap * static_cast<float>((std::max<std::size_t>)(1u, visibleChildren.size()) - 1u);
|
||||
const float remainingHeight = (std::max)(0.0f, contentRect.height - fixedHeight - totalGap);
|
||||
const float stretchHeight =
|
||||
stretchCount > 0u ? remainingHeight / static_cast<float>(stretchCount) : 0.0f;
|
||||
|
||||
float cursorY = contentRect.y;
|
||||
for (std::size_t childOffset = 0; childOffset < node.children.size(); ++childOffset) {
|
||||
LayoutNode& child = state.nodes[node.children[childOffset]];
|
||||
for (std::size_t childOffset = 0; childOffset < visibleChildren.size(); ++childOffset) {
|
||||
LayoutNode& child = state.nodes[visibleChildren[childOffset]];
|
||||
const float childHeight =
|
||||
!IsStretch(child.heightAttr) ? resolvedHeights[childOffset] : stretchHeight;
|
||||
childUsesStretchHeight[childOffset] ? stretchHeight : resolvedHeights[childOffset];
|
||||
const float childWidth =
|
||||
!IsStretch(child.widthAttr)
|
||||
? ResolveScalar(child.widthAttr, contentRect.width, contentRect.width)
|
||||
: contentRect.width;
|
||||
child.rect = UIRect(contentRect.x, cursorY, childWidth, childHeight);
|
||||
cursorY += childHeight + gap;
|
||||
LayoutNodeTree(state, node.children[childOffset]);
|
||||
LayoutNodeTree(state, visibleChildren[childOffset]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,15 +751,16 @@ void LayoutRowChildren(RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
const float padding = ResolvePadding(node, state.theme);
|
||||
const float gap = ResolveGap(node, state.theme);
|
||||
const UIRect contentRect = InsetRect(node.rect, padding);
|
||||
if (node.children.empty()) {
|
||||
const std::vector<std::size_t> visibleChildren = CollectVisibleChildren(state, nodeIndex);
|
||||
if (visibleChildren.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float fixedWidth = 0.0f;
|
||||
std::size_t stretchCount = 0;
|
||||
std::vector<float> resolvedWidths(node.children.size(), 0.0f);
|
||||
for (std::size_t childOffset = 0; childOffset < node.children.size(); ++childOffset) {
|
||||
const LayoutNode& child = state.nodes[node.children[childOffset]];
|
||||
std::vector<float> resolvedWidths(visibleChildren.size(), 0.0f);
|
||||
for (std::size_t childOffset = 0; childOffset < visibleChildren.size(); ++childOffset) {
|
||||
const LayoutNode& child = state.nodes[visibleChildren[childOffset]];
|
||||
if (IsStretch(child.widthAttr)) {
|
||||
++stretchCount;
|
||||
continue;
|
||||
@@ -590,14 +770,15 @@ void LayoutRowChildren(RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
fixedWidth += resolvedWidths[childOffset];
|
||||
}
|
||||
|
||||
const float totalGap = gap * static_cast<float>((std::max<std::size_t>)(1u, node.children.size()) - 1u);
|
||||
const float totalGap =
|
||||
gap * static_cast<float>((std::max<std::size_t>)(1u, visibleChildren.size()) - 1u);
|
||||
const float remainingWidth = (std::max)(0.0f, contentRect.width - fixedWidth - totalGap);
|
||||
const float stretchWidth =
|
||||
stretchCount > 0u ? remainingWidth / static_cast<float>(stretchCount) : 0.0f;
|
||||
|
||||
float cursorX = contentRect.x;
|
||||
for (std::size_t childOffset = 0; childOffset < node.children.size(); ++childOffset) {
|
||||
LayoutNode& child = state.nodes[node.children[childOffset]];
|
||||
for (std::size_t childOffset = 0; childOffset < visibleChildren.size(); ++childOffset) {
|
||||
LayoutNode& child = state.nodes[visibleChildren[childOffset]];
|
||||
const float childWidth =
|
||||
!IsStretch(child.widthAttr) ? resolvedWidths[childOffset] : stretchWidth;
|
||||
const float childHeight =
|
||||
@@ -606,7 +787,7 @@ void LayoutRowChildren(RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
: contentRect.height;
|
||||
child.rect = UIRect(cursorX, contentRect.y, childWidth, childHeight);
|
||||
cursorX += childWidth + gap;
|
||||
LayoutNodeTree(state, node.children[childOffset]);
|
||||
LayoutNodeTree(state, visibleChildren[childOffset]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,7 +795,7 @@ void LayoutOverlayChildren(RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
LayoutNode& node = state.nodes[nodeIndex];
|
||||
const UIRect contentRect = GetContentRect(node, state.theme);
|
||||
|
||||
for (std::size_t childIndex : node.children) {
|
||||
for (const std::size_t childIndex : CollectVisibleChildren(state, nodeIndex)) {
|
||||
LayoutNode& child = state.nodes[childIndex];
|
||||
const float offsetX = ResolveScalar(child.xAttr, contentRect.width, 0.0f);
|
||||
const float offsetY = ResolveScalar(child.yAttr, contentRect.height, 0.0f);
|
||||
@@ -644,17 +825,22 @@ void LayoutScrollViewChildren(RuntimeBuildContext& state, std::size_t nodeIndex)
|
||||
const UIRect contentRect = GetContentRect(node, state.theme);
|
||||
|
||||
float cursorY = contentRect.y - scrollOffset;
|
||||
for (std::size_t childIndex : node.children) {
|
||||
for (const std::size_t childIndex : CollectVisibleChildren(state, nodeIndex)) {
|
||||
LayoutNode& child = state.nodes[childIndex];
|
||||
const float defaultHeight = ResolveDefaultHeight(child, state.theme);
|
||||
const float childHeight = child.heightAttr.empty()
|
||||
? (defaultHeight > 0.0f ? defaultHeight : defaultItemHeight)
|
||||
: (!IsStretch(child.heightAttr)
|
||||
? ResolveScalar(
|
||||
child.heightAttr,
|
||||
contentRect.height,
|
||||
defaultHeight > 0.0f ? defaultHeight : defaultItemHeight)
|
||||
: defaultItemHeight);
|
||||
const float childHeight =
|
||||
UIWidgets::ClassifyUIEditorCollectionPrimitive(child.tagName) ==
|
||||
UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection &&
|
||||
!IsNodeExpanded(state, childIndex)
|
||||
? ResolvePropertySectionCollapsedHeight(state, child)
|
||||
: (child.heightAttr.empty()
|
||||
? (defaultHeight > 0.0f ? defaultHeight : defaultItemHeight)
|
||||
: (!IsStretch(child.heightAttr)
|
||||
? ResolveScalar(
|
||||
child.heightAttr,
|
||||
contentRect.height,
|
||||
defaultHeight > 0.0f ? defaultHeight : defaultItemHeight)
|
||||
: defaultItemHeight));
|
||||
const float childWidth =
|
||||
!IsStretch(child.widthAttr)
|
||||
? (std::min)(
|
||||
@@ -686,6 +872,23 @@ void LayoutNodeTree(RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
void DrawDisclosureGlyph(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
const UIColor& color,
|
||||
bool expanded) {
|
||||
drawList.AddFilledRect(
|
||||
UIRect(rect.x, rect.y + rect.height * 0.5f - 1.0f, rect.width, 2.0f),
|
||||
color,
|
||||
1.0f);
|
||||
if (!expanded) {
|
||||
drawList.AddFilledRect(
|
||||
UIRect(rect.x + rect.width * 0.5f - 1.0f, rect.y, 2.0f, rect.height),
|
||||
color,
|
||||
1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawNode(
|
||||
RuntimeBuildContext& state,
|
||||
std::size_t nodeIndex,
|
||||
@@ -716,14 +919,15 @@ void DrawNode(
|
||||
drawList.AddFilledRect(node.rect, ToUIColor(surfaceColor), rounding);
|
||||
drawList.AddRectOutline(node.rect, ToUIColor(borderColor), 1.0f, rounding);
|
||||
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection) {
|
||||
const bool selected = state.selectionModel.IsSelected(node.id);
|
||||
const bool expanded = IsNodeExpanded(state, nodeIndex);
|
||||
const Color sectionColor = ResolveColorToken(
|
||||
state.theme,
|
||||
"color.card",
|
||||
Color(0.12f, 0.17f, 0.23f, 1.0f));
|
||||
const Color borderColor = ResolveColorToken(
|
||||
state.theme,
|
||||
"color.border",
|
||||
Color(0.24f, 0.34f, 0.43f, 1.0f));
|
||||
const Color borderColor = selected
|
||||
? ResolveColorToken(state.theme, "color.accent", Color(0.30f, 0.46f, 0.58f, 1.0f))
|
||||
: ResolveColorToken(state.theme, "color.border", Color(0.24f, 0.34f, 0.43f, 1.0f));
|
||||
const Color textColor = ResolveColorToken(
|
||||
state.theme,
|
||||
"color.text",
|
||||
@@ -753,6 +957,17 @@ void DrawNode(
|
||||
ToUIColor(mutedColor),
|
||||
bodyFont);
|
||||
}
|
||||
if (IsNodeCollapsible(state, nodeIndex)) {
|
||||
DrawDisclosureGlyph(
|
||||
drawList,
|
||||
UIRect(
|
||||
node.rect.x + node.rect.width - inset - 10.0f,
|
||||
node.rect.y + inset + 3.0f,
|
||||
10.0f,
|
||||
10.0f),
|
||||
ToUIColor(textColor),
|
||||
expanded);
|
||||
}
|
||||
} else if (node.tagName == "Card") {
|
||||
const bool selected = state.selectionModel.IsSelected(node.id);
|
||||
const Color cardColor = ResolveColorToken(
|
||||
@@ -813,6 +1028,8 @@ void DrawNode(
|
||||
primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ListItem) {
|
||||
const bool hovered = node.id == hoveredId;
|
||||
const bool selected = state.selectionModel.IsSelected(node.id);
|
||||
const bool hasTreeChildren = primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem &&
|
||||
HasTreeItemChildren(state, nodeIndex);
|
||||
const Color rowColor = selected
|
||||
? ResolveColorToken(state.theme, "color.accent", Color(0.30f, 0.46f, 0.58f, 1.0f))
|
||||
: (hovered
|
||||
@@ -840,10 +1057,18 @@ void DrawNode(
|
||||
drawList.AddFilledRect(node.rect, ToUIColor(rowColor), rounding);
|
||||
drawList.AddRectOutline(node.rect, ToUIColor(borderColor), 1.0f, rounding);
|
||||
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),
|
||||
2.0f);
|
||||
if (hasTreeChildren) {
|
||||
DrawDisclosureGlyph(
|
||||
drawList,
|
||||
UIRect(node.rect.x + inset + indent - 10.0f, node.rect.y + 10.0f, 8.0f, 8.0f),
|
||||
ToUIColor(textColor),
|
||||
IsNodeExpanded(state, nodeIndex));
|
||||
} else {
|
||||
drawList.AddFilledRect(
|
||||
UIRect(node.rect.x + inset + indent - 8.0f, node.rect.y + 11.0f, 4.0f, 4.0f),
|
||||
ToUIColor(textColor),
|
||||
2.0f);
|
||||
}
|
||||
}
|
||||
drawList.AddText(
|
||||
UIPoint(node.rect.x + inset + indent, node.rect.y + 8.0f),
|
||||
@@ -891,7 +1116,10 @@ void DrawNode(
|
||||
if (clipsChildren) {
|
||||
drawList.PushClipRect(GetContentRect(node, state.theme));
|
||||
}
|
||||
for (std::size_t childIndex : node.children) {
|
||||
for (const std::size_t childIndex : node.children) {
|
||||
if (!IsNodeVisible(state, childIndex)) {
|
||||
continue;
|
||||
}
|
||||
DrawNode(state, childIndex, drawList, hoveredId);
|
||||
}
|
||||
if (clipsChildren) {
|
||||
@@ -928,6 +1156,7 @@ std::size_t HitTest(
|
||||
UIWidgets::IsUIEditorCollectionPrimitiveHoverable(
|
||||
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName));
|
||||
if (!hoverable ||
|
||||
!IsNodeVisible(state, index) ||
|
||||
!ContainsPoint(node.rect, point) ||
|
||||
!IsPointInsideNodeClipping(state, index, point)) {
|
||||
continue;
|
||||
@@ -967,6 +1196,7 @@ bool XCUILayoutLabRuntime::ReloadDocuments() {
|
||||
state.nodes.clear();
|
||||
state.nodeIndexById.clear();
|
||||
state.rectsById.clear();
|
||||
state.expansionModel.Clear();
|
||||
state.selectionModel.ClearSelection();
|
||||
|
||||
state.documentSource.SetPathSet(XCUIAssetDocumentSource::MakeLayoutLabPathSet());
|
||||
@@ -1001,6 +1231,7 @@ bool XCUILayoutLabRuntime::ReloadDocuments() {
|
||||
std::string(),
|
||||
0u,
|
||||
0);
|
||||
SeedDefaultExpansionState(state);
|
||||
state.documentsReady = !state.nodes.empty();
|
||||
state.statusMessage = state.documentsReady
|
||||
? (loadState.usedLegacyFallback
|
||||
@@ -1050,6 +1281,9 @@ const XCUILayoutLabFrameResult& XCUILayoutLabRuntime::Update(const XCUILayoutLab
|
||||
|
||||
if (input.pointerPressed) {
|
||||
if (hoveredIndex != kInvalidIndex) {
|
||||
if (IsNodeCollapsible(state, hoveredIndex)) {
|
||||
state.expansionModel.ToggleExpanded(state.nodes[hoveredIndex].id);
|
||||
}
|
||||
state.selectionModel.SetSelection(state.nodes[hoveredIndex].id);
|
||||
} else {
|
||||
state.selectionModel.ClearSelection();
|
||||
@@ -1069,7 +1303,12 @@ const XCUILayoutLabFrameResult& XCUILayoutLabRuntime::Update(const XCUILayoutLab
|
||||
state.frameResult.stats.drawListCount = state.frameResult.drawData.GetDrawListCount();
|
||||
state.frameResult.stats.commandCount = state.frameResult.drawData.GetTotalCommandCount();
|
||||
AnalyzeNativeOverlayCompatibility(state.frameResult.drawData, state.frameResult.stats);
|
||||
for (const LayoutNode& node : state.nodes) {
|
||||
for (std::size_t nodeIndex = 0; nodeIndex < state.nodes.size(); ++nodeIndex) {
|
||||
const LayoutNode& node = state.nodes[nodeIndex];
|
||||
if (!IsNodeVisible(state, nodeIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const UIWidgets::UIEditorCollectionPrimitiveKind primitiveKind =
|
||||
UIWidgets::ClassifyUIEditorCollectionPrimitive(node.tagName);
|
||||
if (node.tagName == "Row") {
|
||||
@@ -1084,12 +1323,18 @@ const XCUILayoutLabFrameResult& XCUILayoutLabRuntime::Update(const XCUILayoutLab
|
||||
++state.frameResult.stats.treeViewCount;
|
||||
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem) {
|
||||
++state.frameResult.stats.treeItemCount;
|
||||
if (HasTreeItemChildren(state, nodeIndex) && IsNodeExpanded(state, nodeIndex)) {
|
||||
++state.frameResult.stats.expandedTreeItemCount;
|
||||
}
|
||||
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ListView) {
|
||||
++state.frameResult.stats.listViewCount;
|
||||
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ListItem) {
|
||||
++state.frameResult.stats.listItemCount;
|
||||
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::PropertySection) {
|
||||
++state.frameResult.stats.propertySectionCount;
|
||||
if (IsNodeExpanded(state, nodeIndex)) {
|
||||
++state.frameResult.stats.expandedPropertySectionCount;
|
||||
}
|
||||
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::FieldRow) {
|
||||
++state.frameResult.stats.fieldRowCount;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ struct XCUILayoutLabFrameStats {
|
||||
std::size_t listItemCount = 0;
|
||||
std::size_t propertySectionCount = 0;
|
||||
std::size_t fieldRowCount = 0;
|
||||
std::size_t expandedTreeItemCount = 0;
|
||||
std::size_t expandedPropertySectionCount = 0;
|
||||
std::string hoveredElementId = {};
|
||||
std::string selectedElementId = {};
|
||||
};
|
||||
|
||||
@@ -317,6 +317,12 @@ void XCUILayoutLabPanel::Render() {
|
||||
stats.columnCount,
|
||||
stats.overlayCount,
|
||||
stats.scrollViewCount);
|
||||
ImGui::Text(
|
||||
"Tree items: %zu (%zu expanded) | Property sections: %zu (%zu expanded)",
|
||||
stats.treeItemCount,
|
||||
stats.expandedTreeItemCount,
|
||||
stats.propertySectionCount,
|
||||
stats.expandedPropertySectionCount);
|
||||
ImGui::Text(
|
||||
"Draw lists: %zu | Draw commands: %zu",
|
||||
stats.drawListCount,
|
||||
|
||||
Reference in New Issue
Block a user