List Virtualization & Scroll Carousel (#9965) d973e8c253

Add support for List virtualization as well as a parameter to the ScrollConstraint to support infinite scrolling (Carousel).

There are important caveats with Virtualization enabled:

The List instances enough artboards to fit into their parent's bounds and when not rendered, they are pooled and reused as necessary. Lists can be non-uniform meaning they can consist of more than 1 Artboard (ViewModel) type
Does not currently work when its parent is set to wrap because more complex computations may result when wrapping
In order to use Carousel, virtualization must also be enabled
Infinite scroll only works in a single direction, not both at the same time

Co-authored-by: Philip Chung <philterdesign@gmail.com>
This commit is contained in:
philter
2025-07-08 21:48:40 +00:00
parent 6f3badca69
commit fa403da393
33 changed files with 2068 additions and 259 deletions

View File

@@ -1 +1 @@
d2997eeef48f74a50e6eda2d5691e295ad328f51
d973e8c253120fa9e9f9591a2161d6bbf5e8b4e8

View File

@@ -89,6 +89,26 @@
"string": "physicsid"
},
"description": "Identifier used to track the Scroll physics."
},
"virtualize": {
"type": "bool",
"initialValue": "false",
"initialValueRuntime": "false",
"key": {
"int": 850,
"string": "virtualize"
},
"description": "Whether the scroll constraint should virtualize its items."
},
"infinite": {
"type": "bool",
"initialValue": "false",
"initialValueRuntime": "false",
"key": {
"int": 851,
"string": "infinite"
},
"description": "Whether the scroll should wrap when it reaches the extents."
}
}
}

View File

@@ -96,6 +96,8 @@ public:
// Returns true when the StateMachineInstance has more data to process.
bool needsAdvance() const;
void resetState();
// Returns a pointer to the instance's stateMachine
const StateMachine* stateMachine() const { return m_machine; }
@@ -209,6 +211,7 @@ private:
m_bindableDataBindsToSource;
uint8_t m_drawOrderChangeCounter = 0;
void unbind();
void removeEventListeners();
#ifdef WITH_RIVE_TOOLS
public:

View File

@@ -34,7 +34,6 @@ public:
const EntryState* entryState() const { return m_Entry; }
const ExitState* exitState() const { return m_Exit; }
#ifdef TESTING
size_t stateCount() const { return m_States.size(); }
LayerState* state(size_t index) const
{
@@ -44,7 +43,6 @@ public:
}
return nullptr;
}
#endif
};
} // namespace rive

View File

@@ -19,6 +19,7 @@
#include "rive/audio/audio_engine.hpp"
#include "rive/math/raw_path.hpp"
#include "rive/typed_children.hpp"
#include "rive/virtualizing_component.hpp"
#include <queue>
#include <unordered_set>
@@ -51,7 +52,7 @@ class SMITrigger;
typedef void (*ArtboardCallback)(void*);
#endif
class Artboard : public ArtboardBase, public CoreContext
class Artboard : public ArtboardBase, public CoreContext, public Virtualizable
{
friend class File;
friend class ArtboardImporter;
@@ -115,6 +116,7 @@ public:
{
return worldTransform();
}
Component* virtualizableComponent() override { return this; }
private:
#ifdef TESTING
@@ -280,6 +282,20 @@ public:
return nullptr;
}
int objectIndex(Core* component) const
{
int count = 0;
for (auto object : m_Objects)
{
if (object == component)
{
return count;
}
count++;
}
return -1;
}
template <typename T = Component> std::vector<T*> find()
{
std::vector<T*> results;

View File

@@ -4,21 +4,25 @@
#include "rive/advancing_component.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/artboard.hpp"
#include "rive/property_recorder.hpp"
#include "rive/artboard_host.hpp"
#include "rive/data_bind/data_bind_list_item_consumer.hpp"
#include "rive/layout/layout_node_provider.hpp"
#include "rive/viewmodel/viewmodel_instance_list_item.hpp"
#include "rive/virtualizing_component.hpp"
#include <stdio.h>
#include <unordered_map>
namespace rive
{
class File;
class ScrollConstraint;
class ArtboardComponentList : public ArtboardComponentListBase,
public ArtboardHost,
public AdvancingComponent,
public LayoutNodeProvider,
public DataBindListItemConsumer
public DataBindListItemConsumer,
public VirtualizingComponent
{
private:
std::vector<rcp<ViewModelInstanceListItem>> m_listItems;
@@ -31,30 +35,9 @@ public:
void* layoutNode(int index) override;
#endif
size_t artboardCount() override { return m_listItems.size(); }
rcp<ViewModelInstanceListItem> listItem(int index)
{
if (index < m_listItems.size())
{
return m_listItems[index];
}
return nullptr;
}
ArtboardInstance* artboardInstance(int index = 0) override
{
if (index < m_listItems.size())
{
return m_artboardInstancesMap[listItem(index)].get();
}
return nullptr;
}
StateMachineInstance* stateMachineInstance(int index = 0)
{
if (index < m_listItems.size())
{
return m_stateMachinesMap[listItem(index)].get();
}
return nullptr;
}
rcp<ViewModelInstanceListItem> listItem(int index);
ArtboardInstance* artboardInstance(int index = 0) override;
StateMachineInstance* stateMachineInstance(int index = 0);
bool worldToLocal(Vec2D world, Vec2D* local, int index);
bool advanceComponent(float elapsedSeconds,
AdvanceFlags flags = AdvanceFlags::Animate |
@@ -91,16 +74,42 @@ public:
File* file() const override;
Core* clone() const override;
// API used by the virtualizer
Artboard* findArtboard(
const rcp<ViewModelInstanceListItem>& listItem) const;
void addVirtualizable(int index) override;
void removeVirtualizable(int index) override;
void createArtboardAt(int index);
void addArtboardAt(std::unique_ptr<ArtboardInstance> artboard, int index);
void removeArtboardAt(int index);
void removeArtboard(rcp<ViewModelInstanceListItem> item);
bool virtualizationEnabled() override;
ScrollConstraint* scrollConstraint();
int itemCount() override { return (int)m_listItems.size(); }
Virtualizable* item(int index) override { return artboardInstance(index); }
void setItemSize(Vec2D size, int index) override;
Vec2D size() override;
Vec2D itemSize(int index) override;
float gap();
void syncLayoutChildren();
private:
void disposeListItem(const rcp<ViewModelInstanceListItem>& listItem);
std::unique_ptr<ArtboardInstance> createArtboard(
Component* target,
rcp<ViewModelInstanceListItem> listItem) const;
Artboard* findArtboard(
const rcp<ViewModelInstanceListItem>& listItem) const;
void bindArtboard(ArtboardInstance* artboard,
rcp<ViewModelInstanceListItem> listItem);
std::unique_ptr<StateMachineInstance> createStateMachineInstance(
Component* target,
ArtboardInstance* artboard);
void linkStateMachineToArtboard(StateMachineInstance* stateMachineInstance,
ArtboardInstance* artboard);
void computeLayoutBounds();
void createArtboardRecorders(Artboard*);
void applyRecorders(Artboard* artboard, Artboard* sourceArtboard);
void applyRecorders(StateMachineInstance* stateMachineInstance,
Artboard* sourceArtboard);
mutable std::unordered_map<uint32_t, Artboard*> m_artboardsMap;
std::unordered_map<rcp<ViewModelInstanceListItem>,
std::unique_ptr<ArtboardInstance>>
@@ -108,7 +117,18 @@ private:
std::unordered_map<rcp<ViewModelInstanceListItem>,
std::unique_ptr<StateMachineInstance>>
m_stateMachinesMap;
std::unordered_map<Artboard*,
std::vector<std::unique_ptr<ArtboardInstance>>>
m_resourcePool;
std::unordered_map<Artboard*,
std::vector<std::unique_ptr<StateMachineInstance>>>
m_stateMachinesPool;
std::unordered_map<Artboard*, std::unique_ptr<PropertyRecorder>>
m_propertyRecordersMap;
File* m_file = nullptr;
std::vector<Vec2D> m_artboardSizes;
Vec2D m_layoutSize;
};
} // namespace rive

View File

@@ -9,6 +9,8 @@ class LayoutConstraint
{
public:
virtual void constrainChild(LayoutNodeProvider* child) {}
virtual void addLayoutChild(LayoutNodeProvider* child) {}
virtual Constraint* constraint() = 0;
};
} // namespace rive

View File

@@ -11,10 +11,11 @@ private:
public:
Vec2D advance(float elapsedSeconds) override;
void run(Vec2D range,
void run(Vec2D rangeMin,
Vec2D rangeMax,
Vec2D value,
std::vector<Vec2D> snappingPoints) override;
Vec2D clamp(Vec2D range, Vec2D value) override;
Vec2D clamp(Vec2D rangeMin, Vec2D rangeMax, Vec2D value) override;
};
} // namespace rive

View File

@@ -14,7 +14,8 @@ private:
float m_target = 0;
float m_current = 0;
float m_speed = 0;
float m_runRange = 0;
float m_runRangeMin = 0;
float m_runRangeMax = 0;
bool m_isRunning = false;
public:
@@ -28,9 +29,10 @@ public:
}
bool isRunning() { return m_isRunning; }
float clamp(float range, float value);
float clamp(float rangeMin, float rangeMax, float value);
void run(float acceleration,
float range,
float rangeMin,
float rangeMax,
float value,
std::vector<float> snappingPoints);
float advance(float elapsedSeconds);
@@ -54,8 +56,9 @@ public:
(m_physicsY != nullptr && m_physicsY->isRunning());
}
Vec2D advance(float elapsedSeconds) override;
Vec2D clamp(Vec2D range, Vec2D value) override;
void run(Vec2D range,
Vec2D clamp(Vec2D rangeMin, Vec2D rangeMax, Vec2D value) override;
void run(Vec2D rangeMin,
Vec2D rangeMax,
Vec2D value,
std::vector<Vec2D> snappingPoints) override;
void prepare(DraggableConstraintDirection dir) override;

View File

@@ -12,6 +12,7 @@
namespace rive
{
class LayoutNodeProvider;
class ScrollVirtualizer;
class ScrollConstraint : public ScrollConstraintBase,
public AdvancingComponent,
@@ -25,9 +26,14 @@ private:
ScrollPhysics* m_physics;
Mat2D m_scrollTransform;
bool m_isDragging = false;
ScrollVirtualizer* m_virtualizer = nullptr;
std::vector<LayoutNodeProvider*> m_layoutChildren;
int m_childConstraintAppliedCount = 0;
Vec2D positionAtIndex(float index);
float indexAtPosition(Vec2D pos);
float maxOffsetXForPercent();
float maxOffsetYForPercent();
public:
~ScrollConstraint();
@@ -40,6 +46,9 @@ public:
void dragView(Vec2D delta, float timeStamp);
void runPhysics();
void constrainChild(LayoutNodeProvider* child) override;
void addLayoutChild(LayoutNodeProvider* child) override;
Constraint* constraint() override { return this; }
void constrainVirtualized(bool force = false);
bool advanceComponent(float elapsedSeconds,
AdvanceFlags flags = AdvanceFlags::Animate |
AdvanceFlags::NewFrame) override;
@@ -59,102 +68,23 @@ public:
{
return parent()->parent()->as<LayoutComponent>();
}
float contentWidth() { return content()->layoutWidth(); }
float contentHeight() { return content()->layoutHeight(); }
float viewportWidth()
{
return direction() == DraggableConstraintDirection::vertical
? viewport()->layoutWidth()
: std::max(0.0f,
viewport()->layoutWidth() - content()->layoutX());
}
float viewportHeight()
{
return direction() == DraggableConstraintDirection::horizontal
? viewport()->layoutHeight()
: std::max(0.0f,
viewport()->layoutHeight() -
content()->layoutY());
}
float visibleWidthRatio()
{
if (contentWidth() == 0)
{
return 1;
}
return std::min(1.0f, viewportWidth() / contentWidth());
}
float visibleHeightRatio()
{
if (contentHeight() == 0)
{
return 1;
}
return std::min(1.0f, viewportHeight() / contentHeight());
}
float maxOffsetX()
{
return std::min(0.0f,
viewportWidth() - contentWidth() -
viewport()->paddingRight());
}
float maxOffsetY()
{
return std::min(0.0f,
viewportHeight() - contentHeight() -
viewport()->paddingBottom());
}
float clampedOffsetX()
{
if (maxOffsetX() > 0)
{
return 0;
}
if (m_physics != nullptr && m_physics->enabled())
{
return m_physics
->clamp(Vec2D(maxOffsetX(), maxOffsetY()),
Vec2D(m_offsetX, m_offsetY))
.x;
}
return math::clamp(m_offsetX, maxOffsetX(), 0);
}
float clampedOffsetY()
{
if (maxOffsetY() > 0)
{
return 0;
}
if (m_physics != nullptr && m_physics->enabled())
{
return m_physics
->clamp(Vec2D(maxOffsetX(), maxOffsetY()),
Vec2D(m_offsetX, m_offsetY))
.y;
}
return math::clamp(m_offsetY, maxOffsetY(), 0);
}
float contentWidth();
float contentHeight();
float viewportWidth();
float viewportHeight();
float visibleWidthRatio();
float visibleHeightRatio();
float minOffsetX();
float minOffsetY();
float maxOffsetX();
float maxOffsetY();
float clampedOffsetX();
float clampedOffsetY();
float offsetX() { return m_offsetX; }
float offsetY() { return m_offsetY; }
void offsetX(float value)
{
if (m_offsetX == value)
{
return;
}
m_offsetX = value;
content()->markWorldTransformDirty();
}
void offsetY(float value)
{
if (m_offsetY == value)
{
return;
}
m_offsetY = value;
content()->markWorldTransformDirty();
}
void offsetX(float value);
void offsetY(float value);
void scrollOffsetXChanged() override { offsetX(scrollOffsetX()); }
void scrollOffsetYChanged() override { offsetY(scrollOffsetY()); }
@@ -165,8 +95,14 @@ public:
void setScrollPercentX(float value) override;
void setScrollPercentY(float value) override;
void setScrollIndex(float value) override;
size_t scrollItemCount();
std::vector<LayoutNodeProvider*>& scrollChildren()
{
return m_layoutChildren;
}
Vec2D gap();
bool mainAxisIsColumn();
};
} // namespace rive

View File

@@ -44,10 +44,14 @@ public:
reset();
m_direction = dir;
}
virtual Vec2D clamp(Vec2D range, Vec2D value) { return Vec2D(); };
virtual Vec2D clamp(Vec2D rangeMin, Vec2D rangeMax, Vec2D value)
{
return Vec2D();
};
virtual Vec2D advance(float elapsedSeconds) { return Vec2D(); };
virtual void accumulate(Vec2D delta, float timeStamp);
virtual void run(Vec2D range,
virtual void run(Vec2D rangeMin,
Vec2D rangeMax,
Vec2D value,
std::vector<Vec2D> snappingPoints)
{

View File

@@ -0,0 +1,41 @@
#ifndef _RIVE_SCROLL_VIRTUALIZER_HPP_
#define _RIVE_SCROLL_VIRTUALIZER_HPP_
#include "rive/artboard.hpp"
#include "rive/math/vec2d.hpp"
#include "rive/world_transform_component.hpp"
#include "rive/virtualizing_component.hpp"
#include <stdio.h>
#include <unordered_map>
namespace rive
{
class LayoutNodeProvider;
class ScrollConstraint;
class ScrollVirtualizer
{
private:
int m_visibleIndexStart = 0;
int m_visibleIndexEnd = 0;
float m_offset = 0;
bool m_infinite = false;
float m_viewportSize = 0;
VirtualizedDirection m_direction = VirtualizedDirection::horizontal;
void recycleItems(std::vector<int> indices,
std::vector<LayoutNodeProvider*>& children,
int totalItemCount);
float getItemSize(LayoutNodeProvider* child, int index, bool isHorizontal);
public:
~ScrollVirtualizer();
void reset();
bool constrain(ScrollConstraint* scroll,
std::vector<LayoutNodeProvider*>& children,
float offset,
VirtualizedDirection direction);
void virtualize(ScrollConstraint* scroll,
std::vector<LayoutNodeProvider*>& children);
};
} // namespace rive
#endif

View File

@@ -30,6 +30,7 @@ public:
float readFloat32();
uint8_t readByte();
uint32_t readUint32();
std::string readString();
void complete(uint8_t* bytes, size_t length);
void reset(uint8_t* bytes);
};

View File

@@ -7,7 +7,7 @@ class BinaryReader;
class CoreBoolType
{
public:
static const int id = 0;
static const int id = 4;
static bool deserialize(BinaryReader& reader);
};
} // namespace rive

View File

@@ -4,6 +4,7 @@
#include "rive/core/binary_stream.hpp"
#include "rive/core/binary_writer.hpp"
#include <cstring>
#include <vector>
namespace rive
{

View File

@@ -40,6 +40,8 @@ public:
static const uint16_t snapPropertyKey = 724;
static const uint16_t physicsTypeValuePropertyKey = 727;
static const uint16_t physicsIdPropertyKey = 726;
static const uint16_t virtualizePropertyKey = 850;
static const uint16_t infinitePropertyKey = 851;
protected:
float m_ScrollOffsetX = 0.0f;
@@ -47,6 +49,8 @@ protected:
bool m_Snap = false;
uint32_t m_PhysicsTypeValue = 0;
uint32_t m_PhysicsId = -1;
bool m_Virtualize = false;
bool m_Infinite = false;
public:
inline float scrollOffsetX() const { return m_ScrollOffsetX; }
@@ -140,6 +144,28 @@ public:
physicsIdChanged();
}
inline bool virtualize() const { return m_Virtualize; }
void virtualize(bool value)
{
if (m_Virtualize == value)
{
return;
}
m_Virtualize = value;
virtualizeChanged();
}
inline bool infinite() const { return m_Infinite; }
void infinite(bool value)
{
if (m_Infinite == value)
{
return;
}
m_Infinite = value;
infiniteChanged();
}
Core* clone() const override;
void copy(const ScrollConstraintBase& object)
{
@@ -148,6 +174,8 @@ public:
m_Snap = object.m_Snap;
m_PhysicsTypeValue = object.m_PhysicsTypeValue;
m_PhysicsId = object.m_PhysicsId;
m_Virtualize = object.m_Virtualize;
m_Infinite = object.m_Infinite;
DraggableConstraint::copy(object);
}
@@ -170,6 +198,12 @@ public:
case physicsIdPropertyKey:
m_PhysicsId = CoreUintType::deserialize(reader);
return true;
case virtualizePropertyKey:
m_Virtualize = CoreBoolType::deserialize(reader);
return true;
case infinitePropertyKey:
m_Infinite = CoreBoolType::deserialize(reader);
return true;
}
return DraggableConstraint::deserialize(propertyKey, reader);
}
@@ -183,6 +217,8 @@ protected:
virtual void snapChanged() {}
virtual void physicsTypeValueChanged() {}
virtual void physicsIdChanged() {}
virtual void virtualizeChanged() {}
virtual void infiniteChanged() {}
};
} // namespace rive

View File

@@ -1535,6 +1535,12 @@ public:
case ScrollConstraintBase::snapPropertyKey:
object->as<ScrollConstraintBase>()->snap(value);
break;
case ScrollConstraintBase::virtualizePropertyKey:
object->as<ScrollConstraintBase>()->virtualize(value);
break;
case ScrollConstraintBase::infinitePropertyKey:
object->as<ScrollConstraintBase>()->infinite(value);
break;
case ScrollBarConstraintBase::autoSizePropertyKey:
object->as<ScrollBarConstraintBase>()->autoSize(value);
break;
@@ -2845,6 +2851,10 @@ public:
return object->as<FollowPathConstraintBase>()->offset();
case ScrollConstraintBase::snapPropertyKey:
return object->as<ScrollConstraintBase>()->snap();
case ScrollConstraintBase::virtualizePropertyKey:
return object->as<ScrollConstraintBase>()->virtualize();
case ScrollConstraintBase::infinitePropertyKey:
return object->as<ScrollConstraintBase>()->infinite();
case ScrollBarConstraintBase::autoSizePropertyKey:
return object->as<ScrollBarConstraintBase>()->autoSize();
case AxisBase::normalizedPropertyKey:
@@ -3589,6 +3599,8 @@ public:
case FollowPathConstraintBase::orientPropertyKey:
case FollowPathConstraintBase::offsetPropertyKey:
case ScrollConstraintBase::snapPropertyKey:
case ScrollConstraintBase::virtualizePropertyKey:
case ScrollConstraintBase::infinitePropertyKey:
case ScrollBarConstraintBase::autoSizePropertyKey:
case AxisBase::normalizedPropertyKey:
case LayoutComponentStyleBase::intrinsicallySizedValuePropertyKey:
@@ -4327,6 +4339,10 @@ public:
return object->is<FollowPathConstraintBase>();
case ScrollConstraintBase::snapPropertyKey:
return object->is<ScrollConstraintBase>();
case ScrollConstraintBase::virtualizePropertyKey:
return object->is<ScrollConstraintBase>();
case ScrollConstraintBase::infinitePropertyKey:
return object->is<ScrollConstraintBase>();
case ScrollBarConstraintBase::autoSizePropertyKey:
return object->is<ScrollBarConstraintBase>();
case AxisBase::normalizedPropertyKey:

View File

@@ -29,6 +29,13 @@ public:
{
m_nestedEventListeners.push_back(listener);
}
void removeNestedEventListener(NestedEventListener* listener)
{
auto nested = std::remove(m_nestedEventListeners.begin(),
m_nestedEventListeners.end(),
listener);
m_nestedEventListeners.erase(nested, m_nestedEventListeners.end());
}
std::vector<NestedEventListener*> nestedEventListeners()
{
return m_nestedEventListeners;
@@ -70,7 +77,7 @@ public:
// Initialize the animation (make instances as necessary) from the
// source artboard.
virtual void initializeAnimation(ArtboardInstance*) = 0;
// Clear the nested animation dependencies
virtual void releaseDependencies() = 0;
};

View File

@@ -0,0 +1,86 @@
#ifndef _RIVE_PROPERTY_RECORDER_HPP_
#define _RIVE_PROPERTY_RECORDER_HPP_
#include "rive/core/binary_writer.hpp"
#include "rive/core/vector_binary_writer.hpp"
#include "rive/core/binary_data_reader.hpp"
#include "rive/core/binary_data_reader.hpp"
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
namespace rive
{
class Artboard;
class StateMachine;
class StateMachineLayer;
class StateMachineInput;
class LayerState;
class LinearAnimation;
class KeyedObject;
class Core;
class StateMachineInstance;
class CoreObjectData
{
private:
std::vector<uint16_t> m_propertyKeys;
public:
CoreObjectData(uint32_t id) { objectId = id; }
uint32_t objectId;
void addPropertyKey(uint16_t key)
{
auto it = std::find(m_propertyKeys.begin(), m_propertyKeys.end(), key);
if (it == m_propertyKeys.end())
{
m_propertyKeys.push_back(key);
}
}
std::vector<uint16_t>* propertyKeys() { return &m_propertyKeys; }
};
class PropertyRecorder
{
private:
VectorBinaryWriter m_binaryWriter;
BinaryDataReader m_binaryReader;
std::vector<uint8_t> m_WriteBuffer;
VectorBinaryWriter m_binaryWriterSM;
BinaryDataReader m_binaryReaderSM;
std::vector<uint8_t> m_WriteBufferSM;
std::vector<std::unique_ptr<CoreObjectData>> m_coreObjectsData;
void recordDataBinds(const Artboard* artboard);
void recordStateMachine(const StateMachine* stateMachine);
void recordStateMachineInput(const StateMachineInput* stateMachineInput);
void recordStateMachineLayer(const StateMachineLayer* stateMachineLayer);
void recordStateMachineLayerState(const LayerState* layerState);
void recordLinearAnimation(const LinearAnimation* linearAnimation);
void recordKeyedObject(const KeyedObject* keyedObject);
CoreObjectData* getCoreObjectData(uint32_t id);
void writeProperties(const Artboard* artboard);
void addPropertyKey(CoreObjectData* coreObjectData, int propertyKey);
int getObjectId(const Artboard* artboard, Core* object);
void writeObjectId(uint32_t objectId, VectorBinaryWriter& writer);
void writeTotalProperties(uint32_t value, VectorBinaryWriter& writer);
void writePropertyKey(uint32_t value, VectorBinaryWriter& writer);
void writePropertyValue(float value, VectorBinaryWriter& writer);
void writePropertyValue(int value, VectorBinaryWriter& writer);
void writePropertyValue(uint32_t value, VectorBinaryWriter& writer);
void writePropertyValue(std::string value, VectorBinaryWriter& writer);
void writePropertyValue(bool value, VectorBinaryWriter& writer);
void writeSeparator();
void complete(VectorBinaryWriter& writer,
BinaryDataReader& reader,
std::vector<uint8_t>& buffer);
public:
PropertyRecorder();
void recordArtboard(const Artboard* artboard);
void recordStateMachineInputs(const StateMachine* stateMachine);
void apply(Artboard* artboard);
void apply(StateMachineInstance* stateMachineInstace);
void clear();
};
} // namespace rive
#endif

View File

@@ -0,0 +1,35 @@
#ifndef _RIVE_VIRTUALIZING_COMPONENT_HPP_
#define _RIVE_VIRTUALIZING_COMPONENT_HPP_
#include "rive/math/vec2d.hpp"
#include <stdio.h>
namespace rive
{
enum class VirtualizedDirection
{
horizontal,
vertical
};
class Virtualizable
{
public:
virtual Component* virtualizableComponent() = 0;
};
class VirtualizingComponent
{
public:
static VirtualizingComponent* from(Component* component);
virtual bool virtualizationEnabled() = 0;
virtual int itemCount() = 0;
virtual Virtualizable* item(int index) = 0;
virtual Vec2D size() = 0;
virtual Vec2D itemSize(int index) = 0;
virtual void setItemSize(Vec2D size, int index) = 0;
virtual void addVirtualizable(int index) = 0;
virtual void removeVirtualizable(int index) = 0;
};
} // namespace rive
#endif

View File

@@ -0,0 +1,392 @@
#include "rive/property_recorder.hpp"
#include "rive/core/vector_binary_writer.hpp"
#include "rive/generated/core_registry.hpp"
#include "rive/artboard.hpp"
#include "rive/animation/state_machine.hpp"
#include "rive/animation/state_machine_input.hpp"
#include "rive/animation/state_machine_number.hpp"
#include "rive/animation/state_machine_bool.hpp"
#include "rive/animation/state_machine_trigger.hpp"
#include "rive/animation/state_machine_layer.hpp"
#include "rive/animation/layer_state.hpp"
#include "rive/animation/blend_state.hpp"
#include "rive/animation/keyed_object.hpp"
using namespace rive;
PropertyRecorder::PropertyRecorder() :
m_binaryWriter(&m_WriteBuffer),
m_binaryReader(nullptr, 0),
m_binaryWriterSM(&m_WriteBufferSM),
m_binaryReaderSM(nullptr, 0)
{}
void PropertyRecorder::writeObjectId(uint32_t objectId,
VectorBinaryWriter& writer)
{
writer.writeVarUint(objectId);
}
void PropertyRecorder::writeTotalProperties(uint32_t value,
VectorBinaryWriter& writer)
{
writer.writeVarUint(value);
}
void PropertyRecorder::writePropertyKey(uint32_t value,
VectorBinaryWriter& writer)
{
writer.writeVarUint(value);
}
void PropertyRecorder::writePropertyValue(float value,
VectorBinaryWriter& writer)
{
writer.writeFloat(value);
}
void PropertyRecorder::writePropertyValue(int value, VectorBinaryWriter& writer)
{
writer.writeVarUint((uint32_t)value);
}
void PropertyRecorder::writePropertyValue(uint32_t value,
VectorBinaryWriter& writer)
{
writer.writeVarUint(value);
}
void PropertyRecorder::writePropertyValue(std::string value,
VectorBinaryWriter& writer)
{
writer.write(value);
}
void PropertyRecorder::writePropertyValue(bool value,
VectorBinaryWriter& writer)
{
writer.write((uint8_t)value);
}
void PropertyRecorder::clear() { m_binaryWriter.clear(); }
void PropertyRecorder::complete(VectorBinaryWriter& writer,
BinaryDataReader& reader,
std::vector<uint8_t>& buffer)
{
reader.complete(&buffer.front(), writer.size());
}
void PropertyRecorder::recordArtboard(const Artboard* artboard)
{
auto sm = artboard->stateMachine(0);
recordStateMachineInputs(sm);
recordStateMachine(sm);
recordDataBinds(artboard);
writeProperties(artboard);
complete(m_binaryWriter, m_binaryReader, m_WriteBuffer);
}
void PropertyRecorder::recordDataBinds(const Artboard* artboard)
{
auto dataBinds = artboard->dataBinds();
for (auto& dataBind : dataBinds)
{
auto target = dataBind->target();
auto index = getObjectId(artboard, target);
auto propertyKey = dataBind->propertyKey();
if (index >= 0)
{
auto coreObjectData = getCoreObjectData((uint32_t)index);
addPropertyKey(coreObjectData, propertyKey);
}
}
}
void PropertyRecorder::addPropertyKey(CoreObjectData* coreObjectData,
int propertyKey)
{
switch (CoreRegistry::propertyFieldId(propertyKey))
{
case CoreDoubleType::id:
case CoreColorType::id:
case CoreUintType::id:
case CoreStringType::id:
case CoreBoolType::id:
coreObjectData->addPropertyKey(propertyKey);
break;
default:
break;
}
}
void PropertyRecorder::recordStateMachine(const StateMachine* stateMachine)
{
if (stateMachine != nullptr)
{
auto totalLayers = stateMachine->layerCount();
for (size_t i = 0; i < totalLayers; i++)
{
recordStateMachineLayer(stateMachine->layer(i));
}
}
}
void PropertyRecorder::recordStateMachineInputs(
const StateMachine* stateMachine)
{
if (stateMachine != nullptr)
{
auto totalInputs = stateMachine->inputCount();
for (size_t i = 0; i < totalInputs; i++)
{
recordStateMachineInput(stateMachine->input(i));
}
}
complete(m_binaryWriterSM, m_binaryReaderSM, m_WriteBufferSM);
}
void PropertyRecorder::recordStateMachineInput(
const StateMachineInput* stateMachineInput)
{
if (stateMachineInput != nullptr)
{
if (stateMachineInput->is<StateMachineNumber>())
{
writePropertyValue(0, m_binaryWriterSM);
writePropertyValue(
stateMachineInput->as<StateMachineNumber>()->value(),
m_binaryWriterSM);
}
else if (stateMachineInput->is<StateMachineBool>())
{
writePropertyValue(1, m_binaryWriterSM);
writePropertyValue(
stateMachineInput->as<StateMachineBool>()->value(),
m_binaryWriterSM);
}
else if (stateMachineInput->is<StateMachineTrigger>())
{
// No need to write a value for triggers, but writing the type to
// keep the indexes coherent
writePropertyValue(2, m_binaryWriterSM);
}
}
}
void PropertyRecorder::recordStateMachineLayer(
const StateMachineLayer* stateMachineLayer)
{
auto totalStates = stateMachineLayer->stateCount();
for (size_t j = 0; j < totalStates; j++)
{
auto state = stateMachineLayer->state(j);
recordStateMachineLayerState(state);
}
}
void PropertyRecorder::recordStateMachineLayerState(
const LayerState* layerState)
{
if (layerState->is<AnimationState>())
{
recordLinearAnimation(layerState->as<AnimationState>()->animation());
}
else if (layerState->is<BlendState>())
{
auto blendState = layerState->as<BlendState>();
for (auto& blendAnimation : blendState->animations())
{
recordLinearAnimation(blendAnimation->animation());
}
}
}
void PropertyRecorder::recordLinearAnimation(
const LinearAnimation* linearAnimation)
{
if (linearAnimation != nullptr)
{
auto totalObjects = linearAnimation->numKeyedObjects();
for (size_t i = 0; i < totalObjects; i += 1)
{
auto keyedObject = linearAnimation->getObject(i);
recordKeyedObject(keyedObject);
}
}
}
void PropertyRecorder::recordKeyedObject(const KeyedObject* keyedObject)
{
if (keyedObject != nullptr)
{
auto coreObjectData = getCoreObjectData(keyedObject->objectId());
auto totalProperties = keyedObject->numKeyedProperties();
for (size_t i = 0; i < totalProperties; i += 1)
{
auto property = keyedObject->getProperty(i);
addPropertyKey(coreObjectData, property->propertyKey());
}
}
}
CoreObjectData* PropertyRecorder::getCoreObjectData(uint32_t id)
{
for (auto& coreObjectData : m_coreObjectsData)
{
if (coreObjectData->objectId == id)
{
return coreObjectData.get();
}
}
auto newCoreObjectData = rivestd::make_unique<CoreObjectData>(id);
auto ref = newCoreObjectData.get();
m_coreObjectsData.push_back(std::move(newCoreObjectData));
return ref;
}
void PropertyRecorder::writeProperties(const Artboard* artboard)
{
for (auto& coreObjectData : m_coreObjectsData)
{
auto propertyKeys = *coreObjectData->propertyKeys();
if (propertyKeys.size() > 0)
{
auto object = artboard->resolve(coreObjectData->objectId);
writeObjectId(coreObjectData->objectId, m_binaryWriter);
writeTotalProperties((uint32_t)propertyKeys.size(), m_binaryWriter);
for (auto& propertyKey : propertyKeys)
{
switch (CoreRegistry::propertyFieldId(propertyKey))
{
case CoreDoubleType::id:
{
writePropertyKey(propertyKey, m_binaryWriter);
auto value =
CoreRegistry::getDouble(object, propertyKey);
writePropertyValue(value, m_binaryWriter);
}
break;
case CoreColorType::id:
{
writePropertyKey(propertyKey, m_binaryWriter);
auto value =
CoreRegistry::getColor(object, propertyKey);
writePropertyValue(value, m_binaryWriter);
}
case CoreUintType::id:
{
writePropertyKey(propertyKey, m_binaryWriter);
auto value = CoreRegistry::getUint(object, propertyKey);
writePropertyValue(value, m_binaryWriter);
}
break;
case CoreStringType::id:
{
writePropertyKey(propertyKey, m_binaryWriter);
auto value =
CoreRegistry::getString(object, propertyKey);
writePropertyValue(value, m_binaryWriter);
}
break;
case CoreBoolType::id:
{
writePropertyKey(propertyKey, m_binaryWriter);
auto value = CoreRegistry::getBool(object, propertyKey);
writePropertyValue(value, m_binaryWriter);
}
break;
default:
break;
}
}
}
}
}
int PropertyRecorder::getObjectId(const Artboard* artboard, Core* object)
{
return artboard->objectIndex(object);
}
// void PropertyRecorder::applyInputs() {}
void PropertyRecorder::apply(StateMachineInstance* stateMachineInstace)
{
int index = 0;
m_binaryReaderSM.reset(&m_WriteBufferSM.front());
while (!m_binaryReaderSM.isEOF() && index < 20)
{
auto inputType = m_binaryReaderSM.readVarUint32();
auto smInput = stateMachineInstace->input(index);
if (inputType == 0)
{
auto value = m_binaryReaderSM.readFloat32();
auto smInputNumber =
stateMachineInstace->getNumber(smInput->name());
if (smInputNumber != nullptr)
{
smInputNumber->value(value);
}
}
else if (inputType == 1)
{
auto value = m_binaryReaderSM.readByte();
auto smInputBool = stateMachineInstace->getBool(smInput->name());
if (smInputBool != nullptr)
{
smInputBool->value(value);
}
}
index += 1;
}
}
void PropertyRecorder::apply(Artboard* artboard)
{
m_binaryReader.reset(&m_WriteBuffer.front());
while (!m_binaryReader.isEOF())
{
auto objectId = m_binaryReader.readVarUint32();
auto object = artboard->resolve(objectId);
auto totalProperties = m_binaryReader.readVarUint32();
uint32_t currentPropertyIndex = 0;
while (currentPropertyIndex < totalProperties)
{
auto propertyKey = m_binaryReader.readVarUint32();
switch (CoreRegistry::propertyFieldId(propertyKey))
{
case CoreDoubleType::id:
{
auto propertyValue = m_binaryReader.readFloat32();
CoreRegistry::setDouble(object, propertyKey, propertyValue);
break;
}
case CoreColorType::id:
{
auto propertyValue = m_binaryReader.readVarUint32();
CoreRegistry::setColor(object, propertyKey, propertyValue);
break;
}
case CoreUintType::id:
{
auto propertyValue = m_binaryReader.readVarUint32();
CoreRegistry::setUint(object, propertyKey, propertyValue);
break;
}
case CoreStringType::id:
{
auto propertyValue = m_binaryReader.readString();
CoreRegistry::setString(object, propertyKey, propertyValue);
break;
}
case CoreBoolType::id:
{
auto propertyValue = m_binaryReader.readByte();
CoreRegistry::setBool(object,
propertyKey,
(bool)propertyValue);
break;
}
}
currentPropertyIndex++;
}
}
}

View File

@@ -78,6 +78,21 @@ public:
#endif
}
void resetState()
{
if (m_stateFrom != m_anyStateInstance && m_stateFrom != m_currentState)
{
delete m_stateFrom;
}
m_stateFrom = nullptr;
if (m_currentState != m_anyStateInstance)
{
delete m_currentState;
}
m_currentState = nullptr;
changeState(m_layer->entryState());
}
void updateMix(float seconds)
{
if (m_transition != nullptr && m_stateFrom != nullptr &&
@@ -1614,6 +1629,43 @@ StateMachineInstance::~StateMachineInstance()
m_bindablePropertyInstances.clear();
}
void StateMachineInstance::removeEventListeners()
{
if (m_artboardInstance != nullptr)
{
for (auto nestedArtboard : m_artboardInstance->nestedArtboards())
{
if (nestedArtboard == nullptr)
{
continue;
}
for (auto animation : nestedArtboard->nestedAnimations())
{
if (animation == nullptr)
{
continue;
}
if (animation->is<NestedStateMachine>())
{
if (auto notifier = animation->as<NestedStateMachine>()
->stateMachineInstance())
{
notifier->removeNestedEventListener(this);
}
}
else if (animation->is<NestedLinearAnimation>())
{
if (auto notifier = animation->as<NestedLinearAnimation>()
->animationInstance())
{
notifier->removeNestedEventListener(this);
}
}
}
}
}
}
#ifdef WITH_RIVE_TOOLS
void StateMachineInstance::onDataBindChanged(DataBindChanged callback)
{
@@ -1794,6 +1846,14 @@ bool StateMachineInstance::advanceAndApply(float seconds)
void StateMachineInstance::markNeedsAdvance() { m_needsAdvance = true; }
bool StateMachineInstance::needsAdvance() const { return m_needsAdvance; }
void StateMachineInstance::resetState()
{
for (size_t i = 0; i < m_layerCount; i++)
{
m_layers[i].resetState();
}
}
std::string StateMachineInstance::name() const { return m_machine->name(); }
SMIInput* StateMachineInstance::input(size_t index) const

View File

@@ -2,6 +2,7 @@
#include "rive/file.hpp"
#include "rive/artboard_component_list.hpp"
#include "rive/constraints/layout_constraint.hpp"
#include "rive/constraints/scrolling/scroll_constraint.hpp"
#include "rive/layout_component.hpp"
#include "rive/viewmodel/viewmodel_instance_symbol_list_index.hpp"
#include "rive/layout/layout_data.hpp"
@@ -25,6 +26,43 @@ void ArtboardComponentList::reset()
m_stateMachinesMap.clear();
m_listItems.clear();
m_artboardsMap.clear();
m_resourcePool.clear();
m_stateMachinesPool.clear();
}
rcp<ViewModelInstanceListItem> ArtboardComponentList::listItem(int index)
{
if (index < m_listItems.size())
{
return m_listItems[index];
}
return nullptr;
}
ArtboardInstance* ArtboardComponentList::artboardInstance(int index)
{
if (index < m_listItems.size())
{
auto item = listItem(index);
auto itr = m_artboardInstancesMap.find(item);
if (itr != m_artboardInstancesMap.end())
{
return m_artboardInstancesMap[item].get();
}
}
return nullptr;
}
StateMachineInstance* ArtboardComponentList::stateMachineInstance(int index)
{
if (index < m_listItems.size())
{
auto item = listItem(index);
auto itr = m_stateMachinesMap.find(item);
if (itr != m_stateMachinesMap.end())
{
return m_stateMachinesMap[item].get();
}
}
return nullptr;
}
#ifdef WITH_RIVE_LAYOUT
@@ -66,9 +104,12 @@ void ArtboardComponentList::updateLayoutBounds(bool animate)
if (artboard != nullptr)
{
artboard->updateLayoutBounds(animate);
auto bounds = artboard->layoutBounds();
setItemSize(Vec2D(bounds.width(), bounds.height()), i);
}
}
#endif
computeLayoutBounds();
}
bool ArtboardComponentList::syncStyleChanges()
@@ -118,8 +159,7 @@ Artboard* ArtboardComponentList::findArtboard(
void ArtboardComponentList::disposeListItem(
const rcp<ViewModelInstanceListItem>& listItem)
{
m_artboardInstancesMap.erase(listItem);
m_stateMachinesMap.erase(listItem);
removeArtboard(listItem);
}
std::unique_ptr<ArtboardInstance> ArtboardComponentList::createArtboard(
@@ -129,13 +169,8 @@ std::unique_ptr<ArtboardInstance> ArtboardComponentList::createArtboard(
auto artboard = findArtboard(listItem);
if (artboard != nullptr)
{
auto mainArtboard = target->artboard();
auto dataContext = mainArtboard->dataContext();
auto artboardCopy = artboard->instance();
// artboardCopy->name(artboard->name());
artboardCopy->bindViewModelInstance(listItem->viewModelInstance(),
dataContext);
return artboardCopy;
auto inst = artboard->instance();
return inst;
}
return nullptr;
}
@@ -145,14 +180,28 @@ std::unique_ptr<StateMachineInstance> ArtboardComponentList::
{
if (artboard != nullptr)
{
auto dataContext = artboard->dataContext();
auto stateMachineInstance = artboard->stateMachineAt(0);
stateMachineInstance->dataContext(dataContext);
linkStateMachineToArtboard(stateMachineInstance.get(), artboard);
return stateMachineInstance;
}
return nullptr;
}
void ArtboardComponentList::linkStateMachineToArtboard(
StateMachineInstance* stateMachineInstance,
ArtboardInstance* artboard)
{
if (artboard != nullptr)
{
auto dataContext = artboard->dataContext();
stateMachineInstance->dataContext(dataContext);
// TODO: @hernan added this to make sure data binds are procesed in the
// current frame instead of waiting for the next run. But might not be
// necessary. Needs more testing.
stateMachineInstance->updateDataBinds();
}
}
void ArtboardComponentList::updateList(
int propertyKey,
std::vector<rcp<ViewModelInstanceListItem>>* list)
@@ -161,6 +210,7 @@ void ArtboardComponentList::updateList(
m_oldItems.assign(m_listItems.begin(), m_listItems.end());
m_listItems.clear();
m_listItems.assign(list->begin(), list->end());
m_artboardSizes.clear();
if (parent()->is<LayoutComponent>())
{
@@ -193,33 +243,33 @@ void ArtboardComponentList::updateList(
index);
}
}
auto itr = m_artboardInstancesMap.find(item);
if (itr == m_artboardInstancesMap.end())
auto artboard = findArtboard(item);
if (artboard != nullptr)
{
auto artboardCopy = createArtboard(this, item);
if (artboardCopy != nullptr)
{
auto artboardInstance = artboardCopy.get();
auto stateMachineCopy =
createStateMachineInstance(this, artboardInstance);
m_artboardInstancesMap[item] = std::move(artboardCopy);
m_stateMachinesMap[item] = std::move(stateMachineCopy);
if (artboardInstance != nullptr)
{
artboardInstance->host(this);
}
}
m_artboardSizes.push_back(
Vec2D(artboard->width(), artboard->height()));
}
auto itr = m_artboardInstancesMap.find(item);
if (!virtualizationEnabled() && itr == m_artboardInstancesMap.end())
{
createArtboardAt(index);
}
index++;
}
computeLayoutBounds();
syncLayoutChildren();
markLayoutNodeDirty();
addDirt(ComponentDirt::Components);
}
void ArtboardComponentList::syncLayoutChildren()
{
if (parent()->is<LayoutComponent>())
{
#ifdef WITH_RIVE_LAYOUT
parent()->as<LayoutComponent>()->syncLayoutChildren();
#endif
}
markLayoutNodeDirty();
addDirt(ComponentDirt::Components);
}
bool ArtboardComponentList::advanceComponent(float elapsedSeconds,
@@ -283,13 +333,46 @@ bool ArtboardComponentList::advanceComponent(float elapsedSeconds,
return keepGoing;
}
AABB ArtboardComponentList::layoutBounds() { return AABB(); }
AABB ArtboardComponentList::layoutBounds()
{
return AABB(0, 0, m_layoutSize.x, m_layoutSize.y);
}
AABB ArtboardComponentList::layoutBoundsForNode(int index)
{
if (index >= 0 && index < numLayoutNodes())
if (virtualizationEnabled())
{
return artboardInstance(index)->layoutBounds();
auto realIndex = std::fmod(index, m_listItems.size());
auto gap = this->gap();
float runningSize = 0;
bool isHorizontal = true;
if (parent()->is<LayoutComponent>())
{
isHorizontal = parent()->as<LayoutComponent>()->mainAxisIsRow();
}
for (int i = 0; i < realIndex; i++)
{
auto size = m_artboardSizes[i];
if (isHorizontal)
{
runningSize += size.x + gap;
}
else
{
runningSize += size.y + gap;
}
}
auto itemSize = m_artboardSizes[realIndex];
double left = isHorizontal ? runningSize : 0;
double top = isHorizontal ? 0 : runningSize;
return AABB(left, top, left + itemSize.x, top + itemSize.y);
}
else
{
if (index >= 0 && index < numLayoutNodes())
{
return artboardInstance(index)->layoutBounds();
}
}
return AABB();
}
@@ -326,13 +409,19 @@ void ArtboardComponentList::draw(Renderer* renderer)
auto artboard = artboardInstance(i);
if (artboard != nullptr)
{
renderer->save();
auto bounds = artboard->layoutBounds();
auto artboardTransform =
Mat2D::fromTranslate(bounds.left(), bounds.top());
renderer->transform(artboardTransform);
if (!virtualizationEnabled())
{
renderer->save();
auto bounds = artboard->layoutBounds();
auto artboardTransform =
Mat2D::fromTranslate(bounds.left(), bounds.top());
renderer->transform(artboardTransform);
}
artboard->draw(renderer);
renderer->restore();
if (!virtualizationEnabled())
{
renderer->restore();
}
}
}
}
@@ -467,4 +556,258 @@ Core* ArtboardComponentList::clone() const
static_cast<ArtboardComponentList*>(ArtboardComponentListBase::clone());
clone->file(file());
return clone;
}
void ArtboardComponentList::createArtboardAt(int index)
{
auto item = listItem(index);
if (item != nullptr)
{
auto artboardCopy = createArtboard(this, item);
if (artboardCopy != nullptr)
{
addArtboardAt(std::move(artboardCopy), index);
}
}
}
void ArtboardComponentList::addArtboardAt(
std::unique_ptr<ArtboardInstance> artboard,
int index)
{
auto item = listItem(index);
if (item != nullptr)
{
auto artboardInstance = artboard.get();
m_artboardInstancesMap[item] = std::move(artboard);
bindArtboard(artboardInstance, item);
if (artboardInstance != nullptr)
{
artboardInstance->host(this);
}
syncLayoutChildren();
auto artboard = findArtboard(item);
if (artboard != nullptr)
{
auto& smPool = m_stateMachinesPool[artboard];
if (!smPool.empty())
{
auto sm = smPool.back().get();
sm->resetState();
applyRecorders(sm, artboard);
m_stateMachinesMap[item] = std::move(smPool.back());
linkStateMachineToArtboard(sm, artboardInstance);
smPool.pop_back();
return;
}
}
auto stateMachineCopy =
createStateMachineInstance(this, artboardInstance);
m_stateMachinesMap[item] = std::move(stateMachineCopy);
}
}
void ArtboardComponentList::bindArtboard(
ArtboardInstance* artboardInstance,
rcp<ViewModelInstanceListItem> listItem)
{
if (artboardInstance != nullptr)
{
auto mainArtboard = this->artboard();
auto dataContext = mainArtboard->dataContext();
// TODO: @hernan added this to make sure data binds are procesed in the
// current frame instead of waiting for the next run. But might not be
// necessary. Needs more testing.
artboardInstance->bindViewModelInstance(listItem->viewModelInstance(),
dataContext);
artboardInstance->updateDataBinds();
}
}
void ArtboardComponentList::removeArtboardAt(int index)
{
auto item = listItem(index);
removeArtboard(item);
}
void ArtboardComponentList::removeArtboard(rcp<ViewModelInstanceListItem> item)
{
m_artboardInstancesMap.erase(item);
m_stateMachinesMap.erase(item);
}
void ArtboardComponentList::createArtboardRecorders(Artboard* artboard)
{
if (artboard == nullptr)
{
return;
}
auto recorderIt = m_propertyRecordersMap.find(artboard);
if (recorderIt == m_propertyRecordersMap.end())
{
auto propertyRecorder = rivestd::make_unique<PropertyRecorder>();
propertyRecorder->recordArtboard(artboard);
m_propertyRecordersMap[artboard] = std::move(propertyRecorder);
for (auto& nestedArtboard : artboard->nestedArtboards())
{
auto sourceArtboard = nestedArtboard->sourceArtboard();
createArtboardRecorders(sourceArtboard);
}
}
}
void ArtboardComponentList::applyRecorders(Artboard* artboard,
Artboard* sourceArtboard)
{
auto propertyRecorder = m_propertyRecordersMap[sourceArtboard].get();
propertyRecorder->apply(artboard);
auto artboards = m_file->artboards();
for (auto& nestedArtboard : artboard->nestedArtboards())
{
if (nestedArtboard->artboardId() < artboards.size())
{
auto sourceNestedArtboard = artboards[nestedArtboard->artboardId()];
applyRecorders(nestedArtboard->sourceArtboard(),
sourceNestedArtboard);
}
}
}
void ArtboardComponentList::applyRecorders(
StateMachineInstance* stateMachineInstance,
Artboard* sourceArtboard)
{
auto propertyRecorder = m_propertyRecordersMap[sourceArtboard].get();
propertyRecorder->apply(stateMachineInstance);
}
void ArtboardComponentList::addVirtualizable(int index)
{
auto listItem = this->listItem(index);
if (listItem != nullptr)
{
auto artboard = findArtboard(listItem);
if (artboard != nullptr)
{
createArtboardRecorders(artboard);
auto& pool = m_resourcePool[artboard];
if (pool.empty())
{
createArtboardAt(index);
}
else
{
auto pooledArtboard = pool.back().get();
applyRecorders(pooledArtboard, artboard);
addArtboardAt(std::move(pool.back()), index);
pool.pop_back();
}
this->artboard()->markLayoutDirty(artboardInstance(index));
}
}
}
void ArtboardComponentList::removeVirtualizable(int index)
{
auto listItem = this->listItem(index);
if (listItem != nullptr)
{
auto artboard = findArtboard(listItem);
auto artboardInstance = std::move(m_artboardInstancesMap[listItem]);
if (artboard != nullptr && artboardInstance != nullptr)
{
auto& pool = m_resourcePool[artboard];
pool.push_back(std::move(artboardInstance));
}
auto smInstanceIterator = m_stateMachinesMap.find(listItem);
if (smInstanceIterator != m_stateMachinesMap.end())
{
auto& smPool = m_stateMachinesPool[artboard];
smPool.push_back(std::move(smInstanceIterator->second));
}
}
removeArtboardAt(index);
}
bool ArtboardComponentList::virtualizationEnabled()
{
auto virtualizer = scrollConstraint();
return virtualizer != nullptr && virtualizer->virtualize();
}
ScrollConstraint* ArtboardComponentList::scrollConstraint()
{
for (auto parentConstraint : m_layoutConstraints)
{
if (parentConstraint->constraint()->is<ScrollConstraint>())
{
return parentConstraint->constraint()->as<ScrollConstraint>();
}
}
return nullptr;
}
void ArtboardComponentList::computeLayoutBounds()
{
if (virtualizationEnabled())
{
auto gap = this->gap();
auto runningWidth = 0.0f;
auto runningHeight = 0.0f;
bool isHorz = true;
if (parent()->is<LayoutComponent>())
{
isHorz = parent()->as<LayoutComponent>()->mainAxisIsRow();
}
for (int i = 0; i < m_artboardSizes.size(); i++)
{
auto size = m_artboardSizes[i];
auto realGap = i == m_artboardSizes.size() - 1 ? 0 : gap;
if (isHorz)
{
runningWidth += size.x + realGap;
runningHeight = std::max(runningHeight, size.y);
}
else
{
runningWidth = std::max(runningWidth, size.x);
runningHeight += size.y + realGap;
}
}
m_layoutSize = Vec2D(runningWidth, runningHeight);
auto scroll = scrollConstraint();
if (scroll != nullptr)
{
scroll->constrainVirtualized(true);
}
}
}
Vec2D ArtboardComponentList::size() { return m_layoutSize; }
Vec2D ArtboardComponentList::itemSize(int index)
{
return index < m_artboardSizes.size() ? m_artboardSizes[index] : Vec2D();
}
void ArtboardComponentList::setItemSize(Vec2D size, int index)
{
if (index < m_artboardSizes.size())
{
m_artboardSizes[index] = size;
}
}
float ArtboardComponentList::gap()
{
if (parent()->is<LayoutComponent>())
{
auto layoutParent = parent()->as<LayoutComponent>();
return layoutParent->mainAxisIsRow() ? layoutParent->gapHorizontal()
: layoutParent->gapVertical();
}
return 0.0f;
}

View File

@@ -9,16 +9,17 @@ Vec2D ClampedScrollPhysics::advance(float elapsedSeconds)
return m_value;
}
void ClampedScrollPhysics::run(Vec2D range,
void ClampedScrollPhysics::run(Vec2D rangeMin,
Vec2D rangeMax,
Vec2D value,
std::vector<Vec2D> snappingPoints)
{
ScrollPhysics::run(range, value, snappingPoints);
m_value = clamp(range, value);
ScrollPhysics::run(rangeMin, rangeMax, value, snappingPoints);
m_value = clamp(rangeMin, rangeMax, value);
}
Vec2D ClampedScrollPhysics::clamp(Vec2D range, Vec2D value)
Vec2D ClampedScrollPhysics::clamp(Vec2D rangeMin, Vec2D rangeMax, Vec2D value)
{
return Vec2D(math::clamp(value.x, range.x, 0),
math::clamp(value.y, range.y, 0));
return Vec2D(math::clamp(value.x, rangeMin.x, rangeMax.x),
math::clamp(value.y, rangeMin.y, rangeMax.y));
}

View File

@@ -24,20 +24,23 @@ Vec2D ElasticScrollPhysics::advance(float elapsedSeconds)
return Vec2D(advanceX, advanceY);
}
Vec2D ElasticScrollPhysics::clamp(Vec2D range, Vec2D value)
Vec2D ElasticScrollPhysics::clamp(Vec2D rangeMin, Vec2D rangeMax, Vec2D value)
{
float clampX =
m_physicsX != nullptr ? m_physicsX->clamp(range.x, value.x) : 0.0f;
float clampY =
m_physicsY != nullptr ? m_physicsY->clamp(range.y, value.y) : 0.0f;
float clampX = m_physicsX != nullptr
? m_physicsX->clamp(rangeMin.x, rangeMax.x, value.x)
: 0.0f;
float clampY = m_physicsY != nullptr
? m_physicsY->clamp(rangeMin.y, rangeMax.y, value.y)
: 0.0f;
return Vec2D(clampX, clampY);
}
void ElasticScrollPhysics::run(Vec2D range,
void ElasticScrollPhysics::run(Vec2D rangeMin,
Vec2D rangeMax,
Vec2D value,
std::vector<Vec2D> snappingPoints)
{
Super::run(range, value, snappingPoints);
Super::run(rangeMin, rangeMax, value, snappingPoints);
std::vector<float> xPoints;
std::vector<float> yPoints;
for (auto pt : snappingPoints)
@@ -47,11 +50,19 @@ void ElasticScrollPhysics::run(Vec2D range,
}
if (m_physicsX != nullptr)
{
m_physicsX->run(m_acceleration.x, range.x, value.x, xPoints);
m_physicsX->run(m_acceleration.x,
rangeMin.x,
rangeMax.x,
value.x,
xPoints);
}
if (m_physicsY != nullptr)
{
m_physicsY->run(m_acceleration.y, range.y, value.y, yPoints);
m_physicsY->run(m_acceleration.y,
rangeMin.y,
rangeMax.y,
value.y,
yPoints);
}
}
@@ -88,11 +99,11 @@ float ElasticScrollPhysicsHelper::advance(float elapsedSeconds)
m_current += m_speed * elapsedSeconds;
auto friction = m_friction;
if (m_current < m_runRange)
if (m_current < m_runRangeMin)
{
friction *= 4;
}
else if (m_current > 0)
else if (m_current > m_runRangeMax)
{
friction *= 4;
}
@@ -102,13 +113,13 @@ float ElasticScrollPhysicsHelper::advance(float elapsedSeconds)
if (abs(m_speed) < 5)
{
m_speed = 0;
if (m_current < m_runRange)
if (m_current < m_runRangeMin)
{
m_target = m_runRange;
m_target = m_runRangeMin;
}
else if (m_current > 0)
else if (m_current > m_runRangeMax)
{
m_target = 0;
m_target = m_runRangeMax;
}
else
{
@@ -130,26 +141,30 @@ float ElasticScrollPhysicsHelper::advance(float elapsedSeconds)
return m_current;
}
float ElasticScrollPhysicsHelper::clamp(float range, float value)
float ElasticScrollPhysicsHelper::clamp(float rangeMin,
float rangeMax,
float value)
{
if (value < range)
if (value < rangeMin)
{
return range - pow(-(value - range), m_elasticFactor);
return rangeMin - pow(-(value - rangeMin), m_elasticFactor);
}
else if (value > 0)
else if (value > rangeMax)
{
return pow(value, m_elasticFactor);
return rangeMax + pow(value + rangeMax, m_elasticFactor);
}
return value;
}
void ElasticScrollPhysicsHelper::run(float acceleration,
float range,
float rangeMin,
float rangeMax,
float value,
std::vector<float> snappingPoints)
{
m_isRunning = true;
m_runRange = range;
m_runRangeMin = rangeMin;
m_runRangeMax = rangeMax;
if (abs(acceleration) > 100)
{
m_speed = acceleration * 0.16f * 0.16f * 0.1f * m_speedMultiplier;
@@ -158,13 +173,13 @@ void ElasticScrollPhysicsHelper::run(float acceleration,
{
m_speed = 0;
}
if (value < range)
if (value < rangeMin)
{
m_target = range;
m_target = rangeMin;
}
else if (value > 0)
else if (value > rangeMax)
{
m_target = 0;
m_target = rangeMax;
}
else
{

View File

@@ -1,20 +1,201 @@
#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() { delete m_physics; }
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)
@@ -32,6 +213,30 @@ void ScrollConstraint::constrainChild(LayoutNodeProvider* child)
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)
@@ -68,6 +273,7 @@ void ScrollConstraint::runPhysics()
if (m_physics != nullptr)
{
m_physics->run(Vec2D(maxOffsetX(), maxOffsetY()),
Vec2D(minOffsetX(), minOffsetY()),
Vec2D(offsetX(), offsetY()),
snap() ? snappingPoints : std::vector<Vec2D>());
}
@@ -156,6 +362,10 @@ StatusCode ScrollConstraint::import(ImportStack& importStack)
StatusCode ScrollConstraint::onAddedDirty(CoreContext* context)
{
StatusCode result = Super::onAddedDirty(context);
if (virtualize())
{
m_virtualizer = new ScrollVirtualizer();
}
offsetX(scrollOffsetX());
offsetY(scrollOffsetY());
return result;
@@ -178,14 +388,24 @@ void ScrollConstraint::stopPhysics()
}
}
float ScrollConstraint::maxOffsetXForPercent()
{
return infinite() ? contentWidth() : maxOffsetX();
}
float ScrollConstraint::maxOffsetYForPercent()
{
return infinite() ? contentHeight() : maxOffsetY();
}
float ScrollConstraint::scrollPercentX()
{
return maxOffsetX() != 0 ? scrollOffsetX() / maxOffsetX() : 0;
return maxOffsetX() != 0 ? scrollOffsetX() / maxOffsetXForPercent() : 0;
}
float ScrollConstraint::scrollPercentY()
{
return maxOffsetY() != 0 ? scrollOffsetY() / maxOffsetY() : 0;
return maxOffsetY() != 0 ? scrollOffsetY() / maxOffsetYForPercent() : 0;
}
float ScrollConstraint::scrollIndex()
@@ -200,7 +420,7 @@ void ScrollConstraint::setScrollPercentX(float value)
return;
}
stopPhysics();
float to = value * maxOffsetX();
float to = value * maxOffsetXForPercent();
scrollOffsetX(to);
}
@@ -211,7 +431,7 @@ void ScrollConstraint::setScrollPercentY(float value)
return;
}
stopPhysics();
float to = value * maxOffsetY();
float to = value * maxOffsetYForPercent();
scrollOffsetY(to);
}
@@ -235,31 +455,33 @@ void ScrollConstraint::setScrollIndex(float value)
Vec2D ScrollConstraint::positionAtIndex(float index)
{
if (content() == nullptr || content()->children().size() == 0)
auto count = scrollItemCount();
if (content() == nullptr || count == 0)
{
return Vec2D();
}
uint32_t i = 0;
Vec2D contentGap = gap();
float floorIndex = std::floor(index);
float normalizedIndex = infinite() ? std::fmod(index, (float)count) : index;
float floorIndex = std::floor(normalizedIndex);
LayoutNodeProvider* lastChild = nullptr;
for (auto child : content()->children())
for (auto child : scrollChildren())
{
auto c = LayoutNodeProvider::from(child);
if (c != nullptr)
if (child == nullptr)
{
size_t count = c->numLayoutNodes();
if ((uint32_t)floorIndex < i + count)
{
float mod = index - floorIndex;
auto bounds = c->layoutBoundsForNode(floorIndex - i);
return Vec2D(
-bounds.left() - (bounds.width() + contentGap.x) * mod,
-bounds.top() - (bounds.height() + contentGap.y) * mod);
}
lastChild = c;
i += count;
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)
{
@@ -281,47 +503,45 @@ float ScrollConstraint::indexAtPosition(Vec2D pos)
Vec2D contentGap = gap();
if (constrainsHorizontal())
{
for (auto child : content()->children())
for (auto child : scrollChildren())
{
auto c = LayoutNodeProvider::from(child);
if (c != nullptr)
if (child == nullptr)
{
size_t count = c->numLayoutNodes();
for (int j = 0; j < count; j++)
{
auto bounds = c->layoutBoundsForNode(j);
if (pos.x >
-bounds.left() - (bounds.width() + contentGap.x))
{
return (i + j) + (-pos.x - bounds.left()) /
(bounds.width() + contentGap.x);
}
}
i += count;
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 : content()->children())
for (auto child : scrollChildren())
{
auto c = LayoutNodeProvider::from(child);
if (c != nullptr)
if (child == nullptr)
{
size_t count = c->numLayoutNodes();
for (int j = 0; j < count; j++)
{
auto bounds = c->layoutBoundsForNode(j);
if (pos.y >
-bounds.top() - (bounds.height() + contentGap.y))
{
return (i + j) + (-pos.y - bounds.top()) /
(bounds.height() + contentGap.y);
}
}
i += count;
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;
}
@@ -331,13 +551,13 @@ float ScrollConstraint::indexAtPosition(Vec2D pos)
size_t ScrollConstraint::scrollItemCount()
{
size_t count = 0;
for (auto child : content()->children())
for (auto child : scrollChildren())
{
auto c = LayoutNodeProvider::from(child);
if (c != nullptr)
if (child == nullptr)
{
count += c->numLayoutNodes();
continue;
}
count += child->numLayoutNodes();
}
return count;
}

View File

@@ -0,0 +1,347 @@
#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];
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);
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 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);
auto transform =
inverse * Mat2D::fromTranslation(location);
artboardInstance->mutableWorldTransform() =
transform;
artboardInstance->markWorldTransformDirty();
}
}
runningOffset += size + gap;
break;
}
}
}
runningTotal = end;
}
}
}
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();
}

View File

@@ -95,6 +95,26 @@ uint32_t BinaryDataReader::readUint32()
return value;
}
std::string BinaryDataReader::readString()
{
uint64_t length = readVarUint();
if (didOverflow())
{
return std::string();
}
std::string rawValue;
rawValue.resize(length);
auto readBytes = decode_string(length, m_Position, m_End, &rawValue[0]);
if (readBytes != length)
{
overflow();
return std::string();
}
m_Position += readBytes;
return std::string(rawValue);
}
void BinaryDataReader::complete(uint8_t* bytes, size_t length)
{
m_Position = bytes;

View File

@@ -27,4 +27,5 @@ void LayoutNodeProvider::addLayoutConstraint(LayoutConstraint* constraint)
m_layoutConstraints.end(),
constraint) == m_layoutConstraints.end());
m_layoutConstraints.push_back(constraint);
constraint->addLayoutChild(this);
}

View File

@@ -0,0 +1,16 @@
#include "rive/artboard.hpp"
#include "rive/artboard_component_list.hpp"
#include "rive/component.hpp"
#include "rive/virtualizing_component.hpp"
using namespace rive;
VirtualizingComponent* VirtualizingComponent::from(Component* component)
{
switch (component->coreType())
{
case ArtboardComponentList::typeKey:
return component->as<ArtboardComponentList>();
}
return nullptr;
}

Binary file not shown.

View File

@@ -1,6 +1,7 @@
#include <rive/animation/state_machine_input_instance.hpp>
#include "rive/animation/state_machine_instance.hpp"
#include "rive/artboard_component_list.hpp"
#include "rive/constraints/scrolling/scroll_constraint.hpp"
#include "rive/layout/layout_component_style.hpp"
#include "rive/math/transform_components.hpp"
#include "rive/shapes/rectangle.hpp"
@@ -362,4 +363,171 @@ TEST_CASE("Number to List Artboards Labels", "[component_list]")
REQUIRE(label->is<rive::Text>());
REQUIRE(label->as<rive::Text>()->runs()[0]->text() == labels[i]);
}
}
TEST_CASE("Component List Virtualized Artboards & State Machines",
"[component_list]")
{
auto file = ReadRiveFile("assets/component_list_virtualized.riv");
auto artboard = file->artboard("Main")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
artboard->bindViewModelInstance(viewModelInstance);
REQUIRE(artboard->find<rive::ArtboardComponentList>("List") != nullptr);
auto list = artboard->find<rive::ArtboardComponentList>("List");
artboard->advance(0.0f);
REQUIRE(list->artboardCount() == 20);
// Only the first 5 items should be created
for (int i = 0; i < list->artboardCount(); i++)
{
auto artboard = list->artboardInstance(i);
if (i < 5)
{
REQUIRE(artboard != nullptr);
REQUIRE(artboard->name() == "ItemArtboard");
auto sm = list->stateMachineInstance(i);
REQUIRE(sm != nullptr);
REQUIRE(sm->artboard() == artboard);
}
else
{
REQUIRE(artboard == nullptr);
auto sm = list->stateMachineInstance(i);
REQUIRE(sm == nullptr);
}
}
}
TEST_CASE("Component List Virtualized Artboards Layout Bounds",
"[component_list]")
{
auto file = ReadRiveFile("assets/component_list_virtualized.riv");
auto artboard = file->artboard("Main")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
artboard->bindViewModelInstance(viewModelInstance);
REQUIRE(artboard->find<rive::ArtboardComponentList>("List") != nullptr);
auto list = artboard->find<rive::ArtboardComponentList>("List");
artboard->advance(0.0f);
float gap = 10;
float artboardWidth = 100;
for (int i = 0; i < list->artboardCount(); i++)
{
auto artboard = list->artboardInstance(i);
if (i < 5)
{
REQUIRE(artboard != nullptr);
auto bounds = list->layoutBoundsForNode(i);
REQUIRE(bounds.left() == i * (artboardWidth + gap));
}
else
{
REQUIRE(artboard == nullptr);
}
}
}
TEST_CASE("Component List Virtualized Scroll", "[component_list]")
{
auto file = ReadRiveFile("assets/component_list_virtualized.riv");
auto artboard = file->artboard("Main")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
artboard->bindViewModelInstance(viewModelInstance);
REQUIRE(artboard->find<rive::ArtboardComponentList>("List") != nullptr);
REQUIRE(artboard->find<rive::ScrollConstraint>().size() == 1);
REQUIRE(artboard->find<rive::ScrollConstraint>()[0] != nullptr);
auto scroll = artboard->find<rive::ScrollConstraint>()[0];
REQUIRE(scroll->offsetX() == 0);
artboard->advance(0.0f);
// scrollIndex
scroll->setScrollIndex(2);
REQUIRE(scroll->infinite() == true);
REQUIRE(scroll->scrollItemCount() == 20);
REQUIRE(scroll->offsetX() == -220.0f);
REQUIRE(scroll->clampedOffsetX() == -220.0f);
REQUIRE(scroll->minOffsetX() == std::numeric_limits<float>::infinity());
REQUIRE(scroll->maxOffsetX() == -std::numeric_limits<float>::infinity());
REQUIRE(scroll->offsetY() == 0.0f);
REQUIRE(scroll->minOffsetY() == 0.0f);
REQUIRE(scroll->maxOffsetY() == 0.0f);
REQUIRE(scroll->clampedOffsetY() == 0.0f);
REQUIRE(scroll->scrollIndex() == 2);
REQUIRE(scroll->contentWidth() == 2200.0f);
REQUIRE(scroll->viewportWidth() == 500.0f);
}
TEST_CASE("Component List Virtualized Scroll manual", "[component_list]")
{
auto file = ReadRiveFile("assets/component_list_virtualized.riv");
auto artboard = file->artboard("Main");
auto artboardInstance = artboard->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboardInstance.get());
REQUIRE(viewModelInstance != nullptr);
artboardInstance->bindViewModelInstance(viewModelInstance);
REQUIRE(artboard->find<rive::ArtboardComponentList>("List") != nullptr);
auto stateMachine = artboard->stateMachine("State Machine 1");
REQUIRE(artboardInstance != nullptr);
REQUIRE(artboardInstance->stateMachineCount() == 1);
REQUIRE(stateMachine != nullptr);
rive::StateMachineInstance* stateMachineInstance =
new rive::StateMachineInstance(stateMachine, artboardInstance.get());
REQUIRE(artboardInstance->find<rive::ScrollConstraint>().size() == 1);
REQUIRE(artboardInstance->find<rive::ScrollConstraint>()[0] != nullptr);
auto scroll = artboardInstance->find<rive::ScrollConstraint>()[0];
REQUIRE(scroll->scrollPercentY() == 0.0f);
REQUIRE(scroll->offsetY() == 0.0f);
REQUIRE(scroll->scrollIndex() == Approx(0.0f));
REQUIRE(scroll->physics()->isRunning() == false);
artboardInstance->advance(0.0f);
stateMachineInstance->pointerMove(rive::Vec2D(250.0f, 50.0f));
// Start drag
stateMachineInstance->pointerDown(rive::Vec2D(250.0f, 50.0f));
artboardInstance->advance(0.1f);
stateMachineInstance->advanceAndApply(0.1f);
// Move left 200px in 0.1 seconds
stateMachineInstance->pointerMove(rive::Vec2D(50.0f, 50.0f));
artboardInstance->advance(0.0f);
stateMachineInstance->advanceAndApply(0.0f);
REQUIRE(scroll->offsetX() == -200.0f);
REQUIRE(scroll->scrollIndex() == Approx(1.818182f));
// End drag
stateMachineInstance->pointerUp(rive::Vec2D(50.0f, 50.0f));
REQUIRE(scroll->physics()->isRunning() == true);
delete stateMachineInstance;
}