Refactor XCUI editor module layout

This commit is contained in:
2026-04-10 00:41:28 +08:00
parent 4b47764f26
commit 02a0e626fe
263 changed files with 12396 additions and 7592 deletions

View File

@@ -0,0 +1,394 @@
#include <XCEditor/Fields/UIEditorAssetField.h>
#include <XCEditor/Widgets/UIEditorFieldRowLayout.h>
#include <XCEditor/Widgets/UIEditorTextLayout.h>
#include <algorithm>
#include <cctype>
#include <string_view>
namespace XCEngine::UI::Editor::Widgets {
namespace {
using ::XCEngine::UI::UIDrawList;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIRect;
float ClampNonNegative(float value) {
return (std::max)(0.0f, value);
}
float EstimateTextWidth(std::string_view text, float fontSize) {
return static_cast<float>(text.size()) * fontSize * 0.58f;
}
bool ShowsPickerButton(const UIEditorAssetFieldSpec& spec) {
return spec.showPickerButton;
}
bool ShowsClearButton(const UIEditorAssetFieldSpec& spec) {
return spec.allowClear && HasUIEditorAssetFieldValue(spec);
}
bool ShowsStatusBadge(const UIEditorAssetFieldSpec& spec) {
return spec.showStatusBadge && !spec.statusText.empty();
}
::XCEngine::UI::UIColor ResolveRowFillColor(
const UIEditorAssetFieldState& state,
const UIEditorAssetFieldPalette& palette) {
if (state.activeTarget != UIEditorAssetFieldHitTargetKind::None) {
return palette.rowActiveColor;
}
if (state.hoveredTarget != UIEditorAssetFieldHitTargetKind::None) {
return palette.rowHoverColor;
}
return palette.surfaceColor;
}
::XCEngine::UI::UIColor ResolveValueFillColor(
const UIEditorAssetFieldSpec& spec,
const UIEditorAssetFieldState& state,
const UIEditorAssetFieldPalette& palette) {
if (spec.readOnly) {
return palette.readOnlyColor;
}
if (state.activeTarget == UIEditorAssetFieldHitTargetKind::ValueBox ||
state.activeTarget == UIEditorAssetFieldHitTargetKind::PickerButton ||
state.activeTarget == UIEditorAssetFieldHitTargetKind::ClearButton) {
return palette.valueBoxActiveColor;
}
if (state.hoveredTarget == UIEditorAssetFieldHitTargetKind::ValueBox ||
state.hoveredTarget == UIEditorAssetFieldHitTargetKind::PickerButton ||
state.hoveredTarget == UIEditorAssetFieldHitTargetKind::ClearButton) {
return palette.valueBoxHoverColor;
}
return palette.valueBoxColor;
}
::XCEngine::UI::UIColor ResolveActionFillColor(
UIEditorAssetFieldHitTargetKind targetKind,
const UIEditorAssetFieldState& state,
const UIEditorAssetFieldPalette& palette) {
if (state.activeTarget == targetKind) {
return palette.actionButtonActiveColor;
}
if (state.hoveredTarget == targetKind) {
return palette.actionButtonHoverColor;
}
return palette.actionButtonColor;
}
} // namespace
bool HasUIEditorAssetFieldValue(const UIEditorAssetFieldSpec& spec) {
return !spec.assetId.empty() || !spec.displayName.empty();
}
std::string ResolveUIEditorAssetFieldValueText(const UIEditorAssetFieldSpec& spec) {
if (!spec.displayName.empty()) {
return spec.displayName;
}
if (!spec.assetId.empty()) {
return spec.assetId;
}
return spec.emptyText.empty() ? std::string("None") : spec.emptyText;
}
std::string ResolveUIEditorAssetFieldPreviewGlyph(const UIEditorAssetFieldSpec& spec) {
const std::string source =
!spec.statusText.empty() ? spec.statusText : ResolveUIEditorAssetFieldValueText(spec);
for (char character : source) {
if (std::isalnum(static_cast<unsigned char>(character)) != 0) {
return std::string(
1u,
static_cast<char>(std::toupper(static_cast<unsigned char>(character))));
}
}
return "-";
}
bool IsUIEditorAssetFieldPointInside(const UIRect& rect, const UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
UIEditorAssetFieldLayout BuildUIEditorAssetFieldLayout(
const UIRect& bounds,
const UIEditorAssetFieldSpec& spec,
const UIEditorAssetFieldMetrics& metrics) {
const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout(
bounds,
metrics.valueBoxMinWidth,
UIEditorFieldRowLayoutMetrics {
metrics.rowHeight,
metrics.horizontalPadding,
metrics.labelControlGap,
metrics.controlColumnStart,
metrics.controlTrailingInset,
metrics.controlInsetY,
});
UIEditorAssetFieldLayout layout = {};
layout.bounds = hostLayout.bounds;
layout.labelRect = hostLayout.labelRect;
layout.controlRect = hostLayout.controlRect;
layout.valueRect = hostLayout.controlRect;
float trailingX = layout.valueRect.x + layout.valueRect.width;
if (ShowsPickerButton(spec)) {
trailingX -= metrics.actionButtonWidth;
layout.pickerRect = UIRect(
trailingX,
layout.valueRect.y,
metrics.actionButtonWidth,
layout.valueRect.height);
trailingX -= metrics.actionButtonGap;
}
if (ShowsClearButton(spec)) {
trailingX -= metrics.actionButtonWidth;
layout.clearRect = UIRect(
trailingX,
layout.valueRect.y,
metrics.actionButtonWidth,
layout.valueRect.height);
trailingX -= metrics.actionButtonGap;
}
const float previewSize =
(std::min)(metrics.previewSize, ClampNonNegative(layout.valueRect.height - 4.0f));
layout.previewRect = UIRect(
layout.valueRect.x + metrics.previewInsetX,
layout.valueRect.y + ClampNonNegative((layout.valueRect.height - previewSize) * 0.5f),
previewSize,
previewSize);
const float contentLeft = layout.previewRect.x + layout.previewRect.width + metrics.previewGap;
if (ShowsStatusBadge(spec)) {
const float estimatedBadgeWidth =
EstimateTextWidth(spec.statusText, metrics.statusBadgeFontSize) +
metrics.statusBadgePaddingX * 2.0f;
const float badgeWidth = (std::min)(
(std::max)(metrics.statusBadgeMinWidth, estimatedBadgeWidth),
ClampNonNegative((trailingX - contentLeft) * 0.45f));
if (badgeWidth > 20.0f) {
trailingX -= badgeWidth;
layout.statusBadgeRect = UIRect(
trailingX,
layout.valueRect.y + ClampNonNegative((layout.valueRect.height - metrics.statusBadgeHeight) * 0.5f),
badgeWidth,
(std::min)(metrics.statusBadgeHeight, layout.valueRect.height));
trailingX -= metrics.statusBadgeGap;
}
}
layout.textRect = UIRect(
contentLeft + metrics.valueTextInsetX,
layout.valueRect.y,
ClampNonNegative(trailingX - contentLeft - metrics.valueTextInsetX),
layout.valueRect.height);
return layout;
}
UIEditorAssetFieldHitTarget HitTestUIEditorAssetField(
const UIEditorAssetFieldLayout& layout,
const UIPoint& point) {
if (layout.clearRect.width > 0.0f &&
IsUIEditorAssetFieldPointInside(layout.clearRect, point)) {
return { UIEditorAssetFieldHitTargetKind::ClearButton };
}
if (layout.pickerRect.width > 0.0f &&
IsUIEditorAssetFieldPointInside(layout.pickerRect, point)) {
return { UIEditorAssetFieldHitTargetKind::PickerButton };
}
if (IsUIEditorAssetFieldPointInside(layout.valueRect, point)) {
return { UIEditorAssetFieldHitTargetKind::ValueBox };
}
if (IsUIEditorAssetFieldPointInside(layout.bounds, point)) {
return { UIEditorAssetFieldHitTargetKind::Row };
}
return {};
}
void AppendUIEditorAssetFieldBackground(
UIDrawList& drawList,
const UIEditorAssetFieldLayout& layout,
const UIEditorAssetFieldSpec& spec,
const UIEditorAssetFieldState& state,
const UIEditorAssetFieldPalette& palette,
const UIEditorAssetFieldMetrics& metrics) {
const auto rowFillColor = ResolveRowFillColor(state, palette);
if (rowFillColor.a > 0.0f) {
drawList.AddFilledRect(layout.bounds, rowFillColor, metrics.cornerRounding);
}
const auto rowBorderColor = state.focused ? palette.focusedBorderColor : palette.borderColor;
if (rowBorderColor.a > 0.0f) {
drawList.AddRectOutline(
layout.bounds,
rowBorderColor,
state.focused ? metrics.focusedBorderThickness : metrics.borderThickness,
metrics.cornerRounding);
}
drawList.AddFilledRect(
layout.valueRect,
ResolveValueFillColor(spec, state, palette),
metrics.valueBoxRounding);
drawList.AddRectOutline(
layout.valueRect,
state.focused ? palette.controlFocusedBorderColor : palette.controlBorderColor,
state.focused ? metrics.focusedBorderThickness : metrics.borderThickness,
metrics.valueBoxRounding);
if (HasUIEditorAssetFieldValue(spec)) {
drawList.AddFilledRectLinearGradient(
layout.previewRect,
palette.previewBaseColor,
spec.tint,
::XCEngine::UI::UILinearGradientDirection::Vertical,
metrics.previewRounding);
} else {
drawList.AddFilledRect(layout.previewRect, palette.previewEmptyColor, metrics.previewRounding);
}
drawList.AddRectOutline(
layout.previewRect,
palette.previewBorderColor,
metrics.borderThickness,
metrics.previewRounding);
if (layout.statusBadgeRect.width > 0.0f && layout.statusBadgeRect.height > 0.0f) {
drawList.AddFilledRect(layout.statusBadgeRect, palette.statusBadgeColor, metrics.badgeRounding);
drawList.AddRectOutline(
layout.statusBadgeRect,
palette.statusBadgeBorderColor,
metrics.borderThickness,
metrics.badgeRounding);
}
if (layout.pickerRect.width > 0.0f) {
const auto fill =
ResolveActionFillColor(UIEditorAssetFieldHitTargetKind::PickerButton, state, palette);
drawList.AddFilledRect(layout.pickerRect, fill, metrics.valueBoxRounding);
drawList.AddLine(
UIPoint(layout.pickerRect.x, layout.pickerRect.y + 1.0f),
UIPoint(layout.pickerRect.x, layout.pickerRect.y + layout.pickerRect.height - 1.0f),
palette.separatorColor,
1.0f);
}
if (layout.clearRect.width > 0.0f) {
const auto fill =
ResolveActionFillColor(UIEditorAssetFieldHitTargetKind::ClearButton, state, palette);
drawList.AddFilledRect(layout.clearRect, fill, metrics.valueBoxRounding);
drawList.AddLine(
UIPoint(layout.clearRect.x, layout.clearRect.y + 1.0f),
UIPoint(layout.clearRect.x, layout.clearRect.y + layout.clearRect.height - 1.0f),
palette.separatorColor,
1.0f);
}
}
void AppendUIEditorAssetFieldForeground(
UIDrawList& drawList,
const UIEditorAssetFieldLayout& layout,
const UIEditorAssetFieldSpec& spec,
const UIEditorAssetFieldState&,
const UIEditorAssetFieldPalette& palette,
const UIEditorAssetFieldMetrics& metrics) {
drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, metrics.labelFontSize));
drawList.AddText(
UIPoint(
layout.labelRect.x,
ResolveUIEditorTextTop(layout.labelRect, metrics.labelFontSize, metrics.labelTextInsetY)),
spec.label,
palette.labelColor,
metrics.labelFontSize);
drawList.PopClipRect();
drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.previewRect, metrics.previewGlyphFontSize));
drawList.AddText(
UIPoint(
layout.previewRect.x +
ClampNonNegative((layout.previewRect.width - metrics.previewGlyphFontSize) * 0.5f),
ResolveUIEditorTextTop(layout.previewRect, metrics.previewGlyphFontSize, -1.0f)),
ResolveUIEditorAssetFieldPreviewGlyph(spec),
palette.previewGlyphColor,
metrics.previewGlyphFontSize);
drawList.PopClipRect();
drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.textRect, metrics.valueFontSize));
drawList.AddText(
UIPoint(
layout.textRect.x,
ResolveUIEditorTextTop(layout.textRect, metrics.valueFontSize, metrics.valueTextInsetY)),
ResolveUIEditorAssetFieldValueText(spec),
HasUIEditorAssetFieldValue(spec) ? palette.valueColor : palette.emptyValueColor,
metrics.valueFontSize);
drawList.PopClipRect();
if (layout.statusBadgeRect.width > 0.0f && layout.statusBadgeRect.height > 0.0f) {
const UIRect badgeTextRect(
layout.statusBadgeRect.x + metrics.statusBadgePaddingX,
layout.statusBadgeRect.y,
ClampNonNegative(layout.statusBadgeRect.width - metrics.statusBadgePaddingX * 2.0f),
layout.statusBadgeRect.height);
drawList.PushClipRect(ResolveUIEditorTextClipRect(badgeTextRect, metrics.statusBadgeFontSize));
drawList.AddText(
UIPoint(
badgeTextRect.x,
ResolveUIEditorTextTop(badgeTextRect, metrics.statusBadgeFontSize, -1.0f)),
spec.statusText,
palette.statusBadgeTextColor,
metrics.statusBadgeFontSize);
drawList.PopClipRect();
}
if (layout.pickerRect.width > 0.0f) {
drawList.AddText(
UIPoint(
layout.pickerRect.x +
ClampNonNegative((layout.pickerRect.width - metrics.actionGlyphFontSize) * 0.5f),
ResolveUIEditorTextTop(
layout.pickerRect,
metrics.actionGlyphFontSize,
metrics.actionGlyphInsetY)),
"o",
palette.pickerGlyphColor,
metrics.actionGlyphFontSize);
}
if (layout.clearRect.width > 0.0f) {
drawList.AddText(
UIPoint(
layout.clearRect.x +
ClampNonNegative((layout.clearRect.width - metrics.actionGlyphFontSize) * 0.5f),
ResolveUIEditorTextTop(
layout.clearRect,
metrics.actionGlyphFontSize,
metrics.actionGlyphInsetY)),
"X",
palette.clearGlyphColor,
metrics.actionGlyphFontSize);
}
}
void AppendUIEditorAssetField(
UIDrawList& drawList,
const UIRect& bounds,
const UIEditorAssetFieldSpec& spec,
const UIEditorAssetFieldState& state,
const UIEditorAssetFieldPalette& palette,
const UIEditorAssetFieldMetrics& metrics) {
const UIEditorAssetFieldLayout layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics);
AppendUIEditorAssetFieldBackground(drawList, layout, spec, state, palette, metrics);
AppendUIEditorAssetFieldForeground(drawList, layout, spec, state, palette, metrics);
}
} // namespace XCEngine::UI::Editor::Widgets