fix: cache and reset pointer events (#10707) fd286173b1

* fix: cache and reset pointer events

Co-authored-by: hernan <hernan@rive.app>
This commit is contained in:
bodymovin
2025-10-04 00:10:51 +00:00
parent 2fc9456dc0
commit 129076c675
10 changed files with 305 additions and 51 deletions

View File

@@ -1 +1 @@
d79a44ba26499b2e18d01c49f233dbcda8ad317e
fd286173b1412e73461cedaf2007c5c27b50ca4b

View File

@@ -30,8 +30,8 @@ public:
HitResult pointerDown(Vec2D position, int pointerId = 0);
HitResult pointerUp(Vec2D position, int pointerId = 0);
HitResult pointerExit(Vec2D position, int pointerId = 0);
HitResult dragStart(Vec2D position, float timeStamp = 0);
HitResult dragEnd(Vec2D position, float timeStamp = 0);
HitResult dragStart(Vec2D position, float timeStamp = 0, int pointerId = 0);
HitResult dragEnd(Vec2D position, float timeStamp = 0, int pointerId = 0);
bool tryChangeState();
bool hitTest(Vec2D position) const;

View File

@@ -140,8 +140,9 @@ public:
HitResult pointerExit(Vec2D position, int pointerId = 0) override;
HitResult dragStart(Vec2D position,
float timeStamp = 0,
bool disablePointer = true);
HitResult dragEnd(Vec2D position, float timeStamp = 0);
bool disablePointer = true,
int pointerId = 0);
HitResult dragEnd(Vec2D position, float timeStamp = 0, int pointerId = 0);
bool tryChangeState();
bool hitTest(Vec2D position) const;
@@ -207,8 +208,8 @@ public:
}
const LayerState* layerState(size_t index);
#endif
void enablePointerEvents();
void disablePointerEvents();
void enablePointerEvents(int pointerId = 0);
void disablePointerEvents(int pointerId = 0);
private:
std::vector<EventReport> m_reportedEvents;
@@ -265,8 +266,8 @@ public:
ListenerType hitType,
int pointerId) = 0;
virtual bool hitTest(Vec2D position) const = 0;
virtual void enablePointerEvents() {}
virtual void disablePointerEvents() {}
virtual void enablePointerEvents(int pointerId = 0) {}
virtual void disablePointerEvents(int pointerId = 0) {}
#ifdef TESTING
int earlyOutCount = 0;
#endif

View File

@@ -88,20 +88,27 @@ HitResult NestedStateMachine::pointerExit(Vec2D position, int pointerId)
return HitResult::none;
}
HitResult NestedStateMachine::dragStart(Vec2D position, float timeStamp)
HitResult NestedStateMachine::dragStart(Vec2D position,
float timeStamp,
int pointerId)
{
if (m_StateMachineInstance != nullptr)
{
return m_StateMachineInstance->dragStart(position, timeStamp);
return m_StateMachineInstance->dragStart(position,
timeStamp,
true,
pointerId);
}
return HitResult::none;
}
HitResult NestedStateMachine::dragEnd(Vec2D position, float timeStamp)
HitResult NestedStateMachine::dragEnd(Vec2D position,
float timeStamp,
int pointerId)
{
if (m_StateMachineInstance != nullptr)
{
return m_StateMachineInstance->dragEnd(position, timeStamp);
return m_StateMachineInstance->dragEnd(position, timeStamp, pointerId);
}
return HitResult::none;
}

View File

@@ -540,17 +540,29 @@ public:
{
delete pointerData.second;
}
for (auto& pointerData : m_pointersPool)
{
delete pointerData;
}
}
_PointerData* pointerData(int id)
{
if (m_pointers.find(id) == m_pointers.end())
{
m_pointers[id] = new _PointerData();
if (m_pointersPool.size() > 0)
{
m_pointers[id] = m_pointersPool.back();
m_pointersPool.pop_back();
}
else
{
m_pointers[id] = new _PointerData();
}
}
return m_pointers[id];
}
void consume() { m_isConsumed = true; }
//
void hover(int id)
{
auto pointer = pointerData(id);
@@ -558,7 +570,6 @@ public:
}
void reset(int pointerId)
{
auto pointer = pointerData(pointerId);
if (pointer->phase != GestureClickPhase::disabled)
{
@@ -571,19 +582,32 @@ public:
pointer->phase = GestureClickPhase::out;
}
}
virtual void enable()
void releaseEvent(int pointerId)
{
for (auto& pointer : m_pointers)
if (m_pointers.find(pointerId) != m_pointers.end())
{
pointer.second->phase = GestureClickPhase::out;
// Reset pointer data before caching it
auto pointerData = m_pointers[pointerId];
pointerData->isHovered = false;
pointerData->isPrevHovered = false;
pointerData->phase = GestureClickPhase::out;
auto previousPosition = pointerData->previousPosition();
previousPosition->x = 0;
previousPosition->y = 0;
m_pointersPool.push_back(m_pointers[pointerId]);
m_pointers.erase(pointerId);
}
}
virtual void disable()
virtual void enable(int pointerId = 0)
{
for (auto& pointer : m_pointers)
{
pointer.second->phase = GestureClickPhase::disabled;
}
auto pointer = pointerData(pointerId);
pointer->phase = GestureClickPhase::out;
}
virtual void disable(int pointerId = 0)
{
auto pointer = pointerData(pointerId);
pointer->phase = GestureClickPhase::disabled;
consume();
}
bool isConsumed() { return m_isConsumed; }
@@ -711,6 +735,7 @@ private:
bool m_isConsumed = false;
const StateMachineListener* m_listener;
std::unordered_map<int, _PointerData*> m_pointers;
std::vector<_PointerData*> m_pointersPool;
};
class DraggableConstraintListenerGroup : public ListenerGroup
@@ -729,8 +754,8 @@ public:
delete m_draggable;
}
void enable() override {}
void disable() override {}
void enable(int pointerId = 0) override {}
void disable(int pointerId = 0) override {}
DraggableConstraint* constraint() { return m_constraint; }
@@ -764,7 +789,7 @@ public:
m_draggable->endDrag(position, timeStamp);
if (m_hasScrolled)
{
stateMachineInstance->dragEnd(position, timeStamp);
stateMachineInstance->dragEnd(position, timeStamp, pointerId);
m_hasScrolled = false;
return ProcessEventResult::scroll;
}
@@ -783,7 +808,10 @@ public:
{
if (!m_hasScrolled)
{
stateMachineInstance->dragStart(position, timeStamp, false);
stateMachineInstance->dragStart(position,
timeStamp,
false,
pointerId);
}
m_hasScrolled = true;
return ProcessEventResult::scroll;
@@ -918,19 +946,19 @@ public:
listeners.push_back(listenerGroup);
}
void enablePointerEvents() override
void enablePointerEvents(int pointerId) override
{
for (auto listenerGroup : listeners)
{
listenerGroup->enable();
listenerGroup->enable(pointerId);
}
}
void disablePointerEvents() override
void disablePointerEvents(int pointerId) override
{
for (auto listenerGroup : listeners)
{
listenerGroup->disable();
listenerGroup->disable(pointerId);
}
}
};
@@ -1067,15 +1095,18 @@ public:
break;
case ListenerType::dragStart:
nestedStateMachine->dragStart(nestedPosition,
timeStamp);
timeStamp,
pointerId);
break;
case ListenerType::dragEnd:
nestedStateMachine->dragEnd(nestedPosition,
timeStamp);
timeStamp,
pointerId);
break;
case ListenerType::exit:
hitResult =
nestedStateMachine->pointerExit(nestedPosition);
nestedStateMachine->pointerExit(nestedPosition,
pointerId);
break;
case ListenerType::enter:
case ListenerType::event:
@@ -1094,7 +1125,8 @@ public:
case ListenerType::up:
case ListenerType::move:
case ListenerType::exit:
nestedStateMachine->pointerExit(nestedPosition);
nestedStateMachine->pointerExit(nestedPosition,
pointerId);
break;
case ListenerType::dragStart:
case ListenerType::dragEnd:
@@ -1191,17 +1223,22 @@ public:
case ListenerType::move:
itemHitResult =
stateMachine->pointerMove(listPosition,
timeStamp,
pointerId);
break;
case ListenerType::exit:
itemHitResult =
stateMachine->pointerExit(listPosition);
stateMachine->pointerExit(listPosition,
pointerId);
break;
case ListenerType::dragStart:
stateMachine->dragStart(listPosition);
stateMachine->dragStart(listPosition,
0,
true,
pointerId);
break;
case ListenerType::dragEnd:
stateMachine->dragEnd(listPosition);
stateMachine->dragEnd(listPosition, 0, pointerId);
break;
case ListenerType::enter:
case ListenerType::event:
@@ -1327,11 +1364,9 @@ HitResult StateMachineInstance::updateListeners(Vec2D position,
}
bool hitSomething = false;
bool hitOpaque = false;
// Finally process the events
// Process the events
for (const auto& hitShape : m_hitComponents)
{
// TODO: quick reject.
HitResult hitResult = hitShape->processEvent(position,
hitType,
!hitOpaque,
@@ -1346,6 +1381,15 @@ HitResult StateMachineInstance::updateListeners(Vec2D position,
}
}
}
// Finally release events that are complete
if (hitType == ListenerType::exit)
{
for (const auto& listenerGroup : m_listenerGroups)
{
listenerGroup.get()->releaseEvent(pointerId);
}
}
return hitSomething ? hitOpaque ? HitResult::hitOpaque : HitResult::hit
: HitResult::none;
}
@@ -1391,20 +1435,23 @@ HitResult StateMachineInstance::pointerExit(Vec2D position, int id)
}
HitResult StateMachineInstance::dragStart(Vec2D position,
float timeStamp,
bool disablePointer)
bool disablePointer,
int pointerId)
{
if (disablePointer)
{
disablePointerEvents();
disablePointerEvents(pointerId);
}
auto hit = updateListeners(position, ListenerType::dragStart);
return hit;
}
HitResult StateMachineInstance::dragEnd(Vec2D position, float timeStamp)
HitResult StateMachineInstance::dragEnd(Vec2D position,
float timeStamp,
int pointerId)
{
enablePointerEvents();
enablePointerEvents(pointerId);
auto hit = updateListeners(position, ListenerType::dragEnd);
pointerMove(position, timeStamp);
pointerMove(position, timeStamp, pointerId);
return hit;
}
@@ -2265,19 +2312,19 @@ void StateMachineInstance::notifyEventListeners(
}
}
void StateMachineInstance::enablePointerEvents()
void StateMachineInstance::enablePointerEvents(int pointerId)
{
for (const auto& hitShape : m_hitComponents)
{
hitShape->enablePointerEvents();
hitShape->enablePointerEvents(pointerId);
}
}
void StateMachineInstance::disablePointerEvents()
void StateMachineInstance::disablePointerEvents(int pointerId)
{
for (const auto& hitShape : m_hitComponents)
{
hitShape->disablePointerEvents();
hitShape->disablePointerEvents(pointerId);
}
}

Binary file not shown.

View File

@@ -951,4 +951,203 @@ TEST_CASE("Hit testing multi touch events", "[silver]")
artboard->draw(renderer.get());
CHECK(silver.matches("multitouch"));
}
TEST_CASE("Multitouch with nested artboard and pointer exit event", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/multitouch_enter.riv", &silver);
auto artboard = file->artboardNamed("Main");
REQUIRE(artboard != nullptr);
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
// Script
// Advancing by 0.0000s
// Advancing by 0.0167s
// Touch (id: 9) began at {122.5845,443.8406}
// Advancing by 0.0167s
// Touch (id: 8) began at {459.5410,188.4058}
// Touch (id: 7) began at {333.3333,248.1884}
// Advancing by 0.0167s
// Touch (id: 8) ended at {459.5410,188.4058}
// Touch (id: 8) exited at {459.5410,188.4058}
// Touch (id: 9) ended at {123.7923,444.4445}
// Touch (id: 9) exited at {123.7923,444.4445}
// Touch (id: 7) ended at {333.3333,248.1884}
// Touch (id: 7) exited at {333.3333,248.1884}
// Advancing by 0.0167s
// Touch (id: 7) began at {118.9613,439.6135}
// Touch (id: 9) began at {346.6183,269.9276}
// Touch (id: 8) began at {459.5410,194.4444}
// Advancing by 0.0167s
// Touch (id: 9) ended at {346.6183,269.9276}
// Touch (id: 9) exited at {346.6183,269.9276}
// Touch (id: 7) ended at {122.5845,440.8212}
// Touch (id: 7) exited at {122.5845,440.8212}
// Touch (id: 8) ended at {459.5410,194.4444}
// Touch (id: 8) exited at {459.5410,194.4444}
// Advancing by 0.0167s
silver.addFrame();
stateMachine->pointerDown(rive::Vec2D(122.5845f, 443.8406f), 9);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerDown(rive::Vec2D(459.5410f, 188.4058f), 8);
stateMachine->pointerDown(rive::Vec2D(333.3333f, 248.1884f), 7);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerUp(rive::Vec2D(459.5410f, 188.4058f), 8);
stateMachine->pointerExit(rive::Vec2D(459.5410f, 188.4058f), 8);
stateMachine->pointerUp(rive::Vec2D(123.7923f, 444.4445f), 9);
stateMachine->pointerExit(rive::Vec2D(123.7923f, 444.4445f), 9);
stateMachine->pointerUp(rive::Vec2D(333.3333f, 248.1884f), 7);
stateMachine->pointerExit(rive::Vec2D(333.3333f, 248.1884f), 7);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerDown(rive::Vec2D(118.9613f, 439.6135f), 7);
stateMachine->pointerDown(rive::Vec2D(346.6183f, 269.9276f), 9);
stateMachine->pointerDown(rive::Vec2D(459.5410f, 194.4444f), 8);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerUp(rive::Vec2D(346.6183f, 269.9276f), 9);
stateMachine->pointerExit(rive::Vec2D(346.6183f, 269.9276f), 9);
stateMachine->pointerUp(rive::Vec2D(122.5845f, 440.8212f), 7);
stateMachine->pointerExit(rive::Vec2D(122.5845f, 440.8212f), 7);
stateMachine->pointerUp(rive::Vec2D(459.5410f, 194.4444f), 8);
stateMachine->pointerExit(rive::Vec2D(459.5410f, 194.4444f), 8);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
CHECK(silver.matches("multitouch_enter"));
}
TEST_CASE("Multitouch with list and pointer exit event", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/multitouch_enter.riv", &silver);
auto artboard = file->artboardNamed("MainList");
REQUIRE(artboard != nullptr);
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerDown(rive::Vec2D(122.5845f, 443.8406f), 9);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerDown(rive::Vec2D(459.5410f, 188.4058f), 8);
stateMachine->pointerDown(rive::Vec2D(333.3333f, 248.1884f), 7);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerUp(rive::Vec2D(459.5410f, 188.4058f), 8);
stateMachine->pointerExit(rive::Vec2D(459.5410f, 188.4058f), 8);
stateMachine->pointerUp(rive::Vec2D(123.7923f, 444.4445f), 9);
stateMachine->pointerExit(rive::Vec2D(123.7923f, 444.4445f), 9);
stateMachine->pointerUp(rive::Vec2D(333.3333f, 248.1884f), 7);
stateMachine->pointerExit(rive::Vec2D(333.3333f, 248.1884f), 7);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerDown(rive::Vec2D(118.9613f, 439.6135f), 7);
stateMachine->pointerDown(rive::Vec2D(346.6183f, 269.9276f), 9);
stateMachine->pointerDown(rive::Vec2D(459.5410f, 194.4444f), 8);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerUp(rive::Vec2D(346.6183f, 269.9276f), 9);
stateMachine->pointerExit(rive::Vec2D(346.6183f, 269.9276f), 9);
stateMachine->pointerUp(rive::Vec2D(122.5845f, 440.8212f), 7);
stateMachine->pointerExit(rive::Vec2D(122.5845f, 440.8212f), 7);
stateMachine->pointerUp(rive::Vec2D(459.5410f, 194.4444f), 8);
stateMachine->pointerExit(rive::Vec2D(459.5410f, 194.4444f), 8);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerMove(rive::Vec2D(50.0f, 300.0f), 0, 7);
stateMachine->pointerMove(rive::Vec2D(250.0f, 200.0f), 0, 8);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
float xOffset = 0;
while (xOffset < 300)
{
xOffset += 20;
silver.addFrame();
stateMachine->pointerMove(rive::Vec2D(50.0f + xOffset, 300.0f), 0, 7);
stateMachine->pointerMove(rive::Vec2D(250.0f + xOffset, 200.0f), 0, 8);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("multitouch_enter-MainList"));
}
TEST_CASE("Multitouch with multi scroll", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/multitouch_enter.riv", &silver);
auto artboard = file->artboardNamed("MultiScroll");
REQUIRE(artboard != nullptr);
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
float yOffset = 400;
stateMachine->pointerDown(rive::Vec2D(50.0f, yOffset), 7);
stateMachine->pointerDown(rive::Vec2D(350.0f, yOffset), 8);
while (yOffset > 0)
{
yOffset -= 20;
silver.addFrame();
stateMachine->pointerMove(rive::Vec2D(50.0f, yOffset), 0, 7);
stateMachine->pointerMove(rive::Vec2D(350.0f, yOffset), 0, 8);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
stateMachine->pointerUp(rive::Vec2D(50.0f, yOffset), 7);
stateMachine->pointerUp(rive::Vec2D(350.0f, yOffset), 8);
CHECK(silver.matches("multitouch_enter-MultiScroll"));
}

Binary file not shown.