Files
XCEngine/new_editor/src/Widgets/UIEditorVector4Field.cpp

286 lines
11 KiB
C++
Raw Normal View History

#include <XCEditor/Widgets/UIEditorVector4Field.h>
#include <XCEditor/Widgets/UIEditorFieldRowLayout.h>
#include <XCEditor/Widgets/UIEditorNumberField.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);
}
float ApproximateTextWidth(float fontSize, std::size_t characterCount) {
return fontSize * 0.55f * static_cast<float>(characterCount);
}
UIEditorNumberFieldSpec BuildComponentNumberSpec(
const UIEditorVector4FieldSpec& spec,
std::size_t componentIndex) {
UIEditorNumberFieldSpec numberSpec = {};
numberSpec.fieldId = spec.fieldId + "." + std::to_string(componentIndex);
numberSpec.label.clear();
numberSpec.value = componentIndex < spec.values.size() ? spec.values[componentIndex] : 0.0;
numberSpec.step = spec.step;
numberSpec.minValue = spec.minValue;
numberSpec.maxValue = spec.maxValue;
numberSpec.integerMode = spec.integerMode;
numberSpec.readOnly = spec.readOnly;
return numberSpec;
}
::XCEngine::UI::UIColor ResolveRowFillColor(
const UIEditorVector4FieldState& state,
const UIEditorVector4FieldPalette& palette) {
if (state.activeTarget != UIEditorVector4FieldHitTargetKind::None) {
return palette.rowActiveColor;
}
if (state.hoveredTarget != UIEditorVector4FieldHitTargetKind::None) {
return palette.rowHoverColor;
}
return palette.surfaceColor;
}
::XCEngine::UI::UIColor ResolveComponentFillColor(
const UIEditorVector4FieldSpec& spec,
const UIEditorVector4FieldState& state,
const UIEditorVector4FieldPalette& palette,
std::size_t componentIndex) {
if (spec.readOnly) {
return palette.readOnlyColor;
}
if (state.editing && state.selectedComponentIndex == componentIndex) {
return palette.componentEditingColor;
}
if (state.hoveredComponentIndex == componentIndex) {
return palette.componentHoverColor;
}
return palette.componentColor;
}
::XCEngine::UI::UIColor ResolveComponentBorderColor(
const UIEditorVector4FieldState& state,
const UIEditorVector4FieldPalette& palette,
std::size_t componentIndex) {
if (state.editing && state.selectedComponentIndex == componentIndex) {
return palette.componentFocusedBorderColor;
}
return palette.componentBorderColor;
}
::XCEngine::UI::UIColor ResolveAxisColor(
const UIEditorVector4FieldPalette& palette,
std::size_t componentIndex) {
switch (componentIndex) {
case 0u:
return palette.axisXColor;
case 1u:
return palette.axisYColor;
case 2u:
return palette.axisZColor;
case 3u:
default:
return palette.axisWColor;
}
}
} // namespace
bool IsUIEditorVector4FieldPointInside(
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;
}
double NormalizeUIEditorVector4FieldComponentValue(
const UIEditorVector4FieldSpec& spec,
double value) {
return NormalizeUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, 0u), value);
}
bool TryParseUIEditorVector4FieldComponentValue(
const UIEditorVector4FieldSpec& spec,
std::string_view text,
double& outValue) {
return TryParseUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, 0u), text, outValue);
}
std::string FormatUIEditorVector4FieldComponentValue(
const UIEditorVector4FieldSpec& spec,
std::size_t componentIndex) {
return FormatUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, componentIndex));
}
UIEditorVector4FieldLayout BuildUIEditorVector4FieldLayout(
const UIRect& bounds,
const UIEditorVector4FieldSpec&,
const UIEditorVector4FieldMetrics& metrics) {
const float requiredControlWidth = metrics.componentMinWidth * 4.0f + metrics.componentGap * 3.0f;
const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout(
bounds,
requiredControlWidth,
UIEditorFieldRowLayoutMetrics {
metrics.rowHeight,
metrics.horizontalPadding,
metrics.labelControlGap,
metrics.controlColumnStart,
metrics.controlTrailingInset,
metrics.controlInsetY,
});
const float componentWidth =
ClampNonNegative((hostLayout.controlRect.width - metrics.componentGap * 3.0f) / 4.0f);
UIEditorVector4FieldLayout layout = {};
layout.bounds = hostLayout.bounds;
layout.labelRect = hostLayout.labelRect;
layout.controlRect = hostLayout.controlRect;
for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) {
const float componentX =
layout.controlRect.x + (componentWidth + metrics.componentGap) * static_cast<float>(componentIndex);
const UIRect componentRect(componentX, layout.controlRect.y, componentWidth, layout.controlRect.height);
const float prefixWidth = (std::min)(metrics.componentPrefixWidth, componentRect.width);
const float labelGap = ClampNonNegative(metrics.componentLabelGap);
const float valueX = componentRect.x + prefixWidth + labelGap;
layout.componentRects[componentIndex] = componentRect;
layout.componentPrefixRects[componentIndex] =
UIRect(componentRect.x, componentRect.y, prefixWidth, componentRect.height);
layout.componentValueRects[componentIndex] =
UIRect(
valueX,
componentRect.y,
ClampNonNegative(componentRect.width - prefixWidth - labelGap),
componentRect.height);
}
return layout;
}
UIEditorVector4FieldHitTarget HitTestUIEditorVector4Field(
const UIEditorVector4FieldLayout& layout,
const UIPoint& point) {
for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) {
if (IsUIEditorVector4FieldPointInside(layout.componentRects[componentIndex], point)) {
return { UIEditorVector4FieldHitTargetKind::Component, componentIndex };
}
}
if (IsUIEditorVector4FieldPointInside(layout.bounds, point)) {
return { UIEditorVector4FieldHitTargetKind::Row, UIEditorVector4FieldInvalidComponentIndex };
}
return {};
}
void AppendUIEditorVector4FieldBackground(
UIDrawList& drawList,
const UIEditorVector4FieldLayout& layout,
const UIEditorVector4FieldSpec& spec,
const UIEditorVector4FieldState& state,
const UIEditorVector4FieldPalette& palette,
const UIEditorVector4FieldMetrics& metrics) {
if (ResolveRowFillColor(state, palette).a > 0.0f) {
drawList.AddFilledRect(layout.bounds, ResolveRowFillColor(state, palette), metrics.cornerRounding);
}
if ((state.focused ? palette.focusedBorderColor.a : palette.borderColor.a) > 0.0f) {
drawList.AddRectOutline(
layout.bounds,
state.focused ? palette.focusedBorderColor : palette.borderColor,
state.focused ? metrics.focusedBorderThickness : metrics.borderThickness,
metrics.cornerRounding);
}
for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) {
drawList.AddFilledRect(
layout.componentValueRects[componentIndex],
ResolveComponentFillColor(spec, state, palette, componentIndex),
metrics.componentRounding);
drawList.AddRectOutline(
layout.componentValueRects[componentIndex],
ResolveComponentBorderColor(state, palette, componentIndex),
metrics.borderThickness,
metrics.componentRounding);
}
}
void AppendUIEditorVector4FieldForeground(
UIDrawList& drawList,
const UIEditorVector4FieldLayout& layout,
const UIEditorVector4FieldSpec& spec,
const UIEditorVector4FieldState& state,
const UIEditorVector4FieldPalette& palette,
const UIEditorVector4FieldMetrics& 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();
for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) {
drawList.PushClipRect(
ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], metrics.prefixFontSize));
const std::string& componentLabel = spec.componentLabels[componentIndex];
const float prefixTextX =
layout.componentPrefixRects[componentIndex].x +
ClampNonNegative(
(layout.componentPrefixRects[componentIndex].width -
ApproximateTextWidth(metrics.prefixFontSize, componentLabel.size())) *
0.5f) +
metrics.prefixTextInsetX;
drawList.AddText(
UIPoint(
prefixTextX,
ResolveUIEditorTextTop(
layout.componentPrefixRects[componentIndex],
metrics.prefixFontSize,
metrics.prefixTextInsetY)),
componentLabel,
ResolveAxisColor(palette, componentIndex),
metrics.prefixFontSize);
drawList.PopClipRect();
drawList.PushClipRect(
ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], metrics.valueFontSize));
drawList.AddText(
UIPoint(
layout.componentValueRects[componentIndex].x + metrics.valueTextInsetX,
ResolveUIEditorTextTop(
layout.componentValueRects[componentIndex],
metrics.valueFontSize,
metrics.valueTextInsetY)),
state.editing && state.selectedComponentIndex == componentIndex
? state.displayTexts[componentIndex]
: FormatUIEditorVector4FieldComponentValue(spec, componentIndex),
spec.readOnly ? palette.readOnlyValueColor : palette.valueColor,
metrics.valueFontSize);
drawList.PopClipRect();
}
}
void AppendUIEditorVector4Field(
UIDrawList& drawList,
const UIRect& bounds,
const UIEditorVector4FieldSpec& spec,
const UIEditorVector4FieldState& state,
const UIEditorVector4FieldPalette& palette,
const UIEditorVector4FieldMetrics& metrics) {
const UIEditorVector4FieldLayout layout = BuildUIEditorVector4FieldLayout(bounds, spec, metrics);
AppendUIEditorVector4FieldBackground(drawList, layout, spec, state, palette, metrics);
AppendUIEditorVector4FieldForeground(drawList, layout, spec, state, palette, metrics);
}
} // namespace XCEngine::UI::Editor::Widgets