#include #include namespace XCEngine::UI::Editor::Widgets { namespace { float ClampNonNegative(float value) { return (std::max)(value, 0.0f); } float ResolveListViewRowHeight( const UIEditorListViewItem& item, const UIEditorListViewMetrics& metrics) { return item.desiredHeight > 0.0f ? item.desiredHeight : metrics.rowHeight; } } // namespace bool IsUIEditorListViewPointInside( const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) { return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height; } std::size_t FindUIEditorListViewItemIndex( const std::vector& items, std::string_view itemId) { for (std::size_t itemIndex = 0u; itemIndex < items.size(); ++itemIndex) { if (items[itemIndex].itemId == itemId) { return itemIndex; } } return UIEditorListViewInvalidIndex; } UIEditorListViewLayout BuildUIEditorListViewLayout( const ::XCEngine::UI::UIRect& bounds, const std::vector& items, const UIEditorListViewMetrics& metrics) { UIEditorListViewLayout layout = {}; layout.bounds = ::XCEngine::UI::UIRect( bounds.x, bounds.y, ClampNonNegative(bounds.width), ClampNonNegative(bounds.height)); layout.itemIndices.reserve(items.size()); layout.rowRects.reserve(items.size()); layout.primaryTextRects.reserve(items.size()); layout.secondaryTextRects.reserve(items.size()); layout.hasSecondaryText.reserve(items.size()); float rowY = layout.bounds.y; for (std::size_t itemIndex = 0u; itemIndex < items.size(); ++itemIndex) { const UIEditorListViewItem& item = items[itemIndex]; const bool hasSecondaryText = !item.secondaryText.empty(); const float rowHeight = ResolveListViewRowHeight(item, metrics); const ::XCEngine::UI::UIRect rowRect( layout.bounds.x, rowY, layout.bounds.width, rowHeight); const float textX = rowRect.x + ClampNonNegative(metrics.horizontalPadding); const float textWidth = (std::max)(0.0f, rowRect.width - ClampNonNegative(metrics.horizontalPadding) * 2.0f); const ::XCEngine::UI::UIRect primaryTextRect( textX, rowRect.y + (hasSecondaryText ? ClampNonNegative(metrics.primaryTextInsetY) : ClampNonNegative(metrics.singleLineTextInsetY)), textWidth, hasSecondaryText ? 14.0f : 18.0f); const ::XCEngine::UI::UIRect secondaryTextRect( textX, rowRect.y + ClampNonNegative(metrics.secondaryTextInsetY), textWidth, hasSecondaryText ? 12.0f : 0.0f); layout.itemIndices.push_back(itemIndex); layout.rowRects.push_back(rowRect); layout.primaryTextRects.push_back(primaryTextRect); layout.secondaryTextRects.push_back(secondaryTextRect); layout.hasSecondaryText.push_back(hasSecondaryText); rowY += rowHeight + ClampNonNegative(metrics.rowGap); } return layout; } UIEditorListViewHitTarget HitTestUIEditorListView( const UIEditorListViewLayout& layout, const ::XCEngine::UI::UIPoint& point) { for (std::size_t visibleIndex = 0u; visibleIndex < layout.rowRects.size(); ++visibleIndex) { if (!IsUIEditorListViewPointInside(layout.rowRects[visibleIndex], point)) { continue; } UIEditorListViewHitTarget target = {}; target.kind = UIEditorListViewHitTargetKind::Row; target.visibleIndex = visibleIndex; target.itemIndex = layout.itemIndices[visibleIndex]; return target; } return {}; } void AppendUIEditorListViewBackground( ::XCEngine::UI::UIDrawList& drawList, const UIEditorListViewLayout& layout, const std::vector& items, const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, const UIEditorListViewState& state, const UIEditorListViewPalette& palette, const UIEditorListViewMetrics& 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 visibleIndex = 0u; visibleIndex < layout.rowRects.size(); ++visibleIndex) { const UIEditorListViewItem& item = items[layout.itemIndices[visibleIndex]]; const bool selected = selectionModel.IsSelected(item.itemId); const bool hovered = state.hoveredItemId == item.itemId; if (!selected && !hovered) { continue; } const ::XCEngine::UI::UIColor rowColor = selected ? (state.focused ? palette.rowSelectedFocusedColor : palette.rowSelectedColor) : palette.rowHoverColor; drawList.AddFilledRect(layout.rowRects[visibleIndex], rowColor, metrics.cornerRounding); } } void AppendUIEditorListViewForeground( ::XCEngine::UI::UIDrawList& drawList, const UIEditorListViewLayout& layout, const std::vector& items, const UIEditorListViewPalette& palette, const UIEditorListViewMetrics&) { drawList.PushClipRect(layout.bounds); for (std::size_t visibleIndex = 0u; visibleIndex < layout.rowRects.size(); ++visibleIndex) { const UIEditorListViewItem& item = items[layout.itemIndices[visibleIndex]]; drawList.PushClipRect(layout.rowRects[visibleIndex]); drawList.AddText( ::XCEngine::UI::UIPoint( layout.primaryTextRects[visibleIndex].x, layout.primaryTextRects[visibleIndex].y), item.primaryText, palette.primaryTextColor, 12.0f); if (layout.hasSecondaryText[visibleIndex]) { drawList.AddText( ::XCEngine::UI::UIPoint( layout.secondaryTextRects[visibleIndex].x, layout.secondaryTextRects[visibleIndex].y), item.secondaryText, palette.secondaryTextColor, 11.0f); } drawList.PopClipRect(); } drawList.PopClipRect(); } void AppendUIEditorListView( ::XCEngine::UI::UIDrawList& drawList, const ::XCEngine::UI::UIRect& bounds, const std::vector& items, const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, const UIEditorListViewState& state, const UIEditorListViewPalette& palette, const UIEditorListViewMetrics& metrics) { const UIEditorListViewLayout layout = BuildUIEditorListViewLayout(bounds, items, metrics); AppendUIEditorListViewBackground( drawList, layout, items, selectionModel, state, palette, metrics); AppendUIEditorListViewForeground(drawList, layout, items, palette, metrics); } } // namespace XCEngine::UI::Editor::Widgets