Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/fabric-example/ios/Podfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ AnimationProgressState CSSAnimation::getState(double timestamp) const {
return progressProvider_->getState(timestamp);
}

unsigned CSSAnimation::getCurrentIteration() const {
return progressProvider_->getCurrentIteration();
}

double CSSAnimation::getDuration() const {
return progressProvider_->getDuration();
}

double CSSAnimation::getDelay() const {
return progressProvider_->getDelay();
}

double CSSAnimation::getIterationCount() const {
return progressProvider_->getIterationCount();
}

bool CSSAnimation::isReversed() const {
const auto direction = progressProvider_->getDirection();
return direction == AnimationDirection::Reverse || direction == AnimationDirection::AlternateReverse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class CSSAnimation {

double getStartTimestamp(double timestamp) const;
AnimationProgressState getState(double timestamp) const;
unsigned getCurrentIteration() const;
double getDuration() const;
double getDelay() const;
double getIterationCount() const;
bool isReversed() const;

bool hasForwardsFillMode() const;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <reanimated/CSS/core/CSSAnimation.h>
#include <reanimated/CSS/events/CSSAnimationEvent.h>

#include <algorithm>

namespace reanimated::css {

CSSEvent createAnimationStartEvent(const facebook::react::Tag viewTag, const std::shared_ptr<CSSAnimation> &animation) {
const auto activeDuration = animation->getDuration() * animation->getIterationCount();
const auto elapsedTime = std::min(std::max(-animation->getDelay(), 0.0), activeDuration);
return {viewTag, "animationstart", animation->getName(), elapsedTime};
}

CSSEvent createAnimationIterationEvent(
const facebook::react::Tag viewTag,
const std::shared_ptr<CSSAnimation> &animation,
const unsigned iteration) {
const auto elapsedTime = static_cast<double>(iteration - 1) * animation->getDuration();
return {viewTag, "animationiteration", animation->getName(), elapsedTime};
}

CSSEvent createAnimationEndEvent(const facebook::react::Tag viewTag, const std::shared_ptr<CSSAnimation> &animation) {
const auto elapsedTime = animation->getDuration() * animation->getIterationCount();
return {viewTag, "animationend", animation->getName(), elapsedTime};
}

} // namespace reanimated::css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <reanimated/CSS/events/CSSEvent.h>

#include <cstdint>
#include <memory>

namespace reanimated::css {

class CSSAnimation;

enum class CSSAnimationEventType : std::uint8_t {
AnimationStart = 1 << 0,
AnimationEnd = 1 << 1,
AnimationIteration = 1 << 2
};

// Bitmask of CSSAnimationEventType values representing which events a view
// is listening for. A value of 0 means no listeners are registered.
using CSSAnimationEventListeners = std::uint8_t;

inline bool hasListener(CSSAnimationEventListeners listeners, CSSAnimationEventType type) {
return (listeners & static_cast<std::uint8_t>(type)) != 0;
}

CSSEvent createAnimationStartEvent(facebook::react::Tag viewTag, const std::shared_ptr<CSSAnimation> &animation);
CSSEvent createAnimationIterationEvent(
facebook::react::Tag viewTag,
const std::shared_ptr<CSSAnimation> &animation,
unsigned iteration);
CSSEvent createAnimationEndEvent(facebook::react::Tag viewTag, const std::shared_ptr<CSSAnimation> &animation);

} // namespace reanimated::css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include <react/renderer/core/ReactPrimitives.h>

#include <string>

namespace reanimated::css {

struct CSSEvent {
facebook::react::Tag viewTag;
std::string type; // e.g. "animationstart", "transitionend"
std::string targetName; // animation name or property name
double elapsedTime; // in milliseconds (convert to seconds at dispatch time)
};

} // namespace reanimated::css
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include <reanimated/CSS/events/CSSEventsEmitter.h>

#include <utility>

using namespace facebook;

namespace reanimated::css {

namespace {

jsi::Object eventToJSIObject(jsi::Runtime &rt, const CSSEvent &event) {
auto obj = jsi::Object(rt);
obj.setProperty(rt, "viewTag", event.viewTag);
obj.setProperty(rt, "type", jsi::String::createFromUtf8(rt, event.type));
obj.setProperty(rt, "animationName", jsi::String::createFromUtf8(rt, event.targetName));
obj.setProperty(rt, "elapsedTime", event.elapsedTime);
return obj;
}

jsi::Array eventsToJSIArray(jsi::Runtime &rt, const std::vector<CSSEvent> &events) {
auto array = jsi::Array(rt, events.size());
for (size_t i = 0; i < events.size(); ++i) {
array.setValueAtIndex(rt, i, eventToJSIObject(rt, events[i]));
}
return array;
}

} // namespace

CSSEventsEmitter::CSSEventsEmitter(const std::shared_ptr<react::CallInvoker> &jsInvoker) : jsInvoker_(jsInvoker) {}

void CSSEventsEmitter::setEmitFunction(std::shared_ptr<jsi::Function> emitFunction) {
emitFunction_ = std::move(emitFunction);
}

void CSSEventsEmitter::schedule(CSSEvent event) {
pendingEvents_.emplace_back(std::move(event));
}

void CSSEventsEmitter::emit() {
if (pendingEvents_.empty() || !emitFunction_) {
return;
}

auto events = std::make_shared<std::vector<CSSEvent>>();
events->swap(pendingEvents_);
auto emitFunction = emitFunction_;

jsInvoker_->invokeAsync([events = std::move(events), emitFunction = std::move(emitFunction)](jsi::Runtime &rt) {
emitFunction->call(rt, eventsToJSIArray(rt, *events));
});
}

bool CSSEventsEmitter::hasEvents() const {
return !pendingEvents_.empty();
}

} // namespace reanimated::css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <reanimated/CSS/events/CSSEvent.h>

#include <ReactCommon/CallInvoker.h>
#include <jsi/jsi.h>

#include <memory>
#include <vector>

using namespace facebook;

namespace reanimated::css {

class CSSEventsEmitter {
public:
explicit CSSEventsEmitter(const std::shared_ptr<react::CallInvoker> &jsInvoker);

void setEmitFunction(std::shared_ptr<jsi::Function> emitFunction);
void schedule(CSSEvent event);
void emit();
bool hasEvents() const;

private:
const std::shared_ptr<react::CallInvoker> jsInvoker_;
std::shared_ptr<jsi::Function> emitFunction_;
std::vector<CSSEvent> pendingEvents_;
};

} // namespace reanimated::css
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ AnimationDirection AnimationProgressProvider::getDirection() const {
return direction_;
}

unsigned AnimationProgressProvider::getCurrentIteration() const {
return currentIteration_;
}

double AnimationProgressProvider::getIterationCount() const {
return iterationCount_;
}

double AnimationProgressProvider::getGlobalProgress() const {
return applyAnimationDirection(rawProgress_.value_or(0));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class AnimationProgressProvider final : public KeyframeProgressProvider, public
void setEasingFunction(const EasingFunction &easingFunction);

AnimationDirection getDirection() const;
unsigned getCurrentIteration() const;
double getIterationCount() const;
double getGlobalProgress() const override;
double getKeyframeProgress(double fromOffset, double toOffset) const override;
AnimationProgressState getState(double timestamp) const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ namespace reanimated::css {
RawProgressProvider::RawProgressProvider(const double timestamp, const double duration, const double delay)
: duration_(duration), delay_(delay), creationTimestamp_(timestamp) {}

double RawProgressProvider::getDuration() const {
return duration_;
}

double RawProgressProvider::getDelay() const {
return delay_;
}

void RawProgressProvider::setDuration(double duration) {
duration_ = duration;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class RawProgressProvider {
public:
RawProgressProvider(double timestamp, double duration, double delay);

double getDuration() const;
double getDelay() const;
void setDuration(double duration);
void setDelay(double delay);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

namespace reanimated::css {

CSSAnimationsRegistry::CSSAnimationsRegistry(const std::shared_ptr<CSSEventsEmitter> &eventsEmitter)
: eventsEmitter_(eventsEmitter) {}

bool CSSAnimationsRegistry::isEmpty() const {
// The registry is empty if has no registered animations and no updates
// stored in the updates registry
Expand All @@ -24,10 +27,19 @@ void CSSAnimationsRegistry::apply(
const std::optional<std::vector<std::string>> &animationNames,
const CSSAnimationsMap &newAnimations,
const CSSAnimationSettingsUpdatesMap &settingsUpdates,
double timestamp) {
const CSSAnimationEventListeners eventListeners,
const double timestamp) {
auto animationsVector = buildAnimationsVector(rt, shadowNode, animationNames, newAnimations);

const auto viewTag = shadowNode->getTag();

// Update event listener tracking based on current callback props
if (eventListeners != 0) {
eventListenersMap_[viewTag] = eventListeners;
} else {
eventListenersMap_.erase(viewTag);
}

if (animationsVector.empty()) {
remove(viewTag);
return;
Expand Down Expand Up @@ -60,6 +72,7 @@ void CSSAnimationsRegistry::remove(const Tag viewTag) {
removeViewAnimations(viewTag);
removeFromUpdatesRegistry(viewTag);
registry_.erase(viewTag);
eventListenersMap_.erase(viewTag);
}

void CSSAnimationsRegistry::update(const double timestamp) {
Expand Down Expand Up @@ -175,19 +188,29 @@ void CSSAnimationsRegistry::updateViewAnimations(
std::shared_ptr<const ShadowNode> shadowNode = nullptr;
bool hasUpdates = false;

const bool detectEvents = eventListenersMap_.count(viewTag) > 0;

for (const auto animationIndex : animationIndices) {
const auto &animation = registry_[viewTag].animationsVector[animationIndex];
if (!shadowNode) {
shadowNode = animation->getShadowNode();
}
if (animation->getState(timestamp) == AnimationProgressState::Pending) {

const AnimationStateSnapshot beforeSnapshot = {animation->getState(timestamp), animation->getCurrentIteration()};

if (beforeSnapshot.state == AnimationProgressState::Pending) {
animation->run(timestamp);
}

bool updatesAddedToBatch = false;
const auto updates = animation->update(timestamp);
const auto newState = animation->getState(timestamp);

if (detectEvents) {
const AnimationStateSnapshot afterSnapshot = {newState, animation->getCurrentIteration()};
scheduleAnimationEvents(viewTag, animation, beforeSnapshot, afterSnapshot);
}

if (newState == AnimationProgressState::Finished) {
// Revert changes applied during animation if there is no forwards fill
// mode
Expand Down Expand Up @@ -318,6 +341,29 @@ void CSSAnimationsRegistry::handleAnimationsToRevert(const double timestamp) {
animationsToRevertMap_.clear();
}

void CSSAnimationsRegistry::scheduleAnimationEvents(
const Tag viewTag,
const std::shared_ptr<CSSAnimation> &animation,
const AnimationStateSnapshot &before,
const AnimationStateSnapshot &after) {
const auto listeners = eventListenersMap_.at(viewTag);

if (hasListener(listeners, CSSAnimationEventType::AnimationStart) &&
before.state == AnimationProgressState::Pending && after.state != AnimationProgressState::Pending) {
eventsEmitter_->schedule(createAnimationStartEvent(viewTag, animation));
}

if (hasListener(listeners, CSSAnimationEventType::AnimationIteration) &&
after.state == AnimationProgressState::Running && after.iteration > before.iteration) {
eventsEmitter_->schedule(createAnimationIterationEvent(viewTag, animation, after.iteration));
}

if (hasListener(listeners, CSSAnimationEventType::AnimationEnd) && before.state != AnimationProgressState::Finished &&
after.state == AnimationProgressState::Finished) {
eventsEmitter_->schedule(createAnimationEndEvent(viewTag, animation));
}
}

bool CSSAnimationsRegistry::addStyleUpdates(
folly::dynamic &target,
const folly::dynamic &updates,
Expand Down
Loading
Loading