#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); } 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(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(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