feature: add scroll threshold (#10824) 284c801953

Co-authored-by: hernan <hernan@rive.app>
This commit is contained in:
bodymovin
2025-10-17 22:11:56 +00:00
parent d4984fe601
commit 33c9051b6a
11 changed files with 279 additions and 3 deletions

View File

@@ -1 +1 @@
53bc4a7cbb42ca8e5746bc21940d50d530b763ea
284c8019536d32d8767c64897279c2b9ff9ee443

View File

@@ -121,6 +121,16 @@
},
"description": "Whether pointer events trigger the scrolling",
"bindable": true
},
"threshold": {
"type": "double",
"animates": true,
"key": {
"int": 894,
"string": "threshold"
},
"description": "Minimum threshold in pixels to trigger the scrolling behavior",
"bindable": true
}
}
}

View File

@@ -12,6 +12,7 @@ class ViewportDraggableProxy : public DraggableProxy
private:
ScrollConstraint* m_constraint;
Vec2D m_lastPosition;
bool m_isDragging = false;
public:
ViewportDraggableProxy(ScrollConstraint* constraint, Drawable* hittable) :

View File

@@ -43,6 +43,7 @@ public:
static const uint16_t virtualizePropertyKey = 850;
static const uint16_t infinitePropertyKey = 851;
static const uint16_t interactivePropertyKey = 891;
static const uint16_t thresholdPropertyKey = 894;
protected:
float m_ScrollOffsetX = 0.0f;
@@ -53,6 +54,7 @@ protected:
bool m_Virtualize = false;
bool m_Infinite = false;
bool m_Interactive = true;
float m_Threshold = 0.0f;
public:
inline float scrollOffsetX() const { return m_ScrollOffsetX; }
@@ -179,6 +181,17 @@ public:
interactiveChanged();
}
inline float threshold() const { return m_Threshold; }
void threshold(float value)
{
if (m_Threshold == value)
{
return;
}
m_Threshold = value;
thresholdChanged();
}
Core* clone() const override;
void copy(const ScrollConstraintBase& object)
{
@@ -190,6 +203,7 @@ public:
m_Virtualize = object.m_Virtualize;
m_Infinite = object.m_Infinite;
m_Interactive = object.m_Interactive;
m_Threshold = object.m_Threshold;
DraggableConstraint::copy(object);
}
@@ -221,6 +235,9 @@ public:
case interactivePropertyKey:
m_Interactive = CoreBoolType::deserialize(reader);
return true;
case thresholdPropertyKey:
m_Threshold = CoreDoubleType::deserialize(reader);
return true;
}
return DraggableConstraint::deserialize(propertyKey, reader);
}
@@ -237,6 +254,7 @@ protected:
virtual void virtualizeChanged() {}
virtual void infiniteChanged() {}
virtual void interactiveChanged() {}
virtual void thresholdChanged() {}
};
} // namespace rive

View File

@@ -1801,6 +1801,9 @@ public:
case ScrollConstraintBase::scrollIndexPropertyKey:
object->as<ScrollConstraintBase>()->scrollIndex(value);
break;
case ScrollConstraintBase::thresholdPropertyKey:
object->as<ScrollConstraintBase>()->threshold(value);
break;
case ElasticScrollPhysicsBase::frictionPropertyKey:
object->as<ElasticScrollPhysicsBase>()->friction(value);
break;
@@ -3142,6 +3145,8 @@ public:
return object->as<ScrollConstraintBase>()->scrollPercentY();
case ScrollConstraintBase::scrollIndexPropertyKey:
return object->as<ScrollConstraintBase>()->scrollIndex();
case ScrollConstraintBase::thresholdPropertyKey:
return object->as<ScrollConstraintBase>()->threshold();
case ElasticScrollPhysicsBase::frictionPropertyKey:
return object->as<ElasticScrollPhysicsBase>()->friction();
case ElasticScrollPhysicsBase::speedMultiplierPropertyKey:
@@ -3860,6 +3865,7 @@ public:
case ScrollConstraintBase::scrollPercentXPropertyKey:
case ScrollConstraintBase::scrollPercentYPropertyKey:
case ScrollConstraintBase::scrollIndexPropertyKey:
case ScrollConstraintBase::thresholdPropertyKey:
case ElasticScrollPhysicsBase::frictionPropertyKey:
case ElasticScrollPhysicsBase::speedMultiplierPropertyKey:
case ElasticScrollPhysicsBase::elasticFactorPropertyKey:
@@ -4693,6 +4699,8 @@ public:
return object->is<ScrollConstraintBase>();
case ScrollConstraintBase::scrollIndexPropertyKey:
return object->is<ScrollConstraintBase>();
case ScrollConstraintBase::thresholdPropertyKey:
return object->is<ScrollConstraintBase>();
case ElasticScrollPhysicsBase::frictionPropertyKey:
return object->is<ElasticScrollPhysicsBase>();
case ElasticScrollPhysicsBase::speedMultiplierPropertyKey:

View File

@@ -11,7 +11,50 @@ bool ViewportDraggableProxy::drag(Vec2D mousePosition, float timeStamp)
{
return false;
}
m_constraint->dragView(mousePosition - m_lastPosition, timeStamp);
auto deltaPosition = mousePosition - m_lastPosition;
if (!m_isDragging)
{
switch (m_constraint->direction())
{
case DraggableConstraintDirection::vertical:
{
if (std::abs(deltaPosition.y) > m_constraint->threshold())
{
m_isDragging = true;
}
else
{
return false;
}
}
break;
case DraggableConstraintDirection::horizontal:
{
if (std::abs(deltaPosition.x) > m_constraint->threshold())
{
m_isDragging = true;
}
else
{
return false;
}
}
break;
case DraggableConstraintDirection::all:
{
if (deltaPosition.length() > m_constraint->threshold())
{
m_isDragging = true;
}
else
{
return false;
}
}
break;
}
}
m_constraint->dragView(deltaPosition, timeStamp);
m_lastPosition = mousePosition;
return true;
}
@@ -22,6 +65,7 @@ bool ViewportDraggableProxy::startDrag(Vec2D mousePosition, float timeStamp)
{
return false;
}
m_isDragging = false;
m_constraint->initPhysics();
m_lastPosition = mousePosition;
return true;

Binary file not shown.

View File

@@ -13,7 +13,7 @@
using namespace rive;
TEST_CASE("multiple scrolliing artboards", "[silver]")
TEST_CASE("multiple scrolling artboards", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/scroll_test.riv", &silver);
@@ -99,3 +99,198 @@ TEST_CASE("multiple scrolliing artboards", "[silver]")
CHECK(silver.matches("scroll_test"));
}
TEST_CASE("Vertical scroll with threshold", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/scroll_threshold.riv", &silver);
auto artboard = file->artboardNamed("vertical-scroll");
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
auto vmi = file->createViewModelInstance(artboard.get()->viewModelId(), 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
silver.addFrame();
// Threshold value is 40
float pos = 70.0f;
stateMachine->pointerDown(rive::Vec2D(artboard->width() / 2.0f, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
while (pos > 40)
{
silver.addFrame();
stateMachine->pointerMove(rive::Vec2D(artboard->width() / 2.0f, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
pos -= 8;
}
// Since it didn't go over the threshold, element shouldn't have scrolled
// and nested item should trigger its click action
silver.addFrame();
stateMachine->pointerUp(rive::Vec2D(artboard->width() / 2.0f, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Scroll beyond threshold
pos = 70.0f;
stateMachine->pointerDown(rive::Vec2D(artboard->width() / 2.0f, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
while (pos > 10)
{
silver.addFrame();
stateMachine->pointerMove(rive::Vec2D(artboard->width() / 2.0f, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
pos -= 8;
}
// Scrolled beyond threshold and click action is not fired
silver.addFrame();
stateMachine->pointerUp(rive::Vec2D(artboard->width() / 2.0f, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
CHECK(silver.matches("scroll_threshold-vertical-scroll"));
}
TEST_CASE("Horizontal scroll with threshold", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/scroll_threshold.riv", &silver);
auto artboard = file->artboardNamed("horizontal-scroll");
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
auto vmi = file->createViewModelInstance(artboard.get()->viewModelId(), 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
silver.addFrame();
// Threshold value is 40
float pos = 70.0f;
stateMachine->pointerDown(rive::Vec2D(pos, artboard->height() / 2.0f));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
while (pos > 40)
{
silver.addFrame();
stateMachine->pointerMove(rive::Vec2D(pos, artboard->height() / 2.0f));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
pos -= 8;
}
// Since it didn't go over the threshold, element shouldn't have scrolled
// and nested item should trigger its click action
silver.addFrame();
stateMachine->pointerUp(rive::Vec2D(pos, artboard->height() / 2.0f));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Scroll beyond threshold
pos = 70.0f;
stateMachine->pointerDown(rive::Vec2D(pos, artboard->height() / 2.0f));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
while (pos > 10)
{
silver.addFrame();
stateMachine->pointerMove(rive::Vec2D(pos, artboard->height() / 2.0f));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
pos -= 8;
}
// Scrolled beyond threshold and click action is not fired
silver.addFrame();
stateMachine->pointerUp(rive::Vec2D(pos, artboard->height() / 2.0f));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
CHECK(silver.matches("scroll_threshold-horizontal-scroll"));
}
TEST_CASE("Multidirectional scroll with threshold", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/scroll_threshold.riv", &silver);
auto artboard = file->artboardNamed("all-scroll");
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
auto vmi = file->createViewModelInstance(artboard.get()->viewModelId(), 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
silver.addFrame();
// Threshold value is 40
float pos = 70.0f;
stateMachine->pointerDown(rive::Vec2D(pos, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
while (pos > 50)
{
silver.addFrame();
stateMachine->pointerMove(rive::Vec2D(pos, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
pos -= 8;
}
// Since it didn't go over the threshold, element shouldn't have scrolled
// and nested item should trigger its click action
silver.addFrame();
stateMachine->pointerUp(rive::Vec2D(pos, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Scroll beyond threshold
pos = 70.0f;
stateMachine->pointerDown(rive::Vec2D(pos, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
while (pos > 32)
{
silver.addFrame();
stateMachine->pointerMove(rive::Vec2D(pos, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
pos -= 8;
}
// Scrolled diagonnally beyond threshold and click action is not fired
silver.addFrame();
stateMachine->pointerUp(rive::Vec2D(pos, pos));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
CHECK(silver.matches("scroll_threshold-all-scroll"));
}