#include #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 EstimateTextWidth(std::string_view text, float fontSize) { return static_cast(text.size()) * fontSize * 0.58f; } bool ShowsPickerButton(const UIEditorAssetFieldSpec& spec) { return spec.showPickerButton; } bool ShowsClearButton(const UIEditorAssetFieldSpec& spec) { return spec.allowClear && HasUIEditorAssetFieldValue(spec); } bool ShowsStatusBadge(const UIEditorAssetFieldSpec& spec) { return spec.showStatusBadge && !spec.statusText.empty(); } ::XCEngine::UI::UIColor ResolveRowFillColor( const UIEditorAssetFieldState& state, const UIEditorAssetFieldPalette& palette) { if (state.activeTarget != UIEditorAssetFieldHitTargetKind::None) { return palette.rowActiveColor; } if (state.hoveredTarget != UIEditorAssetFieldHitTargetKind::None) { return palette.rowHoverColor; } return palette.surfaceColor; } ::XCEngine::UI::UIColor ResolveValueFillColor( const UIEditorAssetFieldSpec& spec, const UIEditorAssetFieldState& state, const UIEditorAssetFieldPalette& palette) { if (spec.readOnly) { return palette.readOnlyColor; } if (state.activeTarget == UIEditorAssetFieldHitTargetKind::ValueBox || state.activeTarget == UIEditorAssetFieldHitTargetKind::PickerButton || state.activeTarget == UIEditorAssetFieldHitTargetKind::ClearButton) { return palette.valueBoxActiveColor; } if (state.hoveredTarget == UIEditorAssetFieldHitTargetKind::ValueBox || state.hoveredTarget == UIEditorAssetFieldHitTargetKind::PickerButton || state.hoveredTarget == UIEditorAssetFieldHitTargetKind::ClearButton) { return palette.valueBoxHoverColor; } return palette.valueBoxColor; } ::XCEngine::UI::UIColor ResolveActionFillColor( UIEditorAssetFieldHitTargetKind targetKind, const UIEditorAssetFieldState& state, const UIEditorAssetFieldPalette& palette) { if (state.activeTarget == targetKind) { return palette.actionButtonActiveColor; } if (state.hoveredTarget == targetKind) { return palette.actionButtonHoverColor; } return palette.actionButtonColor; } } // namespace bool HasUIEditorAssetFieldValue(const UIEditorAssetFieldSpec& spec) { return !spec.assetId.empty() || !spec.displayName.empty(); } std::string ResolveUIEditorAssetFieldValueText(const UIEditorAssetFieldSpec& spec) { if (!spec.displayName.empty()) { return spec.displayName; } if (!spec.assetId.empty()) { return spec.assetId; } return spec.emptyText.empty() ? std::string("None") : spec.emptyText; } std::string ResolveUIEditorAssetFieldPreviewGlyph(const UIEditorAssetFieldSpec& spec) { const std::string source = !spec.statusText.empty() ? spec.statusText : ResolveUIEditorAssetFieldValueText(spec); for (char character : source) { if (std::isalnum(static_cast(character)) != 0) { return std::string( 1u, static_cast(std::toupper(static_cast(character)))); } } return "-"; } bool IsUIEditorAssetFieldPointInside(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; } UIEditorAssetFieldLayout BuildUIEditorAssetFieldLayout( const UIRect& bounds, const UIEditorAssetFieldSpec& spec, const UIEditorAssetFieldMetrics& metrics) { const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( bounds, metrics.valueBoxMinWidth, UIEditorFieldRowLayoutMetrics { metrics.rowHeight, metrics.horizontalPadding, metrics.labelControlGap, metrics.controlColumnStart, metrics.controlTrailingInset, metrics.controlInsetY, }); UIEditorAssetFieldLayout layout = {}; layout.bounds = hostLayout.bounds; layout.labelRect = hostLayout.labelRect; layout.controlRect = hostLayout.controlRect; layout.valueRect = hostLayout.controlRect; float trailingX = layout.valueRect.x + layout.valueRect.width; if (ShowsPickerButton(spec)) { trailingX -= metrics.actionButtonWidth; layout.pickerRect = UIRect( trailingX, layout.valueRect.y, metrics.actionButtonWidth, layout.valueRect.height); trailingX -= metrics.actionButtonGap; } if (ShowsClearButton(spec)) { trailingX -= metrics.actionButtonWidth; layout.clearRect = UIRect( trailingX, layout.valueRect.y, metrics.actionButtonWidth, layout.valueRect.height); trailingX -= metrics.actionButtonGap; } const float previewSize = (std::min)(metrics.previewSize, ClampNonNegative(layout.valueRect.height - 4.0f)); layout.previewRect = UIRect( layout.valueRect.x + metrics.previewInsetX, layout.valueRect.y + ClampNonNegative((layout.valueRect.height - previewSize) * 0.5f), previewSize, previewSize); const float contentLeft = layout.previewRect.x + layout.previewRect.width + metrics.previewGap; if (ShowsStatusBadge(spec)) { const float estimatedBadgeWidth = EstimateTextWidth(spec.statusText, metrics.statusBadgeFontSize) + metrics.statusBadgePaddingX * 2.0f; const float badgeWidth = (std::min)( (std::max)(metrics.statusBadgeMinWidth, estimatedBadgeWidth), ClampNonNegative((trailingX - contentLeft) * 0.45f)); if (badgeWidth > 20.0f) { trailingX -= badgeWidth; layout.statusBadgeRect = UIRect( trailingX, layout.valueRect.y + ClampNonNegative((layout.valueRect.height - metrics.statusBadgeHeight) * 0.5f), badgeWidth, (std::min)(metrics.statusBadgeHeight, layout.valueRect.height)); trailingX -= metrics.statusBadgeGap; } } layout.textRect = UIRect( contentLeft + metrics.valueTextInsetX, layout.valueRect.y, ClampNonNegative(trailingX - contentLeft - metrics.valueTextInsetX), layout.valueRect.height); return layout; } UIEditorAssetFieldHitTarget HitTestUIEditorAssetField( const UIEditorAssetFieldLayout& layout, const UIPoint& point) { if (layout.clearRect.width > 0.0f && IsUIEditorAssetFieldPointInside(layout.clearRect, point)) { return { UIEditorAssetFieldHitTargetKind::ClearButton }; } if (layout.pickerRect.width > 0.0f && IsUIEditorAssetFieldPointInside(layout.pickerRect, point)) { return { UIEditorAssetFieldHitTargetKind::PickerButton }; } if (IsUIEditorAssetFieldPointInside(layout.valueRect, point)) { return { UIEditorAssetFieldHitTargetKind::ValueBox }; } if (IsUIEditorAssetFieldPointInside(layout.bounds, point)) { return { UIEditorAssetFieldHitTargetKind::Row }; } return {}; } void AppendUIEditorAssetFieldBackground( UIDrawList& drawList, const UIEditorAssetFieldLayout& layout, const UIEditorAssetFieldSpec& spec, const UIEditorAssetFieldState& state, const UIEditorAssetFieldPalette& palette, const UIEditorAssetFieldMetrics& metrics) { const auto rowFillColor = ResolveRowFillColor(state, palette); if (rowFillColor.a > 0.0f) { drawList.AddFilledRect(layout.bounds, rowFillColor, metrics.cornerRounding); } const auto rowBorderColor = state.focused ? palette.focusedBorderColor : palette.borderColor; if (rowBorderColor.a > 0.0f) { drawList.AddRectOutline( layout.bounds, rowBorderColor, state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, metrics.cornerRounding); } drawList.AddFilledRect( layout.valueRect, ResolveValueFillColor(spec, state, palette), metrics.valueBoxRounding); drawList.AddRectOutline( layout.valueRect, state.focused ? palette.controlFocusedBorderColor : palette.controlBorderColor, state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, metrics.valueBoxRounding); if (HasUIEditorAssetFieldValue(spec)) { drawList.AddFilledRectLinearGradient( layout.previewRect, palette.previewBaseColor, spec.tint, ::XCEngine::UI::UILinearGradientDirection::Vertical, metrics.previewRounding); } else { drawList.AddFilledRect(layout.previewRect, palette.previewEmptyColor, metrics.previewRounding); } drawList.AddRectOutline( layout.previewRect, palette.previewBorderColor, metrics.borderThickness, metrics.previewRounding); if (layout.statusBadgeRect.width > 0.0f && layout.statusBadgeRect.height > 0.0f) { drawList.AddFilledRect(layout.statusBadgeRect, palette.statusBadgeColor, metrics.badgeRounding); drawList.AddRectOutline( layout.statusBadgeRect, palette.statusBadgeBorderColor, metrics.borderThickness, metrics.badgeRounding); } if (layout.pickerRect.width > 0.0f) { const auto fill = ResolveActionFillColor(UIEditorAssetFieldHitTargetKind::PickerButton, state, palette); drawList.AddFilledRect(layout.pickerRect, fill, metrics.valueBoxRounding); drawList.AddLine( UIPoint(layout.pickerRect.x, layout.pickerRect.y + 1.0f), UIPoint(layout.pickerRect.x, layout.pickerRect.y + layout.pickerRect.height - 1.0f), palette.separatorColor, 1.0f); } if (layout.clearRect.width > 0.0f) { const auto fill = ResolveActionFillColor(UIEditorAssetFieldHitTargetKind::ClearButton, state, palette); drawList.AddFilledRect(layout.clearRect, fill, metrics.valueBoxRounding); drawList.AddLine( UIPoint(layout.clearRect.x, layout.clearRect.y + 1.0f), UIPoint(layout.clearRect.x, layout.clearRect.y + layout.clearRect.height - 1.0f), palette.separatorColor, 1.0f); } } void AppendUIEditorAssetFieldForeground( UIDrawList& drawList, const UIEditorAssetFieldLayout& layout, const UIEditorAssetFieldSpec& spec, const UIEditorAssetFieldState&, const UIEditorAssetFieldPalette& palette, const UIEditorAssetFieldMetrics& 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(); drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.previewRect, metrics.previewGlyphFontSize)); drawList.AddText( UIPoint( layout.previewRect.x + ClampNonNegative((layout.previewRect.width - metrics.previewGlyphFontSize) * 0.5f), ResolveUIEditorTextTop(layout.previewRect, metrics.previewGlyphFontSize, -1.0f)), ResolveUIEditorAssetFieldPreviewGlyph(spec), palette.previewGlyphColor, metrics.previewGlyphFontSize); drawList.PopClipRect(); drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.textRect, metrics.valueFontSize)); drawList.AddText( UIPoint( layout.textRect.x, ResolveUIEditorTextTop(layout.textRect, metrics.valueFontSize, metrics.valueTextInsetY)), ResolveUIEditorAssetFieldValueText(spec), HasUIEditorAssetFieldValue(spec) ? palette.valueColor : palette.emptyValueColor, metrics.valueFontSize); drawList.PopClipRect(); if (layout.statusBadgeRect.width > 0.0f && layout.statusBadgeRect.height > 0.0f) { const UIRect badgeTextRect( layout.statusBadgeRect.x + metrics.statusBadgePaddingX, layout.statusBadgeRect.y, ClampNonNegative(layout.statusBadgeRect.width - metrics.statusBadgePaddingX * 2.0f), layout.statusBadgeRect.height); drawList.PushClipRect(ResolveUIEditorTextClipRect(badgeTextRect, metrics.statusBadgeFontSize)); drawList.AddText( UIPoint( badgeTextRect.x, ResolveUIEditorTextTop(badgeTextRect, metrics.statusBadgeFontSize, -1.0f)), spec.statusText, palette.statusBadgeTextColor, metrics.statusBadgeFontSize); drawList.PopClipRect(); } if (layout.pickerRect.width > 0.0f) { drawList.AddText( UIPoint( layout.pickerRect.x + ClampNonNegative((layout.pickerRect.width - metrics.actionGlyphFontSize) * 0.5f), ResolveUIEditorTextTop( layout.pickerRect, metrics.actionGlyphFontSize, metrics.actionGlyphInsetY)), "o", palette.pickerGlyphColor, metrics.actionGlyphFontSize); } if (layout.clearRect.width > 0.0f) { drawList.AddText( UIPoint( layout.clearRect.x + ClampNonNegative((layout.clearRect.width - metrics.actionGlyphFontSize) * 0.5f), ResolveUIEditorTextTop( layout.clearRect, metrics.actionGlyphFontSize, metrics.actionGlyphInsetY)), "X", palette.clearGlyphColor, metrics.actionGlyphFontSize); } } void AppendUIEditorAssetField( UIDrawList& drawList, const UIRect& bounds, const UIEditorAssetFieldSpec& spec, const UIEditorAssetFieldState& state, const UIEditorAssetFieldPalette& palette, const UIEditorAssetFieldMetrics& metrics) { const UIEditorAssetFieldLayout layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics); AppendUIEditorAssetFieldBackground(drawList, layout, spec, state, palette, metrics); AppendUIEditorAssetFieldForeground(drawList, layout, spec, state, palette, metrics); } } // namespace XCEngine::UI::Editor::Widgets