blob: 3ffd2a3cb929d11ea1e1ad4660fd8fd95b294ee6 [file] [log] [blame] [edit]
#include "rive/constraints/scrolling/scroll_constraint.hpp"
#include "rive/layout/layout_node_provider.hpp"
#include "rive/constraints/scrolling/scroll_virtualizer.hpp"
using namespace rive;
ScrollVirtualizer::~ScrollVirtualizer() { reset(); }
void ScrollVirtualizer::reset() { m_visibleIndexStart = m_visibleIndexEnd = 0; }
bool ScrollVirtualizer::constrain(ScrollConstraint* scroll,
std::vector<LayoutNodeProvider*>& children,
float offset,
VirtualizedDirection direction)
{
bool isHorz = direction == VirtualizedDirection::horizontal;
double contentSize =
isHorz ? scroll->contentWidth() : scroll->contentHeight();
if (contentSize > 0.0f)
{
float normalizedOffset = -offset;
m_direction = direction;
m_viewportSize =
isHorz ? scroll->viewportWidth() : scroll->viewportHeight();
m_infinite = scroll->infinite();
if (offset > 0.0f)
{
if (m_infinite)
{
int offsetMultiplier =
static_cast<int>(std::floor(offset / contentSize)) + 1;
m_offset = -1.0f * (offset - (offsetMultiplier * contentSize));
}
else
{
m_offset = -offset;
}
}
else
{
int offsetMultiplier =
static_cast<int>(std::floor(normalizedOffset / contentSize));
m_offset = offsetMultiplier > 0
? std::fmod(normalizedOffset,
offsetMultiplier * contentSize)
: normalizedOffset;
}
virtualize(scroll, children);
}
return true;
}
void ScrollVirtualizer::virtualize(ScrollConstraint* scroll,
std::vector<LayoutNodeProvider*>& children)
{
int totalItemCount = 0;
for (auto child : children)
{
totalItemCount += child->numLayoutNodes();
}
// All changes in this function are intended to compare the
// ranges of the previous render to the ranges of the upcoming list. This is
// removing the carousel overflow.
// normalizing the two values to the actual indexes of available children
int lastVisibleIndexStart = m_infinite && totalItemCount > 0
? m_visibleIndexStart % totalItemCount
: m_visibleIndexStart;
int lastVisibleIndexEnd = m_infinite && totalItemCount > 0
? m_visibleIndexEnd % totalItemCount
: m_visibleIndexEnd;
m_visibleIndexStart = 0;
m_visibleIndexEnd = totalItemCount - 1;
float runningSize = 0.0f;
float runningOffset = 0.0f;
int runningIndex = 0;
int childIndex = 0;
int currentChildIndex = 0;
bool isHorz = m_direction == VirtualizedDirection::horizontal;
float gap = isHorz ? scroll->gap().x : scroll->gap().y;
for (int i = 0; i < children.size(); i++)
{
auto child = children[i];
auto component = child->transformComponent();
if (component != nullptr)
{
auto virt = VirtualizingComponent::from(component);
if (virt != nullptr)
{
virt->setVisibleIndices(-1, -1);
}
}
}
for (int i = 0; i < children.size(); i++)
{
auto child = children[i];
for (int j = 0; j < child->numLayoutNodes(); j++)
{
auto size = getItemSize(child, j, isHorz);
if (runningSize + size > m_offset)
{
runningOffset = runningSize - m_offset;
m_visibleIndexStart = runningIndex;
if (currentChildIndex == children.size() - 1)
{
childIndex++;
currentChildIndex = 0;
}
else
{
currentChildIndex++;
}
goto findVisibleEnd;
}
runningSize += size;
currentChildIndex = j;
runningIndex++;
if (runningSize + gap > m_offset)
{
if (runningIndex == totalItemCount)
{
runningIndex = 0;
}
if (currentChildIndex == children.size() - 1)
{
childIndex++;
currentChildIndex = 0;
}
else
{
currentChildIndex++;
}
runningSize += gap;
runningOffset = runningSize - m_offset;
m_visibleIndexStart = runningIndex;
goto findVisibleEnd;
}
runningSize += gap;
}
childIndex++;
}
findVisibleEnd:
childIndex = childIndex % children.size();
int i = m_visibleIndexStart;
bool wrapped = false;
int cycleCount = 0;
while (i < totalItemCount && cycleCount < 2)
{
auto child = children[childIndex];
for (int j = currentChildIndex; j < child->numLayoutNodes(); j++)
{
auto size = getItemSize(child, j, isHorz);
if (runningSize + size + gap >= m_offset + m_viewportSize)
{
m_visibleIndexEnd =
m_infinite ? (wrapped ? i + totalItemCount : i) : i;
goto recycle;
}
runningSize += size + gap;
runningIndex++;
if (m_infinite && i == totalItemCount - 1)
{
wrapped = true;
i = -1; // will become 0 after increment
cycleCount++;
}
i++;
}
currentChildIndex = 0;
}
recycle:
std::vector<int> indicesToRecycle;
int actualStart = m_infinite && totalItemCount > 0
? m_visibleIndexStart % totalItemCount
: m_visibleIndexStart;
int actualEnd = m_infinite && totalItemCount > 0
? m_visibleIndexEnd % totalItemCount
: m_visibleIndexEnd;
std::unordered_map<int, bool> usedIndexes = {};
// If start < end it means that the range is not going over
// the end of the list, so we know we can add the full range to the used
// items.
if (actualStart <= actualEnd)
{
for (int i = actualStart; i <= actualEnd; i++)
{
usedIndexes[i] = true;
}
}
// If end > start, we know that the range wraps, so we
// actually need to add two ranges, from [start to totalIItems] and from [0
// to end]
else
{
for (int i = actualStart; i < totalItemCount; i++)
{
usedIndexes[i] = true;
}
for (int i = 0; i <= actualEnd; i++)
{
usedIndexes[i] = true;
}
}
// Similarly, we check the previous ranges and check which
// ones overlap with the new range and which ones can be recycled.
if (lastVisibleIndexStart <= lastVisibleIndexEnd)
{
for (int i = lastVisibleIndexStart; i <= lastVisibleIndexEnd; i++)
{
if (usedIndexes.find(i) == usedIndexes.end())
{
indicesToRecycle.push_back(i);
}
}
}
else
{
for (int i = lastVisibleIndexStart; i < totalItemCount; i++)
{
if (usedIndexes.find(i) == usedIndexes.end())
{
indicesToRecycle.push_back(i);
}
}
for (int i = 0; i <= lastVisibleIndexEnd; i++)
{
if (usedIndexes.find(i) == usedIndexes.end())
{
indicesToRecycle.push_back(i);
}
}
}
recycleItems(indicesToRecycle, children, totalItemCount);
std::vector<Vec2D> visibleIndices(children.size(), Vec2D(-1, -1));
for (int i = m_visibleIndexStart; i <= m_visibleIndexEnd; ++i)
{
int actualIndex = m_infinite ? i % totalItemCount : i;
int runningTotal = 0;
for (int i = 0; i < children.size(); i++)
{
auto child = children[i];
int start = runningTotal;
int end = start + (int)child->numLayoutNodes();
auto component = child->transformComponent();
if (component != nullptr)
{
auto virt = VirtualizingComponent::from(component);
if (virt != nullptr && start < end)
{
if (actualIndex < end && actualIndex >= start)
{
int childIndex = actualIndex - start;
auto& visibleInd = visibleIndices[i];
if (visibleInd.x == -1)
{
visibleInd.x = childIndex;
}
visibleInd.y = childIndex;
auto item = virt->item(childIndex);
if (item == nullptr)
{
virt->addVirtualizable(childIndex);
}
auto size = getItemSize(child, childIndex, isHorz);
auto virtualizable = virt->item(childIndex);
if (virtualizable != nullptr)
{
auto virtualizableComponent =
virtualizable->virtualizableComponent();
if (virtualizableComponent != nullptr &&
virtualizableComponent->is<ArtboardInstance>())
{
auto artboardInstance =
virtualizableComponent
->as<ArtboardInstance>();
auto parentWorld = component->worldTransform();
Mat2D inverse;
if (!parentWorld.invert(&inverse))
{
continue;
}
auto location =
isHorz ? Vec2D(runningOffset,
artboardInstance->layoutY())
: Vec2D(artboardInstance->layoutX(),
runningOffset);
virt->setVirtualizablePosition(childIndex,
location);
}
}
runningOffset += size + gap;
break;
}
}
}
runningTotal = end;
}
}
for (int i = 0; i < children.size(); i++)
{
auto child = children[i];
auto visible = visibleIndices[i];
auto component = child->transformComponent();
if (component != nullptr)
{
auto virt = VirtualizingComponent::from(component);
if (virt != nullptr)
{
virt->setVisibleIndices(visible.x, visible.y);
}
}
}
}
void ScrollVirtualizer::recycleItems(std::vector<int> indices,
std::vector<LayoutNodeProvider*>& children,
int totalItemCount)
{
if (totalItemCount == 0)
{
return;
}
std::sort(indices.begin(), indices.end());
for (auto globalIndex : indices)
{
auto actualIndex =
m_infinite ? globalIndex % totalItemCount : globalIndex;
int runningTotal = 0;
for (int i = 0; i < children.size(); i++)
{
auto child = children[i];
int start = runningTotal;
int end = start + (int)child->numLayoutNodes();
auto component = child->transformComponent();
if (component != nullptr)
{
auto virt = VirtualizingComponent::from(component);
if (virt != nullptr && start < end)
{
if (actualIndex < end && actualIndex >= start)
{
int childIndex = actualIndex - start;
virt->removeVirtualizable(childIndex);
break;
}
}
}
runningTotal = end;
}
}
}
float ScrollVirtualizer::getItemSize(LayoutNodeProvider* child,
int index,
bool isHorizontal)
{
auto component = child->transformComponent();
if (component != nullptr)
{
auto virt = VirtualizingComponent::from(component);
if (virt != nullptr)
{
auto size = virt->itemSize(index);
return isHorizontal ? size.x : size.y;
}
}
auto bounds = child->layoutBounds();
return isHorizontal ? bounds.width() : bounds.height();
}