Refine XCEditor docking and DPI rendering

This commit is contained in:
2026-04-11 17:07:37 +08:00
parent 35d3d6328b
commit 2958dcc491
46 changed files with 4839 additions and 471 deletions

View File

@@ -18,8 +18,7 @@ using ::XCEngine::UI::Layout::MeasureUITabStripHeaderWidth;
constexpr float kTabRounding = 0.0f;
constexpr float kStripRounding = 0.0f;
constexpr float kHeaderFontSize = 11.0f;
constexpr float kCloseFontSize = 10.0f;
constexpr float kHeaderFontSize = 13.0f;
float ClampNonNegative(float value) {
return (std::max)(value, 0.0f);
@@ -44,10 +43,6 @@ float ResolveTabRounding(const UIEditorTabStripMetrics& metrics) {
return kTabRounding;
}
float ResolveCloseButtonRounding(const UIEditorTabStripMetrics& metrics) {
return (std::min)(ClampNonNegative(metrics.closeButtonExtent) * 0.2f, 2.0f);
}
std::size_t ResolveSelectedIndex(
std::size_t itemCount,
std::size_t selectedIndex) {
@@ -78,20 +73,18 @@ float ResolveTabTextTop(
return rect.y + (std::max)(0.0f, (rect.height - kHeaderFontSize) * 0.5f) + metrics.labelInsetY;
}
float ResolveCloseTextTop(const UIRect& rect) {
return rect.y + (std::max)(0.0f, (rect.height - kCloseFontSize) * 0.5f) - 0.5f;
}
UIColor ResolveStripBorderColor(
const UIEditorTabStripState& state,
const UIEditorTabStripPalette& palette) {
return state.focused ? palette.focusedBorderColor : palette.stripBorderColor;
(void)state;
return palette.stripBorderColor;
}
float ResolveStripBorderThickness(
const UIEditorTabStripState& state,
const UIEditorTabStripMetrics& metrics) {
return state.focused ? metrics.focusedBorderThickness : metrics.baseBorderThickness;
(void)state;
return metrics.baseBorderThickness;
}
UIColor ResolveTabFillColor(
@@ -115,7 +108,8 @@ UIColor ResolveTabBorderColor(
bool focused,
const UIEditorTabStripPalette& palette) {
if (selected) {
return focused ? palette.focusedBorderColor : palette.tabSelectedBorderColor;
(void)focused;
return palette.tabSelectedBorderColor;
}
if (hovered) {
@@ -125,33 +119,121 @@ UIColor ResolveTabBorderColor(
return palette.tabBorderColor;
}
float ResolveTabBorderThickness(
bool selected,
bool focused,
const UIEditorTabStripMetrics& metrics) {
float ResolveTabBorderThickness(bool selected, bool focused, const UIEditorTabStripMetrics& metrics) {
if (selected) {
return focused ? metrics.focusedBorderThickness : metrics.selectedBorderThickness;
(void)focused;
return metrics.selectedBorderThickness;
}
return metrics.baseBorderThickness;
}
UIRect BuildCloseButtonRect(
const UIRect& headerRect,
void AppendHeaderContentSeparator(
UIDrawList& drawList,
const UIEditorTabStripLayout& layout,
const UIEditorTabStripPalette& palette,
const UIEditorTabStripMetrics& metrics) {
const float insetY = ClampNonNegative(metrics.closeInsetY);
const float extent = (std::min)(
ClampNonNegative(metrics.closeButtonExtent),
(std::max)(headerRect.height - insetY * 2.0f, 0.0f));
if (extent <= 0.0f) {
return {};
if (layout.headerRect.width <= 0.0f ||
layout.headerRect.height <= 0.0f ||
layout.contentRect.height <= 0.0f) {
return;
}
return UIRect(
headerRect.x + headerRect.width - ClampNonNegative(metrics.closeInsetRight) - extent,
headerRect.y + insetY + (std::max)(0.0f, headerRect.height - insetY * 2.0f - extent) * 0.5f,
extent,
extent);
const float thickness = (std::max)(ClampNonNegative(metrics.baseBorderThickness), 1.0f);
const float separatorY = layout.contentRect.y;
const float separatorLeft = layout.headerRect.x;
const float separatorRight = layout.headerRect.x + layout.headerRect.width;
if (layout.selectedIndex == UIEditorTabStripInvalidIndex ||
layout.selectedIndex >= layout.tabHeaderRects.size()) {
drawList.AddFilledRect(
UIRect(
separatorLeft,
separatorY,
(std::max)(separatorRight - separatorLeft, 0.0f),
thickness),
palette.headerContentSeparatorColor);
return;
}
const UIRect& selectedRect = layout.tabHeaderRects[layout.selectedIndex];
const float gapLeft = (std::max)(separatorLeft, selectedRect.x + 1.0f);
const float gapRight = (std::min)(separatorRight, selectedRect.x + selectedRect.width - 1.0f);
if (gapLeft > separatorLeft) {
drawList.AddFilledRect(
UIRect(
separatorLeft,
separatorY,
(std::max)(gapLeft - separatorLeft, 0.0f),
thickness),
palette.headerContentSeparatorColor);
}
if (gapRight < separatorRight) {
drawList.AddFilledRect(
UIRect(
gapRight,
separatorY,
(std::max)(separatorRight - gapRight, 0.0f),
thickness),
palette.headerContentSeparatorColor);
}
}
void AppendSelectedTabBottomBorderMask(
UIDrawList& drawList,
const UIRect& rect,
float thickness,
const UIEditorTabStripPalette& palette) {
if (rect.width <= 0.0f || rect.height <= 0.0f || thickness <= 0.0f) {
return;
}
const float maskHeight = (std::max)(1.0f, thickness + 1.0f);
drawList.AddFilledRect(
UIRect(
rect.x + 1.0f,
rect.y + rect.height - maskHeight,
(std::max)(rect.width - 2.0f, 0.0f),
maskHeight + 1.0f),
palette.contentBackgroundColor,
0.0f);
}
UIEditorTabStripInsertionPreview BuildInsertionPreview(
const UIEditorTabStripLayout& layout,
const UIEditorTabStripState& state,
const UIEditorTabStripMetrics& metrics) {
UIEditorTabStripInsertionPreview preview = {};
if (!state.reorder.dragging ||
state.reorder.previewInsertionIndex == UIEditorTabStripInvalidIndex ||
layout.tabHeaderRects.empty() ||
state.reorder.previewInsertionIndex > layout.tabHeaderRects.size() ||
layout.headerRect.height <= 0.0f) {
return preview;
}
float indicatorX = layout.tabHeaderRects.front().x;
if (state.reorder.previewInsertionIndex >= layout.tabHeaderRects.size()) {
const UIRect& lastRect = layout.tabHeaderRects.back();
indicatorX = lastRect.x + lastRect.width;
} else {
indicatorX = layout.tabHeaderRects[state.reorder.previewInsertionIndex].x;
}
const float thickness = (std::max)(ClampNonNegative(metrics.reorderPreviewThickness), 1.0f);
const float insetY = ClampNonNegative(metrics.reorderPreviewInsetY);
const float indicatorHeight = (std::max)(layout.headerRect.height - insetY * 2.0f, thickness);
preview.visible = true;
preview.insertionIndex = state.reorder.previewInsertionIndex;
preview.indicatorRect = UIRect(
indicatorX - thickness * 0.5f,
layout.headerRect.y + insetY,
thickness,
indicatorHeight);
return preview;
}
} // namespace
@@ -162,13 +244,8 @@ float ResolveUIEditorTabStripDesiredHeaderLabelWidth(
const float labelWidth = ResolveEstimatedLabelWidth(item, metrics);
const float horizontalPadding = ClampNonNegative(metrics.layoutMetrics.tabHorizontalPadding);
const float extraLeftInset = (std::max)(ClampNonNegative(metrics.labelInsetX) - horizontalPadding, 0.0f);
const float extraRightInset = (std::max)(ClampNonNegative(metrics.closeInsetRight) - horizontalPadding, 0.0f);
const float closeBudget = item.closable
? ClampNonNegative(metrics.closeButtonExtent) +
ClampNonNegative(metrics.closeButtonGap) +
extraRightInset
: 0.0f;
return labelWidth + extraLeftInset + closeBudget;
(void)item;
return labelWidth + extraLeftInset;
}
std::size_t ResolveUIEditorTabStripSelectedIndex(
@@ -188,34 +265,6 @@ std::size_t ResolveUIEditorTabStripSelectedIndex(
return ResolveSelectedIndex(items.size(), fallbackIndex);
}
std::size_t ResolveUIEditorTabStripSelectedIndexAfterClose(
std::size_t selectedIndex,
std::size_t closedIndex,
std::size_t itemCountBeforeClose) {
if (itemCountBeforeClose == 0u || closedIndex >= itemCountBeforeClose) {
return UIEditorTabStripInvalidIndex;
}
const std::size_t itemCountAfterClose = itemCountBeforeClose - 1u;
if (itemCountAfterClose == 0u) {
return UIEditorTabStripInvalidIndex;
}
if (selectedIndex == UIEditorTabStripInvalidIndex || selectedIndex >= itemCountBeforeClose) {
return (std::min)(closedIndex, itemCountAfterClose - 1u);
}
if (closedIndex < selectedIndex) {
return selectedIndex - 1u;
}
if (closedIndex > selectedIndex) {
return selectedIndex;
}
return (std::min)(selectedIndex, itemCountAfterClose - 1u);
}
UIEditorTabStripLayout BuildUIEditorTabStripLayout(
const UIRect& bounds,
const std::vector<UIEditorTabStripItem>& items,
@@ -245,17 +294,7 @@ UIEditorTabStripLayout BuildUIEditorTabStripLayout(
layout.tabHeaderRects = arranged.tabHeaderRects;
layout.closeButtonRects.resize(items.size());
layout.showCloseButtons.resize(items.size(), false);
for (std::size_t index = 0; index < items.size(); ++index) {
if (!items[index].closable) {
continue;
}
layout.closeButtonRects[index] = BuildCloseButtonRect(layout.tabHeaderRects[index], metrics);
layout.showCloseButtons[index] =
layout.closeButtonRects[index].width > 0.0f && layout.closeButtonRects[index].height > 0.0f;
}
layout.insertionPreview = BuildInsertionPreview(layout, state, metrics);
return layout;
}
@@ -269,15 +308,6 @@ UIEditorTabStripHitTarget HitTestUIEditorTabStrip(
return target;
}
for (std::size_t index = 0; index < layout.closeButtonRects.size(); ++index) {
if (layout.showCloseButtons[index] &&
IsPointInsideRect(layout.closeButtonRects[index], point)) {
target.kind = UIEditorTabStripHitTargetKind::CloseButton;
target.index = index;
return target;
}
}
for (std::size_t index = 0; index < layout.tabHeaderRects.size(); ++index) {
if (IsPointInsideRect(layout.tabHeaderRects[index], point)) {
target.kind = UIEditorTabStripHitTargetKind::Tab;
@@ -322,7 +352,7 @@ void AppendUIEditorTabStripBackground(
for (std::size_t index = 0; index < layout.tabHeaderRects.size(); ++index) {
const bool selected = layout.selectedIndex == index;
const bool hovered = state.hoveredIndex == index || state.closeHoveredIndex == index;
const bool hovered = state.hoveredIndex == index;
drawList.AddFilledRect(
layout.tabHeaderRects[index],
ResolveTabFillColor(selected, hovered, palette),
@@ -332,6 +362,20 @@ void AppendUIEditorTabStripBackground(
ResolveTabBorderColor(selected, hovered, state.focused, palette),
ResolveTabBorderThickness(selected, state.focused, metrics),
tabRounding);
if (selected) {
AppendSelectedTabBottomBorderMask(
drawList,
layout.tabHeaderRects[index],
ResolveTabBorderThickness(selected, state.focused, metrics),
palette);
}
}
if (layout.insertionPreview.visible) {
drawList.AddFilledRect(
layout.insertionPreview.indicatorRect,
palette.reorderPreviewColor,
0.0f);
}
}
@@ -342,6 +386,8 @@ void AppendUIEditorTabStripForeground(
const UIEditorTabStripState& state,
const UIEditorTabStripPalette& palette,
const UIEditorTabStripMetrics& metrics) {
AppendHeaderContentSeparator(drawList, layout, palette, metrics);
const float leftInset = (std::max)(
ClampNonNegative(metrics.layoutMetrics.tabHorizontalPadding),
ClampNonNegative(metrics.labelInsetX));
@@ -349,12 +395,9 @@ void AppendUIEditorTabStripForeground(
for (std::size_t index = 0; index < items.size() && index < layout.tabHeaderRects.size(); ++index) {
const UIRect& tabRect = layout.tabHeaderRects[index];
const bool selected = layout.selectedIndex == index;
const bool hovered = state.hoveredIndex == index || state.closeHoveredIndex == index;
const bool hovered = state.hoveredIndex == index;
const float textLeft = tabRect.x + leftInset;
float textRight = tabRect.x + tabRect.width - ClampNonNegative(metrics.layoutMetrics.tabHorizontalPadding);
if (layout.showCloseButtons[index]) {
textRight = layout.closeButtonRects[index].x - ClampNonNegative(metrics.closeButtonGap);
}
const float textRight = tabRect.x + tabRect.width - ClampNonNegative(metrics.layoutMetrics.tabHorizontalPadding);
if (textRight > textLeft) {
const UIRect clipRect(
@@ -370,30 +413,6 @@ void AppendUIEditorTabStripForeground(
kHeaderFontSize);
drawList.PopClipRect();
}
if (!layout.showCloseButtons[index]) {
continue;
}
const bool closeHovered = state.closeHoveredIndex == index;
const UIRect& closeRect = layout.closeButtonRects[index];
const float closeRounding = ResolveCloseButtonRounding(metrics);
drawList.AddFilledRect(
closeRect,
closeHovered ? palette.closeButtonHoveredColor : palette.closeButtonColor,
closeRounding);
drawList.AddRectOutline(
closeRect,
palette.closeButtonBorderColor,
1.0f,
closeRounding);
drawList.AddText(
UIPoint(
closeRect.x + (std::max)(0.0f, (closeRect.width - 7.0f) * 0.5f),
ResolveCloseTextTop(closeRect)),
"X",
palette.closeGlyphColor,
kCloseFontSize);
}
}