Something went wrong. Try again.
art software for the rest of us
drawing paint image sdl art
Something went wrong. Try again.
7.3 kB · 211 lines
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212#include "VirtualizingStackLayout.h"#include <algorithm>#include "../../GPU.h"#include "../Engine/Scale.h"
namespace UI {namespace {// anything aboive this means anything goesconstexpr float UnboundedConstraint = 1.0e7f;
bool isUnbounded(float value) { return !(value >= 0.0f && value < UnboundedConstraint);}}
VirtualizingStackLayout::VirtualizingStackLayout( std::size_t count, float itemSpacing, float itemOverscan, std::function<Size(std::size_t)> itemSizeProvider, std::function<std::unique_ptr<Widget>(std::size_t)> factory, VirtualizingStackSizing sizing) : itemCount(count), spacing(itemSpacing), overscan(itemOverscan), itemSizeProvider(std::move(itemSizeProvider)), itemFactory(std::move(factory)), sizing(sizing) {}
Size VirtualizingStackLayout::ViewportExtent() const { Size extent(0,0); if (parent) { const Rect parentBounds = parent->GetLocalBounds(); const Thickness parentPadding = parent->GetPadding(); extent.width = std::max(0.0f, parentBounds.Width - parentPadding.HorizontalTotal()); extent.height = std::max(0.0f, parentBounds.Height - parentPadding.VerticalTotal()); if (extent.width > 0.0f && extent.height > 0.0f) { return extent; } } // fallback instead int width = 0; int height = 0; SDL_GetWindowSizeInPixels(GPU::GetWindow(), &width, &height); // the window reports physical pixels, the layout speaks logical units const Size viewport(PixelsToUnits(static_cast<float>(width)), PixelsToUnits(static_cast<float>(height))); if (!(extent.width > 0.0f)) { extent.width = viewport.width; } if (!(extent.height > 0.0f)) { extent.height = viewport.height; } return extent;}
Size VirtualizingStackLayout::ResolveAvailableSpace(Size availableSpace) const { Size space = availableSpace; // use sane value instead of unbounded for scrolling const Size viewport = ViewportExtent(); if (isUnbounded(space.width)) { space.width = viewport.width; } if (isUnbounded(space.height)) { space.height = viewport.height; } // ignoring parent padding if (parent) { space.width -= parent->GetPadding().HorizontalTotal(); space.height -= parent->GetPadding().VerticalTotal(); } space.width = std::max(0.0f, space.width - margin.HorizontalTotal() - padding.HorizontalTotal()); space.height = std::max(0.0f, space.height - margin.VerticalTotal() - padding.VerticalTotal()); return space;}
void VirtualizingStackLayout::RebuildMetrics(Size availableSpace) { if (availableSpace == metricsSpace && itemSizes.size() == itemCount) { return; } metricsSpace = availableSpace; hasVisibleRange = false; // offsets moved → stale items
itemSizes.clear(); itemOffsets.clear(); itemSizes.reserve(itemCount); itemOffsets.reserve(itemCount);
float currentOffset = padding.Left; for (std::size_t index = 0; index < itemCount; index++) { itemOffsets.push_back(currentOffset); Size itemSize = itemSizeProvider ? itemSizeProvider(index) : availableSpace; itemSizes.push_back({std::max(0.0f, itemSize.width), std::max(0.0f, itemSize.height)}); currentOffset += itemSizes.back().width; if (index + 1 < itemCount) { currentOffset += spacing; } }}
std::size_t VirtualizingStackLayout::FindFirstVisibleIndex(float start) const { auto iterator = std::lower_bound(itemOffsets.begin(), itemOffsets.end(), start); if (iterator != itemOffsets.begin()) { --iterator; } return static_cast<std::size_t>(std::distance(itemOffsets.begin(), iterator));}
std::size_t VirtualizingStackLayout::FindLastVisibleIndex(float end) const { auto iterator = std::upper_bound(itemOffsets.begin(), itemOffsets.end(), end); if (iterator == itemOffsets.begin()) { return 0; } return static_cast<std::size_t>(std::distance(itemOffsets.begin(), iterator) - 1);}
bool VirtualizingStackLayout::UpdateVisibleChildren() { if (!itemCount || !itemFactory || !parent) { children.clear(); hasVisibleRange = false; return true; }
float viewportWidth = std::max(0.0f, parent->GetLocalBounds().Width - parent->GetPadding().HorizontalTotal()); float visibleStartOffset = std::max(0.0f, scrollOffset - overscan); float visibleEndOffset = scrollOffset + viewportWidth + overscan; std::size_t first = FindFirstVisibleIndex(visibleStartOffset); std::size_t last = FindLastVisibleIndex(visibleEndOffset); last = std::min(last, itemCount - 1);
if (hasVisibleRange && first == visibleStart && last == visibleEnd) { return false; }
children.clear(); visibleStart = first; visibleEnd = last; hasVisibleRange = true;
for (std::size_t index = visibleStart; index <= visibleEnd; index++) { std::unique_ptr<Widget> child = itemFactory(index); if (!child) { continue; } AddChild(std::move(child)); }
return true;}
void VirtualizingStackLayout::Measure(Size availableSpace) { RebuildMetrics(ResolveAvailableSpace(availableSpace));
float totalWidth = padding.HorizontalTotal(); if (itemCount) { totalWidth += itemOffsets.back() - padding.Left + itemSizes.back().width; totalWidth += padding.Right; }
desiredSize.width = std::clamp(totalWidth + margin.HorizontalTotal(), minSize.width, maxSize.width); float contentHeight = 0.0f; for (std::size_t index = 0; index < itemCount; index++) { contentHeight = std::max(contentHeight, itemSizes[index].height); } desiredSize.height = std::clamp(contentHeight + padding.VerticalTotal() + margin.VerticalTotal(), minSize.height, maxSize.height);}
void VirtualizingStackLayout::Arrange(Rect finalBounds) { ArrangeCore(finalBounds); UpdateVisibleChildren();
if (!hasVisibleRange) { ClearDirty(DirtyFlags::Layout | DirtyFlags::Hierarchy); return; }
const float contentHeight = std::max(0.0f, localBounds.Height - padding.VerticalTotal()); for (std::size_t childIndex = 0; childIndex < children.size(); childIndex++) { std::size_t itemIndex = visibleStart + childIndex; if (itemIndex >= itemCount) { break; }
const float childHeight = sizing == VirtualizingStackSizing::Fill ? std::min(itemSizes[itemIndex].height, contentHeight) : contentHeight;
Widget* child = children[childIndex].get(); child->Measure(Size(itemSizes[itemIndex].width, childHeight)); child->Arrange(Rect( itemOffsets[itemIndex], padding.Top, itemSizes[itemIndex].width, childHeight )); }
ClearDirty(DirtyFlags::Layout | DirtyFlags::Hierarchy);}
void VirtualizingStackLayout::SetScrollOffset(Point offset) { float newOffset = std::max(0.0f, offset.x); if (newOffset == scrollOffset) { return; }
scrollOffset = newOffset; bool rangeChanged = UpdateVisibleChildren(); if (rangeChanged && localBounds.Width > 0.0f) { Arrange(localBounds); } MarkDirty(DirtyFlags::Render);}
}