#include #include namespace XCEngine::UI::Editor::Widgets { namespace { using ::XCEngine::UI::UIColor; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIRect; bool ContainsPoint(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; } bool HasArea(const UIRect& rect) { return rect.width > 0.0f && rect.height > 0.0f; } float ClampNonNegative(float value) { return (std::max)(value, 0.0f); } UIColor ResolveSegmentFillColor( bool hovered, bool active, const UIEditorStatusBarPalette& palette) { if (active) { return palette.segmentActiveColor; } if (hovered) { return palette.segmentHoveredColor; } return palette.segmentColor; } } // namespace float ResolveUIEditorStatusBarDesiredSegmentWidth( const UIEditorStatusBarSegment& segment, const UIEditorStatusBarMetrics& metrics) { if (segment.desiredWidth > 0.0f) { return segment.desiredWidth; } return segment.label.empty() ? metrics.segmentPaddingX * 2.0f : metrics.segmentPaddingX * 2.0f + static_cast(segment.label.size()) * metrics.estimatedGlyphWidth; } UIEditorStatusBarLayout BuildUIEditorStatusBarLayout( const UIRect& bounds, const std::vector& segments, const UIEditorStatusBarMetrics& metrics) { UIEditorStatusBarLayout layout = {}; layout.bounds = UIRect( bounds.x, bounds.y, ClampNonNegative(bounds.width), ClampNonNegative(bounds.height)); layout.segmentRects.resize(segments.size(), UIRect{}); layout.separatorRects.resize(segments.size(), UIRect{}); const float contentTop = layout.bounds.y; const float contentHeight = layout.bounds.height; const float leftStart = layout.bounds.x + metrics.outerPaddingX; const float rightLimit = layout.bounds.x + layout.bounds.width - metrics.outerPaddingX; float leadingCursor = leftStart; float leadingRight = leftStart; for (std::size_t index = 0u; index < segments.size(); ++index) { const auto& segment = segments[index]; if (segment.slot != UIEditorStatusBarSlot::Leading) { continue; } const float segmentWidth = ResolveUIEditorStatusBarDesiredSegmentWidth(segment, metrics); layout.segmentRects[index] = UIRect( leadingCursor, contentTop, segmentWidth, contentHeight); leadingCursor += segmentWidth; leadingRight = leadingCursor; if (segment.showSeparator) { layout.separatorRects[index] = UIRect( leadingCursor, contentTop + metrics.separatorInsetY, metrics.separatorWidth, (std::max)(contentHeight - metrics.separatorInsetY * 2.0f, 0.0f)); leadingCursor += metrics.separatorWidth; } leadingCursor += metrics.segmentGap; leadingRight = (std::max)(leadingRight, leadingCursor - metrics.segmentGap); } float trailingCursor = rightLimit; float trailingLeft = rightLimit; for (std::size_t reverseIndex = segments.size(); reverseIndex > 0u; --reverseIndex) { const std::size_t index = reverseIndex - 1u; const auto& segment = segments[index]; if (segment.slot != UIEditorStatusBarSlot::Trailing) { continue; } const float segmentWidth = ResolveUIEditorStatusBarDesiredSegmentWidth(segment, metrics); const bool hasSeparator = segment.showSeparator; if (hasSeparator) { trailingCursor -= metrics.separatorWidth; layout.separatorRects[index] = UIRect( trailingCursor, contentTop + metrics.separatorInsetY, metrics.separatorWidth, (std::max)(contentHeight - metrics.separatorInsetY * 2.0f, 0.0f)); trailingCursor -= metrics.segmentGap; } trailingCursor -= segmentWidth; layout.segmentRects[index] = UIRect( trailingCursor, contentTop, segmentWidth, contentHeight); trailingCursor -= metrics.segmentGap; trailingLeft = (std::min)(trailingLeft, layout.segmentRects[index].x); } const float leadingWidth = leadingRight > leftStart ? leadingRight - leftStart : 0.0f; layout.leadingSlotRect = UIRect(leftStart, contentTop, leadingWidth, contentHeight); const float trailingWidth = trailingLeft < rightLimit ? rightLimit - trailingLeft : 0.0f; layout.trailingSlotRect = UIRect(trailingLeft, contentTop, trailingWidth, contentHeight); if (HasArea(layout.leadingSlotRect) && HasArea(layout.trailingSlotRect) && layout.leadingSlotRect.x + layout.leadingSlotRect.width + metrics.slotGapMin > layout.trailingSlotRect.x) { const float overlap = layout.leadingSlotRect.x + layout.leadingSlotRect.width + metrics.slotGapMin - layout.trailingSlotRect.x; layout.trailingSlotRect.x += overlap; layout.trailingSlotRect.width = (std::max)(layout.trailingSlotRect.width - overlap, 0.0f); } return layout; } UIEditorStatusBarHitTarget HitTestUIEditorStatusBar( const UIEditorStatusBarLayout& layout, const UIPoint& point) { if (!ContainsPoint(layout.bounds, point)) { return {}; } for (std::size_t index = 0u; index < layout.separatorRects.size(); ++index) { if (HasArea(layout.separatorRects[index]) && ContainsPoint(layout.separatorRects[index], point)) { return { UIEditorStatusBarHitTargetKind::Separator, index }; } } for (std::size_t index = 0u; index < layout.segmentRects.size(); ++index) { if (HasArea(layout.segmentRects[index]) && ContainsPoint(layout.segmentRects[index], point)) { return { UIEditorStatusBarHitTargetKind::Segment, index }; } } return { UIEditorStatusBarHitTargetKind::Background, UIEditorStatusBarInvalidIndex }; } UIColor ResolveUIEditorStatusBarTextColor( UIEditorStatusBarTextTone tone, const UIEditorStatusBarPalette& palette) { switch (tone) { case UIEditorStatusBarTextTone::Muted: return palette.textMuted; case UIEditorStatusBarTextTone::Accent: return palette.textAccent; case UIEditorStatusBarTextTone::Primary: default: return palette.textPrimary; } } void AppendUIEditorStatusBarBackground( UIDrawList& drawList, const UIEditorStatusBarLayout& layout, const std::vector& segments, const UIEditorStatusBarState& state, const UIEditorStatusBarPalette& palette, const UIEditorStatusBarMetrics& metrics) { drawList.AddFilledRect(layout.bounds, palette.surfaceColor, metrics.cornerRounding); drawList.AddRectOutline( layout.bounds, state.focused ? palette.focusedBorderColor : palette.borderColor, state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, metrics.cornerRounding); for (std::size_t index = 0u; index < segments.size(); ++index) { if (!HasArea(layout.segmentRects[index])) { continue; } const bool hovered = state.hoveredIndex == index; const bool active = state.activeIndex == index; if (!hovered && !active) { continue; } drawList.AddFilledRect( layout.segmentRects[index], ResolveSegmentFillColor(hovered, active, palette), 6.0f); drawList.AddRectOutline( layout.segmentRects[index], palette.segmentBorderColor, 1.0f, 6.0f); } for (const UIRect& separatorRect : layout.separatorRects) { if (!HasArea(separatorRect)) { continue; } drawList.AddFilledRect(separatorRect, palette.separatorColor); } } void AppendUIEditorStatusBarForeground( UIDrawList& drawList, const UIEditorStatusBarLayout& layout, const std::vector& segments, const UIEditorStatusBarState&, const UIEditorStatusBarPalette& palette, const UIEditorStatusBarMetrics& metrics) { for (std::size_t index = 0u; index < segments.size(); ++index) { if (!HasArea(layout.segmentRects[index]) || segments[index].label.empty()) { continue; } drawList.AddText( UIPoint( layout.segmentRects[index].x + metrics.segmentPaddingX, layout.segmentRects[index].y + metrics.segmentPaddingY), segments[index].label, ResolveUIEditorStatusBarTextColor(segments[index].tone, palette), 12.0f); } } void AppendUIEditorStatusBar( UIDrawList& drawList, const UIRect& bounds, const std::vector& segments, const UIEditorStatusBarState& state, const UIEditorStatusBarPalette& palette, const UIEditorStatusBarMetrics& metrics) { const UIEditorStatusBarLayout layout = BuildUIEditorStatusBarLayout(bounds, segments, metrics); AppendUIEditorStatusBarBackground(drawList, layout, segments, state, palette, metrics); AppendUIEditorStatusBarForeground(drawList, layout, segments, state, palette, metrics); } } // namespace XCEngine::UI::Editor::Widgets