321 lines
12 KiB
C++
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
|