Refactor XCUI editor module layout
This commit is contained in:
394
new_editor/src/Fields/UIEditorAssetField.cpp
Normal file
394
new_editor/src/Fields/UIEditorAssetField.cpp
Normal 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
|
||||
Reference in New Issue
Block a user