#include #include #include #include #include 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(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(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