blob: 640564e3eac2f9672ce86280ebb585940216df6b [file] [log] [blame] [edit]
#include "rive/constraints/scrolling/scroll_constraint.hpp"
#include "rive/constraints/scrolling/scroll_constraint_proxy.hpp"
#include "rive/constraints/scrolling/scroll_virtualizer.hpp"
#include "rive/constraints/transform_constraint.hpp"
#include "rive/core_context.hpp"
#include "rive/layout/layout_node_provider.hpp"
#include "rive/transform_component.hpp"
#include "rive/virtualizing_component.hpp"
#include "rive/math/mat2d.hpp"
using namespace rive;
ScrollConstraint::~ScrollConstraint()
{
if (m_virtualizer != nullptr)
{
delete m_virtualizer;
m_virtualizer = nullptr;
}
m_layoutChildren.clear();
delete m_physics;
}
float ScrollConstraint::contentWidth()
{
if (virtualize() && !mainAxisIsColumn())
{
auto contentSize = 0.0f;
for (auto child : scrollChildren())
{
if (child == nullptr)
{
continue;
}
contentSize += child->layoutBounds().width();
}
auto lenOffset = infinite() ? 0 : 1;
return contentSize + gap().x * (scrollChildren().size() - lenOffset);
}
return content()->layoutWidth();
}
float ScrollConstraint::contentHeight()
{
if (virtualize() && mainAxisIsColumn())
{
auto contentSize = 0.0f;
for (auto child : scrollChildren())
{
if (child == nullptr)
{
continue;
}
contentSize += child->layoutBounds().height();
}
auto lenOffset = infinite() ? 0 : 1;
return contentSize + gap().y * (scrollChildren().size() - lenOffset);
}
return content()->layoutHeight();
}
float ScrollConstraint::viewportWidth()
{
return direction() == DraggableConstraintDirection::vertical
? viewport()->layoutWidth()
: std::max(0.0f,
viewport()->layoutWidth() - content()->layoutX());
}
float ScrollConstraint::viewportHeight()
{
return direction() == DraggableConstraintDirection::horizontal
? viewport()->layoutHeight()
: std::max(0.0f,
viewport()->layoutHeight() - content()->layoutY());
}
float ScrollConstraint::visibleWidthRatio()
{
if (contentWidth() == 0)
{
return 1;
}
return std::min(1.0f, viewportWidth() / contentWidth());
}
float ScrollConstraint::visibleHeightRatio()
{
if (contentHeight() == 0)
{
return 1;
}
return std::min(1.0f, viewportHeight() / contentHeight());
}
float ScrollConstraint::minOffsetX()
{
if (infinite() && !mainAxisIsColumn())
{
return std::numeric_limits<float>::infinity();
}
return 0;
}
float ScrollConstraint::minOffsetY()
{
if (infinite() && mainAxisIsColumn())
{
return std::numeric_limits<float>::infinity();
}
return 0;
}
float ScrollConstraint::maxOffsetX()
{
if (infinite() && !mainAxisIsColumn())
{
return -std::numeric_limits<float>::infinity();
}
return std::min(0.0f,
viewportWidth() - contentWidth() -
viewport()->paddingRight());
}
float ScrollConstraint::maxOffsetY()
{
if (infinite() && mainAxisIsColumn())
{
return -std::numeric_limits<float>::infinity();
}
return std::min(0.0f,
viewportHeight() - contentHeight() -
viewport()->paddingBottom());
}
float ScrollConstraint::clampedOffsetX()
{
if (infinite())
{
return offsetX();
}
if (maxOffsetX() > 0)
{
return 0;
}
if (m_physics != nullptr && m_physics->enabled())
{
return m_physics
->clamp(Vec2D(maxOffsetX(), maxOffsetY()),
Vec2D(minOffsetX(), minOffsetY()),
Vec2D(m_offsetX, m_offsetY))
.x;
}
return math::clamp(m_offsetX, maxOffsetX(), 0);
}
float ScrollConstraint::clampedOffsetY()
{
if (infinite())
{
return offsetY();
}
if (maxOffsetY() > 0)
{
return 0;
}
if (m_physics != nullptr && m_physics->enabled())
{
return m_physics
->clamp(Vec2D(maxOffsetX(), maxOffsetY()),
Vec2D(minOffsetX(), minOffsetY()),
Vec2D(m_offsetX, m_offsetY))
.y;
}
return math::clamp(m_offsetY, maxOffsetY(), 0);
}
void ScrollConstraint::offsetX(float value)
{
if (m_offsetX == value)
{
return;
}
m_offsetX = value;
content()->markWorldTransformDirty();
}
void ScrollConstraint::offsetY(float value)
{
if (m_offsetY == value)
{
return;
}
m_offsetY = value;
content()->markWorldTransformDirty();
}
bool ScrollConstraint::mainAxisIsColumn()
{
return content() != nullptr && content()->mainAxisIsColumn();
}
void ScrollConstraint::constrain(TransformComponent* component)
{
m_scrollTransform =
Mat2D::fromTranslate(constrainsHorizontal() ? clampedOffsetX() : 0,
constrainsVertical() ? clampedOffsetY() : 0);
m_childConstraintAppliedCount = 0;
}
void ScrollConstraint::constrainChild(LayoutNodeProvider* child)
{
auto component = child->transformComponent();
if (component == nullptr)
{
return;
}
auto targetTransform =
Mat2D::multiply(component->worldTransform(), m_scrollTransform);
TransformConstraint::constrainWorld(component,
component->worldTransform(),
m_componentsA,
targetTransform,
m_componentsB,
strength());
m_childConstraintAppliedCount += 1;
constrainVirtualized();
}
void ScrollConstraint::constrainVirtualized(bool force)
{
if (virtualize() && m_virtualizer != nullptr)
{
auto children = scrollChildren();
if (m_childConstraintAppliedCount < children.size() && !force)
{
return;
}
auto isColumn = mainAxisIsColumn();
auto direction = isColumn ? VirtualizedDirection::vertical
: VirtualizedDirection::horizontal;
auto offset = isColumn ? clampedOffsetY() : clampedOffsetX();
m_virtualizer->constrain(this, children, offset, direction);
}
}
void ScrollConstraint::addLayoutChild(LayoutNodeProvider* child)
{
m_layoutChildren.push_back(child);
}
void ScrollConstraint::dragView(Vec2D delta, float timeStamp)
{
if (m_physics != nullptr)
{
m_physics->accumulate(delta, timeStamp);
}
scrollOffsetX(offsetX() + delta.x);
scrollOffsetY(offsetY() + delta.y);
}
void ScrollConstraint::runPhysics()
{
m_isDragging = false;
std::vector<Vec2D> snappingPoints;
if (snap())
{
for (auto child : content()->children())
{
auto c = LayoutNodeProvider::from(child);
if (c != nullptr)
{
size_t count = c->numLayoutNodes();
for (int j = 0; j < count; j++)
{
auto bounds = c->layoutBoundsForNode(j);
if (isBoundsCollapsed(bounds))
{
continue;
}
snappingPoints.push_back(
Vec2D(bounds.left(), bounds.top()));
}
}
}
}
if (m_physics != nullptr)
{
m_physics->run(Vec2D(maxOffsetX(), maxOffsetY()),
Vec2D(minOffsetX(), minOffsetY()),
Vec2D(offsetX(), offsetY()),
snap() ? snappingPoints : std::vector<Vec2D>(),
mainAxisIsColumn() ? contentHeight() : contentWidth());
}
}
bool ScrollConstraint::advanceComponent(float elapsedSeconds,
AdvanceFlags flags)
{
if ((flags & AdvanceFlags::AdvanceNested) != AdvanceFlags::AdvanceNested)
{
// offsetX(0);
// offsetY(0);
return false;
}
if (m_physics == nullptr)
{
return false;
}
if (m_physics->isRunning())
{
auto offset = m_physics->advance(elapsedSeconds);
scrollOffsetX(offset.x);
scrollOffsetY(offset.y);
}
return m_physics->enabled();
}
std::vector<DraggableProxy*> ScrollConstraint::draggables()
{
std::vector<DraggableProxy*> items;
items.push_back(new ViewportDraggableProxy(this, viewport()->proxy()));
return items;
}
void ScrollConstraint::buildDependencies()
{
Super::buildDependencies();
for (auto child : content()->children())
{
auto layout = LayoutNodeProvider::from(child);
if (layout != nullptr)
{
addDependent(child);
layout->addLayoutConstraint(static_cast<LayoutConstraint*>(this));
}
}
}
Core* ScrollConstraint::clone() const
{
auto cloned = ScrollConstraintBase::clone();
if (physics() != nullptr)
{
auto constraint = cloned->as<ScrollConstraint>();
auto clonedPhysics = physics()->clone()->as<ScrollPhysics>();
constraint->physics(clonedPhysics);
}
return cloned;
}
StatusCode ScrollConstraint::import(ImportStack& importStack)
{
auto backboardImporter =
importStack.latest<BackboardImporter>(BackboardBase::typeKey);
if (backboardImporter != nullptr)
{
std::vector<ScrollPhysics*> physicsObjects =
backboardImporter->physics();
if (physicsId() != -1 && physicsId() < physicsObjects.size())
{
auto phys = physicsObjects[physicsId()];
if (phys != nullptr)
{
auto cloned = phys->clone()->as<ScrollPhysics>();
physics(cloned);
}
}
}
else
{
return StatusCode::MissingObject;
}
return Super::import(importStack);
}
StatusCode ScrollConstraint::onAddedDirty(CoreContext* context)
{
StatusCode result = Super::onAddedDirty(context);
if (virtualize())
{
m_virtualizer = new ScrollVirtualizer();
}
offsetX(scrollOffsetX());
offsetY(scrollOffsetY());
return result;
}
void ScrollConstraint::initPhysics()
{
m_isDragging = true;
if (m_physics != nullptr)
{
m_physics->prepare(direction());
}
}
void ScrollConstraint::stopPhysics()
{
if (m_physics != nullptr)
{
m_physics->reset();
}
}
float ScrollConstraint::maxOffsetXForPercent()
{
return infinite() ? contentWidth() : maxOffsetX();
}
float ScrollConstraint::maxOffsetYForPercent()
{
return infinite() ? contentHeight() : maxOffsetY();
}
float ScrollConstraint::scrollPercentX()
{
return maxOffsetX() != 0 ? scrollOffsetX() / maxOffsetXForPercent() : 0;
}
float ScrollConstraint::scrollPercentY()
{
return maxOffsetY() != 0 ? scrollOffsetY() / maxOffsetYForPercent() : 0;
}
float ScrollConstraint::scrollIndex()
{
return indexAtPosition(Vec2D(scrollOffsetX(), scrollOffsetY()));
}
void ScrollConstraint::setScrollPercentX(float value)
{
if (m_isDragging)
{
return;
}
stopPhysics();
float to = value * maxOffsetXForPercent();
scrollOffsetX(to);
}
void ScrollConstraint::setScrollPercentY(float value)
{
if (m_isDragging)
{
return;
}
stopPhysics();
float to = value * maxOffsetYForPercent();
scrollOffsetY(to);
}
void ScrollConstraint::setScrollIndex(float value)
{
if (m_isDragging)
{
return;
}
stopPhysics();
Vec2D to = positionAtIndex(value);
if (constrainsHorizontal())
{
scrollOffsetX(to.x);
}
else if (constrainsVertical())
{
scrollOffsetY(to.y);
}
}
Vec2D ScrollConstraint::positionAtIndex(float index)
{
auto count = scrollItemCount();
if (content() == nullptr || count == 0)
{
return Vec2D();
}
uint32_t i = 0;
Vec2D contentGap = gap();
float normalizedIndex = infinite() ? std::fmod(index, (float)count) : index;
float floorIndex = std::floor(normalizedIndex);
LayoutNodeProvider* lastChild = nullptr;
for (auto child : scrollChildren())
{
if (child == nullptr)
{
continue;
}
size_t count = child->numLayoutNodes();
if ((uint32_t)floorIndex < i + count)
{
float mod = normalizedIndex - floorIndex;
auto bounds = child->layoutBoundsForNode(floorIndex - i);
return Vec2D(-bounds.left() - (bounds.width() + contentGap.x) * mod,
-bounds.top() -
(bounds.height() + contentGap.y) * mod);
}
lastChild = child;
i += count;
}
if (lastChild == nullptr)
{
return Vec2D();
}
auto bounds =
lastChild->layoutBoundsForNode((int)lastChild->numLayoutNodes() - 1);
return Vec2D(-bounds.left(), -bounds.top());
}
float ScrollConstraint::indexAtPosition(Vec2D pos)
{
if (content() == nullptr || content()->children().size() == 0)
{
return 0;
}
float i = 0.0f;
Vec2D contentGap = gap();
if (constrainsHorizontal())
{
for (auto child : scrollChildren())
{
if (child == nullptr)
{
continue;
}
size_t count = child->numLayoutNodes();
for (int j = 0; j < count; j++)
{
auto bounds = child->layoutBoundsForNode(j);
if (pos.x > -bounds.left() - (bounds.width() + contentGap.x))
{
return (i + j) + (-pos.x - bounds.left()) /
(bounds.width() + contentGap.x);
}
}
i += count;
}
return i;
}
else if (constrainsVertical())
{
for (auto child : scrollChildren())
{
if (child == nullptr)
{
continue;
}
size_t count = child->numLayoutNodes();
for (int j = 0; j < count; j++)
{
auto bounds = child->layoutBoundsForNode(j);
if (pos.y > -bounds.top() - (bounds.height() + contentGap.y))
{
return (i + j) + (-pos.y - bounds.top()) /
(bounds.height() + contentGap.y);
}
}
i += count;
}
return i;
}
return 0;
}
bool ScrollConstraint::isBoundsCollapsed(AABB bounds)
{
return (constrainsHorizontal() && bounds.width() <= 0) ||
(constrainsVertical() && bounds.height() <= 0);
}
size_t ScrollConstraint::scrollItemCount()
{
size_t count = 0;
for (auto child : scrollChildren())
{
if (child == nullptr)
{
continue;
}
count += child->numLayoutNodes();
}
return count;
}
Vec2D ScrollConstraint::gap()
{
if (content() == nullptr)
{
return Vec2D();
}
return Vec2D(content()->gapHorizontal(), content()->gapVertical());
}