Expand XCUI layout lab editor widgets
This commit is contained in:
@@ -60,6 +60,7 @@ struct LayoutNode {
|
||||
std::string gapAttr = {};
|
||||
std::string paddingAttr = {};
|
||||
std::string scrollYAttr = {};
|
||||
std::string indentAttr = {};
|
||||
std::size_t parentIndex = kInvalidIndex;
|
||||
std::vector<std::size_t> children = {};
|
||||
UIRect rect = {};
|
||||
@@ -287,6 +288,30 @@ 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,
|
||||
@@ -417,6 +442,7 @@ void BuildNodesRecursive(
|
||||
layoutNode.gapAttr = GetAttributeValue(node, "gap");
|
||||
layoutNode.paddingAttr = GetAttributeValue(node, "padding");
|
||||
layoutNode.scrollYAttr = GetAttributeValue(node, "scrollY");
|
||||
layoutNode.indentAttr = GetAttributeValue(node, "indent");
|
||||
layoutNode.parentIndex = parentIndex;
|
||||
layoutNode.depth = depth;
|
||||
|
||||
@@ -458,6 +484,12 @@ 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);
|
||||
}
|
||||
|
||||
return node.tagName == "View"
|
||||
? ResolveFloatToken(theme, "space.outer", 18.0f)
|
||||
: 0.0f;
|
||||
@@ -480,10 +512,44 @@ float ResolveListItemHeight(const Style::UITheme& theme) {
|
||||
return ResolveFloatToken(theme, "size.listItemHeight", 60.0f);
|
||||
}
|
||||
|
||||
float ResolveTreeIndent(const LayoutNode& node, const Style::UITheme& theme) {
|
||||
float indentLevel = 0.0f;
|
||||
if (!node.indentAttr.empty()) {
|
||||
TryParseFloat(node.indentAttr, indentLevel);
|
||||
}
|
||||
|
||||
return (std::max)(0.0f, indentLevel) *
|
||||
ResolveFloatToken(theme, "size.treeIndent", 18.0f);
|
||||
}
|
||||
|
||||
float ResolveFieldRowHeight(const Style::UITheme& theme) {
|
||||
return ResolveFloatToken(theme, "size.fieldRowHeight", 32.0f);
|
||||
}
|
||||
|
||||
UIRect GetContentRect(const LayoutNode& node, const Style::UITheme& theme) {
|
||||
return InsetRect(node.rect, ResolvePadding(node, 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;
|
||||
}
|
||||
|
||||
void LayoutNodeTree(RuntimeBuildContext& state, std::size_t nodeIndex);
|
||||
|
||||
void LayoutColumnChildren(RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
@@ -505,7 +571,10 @@ void LayoutColumnChildren(RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
resolvedHeights[childOffset] = ResolveScalar(child.heightAttr, contentRect.height, 0.0f);
|
||||
resolvedHeights[childOffset] = ResolveScalar(
|
||||
child.heightAttr,
|
||||
contentRect.height,
|
||||
ResolveDefaultHeight(child, state.theme));
|
||||
fixedHeight += resolvedHeights[childOffset];
|
||||
}
|
||||
|
||||
@@ -608,10 +677,15 @@ void LayoutScrollViewChildren(RuntimeBuildContext& state, std::size_t nodeIndex)
|
||||
float cursorY = contentRect.y - scrollOffset;
|
||||
for (std::size_t childIndex : node.children) {
|
||||
LayoutNode& child = state.nodes[childIndex];
|
||||
const float childHeight =
|
||||
!IsStretch(child.heightAttr)
|
||||
? ResolveScalar(child.heightAttr, contentRect.height, defaultItemHeight)
|
||||
: defaultItemHeight;
|
||||
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 childWidth =
|
||||
!IsStretch(child.widthAttr)
|
||||
? (std::min)(
|
||||
@@ -628,7 +702,11 @@ void LayoutNodeTree(RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||
const LayoutNode& node = state.nodes[nodeIndex];
|
||||
state.rectsById[node.id] = node.rect;
|
||||
|
||||
if (node.tagName == "View" || node.tagName == "Column") {
|
||||
if (node.tagName == "View" ||
|
||||
node.tagName == "Column" ||
|
||||
IsTreeViewTag(node.tagName) ||
|
||||
IsListViewTag(node.tagName) ||
|
||||
IsPropertySectionTag(node.tagName)) {
|
||||
LayoutColumnChildren(state, nodeIndex);
|
||||
} else if (node.tagName == "Row") {
|
||||
LayoutRowChildren(state, nodeIndex);
|
||||
@@ -652,7 +730,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)) {
|
||||
} else if (IsScrollViewTag(node.tagName) ||
|
||||
IsTreeViewTag(node.tagName) ||
|
||||
IsListViewTag(node.tagName)) {
|
||||
const Color surfaceColor = ResolveColorToken(
|
||||
state.theme,
|
||||
"color.scroll.surface",
|
||||
@@ -664,6 +744,44 @@ 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)) {
|
||||
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 textColor = ResolveColorToken(
|
||||
state.theme,
|
||||
"color.text",
|
||||
Color(0.95f, 0.97f, 1.0f, 1.0f));
|
||||
const Color mutedColor = ResolveColorToken(
|
||||
state.theme,
|
||||
"color.text.muted",
|
||||
Color(0.72f, 0.79f, 0.86f, 1.0f));
|
||||
const float rounding = ResolveFloatToken(state.theme, "radius.card", 10.0f);
|
||||
const float inset = ResolveFloatToken(state.theme, "space.cardInset", 12.0f);
|
||||
const float titleFont = ResolveFloatToken(state.theme, "font.title", 16.0f);
|
||||
const float bodyFont = ResolveFloatToken(state.theme, "font.body", 13.0f);
|
||||
|
||||
drawList.AddFilledRect(node.rect, ToUIColor(sectionColor), rounding);
|
||||
drawList.AddRectOutline(node.rect, ToUIColor(borderColor), 1.0f, rounding);
|
||||
if (!node.title.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(node.rect.x + inset, node.rect.y + inset),
|
||||
node.title,
|
||||
ToUIColor(textColor),
|
||||
titleFont);
|
||||
}
|
||||
if (!node.subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(node.rect.x + inset, node.rect.y + inset + titleFont + 6.0f),
|
||||
node.subtitle,
|
||||
ToUIColor(mutedColor),
|
||||
bodyFont);
|
||||
}
|
||||
} else if (node.tagName == "Card") {
|
||||
const Color cardColor = ResolveColorToken(
|
||||
state.theme,
|
||||
@@ -713,9 +831,82 @@ void DrawNode(
|
||||
2.0f,
|
||||
rounding);
|
||||
}
|
||||
} else if (IsTreeItemTag(node.tagName) || IsListItemTag(node.tagName)) {
|
||||
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))
|
||||
: 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 textColor = ResolveColorToken(
|
||||
state.theme,
|
||||
"color.text",
|
||||
Color(0.95f, 0.97f, 1.0f, 1.0f));
|
||||
const Color mutedColor = ResolveColorToken(
|
||||
state.theme,
|
||||
"color.text.muted",
|
||||
Color(0.72f, 0.79f, 0.86f, 1.0f));
|
||||
const float inset = ResolveFloatToken(state.theme, "space.cardInset", 12.0f);
|
||||
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;
|
||||
|
||||
drawList.AddFilledRect(node.rect, ToUIColor(rowColor), rounding);
|
||||
drawList.AddRectOutline(node.rect, ToUIColor(borderColor), 1.0f, rounding);
|
||||
if (IsTreeItemTag(node.tagName)) {
|
||||
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),
|
||||
node.title,
|
||||
ToUIColor(textColor),
|
||||
titleFont);
|
||||
if (!node.subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(node.rect.x + inset + indent, node.rect.y + 8.0f + titleFont + 4.0f),
|
||||
node.subtitle,
|
||||
ToUIColor(mutedColor),
|
||||
bodyFont);
|
||||
}
|
||||
} else if (IsFieldRowTag(node.tagName)) {
|
||||
const bool hovered = node.id == hoveredId;
|
||||
const Color textColor = ResolveColorToken(
|
||||
state.theme,
|
||||
"color.text",
|
||||
Color(0.95f, 0.97f, 1.0f, 1.0f));
|
||||
const Color mutedColor = ResolveColorToken(
|
||||
state.theme,
|
||||
"color.text.muted",
|
||||
Color(0.72f, 0.79f, 0.86f, 1.0f));
|
||||
const Color lineColor = hovered
|
||||
? 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 float inset = ResolveFloatToken(state.theme, "space.cardInset", 12.0f);
|
||||
const float fontSize = ResolveFloatToken(state.theme, "font.body", 13.0f);
|
||||
|
||||
drawList.AddRectOutline(node.rect, ToUIColor(lineColor), 1.0f, 6.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(node.rect.x + inset, node.rect.y + 8.0f),
|
||||
node.title,
|
||||
ToUIColor(textColor),
|
||||
fontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(node.rect.x + node.rect.width * 0.54f, node.rect.y + 8.0f),
|
||||
node.subtitle,
|
||||
ToUIColor(mutedColor),
|
||||
fontSize);
|
||||
}
|
||||
|
||||
const bool clipsChildren = IsScrollViewTag(node.tagName);
|
||||
const bool clipsChildren =
|
||||
IsScrollViewTag(node.tagName) ||
|
||||
IsTreeViewTag(node.tagName) ||
|
||||
IsListViewTag(node.tagName);
|
||||
if (clipsChildren) {
|
||||
drawList.PushClipRect(GetContentRect(node, state.theme));
|
||||
}
|
||||
@@ -734,7 +925,9 @@ bool IsPointInsideNodeClipping(
|
||||
std::size_t currentIndex = nodeIndex;
|
||||
while (currentIndex != kInvalidIndex) {
|
||||
const LayoutNode& currentNode = state.nodes[currentIndex];
|
||||
if (IsScrollViewTag(currentNode.tagName) &&
|
||||
if ((IsScrollViewTag(currentNode.tagName) ||
|
||||
IsTreeViewTag(currentNode.tagName) ||
|
||||
IsListViewTag(currentNode.tagName)) &&
|
||||
!ContainsPoint(GetContentRect(currentNode, state.theme), point)) {
|
||||
return false;
|
||||
}
|
||||
@@ -751,7 +944,12 @@ std::size_t HitTest(
|
||||
int bestDepth = -1;
|
||||
for (std::size_t index = 0; index < state.nodes.size(); ++index) {
|
||||
const LayoutNode& node = state.nodes[index];
|
||||
if (node.tagName != "Card" ||
|
||||
const bool hoverable =
|
||||
node.tagName == "Card" ||
|
||||
IsTreeItemTag(node.tagName) ||
|
||||
IsListItemTag(node.tagName) ||
|
||||
IsFieldRowTag(node.tagName);
|
||||
if (!hoverable ||
|
||||
!ContainsPoint(node.rect, point) ||
|
||||
!IsPointInsideNodeClipping(state, index, point)) {
|
||||
continue;
|
||||
@@ -890,6 +1088,18 @@ const XCUILayoutLabFrameResult& XCUILayoutLabRuntime::Update(const XCUILayoutLab
|
||||
++state.frameResult.stats.overlayCount;
|
||||
} else if (IsScrollViewTag(node.tagName)) {
|
||||
++state.frameResult.stats.scrollViewCount;
|
||||
} else if (IsTreeViewTag(node.tagName)) {
|
||||
++state.frameResult.stats.treeViewCount;
|
||||
} else if (IsTreeItemTag(node.tagName)) {
|
||||
++state.frameResult.stats.treeItemCount;
|
||||
} else if (IsListViewTag(node.tagName)) {
|
||||
++state.frameResult.stats.listViewCount;
|
||||
} else if (IsListItemTag(node.tagName)) {
|
||||
++state.frameResult.stats.listItemCount;
|
||||
} else if (IsPropertySectionTag(node.tagName)) {
|
||||
++state.frameResult.stats.propertySectionCount;
|
||||
} else if (IsFieldRowTag(node.tagName)) {
|
||||
++state.frameResult.stats.fieldRowCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,12 @@ struct XCUILayoutLabFrameStats {
|
||||
std::size_t columnCount = 0;
|
||||
std::size_t overlayCount = 0;
|
||||
std::size_t scrollViewCount = 0;
|
||||
std::size_t treeViewCount = 0;
|
||||
std::size_t treeItemCount = 0;
|
||||
std::size_t listViewCount = 0;
|
||||
std::size_t listItemCount = 0;
|
||||
std::size_t propertySectionCount = 0;
|
||||
std::size_t fieldRowCount = 0;
|
||||
std::string hoveredElementId = {};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user