Files
XCEngine/new_editor/src/Fields/UIEditorVectorFieldShared.h

508 lines
18 KiB
C
Raw Normal View History

#pragma once
#include <XCEditor/Fields/UIEditorNumberField.h>
#include <XCEditor/Widgets/UIEditorFieldRowLayout.h>
#include <XCEditor/Widgets/UIEditorTextLayout.h>
#include <algorithm>
#include <cstddef>
#include <string>
#include <string_view>
namespace XCEngine::UI::Editor::Internal::VectorFieldWidgetShared {
using ::XCEngine::UI::UIDrawList;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIRect;
inline float ClampNonNegative(float value) {
return (std::max)(0.0f, value);
}
inline ::XCEngine::UI::UIColor DefaultAxisPlaceholderColor() {
return ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f);
}
inline void OverrideMetricIfDefault(
float& value,
float placeholder,
float replacement) {
if (Widgets::AreUIEditorFieldMetricsEqual(value, placeholder)) {
value = replacement;
}
}
inline void OverrideColorIfDefault(
::XCEngine::UI::UIColor& value,
const ::XCEngine::UI::UIColor& placeholder,
const ::XCEngine::UI::UIColor& replacement) {
if (Widgets::AreUIEditorFieldColorsEqual(value, placeholder)) {
value = replacement;
}
}
inline const ::XCEngine::UI::UIColor& ResolveAxisTokenColor(
const Widgets::UIEditorInspectorFieldStyleTokens& tokens,
std::size_t componentIndex) {
switch (componentIndex) {
case 0u:
return tokens.axisXColor;
case 1u:
return tokens.axisYColor;
case 2u:
return tokens.axisZColor;
default:
return tokens.axisWColor;
}
}
template <typename Traits>
typename Traits::Metrics ResolveMetrics(const typename Traits::Metrics& metrics) {
const auto& tokens = Widgets::GetUIEditorInspectorFieldStyleTokens();
typename Traits::Metrics resolved = metrics;
OverrideMetricIfDefault(
resolved.controlTrailingInset,
8.0f,
tokens.controlTrailingInset);
OverrideMetricIfDefault(
resolved.componentMinWidth,
72.0f,
tokens.vectorComponentMinWidth);
OverrideMetricIfDefault(
resolved.componentPrefixWidth,
9.0f,
tokens.vectorPrefixWidth);
OverrideMetricIfDefault(
resolved.componentLabelGap,
4.0f,
tokens.vectorPrefixGap);
return resolved;
}
template <typename Traits>
typename Traits::Palette ResolvePalette(const typename Traits::Palette& palette) {
const auto& tokens = Widgets::GetUIEditorInspectorFieldStyleTokens();
typename Traits::Palette resolved = palette;
OverrideColorIfDefault(
resolved.componentColor,
::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f),
tokens.controlColor);
OverrideColorIfDefault(
resolved.componentHoverColor,
::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f),
tokens.controlHoverColor);
OverrideColorIfDefault(
resolved.componentEditingColor,
::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f),
tokens.controlEditingColor);
OverrideColorIfDefault(
resolved.readOnlyColor,
::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f),
tokens.controlReadOnlyColor);
OverrideColorIfDefault(
resolved.componentBorderColor,
::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f),
tokens.controlBorderColor);
OverrideColorIfDefault(
resolved.componentFocusedBorderColor,
::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f),
tokens.controlFocusedBorderColor);
OverrideColorIfDefault(
resolved.prefixColor,
::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f),
tokens.prefixColor);
OverrideColorIfDefault(
resolved.prefixBorderColor,
::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f),
tokens.prefixBorderColor);
OverrideColorIfDefault(
resolved.labelColor,
::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f),
tokens.labelColor);
OverrideColorIfDefault(
resolved.valueColor,
::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f),
tokens.valueColor);
OverrideColorIfDefault(
resolved.readOnlyValueColor,
::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f),
tokens.readOnlyValueColor);
for (std::size_t componentIndex = 0u;
componentIndex < Traits::kComponentCount;
++componentIndex) {
if (Widgets::AreUIEditorFieldColorsEqual(
Traits::AxisColor(palette, componentIndex),
DefaultAxisPlaceholderColor())) {
Traits::AxisColor(resolved, componentIndex) =
ResolveAxisTokenColor(tokens, componentIndex);
}
}
return resolved;
}
inline float ApproximateTextWidth(float fontSize, std::size_t characterCount) {
return fontSize * 0.55f * static_cast<float>(characterCount);
}
template <typename Traits>
Widgets::UIEditorNumberFieldSpec BuildComponentNumberSpec(
const typename Traits::Spec& spec,
std::size_t componentIndex) {
Widgets::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;
}
template <typename Traits>
::XCEngine::UI::UIColor ResolveRowFillColor(
const typename Traits::State& state,
const typename Traits::Palette& palette) {
if (state.activeTarget != Traits::kNoneHitTargetKind) {
return palette.rowActiveColor;
}
if (state.hoveredTarget != Traits::kNoneHitTargetKind) {
return palette.rowHoverColor;
}
return palette.surfaceColor;
}
template <typename Traits>
::XCEngine::UI::UIColor ResolveComponentFillColor(
const typename Traits::Spec& spec,
const typename Traits::State& state,
const typename Traits::Palette& 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;
}
template <typename Traits>
::XCEngine::UI::UIColor ResolveComponentBorderColor(
const typename Traits::State& state,
const typename Traits::Palette& palette,
std::size_t componentIndex) {
if (state.editing && state.selectedComponentIndex == componentIndex) {
return palette.componentFocusedBorderColor;
}
return palette.componentBorderColor;
}
inline bool IsPointInside(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;
}
template <typename Traits>
double NormalizeComponentValue(
const typename Traits::Spec& spec,
double value) {
return Widgets::NormalizeUIEditorNumberFieldValue(
BuildComponentNumberSpec<Traits>(spec, 0u),
value);
}
template <typename Traits>
bool TryParseComponentValue(
const typename Traits::Spec& spec,
std::string_view text,
double& outValue) {
return Widgets::TryParseUIEditorNumberFieldValue(
BuildComponentNumberSpec<Traits>(spec, 0u),
text,
outValue);
}
template <typename Traits>
std::string FormatComponentValue(
const typename Traits::Spec& spec,
std::size_t componentIndex) {
return Widgets::FormatUIEditorNumberFieldValue(
BuildComponentNumberSpec<Traits>(spec, componentIndex));
}
template <typename Traits>
typename Traits::Layout BuildLayout(
const UIRect& bounds,
const typename Traits::Spec&,
const typename Traits::Metrics& metrics) {
static_assert(Traits::kComponentCount > 0u);
const typename Traits::Metrics resolvedMetrics = ResolveMetrics<Traits>(metrics);
const float gapCount = static_cast<float>(Traits::kComponentCount - 1u);
const float requiredControlWidth =
resolvedMetrics.componentMinWidth * static_cast<float>(Traits::kComponentCount) +
resolvedMetrics.componentGap * gapCount;
const Widgets::UIEditorFieldRowLayout hostLayout =
Widgets::BuildUIEditorFieldRowLayout(
bounds,
requiredControlWidth,
Widgets::UIEditorFieldRowLayoutMetrics{
resolvedMetrics.rowHeight,
resolvedMetrics.horizontalPadding,
resolvedMetrics.labelControlGap,
resolvedMetrics.controlColumnStart,
resolvedMetrics.sharedControlColumnMinWidth,
resolvedMetrics.controlTrailingInset,
resolvedMetrics.controlInsetY,
});
const float componentWidth = ClampNonNegative(
(hostLayout.controlRect.width - resolvedMetrics.componentGap * gapCount) /
static_cast<float>(Traits::kComponentCount));
typename Traits::Layout 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;
}
template <typename Traits>
typename Traits::HitTarget HitTest(
const typename Traits::Layout& layout,
const UIPoint& point) {
for (std::size_t componentIndex = 0u;
componentIndex < layout.componentRects.size();
++componentIndex) {
if (IsPointInside(layout.componentRects[componentIndex], point)) {
return { Traits::kComponentHitTargetKind, componentIndex };
}
}
if (IsPointInside(layout.bounds, point)) {
return { Traits::kRowHitTargetKind, Traits::kInvalidComponentIndex };
}
return {};
}
template <typename Traits>
void AppendBackground(
UIDrawList& drawList,
const typename Traits::Layout& layout,
const typename Traits::Spec& spec,
const typename Traits::State& state,
const typename Traits::Palette& palette,
const typename Traits::Metrics& metrics) {
const typename Traits::Palette resolvedPalette = ResolvePalette<Traits>(palette);
const typename Traits::Metrics resolvedMetrics = ResolveMetrics<Traits>(metrics);
if (ResolveRowFillColor<Traits>(state, resolvedPalette).a > 0.0f) {
drawList.AddFilledRect(
layout.bounds,
ResolveRowFillColor<Traits>(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) {
drawList.AddFilledRect(
layout.componentValueRects[componentIndex],
ResolveComponentFillColor<Traits>(
spec,
state,
resolvedPalette,
componentIndex),
resolvedMetrics.componentRounding);
drawList.AddRectOutline(
layout.componentValueRects[componentIndex],
ResolveComponentBorderColor<Traits>(
state,
resolvedPalette,
componentIndex),
resolvedMetrics.borderThickness,
resolvedMetrics.componentRounding);
}
}
template <typename Traits>
void AppendForeground(
UIDrawList& drawList,
const typename Traits::Layout& layout,
const typename Traits::Spec& spec,
const typename Traits::State& state,
const typename Traits::Palette& palette,
2026-04-23 01:43:23 +08:00
const typename Traits::Metrics& metrics,
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer) {
const typename Traits::Palette resolvedPalette = ResolvePalette<Traits>(palette);
const typename Traits::Metrics resolvedMetrics = ResolveMetrics<Traits>(metrics);
drawList.PushClipRect(
Widgets::ResolveUIEditorTextClipRect(
layout.labelRect,
resolvedMetrics.labelFontSize));
drawList.AddText(
UIPoint(
layout.labelRect.x,
Widgets::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(
Widgets::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,
Widgets::ResolveUIEditorTextTop(
layout.componentPrefixRects[componentIndex],
resolvedMetrics.prefixFontSize,
resolvedMetrics.prefixTextInsetY)),
componentLabel,
resolvedPalette.labelColor,
resolvedMetrics.prefixFontSize);
drawList.PopClipRect();
drawList.PushClipRect(
Widgets::ResolveUIEditorTextClipRect(
layout.componentValueRects[componentIndex],
resolvedMetrics.valueFontSize));
drawList.AddText(
UIPoint(
layout.componentValueRects[componentIndex].x +
resolvedMetrics.valueTextInsetX,
Widgets::ResolveUIEditorTextTop(
layout.componentValueRects[componentIndex],
resolvedMetrics.valueFontSize,
resolvedMetrics.valueTextInsetY)),
state.editing && state.selectedComponentIndex == componentIndex
? state.displayTexts[componentIndex]
: FormatComponentValue<Traits>(spec, componentIndex),
spec.readOnly
? resolvedPalette.readOnlyValueColor
: resolvedPalette.valueColor,
resolvedMetrics.valueFontSize);
if (state.editing && state.selectedComponentIndex == componentIndex) {
Widgets::AppendUIEditorTextCaret(
drawList,
layout.componentValueRects[componentIndex],
state.displayTexts[componentIndex],
state.caretOffset,
state.caretBlinkStartNanoseconds,
resolvedPalette.valueColor,
resolvedMetrics.valueFontSize,
resolvedMetrics.valueTextInsetX,
2026-04-23 01:43:23 +08:00
resolvedMetrics.valueTextInsetY,
1.0f,
textMeasurer);
}
drawList.PopClipRect();
}
}
template <typename Traits>
void AppendField(
UIDrawList& drawList,
const UIRect& bounds,
const typename Traits::Spec& spec,
const typename Traits::State& state,
const typename Traits::Palette& palette,
2026-04-23 01:43:23 +08:00
const typename Traits::Metrics& metrics,
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer) {
const typename Traits::Palette resolvedPalette = ResolvePalette<Traits>(palette);
const typename Traits::Metrics resolvedMetrics = ResolveMetrics<Traits>(metrics);
const typename Traits::Layout layout =
BuildLayout<Traits>(bounds, spec, resolvedMetrics);
AppendBackground<Traits>(
drawList,
layout,
spec,
state,
resolvedPalette,
resolvedMetrics);
AppendForeground<Traits>(
drawList,
layout,
spec,
state,
resolvedPalette,
2026-04-23 01:43:23 +08:00
resolvedMetrics,
textMeasurer);
}
} // namespace XCEngine::UI::Editor::Internal::VectorFieldWidgetShared