402 lines
17 KiB
C++
402 lines
17 KiB
C++
#include <XCEditor/Fields/UIEditorVector4Field.h>
|
|
|
|
#include <XCEditor/Widgets/UIEditorFieldRowLayout.h>
|
|
#include <XCEditor/Fields/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);
|
|
}
|
|
|
|
UIEditorVector4FieldMetrics ResolveMetrics(const UIEditorVector4FieldMetrics& metrics) {
|
|
const auto& tokens = GetUIEditorInspectorFieldStyleTokens();
|
|
|
|
UIEditorVector4FieldMetrics resolved = metrics;
|
|
if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) {
|
|
resolved.controlTrailingInset = tokens.controlTrailingInset;
|
|
}
|
|
if (AreUIEditorFieldMetricsEqual(metrics.componentMinWidth, 72.0f)) {
|
|
resolved.componentMinWidth = tokens.vectorComponentMinWidth;
|
|
}
|
|
if (AreUIEditorFieldMetricsEqual(metrics.componentPrefixWidth, 9.0f)) {
|
|
resolved.componentPrefixWidth = tokens.vectorPrefixWidth;
|
|
}
|
|
if (AreUIEditorFieldMetricsEqual(metrics.componentLabelGap, 4.0f)) {
|
|
resolved.componentLabelGap = tokens.vectorPrefixGap;
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
UIEditorVector4FieldPalette ResolvePalette(const UIEditorVector4FieldPalette& palette) {
|
|
const auto& tokens = GetUIEditorInspectorFieldStyleTokens();
|
|
|
|
UIEditorVector4FieldPalette resolved = palette;
|
|
if (AreUIEditorFieldColorsEqual(palette.rowHoverColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) {
|
|
resolved.rowHoverColor = tokens.rowHoverColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(palette.rowActiveColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) {
|
|
resolved.rowActiveColor = tokens.rowActiveColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(palette.componentColor, ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f))) {
|
|
resolved.componentColor = tokens.controlColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(
|
|
palette.componentHoverColor,
|
|
::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f))) {
|
|
resolved.componentHoverColor = tokens.controlHoverColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(
|
|
palette.componentEditingColor,
|
|
::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f))) {
|
|
resolved.componentEditingColor = tokens.controlEditingColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f))) {
|
|
resolved.readOnlyColor = tokens.controlReadOnlyColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(
|
|
palette.componentBorderColor,
|
|
::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) {
|
|
resolved.componentBorderColor = tokens.controlBorderColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(
|
|
palette.componentFocusedBorderColor,
|
|
::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f))) {
|
|
resolved.componentFocusedBorderColor = tokens.controlFocusedBorderColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(palette.prefixColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) {
|
|
resolved.prefixColor = tokens.prefixColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(
|
|
palette.prefixBorderColor,
|
|
::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) {
|
|
resolved.prefixBorderColor = tokens.prefixBorderColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 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.readOnlyValueColor,
|
|
::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f))) {
|
|
resolved.readOnlyValueColor = tokens.readOnlyValueColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(palette.axisXColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) {
|
|
resolved.axisXColor = tokens.axisXColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(palette.axisYColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) {
|
|
resolved.axisYColor = tokens.axisYColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(palette.axisZColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) {
|
|
resolved.axisZColor = tokens.axisZColor;
|
|
}
|
|
if (AreUIEditorFieldColorsEqual(palette.axisWColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) {
|
|
resolved.axisWColor = tokens.axisWColor;
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
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 UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics);
|
|
const float requiredControlWidth =
|
|
resolvedMetrics.componentMinWidth * 4.0f + resolvedMetrics.componentGap * 3.0f;
|
|
const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout(
|
|
bounds,
|
|
requiredControlWidth,
|
|
UIEditorFieldRowLayoutMetrics {
|
|
resolvedMetrics.rowHeight,
|
|
resolvedMetrics.horizontalPadding,
|
|
resolvedMetrics.labelControlGap,
|
|
resolvedMetrics.controlColumnStart,
|
|
resolvedMetrics.controlTrailingInset,
|
|
resolvedMetrics.controlInsetY,
|
|
});
|
|
const float componentWidth =
|
|
ClampNonNegative((hostLayout.controlRect.width - resolvedMetrics.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 + resolvedMetrics.componentGap) * static_cast<float>(componentIndex);
|
|
const UIRect componentRect(componentX, layout.controlRect.y, componentWidth, layout.controlRect.height);
|
|
const float prefixWidth = (std::min)(resolvedMetrics.componentPrefixWidth, componentRect.width);
|
|
const float labelGap = ClampNonNegative(resolvedMetrics.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) {
|
|
const UIEditorVector4FieldPalette resolvedPalette = ResolvePalette(palette);
|
|
const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics);
|
|
if (ResolveRowFillColor(state, resolvedPalette).a > 0.0f) {
|
|
drawList.AddFilledRect(
|
|
layout.bounds,
|
|
ResolveRowFillColor(state, resolvedPalette),
|
|
resolvedMetrics.cornerRounding);
|
|
}
|
|
if ((state.focused ? resolvedPalette.focusedBorderColor.a : resolvedPalette.borderColor.a) > 0.0f) {
|
|
drawList.AddRectOutline(
|
|
layout.bounds,
|
|
state.focused ? resolvedPalette.focusedBorderColor : resolvedPalette.borderColor,
|
|
state.focused ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness,
|
|
resolvedMetrics.cornerRounding);
|
|
}
|
|
|
|
for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) {
|
|
if (resolvedPalette.prefixColor.a > 0.0f) {
|
|
drawList.AddFilledRect(
|
|
layout.componentPrefixRects[componentIndex],
|
|
resolvedPalette.prefixColor,
|
|
resolvedMetrics.componentRounding);
|
|
}
|
|
if (resolvedPalette.prefixBorderColor.a > 0.0f) {
|
|
drawList.AddRectOutline(
|
|
layout.componentPrefixRects[componentIndex],
|
|
resolvedPalette.prefixBorderColor,
|
|
resolvedMetrics.borderThickness,
|
|
resolvedMetrics.componentRounding);
|
|
}
|
|
drawList.AddFilledRect(
|
|
layout.componentValueRects[componentIndex],
|
|
ResolveComponentFillColor(spec, state, resolvedPalette, componentIndex),
|
|
resolvedMetrics.componentRounding);
|
|
drawList.AddRectOutline(
|
|
layout.componentValueRects[componentIndex],
|
|
ResolveComponentBorderColor(state, resolvedPalette, componentIndex),
|
|
resolvedMetrics.borderThickness,
|
|
resolvedMetrics.componentRounding);
|
|
}
|
|
}
|
|
|
|
void AppendUIEditorVector4FieldForeground(
|
|
UIDrawList& drawList,
|
|
const UIEditorVector4FieldLayout& layout,
|
|
const UIEditorVector4FieldSpec& spec,
|
|
const UIEditorVector4FieldState& state,
|
|
const UIEditorVector4FieldPalette& palette,
|
|
const UIEditorVector4FieldMetrics& metrics) {
|
|
const UIEditorVector4FieldPalette resolvedPalette = ResolvePalette(palette);
|
|
const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics);
|
|
drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize));
|
|
drawList.AddText(
|
|
UIPoint(
|
|
layout.labelRect.x,
|
|
ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)),
|
|
spec.label,
|
|
resolvedPalette.labelColor,
|
|
resolvedMetrics.labelFontSize);
|
|
drawList.PopClipRect();
|
|
|
|
for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) {
|
|
drawList.PushClipRect(
|
|
ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], resolvedMetrics.prefixFontSize));
|
|
const std::string& componentLabel = spec.componentLabels[componentIndex];
|
|
const float prefixTextX =
|
|
layout.componentPrefixRects[componentIndex].x +
|
|
ClampNonNegative(
|
|
(layout.componentPrefixRects[componentIndex].width -
|
|
ApproximateTextWidth(resolvedMetrics.prefixFontSize, componentLabel.size())) *
|
|
0.5f) +
|
|
resolvedMetrics.prefixTextInsetX;
|
|
drawList.AddText(
|
|
UIPoint(
|
|
prefixTextX,
|
|
ResolveUIEditorTextTop(
|
|
layout.componentPrefixRects[componentIndex],
|
|
resolvedMetrics.prefixFontSize,
|
|
resolvedMetrics.prefixTextInsetY)),
|
|
componentLabel,
|
|
ResolveAxisColor(resolvedPalette, componentIndex),
|
|
resolvedMetrics.prefixFontSize);
|
|
drawList.PopClipRect();
|
|
|
|
drawList.PushClipRect(
|
|
ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], resolvedMetrics.valueFontSize));
|
|
drawList.AddText(
|
|
UIPoint(
|
|
layout.componentValueRects[componentIndex].x + resolvedMetrics.valueTextInsetX,
|
|
ResolveUIEditorTextTop(
|
|
layout.componentValueRects[componentIndex],
|
|
resolvedMetrics.valueFontSize,
|
|
resolvedMetrics.valueTextInsetY)),
|
|
state.editing && state.selectedComponentIndex == componentIndex
|
|
? state.displayTexts[componentIndex]
|
|
: FormatUIEditorVector4FieldComponentValue(spec, componentIndex),
|
|
spec.readOnly ? resolvedPalette.readOnlyValueColor : resolvedPalette.valueColor,
|
|
resolvedMetrics.valueFontSize);
|
|
drawList.PopClipRect();
|
|
}
|
|
}
|
|
|
|
void AppendUIEditorVector4Field(
|
|
UIDrawList& drawList,
|
|
const UIRect& bounds,
|
|
const UIEditorVector4FieldSpec& spec,
|
|
const UIEditorVector4FieldState& state,
|
|
const UIEditorVector4FieldPalette& palette,
|
|
const UIEditorVector4FieldMetrics& metrics) {
|
|
const UIEditorVector4FieldPalette resolvedPalette = ResolvePalette(palette);
|
|
const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics);
|
|
const UIEditorVector4FieldLayout layout = BuildUIEditorVector4FieldLayout(bounds, spec, resolvedMetrics);
|
|
AppendUIEditorVector4FieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics);
|
|
AppendUIEditorVector4FieldForeground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics);
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::Widgets
|