Files
XCEngine/editor/src/Fields/UIEditorObjectField.cpp
2026-04-25 16:46:01 +08:00

321 lines
12 KiB
C++

#include <XCEditor/Fields/UIEditorObjectField.h>
#include <XCEditor/Widgets/UIEditorFieldRowLayout.h>
#include <XCEditor/Widgets/UIEditorTextLayout.h>
#include <algorithm>
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);
}
bool ContainsPoint(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;
}
bool HasClearButton(const UIEditorObjectFieldSpec& spec) {
return spec.showClearButton && spec.hasValue;
}
bool HasPickerButton(const UIEditorObjectFieldSpec& spec) {
return spec.showPickerButton;
}
bool HasTypeText(const UIEditorObjectFieldSpec& spec) {
return spec.hasValue && !spec.objectTypeName.empty();
}
::XCEngine::UI::UIColor ResolveRowFillColor(
const UIEditorObjectFieldState& state,
const UIEditorObjectFieldPalette& palette) {
if (state.activeTarget != UIEditorObjectFieldHitTargetKind::None) {
return palette.rowActiveColor;
}
if (state.hoveredTarget != UIEditorObjectFieldHitTargetKind::None) {
return palette.rowHoverColor;
}
return palette.surfaceColor;
}
::XCEngine::UI::UIColor ResolveControlFillColor(
const UIEditorObjectFieldSpec& spec,
const UIEditorObjectFieldState& state,
const UIEditorObjectFieldPalette& palette) {
if (spec.readOnly) {
return palette.readOnlyColor;
}
if (state.hoveredTarget == UIEditorObjectFieldHitTargetKind::ValueBox ||
state.activeTarget == UIEditorObjectFieldHitTargetKind::ValueBox) {
return palette.valueBoxHoverColor;
}
return palette.valueBoxColor;
}
::XCEngine::UI::UIColor ResolveButtonFillColor(
const UIEditorObjectFieldSpec& spec,
const UIEditorObjectFieldState& state,
UIEditorObjectFieldHitTargetKind target,
const UIEditorObjectFieldPalette& palette) {
if (spec.readOnly) {
return palette.readOnlyColor;
}
if (state.activeTarget == target) {
return palette.buttonActiveColor;
}
if (state.hoveredTarget == target) {
return palette.buttonHoverColor;
}
return palette.buttonColor;
}
void AppendButtonGlyph(
UIDrawList& drawList,
const UIRect& rect,
const char* glyph,
const UIEditorObjectFieldPalette& palette,
const UIEditorObjectFieldMetrics& metrics) {
if (rect.width <= 0.0f || rect.height <= 0.0f) {
return;
}
drawList.AddText(
UIPoint(
rect.x + ClampNonNegative((rect.width - metrics.buttonGlyphFontSize) * 0.5f),
ResolveUIEditorTextTop(rect, metrics.buttonGlyphFontSize, metrics.buttonGlyphInsetY)),
glyph,
palette.buttonGlyphColor,
metrics.buttonGlyphFontSize);
}
} // namespace
std::string ResolveUIEditorObjectFieldDisplayText(const UIEditorObjectFieldSpec& spec) {
if (!spec.hasValue) {
return spec.emptyText.empty() ? "(none)" : spec.emptyText;
}
return spec.objectName.empty() ? "(unnamed)" : spec.objectName;
}
UIEditorObjectFieldLayout BuildUIEditorObjectFieldLayout(
const UIRect& bounds,
const UIEditorObjectFieldSpec& spec,
const UIEditorObjectFieldMetrics& metrics) {
const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout(
bounds,
metrics.valueBoxMinWidth,
UIEditorFieldRowLayoutMetrics {
metrics.rowHeight,
metrics.horizontalPadding,
metrics.labelControlGap,
metrics.controlColumnStart,
metrics.controlMinWidth,
metrics.controlTrailingInset,
metrics.controlInsetY,
});
UIEditorObjectFieldLayout layout = {};
layout.bounds = hostLayout.bounds;
layout.labelRect = hostLayout.labelRect;
layout.controlRect = hostLayout.controlRect;
const float pickerWidth =
HasPickerButton(spec) ? (std::min)(metrics.buttonWidth, layout.controlRect.width) : 0.0f;
const float clearWidth =
HasClearButton(spec)
? (std::min)(metrics.buttonWidth, ClampNonNegative(layout.controlRect.width - pickerWidth))
: 0.0f;
const float buttonWidth = pickerWidth + clearWidth;
layout.valueRect = UIRect(
layout.controlRect.x,
layout.controlRect.y,
ClampNonNegative(layout.controlRect.width - buttonWidth),
layout.controlRect.height);
float nextButtonX = layout.valueRect.x + layout.valueRect.width;
if (clearWidth > 0.0f) {
layout.clearButtonRect = UIRect(
nextButtonX,
layout.controlRect.y,
clearWidth,
layout.controlRect.height);
nextButtonX += clearWidth;
}
if (pickerWidth > 0.0f) {
layout.pickerButtonRect = UIRect(
nextButtonX,
layout.controlRect.y,
pickerWidth,
layout.controlRect.height);
}
if (HasTypeText(spec) && layout.valueRect.width > metrics.typeMinWidth + metrics.valueTextInsetX) {
const float reservedWidth = ClampNonNegative(
layout.valueRect.width * 0.35f + metrics.typeTextInsetX);
const float typeWidth = (std::min)(
metrics.typeMaxWidth,
(std::max)(metrics.typeMinWidth, reservedWidth));
if (layout.valueRect.width > typeWidth + metrics.valueTextInsetX + metrics.valueTypeGap) {
layout.typeRect = UIRect(
layout.valueRect.x + layout.valueRect.width - typeWidth,
layout.valueRect.y,
typeWidth,
layout.valueRect.height);
}
}
return layout;
}
UIEditorObjectFieldHitTarget HitTestUIEditorObjectField(
const UIEditorObjectFieldLayout& layout,
const UIPoint& point) {
if (!ContainsPoint(layout.bounds, point)) {
return {};
}
if (layout.pickerButtonRect.width > 0.0f && ContainsPoint(layout.pickerButtonRect, point)) {
return { UIEditorObjectFieldHitTargetKind::PickerButton };
}
if (layout.clearButtonRect.width > 0.0f && ContainsPoint(layout.clearButtonRect, point)) {
return { UIEditorObjectFieldHitTargetKind::ClearButton };
}
if (ContainsPoint(layout.valueRect, point)) {
return { UIEditorObjectFieldHitTargetKind::ValueBox };
}
return { UIEditorObjectFieldHitTargetKind::Row };
}
void AppendUIEditorObjectFieldBackground(
UIDrawList& drawList,
const UIEditorObjectFieldLayout& layout,
const UIEditorObjectFieldSpec& spec,
const UIEditorObjectFieldState& state,
const UIEditorObjectFieldPalette& palette,
const UIEditorObjectFieldMetrics& 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.controlRect,
ResolveControlFillColor(spec, state, palette),
metrics.valueBoxRounding);
drawList.AddRectOutline(
layout.controlRect,
state.focused ? palette.controlFocusedBorderColor : palette.controlBorderColor,
state.focused ? metrics.focusedBorderThickness : metrics.borderThickness,
metrics.valueBoxRounding);
if (layout.clearButtonRect.width > 0.0f) {
drawList.AddFilledRect(
layout.clearButtonRect,
ResolveButtonFillColor(spec, state, UIEditorObjectFieldHitTargetKind::ClearButton, palette));
drawList.AddLine(
UIPoint(layout.clearButtonRect.x, layout.clearButtonRect.y + 1.0f),
UIPoint(layout.clearButtonRect.x, layout.clearButtonRect.y + layout.clearButtonRect.height - 1.0f),
palette.separatorColor,
1.0f);
}
if (layout.pickerButtonRect.width > 0.0f) {
drawList.AddFilledRect(
layout.pickerButtonRect,
ResolveButtonFillColor(spec, state, UIEditorObjectFieldHitTargetKind::PickerButton, palette));
drawList.AddLine(
UIPoint(layout.pickerButtonRect.x, layout.pickerButtonRect.y + 1.0f),
UIPoint(layout.pickerButtonRect.x, layout.pickerButtonRect.y + layout.pickerButtonRect.height - 1.0f),
palette.separatorColor,
1.0f);
}
}
void AppendUIEditorObjectFieldForeground(
UIDrawList& drawList,
const UIEditorObjectFieldLayout& layout,
const UIEditorObjectFieldSpec& spec,
const UIEditorObjectFieldPalette& palette,
const UIEditorObjectFieldMetrics& 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();
float primaryClipWidth = layout.valueRect.width;
if (layout.typeRect.width > 0.0f) {
primaryClipWidth =
ClampNonNegative(layout.typeRect.x - layout.valueRect.x - metrics.valueTypeGap);
}
const UIRect primaryClipRect(
layout.valueRect.x + metrics.valueTextInsetX,
layout.valueRect.y,
ClampNonNegative(primaryClipWidth - metrics.valueTextInsetX),
layout.valueRect.height);
drawList.PushClipRect(ResolveUIEditorTextClipRect(primaryClipRect, metrics.valueFontSize));
drawList.AddText(
UIPoint(
layout.valueRect.x + metrics.valueTextInsetX,
ResolveUIEditorTextTop(layout.valueRect, metrics.valueFontSize, metrics.valueTextInsetY)),
ResolveUIEditorObjectFieldDisplayText(spec),
spec.hasValue ? palette.valueColor : palette.emptyValueColor,
metrics.valueFontSize);
drawList.PopClipRect();
if (layout.typeRect.width > 0.0f) {
const UIRect typeClipRect(
layout.typeRect.x + metrics.typeTextInsetX,
layout.typeRect.y,
ClampNonNegative(layout.typeRect.width - metrics.typeTextInsetX),
layout.typeRect.height);
drawList.PushClipRect(ResolveUIEditorTextClipRect(typeClipRect, metrics.typeFontSize));
drawList.AddText(
UIPoint(
layout.typeRect.x + metrics.typeTextInsetX,
ResolveUIEditorTextTop(layout.typeRect, metrics.typeFontSize, metrics.typeTextInsetY)),
spec.objectTypeName,
palette.typeColor,
metrics.typeFontSize);
drawList.PopClipRect();
}
AppendButtonGlyph(drawList, layout.clearButtonRect, "X", palette, metrics);
AppendButtonGlyph(drawList, layout.pickerButtonRect, "o", palette, metrics);
}
void AppendUIEditorObjectField(
UIDrawList& drawList,
const UIRect& bounds,
const UIEditorObjectFieldSpec& spec,
const UIEditorObjectFieldState& state,
const UIEditorObjectFieldPalette& palette,
const UIEditorObjectFieldMetrics& metrics) {
const UIEditorObjectFieldLayout layout = BuildUIEditorObjectFieldLayout(bounds, spec, metrics);
AppendUIEditorObjectFieldBackground(drawList, layout, spec, state, palette, metrics);
AppendUIEditorObjectFieldForeground(drawList, layout, spec, palette, metrics);
}
} // namespace XCEngine::UI::Editor::Widgets