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

260 lines
11 KiB
C++

#include <XCEditor/Fields/UIEditorEnumField.h>
#include <XCEditor/Widgets/UIEditorFieldRowLayout.h>
#include <XCEditor/Widgets/UIEditorTextLayout.h>
#include <algorithm>
#include <cmath>
namespace XCEngine::UI::Editor::Widgets {
namespace {
float ClampNonNegative(float value) {
return (std::max)(0.0f, value);
}
UIEditorEnumFieldMetrics ResolveMetrics(const UIEditorEnumFieldMetrics& metrics) {
const auto& tokens = GetUIEditorInspectorFieldStyleTokens();
UIEditorEnumFieldMetrics resolved = metrics;
if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) {
resolved.controlTrailingInset = tokens.controlTrailingInset;
}
if (AreUIEditorFieldMetricsEqual(metrics.valueBoxMinWidth, 96.0f)) {
resolved.valueBoxMinWidth = tokens.controlMinWidth;
}
if (AreUIEditorFieldMetricsEqual(metrics.dropdownArrowWidth, 20.0f)) {
resolved.dropdownArrowWidth = tokens.dropdownArrowWidth;
}
return resolved;
}
UIEditorEnumFieldPalette ResolvePalette(const UIEditorEnumFieldPalette& palette) {
const auto& tokens = GetUIEditorInspectorFieldStyleTokens();
UIEditorEnumFieldPalette resolved = palette;
if (AreUIEditorFieldColorsEqual(palette.focusedBorderColor, ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f))) {
resolved.focusedBorderColor = tokens.controlFocusedBorderColor;
}
if (AreUIEditorFieldColorsEqual(palette.valueBoxColor, ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f))) {
resolved.valueBoxColor = tokens.controlColor;
}
if (AreUIEditorFieldColorsEqual(palette.valueBoxHoverColor, ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) {
resolved.valueBoxHoverColor = tokens.controlHoverColor;
}
if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f))) {
resolved.readOnlyColor = tokens.controlReadOnlyColor;
}
if (AreUIEditorFieldColorsEqual(palette.controlBorderColor, ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f))) {
resolved.controlBorderColor = tokens.controlBorderColor;
}
if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f))) {
resolved.labelColor = tokens.labelColor;
}
if (AreUIEditorFieldColorsEqual(palette.valueColor, ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f))) {
resolved.valueColor = tokens.valueColor;
}
if (AreUIEditorFieldColorsEqual(palette.arrowColor, ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f))) {
resolved.arrowColor = tokens.arrowColor;
}
return resolved;
}
bool ContainsPoint(const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
std::size_t ClampSelectedIndex(const UIEditorEnumFieldSpec& spec) {
if (spec.options.empty()) {
return 0u;
}
return (std::min)(spec.selectedIndex, spec.options.size() - 1u);
}
void AppendDropdownChevron(
::XCEngine::UI::UIDrawList& drawList,
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIColor& color) {
const float centerX = std::floor(rect.x + rect.width * 0.5f) + 0.5f;
const float centerY = std::floor(rect.y + rect.height * 0.5f) + 0.5f;
const float halfWidth = ClampNonNegative(rect.width * 0.18f);
const float halfHeight = ClampNonNegative(rect.height * 0.12f);
drawList.AddFilledTriangle(
::XCEngine::UI::UIPoint(centerX - halfWidth, centerY - halfHeight),
::XCEngine::UI::UIPoint(centerX + halfWidth, centerY - halfHeight),
::XCEngine::UI::UIPoint(centerX, centerY + halfHeight),
color);
}
} // namespace
std::string ResolveUIEditorEnumFieldValueText(const UIEditorEnumFieldSpec& spec) {
if (spec.options.empty()) {
return "(none)";
}
return spec.options[ClampSelectedIndex(spec)];
}
UIEditorEnumFieldLayout BuildUIEditorEnumFieldLayout(
const ::XCEngine::UI::UIRect& bounds,
const UIEditorEnumFieldSpec&,
const UIEditorEnumFieldMetrics& metrics) {
const UIEditorEnumFieldMetrics resolvedMetrics = ResolveMetrics(metrics);
const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout(
bounds,
resolvedMetrics.valueBoxMinWidth,
UIEditorFieldRowLayoutMetrics {
resolvedMetrics.rowHeight,
resolvedMetrics.horizontalPadding,
resolvedMetrics.labelControlGap,
resolvedMetrics.controlColumnStart,
resolvedMetrics.controlMinWidth,
resolvedMetrics.controlTrailingInset,
resolvedMetrics.controlInsetY,
});
UIEditorEnumFieldLayout layout = {};
layout.bounds = hostLayout.bounds;
layout.labelRect = hostLayout.labelRect;
layout.controlRect = hostLayout.controlRect;
layout.valueRect = layout.controlRect;
const float arrowWidth = (std::min)(
(std::max)(resolvedMetrics.dropdownArrowWidth, 0.0f),
layout.valueRect.width);
layout.arrowRect = ::XCEngine::UI::UIRect(
layout.valueRect.x + (std::max)(0.0f, layout.valueRect.width - arrowWidth),
layout.valueRect.y,
arrowWidth,
layout.valueRect.height);
return layout;
}
UIEditorEnumFieldHitTarget HitTestUIEditorEnumField(
const UIEditorEnumFieldLayout& layout,
const ::XCEngine::UI::UIPoint& point) {
if (!ContainsPoint(layout.bounds, point)) {
return {};
}
if (ContainsPoint(layout.arrowRect, point)) {
return { UIEditorEnumFieldHitTargetKind::DropdownArrow };
}
if (ContainsPoint(layout.valueRect, point)) {
return { UIEditorEnumFieldHitTargetKind::ValueBox };
}
return { UIEditorEnumFieldHitTargetKind::Row };
}
void AppendUIEditorEnumFieldBackground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorEnumFieldLayout& layout,
const UIEditorEnumFieldSpec& spec,
const UIEditorEnumFieldState& state,
const UIEditorEnumFieldPalette& palette,
const UIEditorEnumFieldMetrics& metrics) {
const UIEditorEnumFieldPalette resolvedPalette = ResolvePalette(palette);
const UIEditorEnumFieldMetrics resolvedMetrics = ResolveMetrics(metrics);
const auto rowFillColor =
state.active ? resolvedPalette.rowActiveColor
: (state.hoveredTarget != UIEditorEnumFieldHitTargetKind::None
? resolvedPalette.rowHoverColor
: resolvedPalette.surfaceColor);
if (rowFillColor.a > 0.0f) {
drawList.AddFilledRect(layout.bounds, rowFillColor, resolvedMetrics.cornerRounding);
}
const bool controlHovered =
state.hoveredTarget == UIEditorEnumFieldHitTargetKind::ValueBox ||
state.hoveredTarget == UIEditorEnumFieldHitTargetKind::DropdownArrow;
const ::XCEngine::UI::UIColor previewColor =
spec.readOnly
? resolvedPalette.readOnlyColor
: (controlHovered || state.popupOpen ? resolvedPalette.valueBoxHoverColor : resolvedPalette.valueBoxColor);
const ::XCEngine::UI::UIColor arrowColor =
spec.readOnly
? resolvedPalette.readOnlyColor
: (state.popupOpen || state.hoveredTarget == UIEditorEnumFieldHitTargetKind::DropdownArrow
? resolvedPalette.valueBoxHoverColor
: resolvedPalette.valueBoxColor);
const ::XCEngine::UI::UIRect previewRect(
layout.valueRect.x,
layout.valueRect.y,
ClampNonNegative(layout.arrowRect.x - layout.valueRect.x),
layout.valueRect.height);
drawList.AddFilledRect(
previewRect,
previewColor,
resolvedMetrics.valueBoxRounding);
drawList.AddFilledRect(
layout.arrowRect,
arrowColor,
resolvedMetrics.valueBoxRounding);
drawList.AddRectOutline(
layout.valueRect,
state.popupOpen ? resolvedPalette.focusedBorderColor : resolvedPalette.controlBorderColor,
state.popupOpen ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness,
resolvedMetrics.valueBoxRounding);
drawList.AddLine(
::XCEngine::UI::UIPoint(layout.arrowRect.x, layout.arrowRect.y + 1.0f),
::XCEngine::UI::UIPoint(layout.arrowRect.x, layout.arrowRect.y + layout.arrowRect.height - 1.0f),
resolvedPalette.controlBorderColor,
1.0f);
}
void AppendUIEditorEnumFieldForeground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorEnumFieldLayout& layout,
const UIEditorEnumFieldSpec& spec,
const UIEditorEnumFieldPalette& palette,
const UIEditorEnumFieldMetrics& metrics) {
const UIEditorEnumFieldPalette resolvedPalette = ResolvePalette(palette);
const UIEditorEnumFieldMetrics resolvedMetrics = ResolveMetrics(metrics);
drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize));
drawList.AddText(
::XCEngine::UI::UIPoint(
layout.labelRect.x,
ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)),
spec.label,
resolvedPalette.labelColor,
resolvedMetrics.labelFontSize);
drawList.PopClipRect();
const ::XCEngine::UI::UIRect valueTextClipRect(
layout.valueRect.x + resolvedMetrics.valueTextInsetX,
layout.valueRect.y,
ClampNonNegative(layout.arrowRect.x - layout.valueRect.x - resolvedMetrics.valueTextInsetX),
layout.valueRect.height);
drawList.PushClipRect(ResolveUIEditorTextClipRect(valueTextClipRect, resolvedMetrics.valueFontSize));
drawList.AddText(
::XCEngine::UI::UIPoint(
layout.valueRect.x + resolvedMetrics.valueTextInsetX,
ResolveUIEditorTextTop(layout.valueRect, resolvedMetrics.valueFontSize, resolvedMetrics.valueTextInsetY)),
ResolveUIEditorEnumFieldValueText(spec),
resolvedPalette.valueColor,
resolvedMetrics.valueFontSize);
drawList.PopClipRect();
AppendDropdownChevron(drawList, layout.arrowRect, resolvedPalette.arrowColor);
}
void AppendUIEditorEnumField(
::XCEngine::UI::UIDrawList& drawList,
const ::XCEngine::UI::UIRect& bounds,
const UIEditorEnumFieldSpec& spec,
const UIEditorEnumFieldState& state,
const UIEditorEnumFieldPalette& palette,
const UIEditorEnumFieldMetrics& metrics) {
const UIEditorEnumFieldPalette resolvedPalette = ResolvePalette(palette);
const UIEditorEnumFieldMetrics resolvedMetrics = ResolveMetrics(metrics);
const UIEditorEnumFieldLayout layout = BuildUIEditorEnumFieldLayout(bounds, spec, resolvedMetrics);
AppendUIEditorEnumFieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics);
AppendUIEditorEnumFieldForeground(drawList, layout, spec, resolvedPalette, resolvedMetrics);
}
} // namespace XCEngine::UI::Editor::Widgets