diff --git a/change/react-native-windows-bf20335c-eccd-487b-bbcf-9fcc46a3b849.json b/change/react-native-windows-bf20335c-eccd-487b-bbcf-9fcc46a3b849.json
new file mode 100644
index 00000000000..7e385668313
--- /dev/null
+++ b/change/react-native-windows-bf20335c-eccd-487b-bbcf-9fcc46a3b849.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Adds Rendering driver option to NativeAnimated",
+ "packageName": "react-native-windows",
+ "email": "erozell@outlook.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/@react-native-windows/tester/src/js/examples-win/NativeAnimation/CompositionBugsExample.js b/packages/@react-native-windows/tester/src/js/examples-win/NativeAnimation/CompositionBugsExample.js
new file mode 100644
index 00000000000..cb0cf6dbdf0
--- /dev/null
+++ b/packages/@react-native-windows/tester/src/js/examples-win/NativeAnimation/CompositionBugsExample.js
@@ -0,0 +1,476 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @format
+ * @flow
+ */
+
+'use strict';
+
+const React = require('react');
+
+const {
+ View,
+ Text,
+ Animated,
+ StyleSheet,
+ TouchableWithoutFeedback,
+} = require('react-native');
+
+class Tester extends React.Component<$FlowFixMeProps, $FlowFixMeState> {
+ state = {
+ native: new Animated.Value(0),
+ tick: new Animated.Value(0),
+ js: new Animated.Value(0),
+ };
+
+ current = 0;
+
+ onPress = () => {
+ const animConfig =
+ this.current && this.props.reverseConfig
+ ? this.props.reverseConfig
+ : this.props.config;
+ this.current = this.current ? 0 : 1;
+ const config: Object = {
+ ...animConfig,
+ toValue: this.current,
+ };
+
+ Animated[this.props.type](this.state.native, {
+ ...config,
+ useNativeDriver: true,
+ }).start();
+ Animated[this.props.type](this.state.tick, {
+ ...config,
+ useNativeDriver: true,
+ platformConfig: {
+ useComposition: false,
+ },
+ }).start();
+ Animated[this.props.type](this.state.js, {
+ ...config,
+ useNativeDriver: false,
+ }).start();
+
+ if (this.props.onPress) {
+ this.props.onPress(this.state.native);
+ this.props.onPress(this.state.tick);
+ this.props.onPress(this.state.js);
+ }
+ };
+
+ render() {
+ return (
+
+
+
+ Composition:
+
+
+ {this.props.children(this.state.native)}
+
+
+ UI Tick{':'}
+
+ {this.props.children(this.state.tick)}
+
+ JavaScript{':'}
+
+ {this.props.children(this.state.js)}
+
+
+ );
+ }
+}
+
+class ValueListenerTester extends Tester {
+ state = {
+ native: new Animated.Value(0),
+ tick: new Animated.Value(0),
+ js: new Animated.Value(0),
+ nativeValue: 0,
+ tickValue: 0,
+ jsValue: 0,
+ };
+
+ componentDidMount() {
+ this.state.native.addListener(e => this.setState({nativeValue: e.value}));
+ this.state.tick.addListener(e => this.setState({tickValue: e.value}));
+ this.state.js.addListener(e => this.setState({jsValue: e.value}));
+ }
+
+ componentWillUnmount() {
+ this.state.native.removeAllListeners();
+ this.state.tick.removeAllListeners();
+ this.state.js.removeAllListeners();
+ }
+
+ render() {
+ return (
+
+
+
+ Composition:
+
+
+ {this.props.children(this.state.native)}
+
+
+ Value: {this.state.nativeValue}
+ {'\n'}
+
+
+ UI Tick{':'}
+
+ {this.props.children(this.state.tick)}
+
+ Value: {this.state.tickValue}
+ {'\n'}
+
+
+ JavaScript{':'}
+
+ {this.props.children(this.state.js)}
+
+ Value: {this.state.jsValue}
+ {'\n'}
+
+
+
+ );
+ }
+}
+
+class RevertToStaticPropsExample extends React.Component<
+ $FlowFixMeProps,
+ $FlowFixMeState,
+> {
+ state = {
+ native: new Animated.Value(0),
+ tick: new Animated.Value(0),
+ js: new Animated.Value(0),
+ resetProp: false,
+ };
+
+ current = 0;
+
+ onPress = () => {
+ if (this.current) {
+ this.state.native.setValue(0);
+ this.state.tick.setValue(0);
+ this.state.js.setValue(0);
+ this.setState({resetProp: true});
+ } else {
+ this.setState({resetProp: false});
+ const config: Object = {
+ ...this.props.config,
+ toValue: 50,
+ };
+
+ Animated[this.props.type](this.state.native, {
+ ...config,
+ useNativeDriver: true,
+ }).start();
+ Animated[this.props.type](this.state.tick, {
+ ...config,
+ useNativeDriver: true,
+ platformConfig: {
+ useComposition: false,
+ },
+ }).start();
+ Animated[this.props.type](this.state.js, {
+ ...config,
+ useNativeDriver: false,
+ }).start();
+
+ if (this.props.onPress) {
+ this.props.onPress(this.state.native);
+ this.props.onPress(this.state.tick);
+ this.props.onPress(this.state.js);
+ }
+ }
+ this.current = this.current ? 0 : 1;
+ };
+
+ render() {
+ return (
+
+
+
+ Composition:
+
+
+ {this.props.children(
+ this.state.resetProp ? undefined : this.state.native,
+ )}
+
+
+ UI Tick{':'}
+
+
+ {this.props.children(
+ this.state.resetProp ? undefined : this.state.tick,
+ )}
+
+
+ JavaScript{':'}
+
+
+ {this.props.children(
+ this.state.resetProp ? undefined : this.state.js,
+ )}
+
+
+
+ );
+ }
+}
+
+class StopAnimationCallbackTester extends Tester {
+ onPress = () => {
+ const animConfig =
+ this.current && this.props.reverseConfig
+ ? this.props.reverseConfig
+ : this.props.config;
+ this.current = this.current ? 0 : 1;
+ const config: Object = {
+ ...animConfig,
+ toValue: this.current,
+ };
+
+ Animated[this.props.type](this.state.native, {
+ ...config,
+ useNativeDriver: true,
+ }).start();
+ Animated[this.props.type](this.state.tick, {
+ ...config,
+ useNativeDriver: true,
+ platformConfig: {
+ useComposition: false,
+ },
+ }).start();
+ Animated[this.props.type](this.state.js, {
+ ...config,
+ useNativeDriver: false,
+ }).start();
+
+ setTimeout(() => {
+ this.state.native.stopAnimation(nativeValue =>
+ this.setState({nativeValue}),
+ );
+ this.state.tick.stopAnimation(tickValue => this.setState({tickValue}));
+ this.state.js.stopAnimation(jsValue => this.setState({jsValue}));
+ }, config.duration / 2);
+ };
+
+ render() {
+ return (
+
+
+
+ Composition:
+
+
+ {this.props.children(this.state.native)}
+
+
+ Final Value: {this.state.nativeValue}
+ {'\n'}
+
+
+ UI Tick{':'}
+
+ {this.props.children(this.state.tick)}
+
+ Final Value: {this.state.tickValue}
+ {'\n'}
+
+
+ JavaScript{':'}
+
+ {this.props.children(this.state.js)}
+
+ Final Value: {this.state.jsValue}
+ {'\n'}
+
+
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ row: {
+ padding: 10,
+ zIndex: 1,
+ },
+ block: {
+ width: 50,
+ height: 50,
+ backgroundColor: 'blue',
+ },
+ line: {
+ position: 'absolute',
+ left: 35,
+ top: 0,
+ bottom: 0,
+ width: 1,
+ backgroundColor: 'red',
+ },
+});
+
+exports.framework = 'React';
+exports.title = 'Composition Bugs Example';
+exports.category = 'UI';
+exports.description = 'See bugs in UI.Composition driven native animations';
+
+exports.examples = [
+ {
+ title: 'Animated value listener',
+ render: function (): React.Node {
+ return (
+
+ {anim => (
+
+ )}
+
+ );
+ },
+ },
+ {
+ title: "Arbitrary props (e.g., 'borderRadius')",
+ render: function (): React.Node {
+ return (
+
+ {anim => (
+
+ )}
+
+ );
+ },
+ },
+ {
+ title: 'diffClamp',
+ render: function (): React.Node {
+ return (
+
+ {anim => (
+
+ )}
+
+ );
+ },
+ },
+ {
+ title: 'setValue in active animation',
+ render: function (): React.Node {
+ return (
+ setTimeout(() => anim.setValue(0.5), 500)}>
+ {anim => {
+ return (
+
+ );
+ }}
+
+ );
+ },
+ },
+ {
+ title: 'stopAnimation callback',
+ render: function (): React.Node {
+ return (
+
+ {anim => {
+ return (
+
+ );
+ }}
+
+ );
+ },
+ },
+ {
+ title: "Animated 'transform' prop value persisted",
+ render: function (): React.Node {
+ return (
+
+ {value => {
+ return (
+
+ );
+ }}
+
+ );
+ },
+ },
+];
diff --git a/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js b/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js
index 2c662135d7f..40b8a6c7531 100644
--- a/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js
+++ b/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js
@@ -299,6 +299,11 @@ const APIs: Array = [
category: 'UI',
module: require('../examples/NativeAnimation/NativeAnimationsExample'),
},
+ {
+ key: 'CompositionBugsExample',
+ category: 'UI',
+ module: require('../examples-win/NativeAnimation/CompositionBugsExample'),
+ },
{
key: 'PanResponderExample',
category: 'Basic',
diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
index 5bd2e634cdb..10d83b86500 100644
--- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
+++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
@@ -233,9 +233,11 @@
+
+
@@ -466,6 +468,7 @@
+
diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters
index c1d94412580..ff71e587beb 100644
--- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters
+++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters
@@ -65,6 +65,9 @@
Modules\Animated
+
+ Modules\Animated
+
Modules\Animated
@@ -412,6 +415,9 @@
Modules\Animated
+
+ Modules\Animated
+
Modules\Animated
@@ -421,6 +427,9 @@
Modules\Animated
+
+ Modules\Animated
+
Modules\Animated
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/AdditionAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/AdditionAnimatedNode.cpp
index ef5c1637e88..92a94208148 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/AdditionAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/AdditionAnimatedNode.cpp
@@ -12,24 +12,41 @@ AdditionAnimatedNode::AdditionAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : ValueAnimatedNode(tag, manager) {
+ : ValueAnimatedNode(tag, config, manager) {
for (const auto &inputNode : config[s_inputName].AsArray()) {
- m_inputNodes.insert(static_cast(inputNode.AsDouble()));
+ const auto inputTag = inputNode.AsInt64();
+ assert(HasCompatibleAnimationDriver(inputTag));
+ m_inputNodes.insert(inputTag);
}
- m_propertySet.StartAnimation(s_valueName, [nodes = m_inputNodes, manager]() {
- const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
+ if (m_useComposition) {
+ m_propertySet.StartAnimation(s_valueName, [nodes = m_inputNodes, manager]() {
+ const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
- anim.Expression([nodes, manager, anim]() {
- winrt::hstring expr = L"0";
- for (const auto tag : nodes) {
- const auto identifier = L"n" + std::to_wstring(tag);
- anim.SetReferenceParameter(identifier, manager->GetValueAnimatedNode(tag)->PropertySet());
- expr = expr + L" + " + identifier + L"." + s_valueName + L" + " + identifier + L"." + s_offsetName;
- }
- return expr;
+ anim.Expression([nodes, manager, anim]() {
+ winrt::hstring expr = L"0";
+ for (const auto tag : nodes) {
+ const auto identifier = L"n" + std::to_wstring(tag);
+ anim.SetReferenceParameter(identifier, manager->GetValueAnimatedNode(tag)->PropertySet());
+ expr = expr + L" + " + identifier + L"." + s_valueName + L" + " + identifier + L"." + s_offsetName;
+ }
+ return expr;
+ }());
+ return anim;
}());
- return anim;
- }());
+ }
+}
+
+void AdditionAnimatedNode::Update() {
+ assert(!m_useComposition);
+ auto rawValue = 0.0;
+ if (const auto manager = m_manager.lock()) {
+ for (const auto tag : m_inputNodes) {
+ if (const auto node = manager->GetValueAnimatedNode(tag)) {
+ rawValue += node->Value();
+ }
+ }
+ }
+ RawValue(rawValue);
}
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/AdditionAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/AdditionAnimatedNode.h
index c2fc087640b..053bb0303e0 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/AdditionAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/AdditionAnimatedNode.h
@@ -12,6 +12,8 @@ class AdditionAnimatedNode final : public ValueAnimatedNode {
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager);
+ virtual void Update() override;
+
private:
std::unordered_set m_inputNodes{};
};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedNode.cpp
index 3b65c3dcb2a..f7c09340af7 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedNode.cpp
@@ -4,12 +4,18 @@
#include "pch.h"
#include "AnimatedNode.h"
+#include "AnimatedPlatformConfig.h"
#include "NativeAnimatedNodeManager.h"
namespace Microsoft::ReactNative {
-AnimatedNode::AnimatedNode(int64_t tag, const std::shared_ptr &manager)
- : m_tag(tag), m_manager(manager) {}
+AnimatedNode::AnimatedNode(
+ int64_t tag,
+ const winrt::Microsoft::ReactNative::JSValueObject &config,
+ const std::shared_ptr &manager)
+ : m_tag(tag), m_manager(manager) {
+ m_useComposition = AnimatedPlatformConfig::ShouldUseComposition(config);
+}
int64_t AnimatedNode::Tag() {
return m_tag;
@@ -36,4 +42,13 @@ AnimatedNode *AnimatedNode::GetChildNode(int64_t tag) {
return static_cast(nullptr);
}
+
+bool AnimatedNode::HasCompatibleAnimationDriver(int64_t tag) {
+#if DEBUG
+ if (const auto manager = m_manager.lock()) {
+ return manager->GetAnimatedNode(tag)->UseComposition() == m_useComposition;
+ }
+#endif
+ return true;
+}
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedNode.h
index 3be03bcf815..4b94ffd49ce 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedNode.h
@@ -3,6 +3,7 @@
#pragma once
+#include
#include
#include
#include
@@ -11,11 +12,22 @@ namespace Microsoft::ReactNative {
class NativeAnimatedNodeManager;
class AnimatedNode {
public:
- AnimatedNode(int64_t tag, const std::shared_ptr &manager);
+ AnimatedNode(
+ int64_t tag,
+ const winrt::Microsoft::ReactNative::JSValueObject &config,
+ const std::shared_ptr &manager);
int64_t Tag();
void AddChild(int64_t animatedNode);
void RemoveChild(int64_t animatedNode);
+ std::vector &Children() {
+ return m_children;
+ }
+
+ virtual bool UseComposition() const noexcept {
+ return m_useComposition;
+ }
+
virtual void Update(){};
virtual void OnDetachedFromNode(int64_t /*animatedNodeTag*/){};
virtual void OnAttachToNode(int64_t /*animatedNodeTag*/){};
@@ -26,5 +38,11 @@ class AnimatedNode {
int64_t m_tag{0};
const std::weak_ptr m_manager;
std::vector m_children{};
+ bool m_useComposition{false};
+
+ bool HasCompatibleAnimationDriver(int64_t tag);
+
+ static constexpr std::string_view s_platformConfigName{"platformConfig"};
+ static constexpr std::string_view s_useCompositionName{"useComposition"};
};
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedPlatformConfig.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedPlatformConfig.cpp
new file mode 100644
index 00000000000..c940b16d23a
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedPlatformConfig.cpp
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "AnimatedPlatformConfig.h"
+
+namespace Microsoft::ReactNative {
+
+// We could consider converting this value to a quirk setting in the future if
+// we want to change the default behavior to use the frame rendering approach.
+static constexpr auto DEFAULT_USE_COMPOSITION = true;
+
+/*static*/ bool AnimatedPlatformConfig::ShouldUseComposition(
+ const winrt::Microsoft::ReactNative::JSValueObject &config) {
+ if (config.count(s_platformConfigName)) {
+ const auto &platformConfig = config[s_platformConfigName].AsObject();
+ if (platformConfig.count(s_useCompositionName)) {
+ return platformConfig[s_useCompositionName].AsBoolean();
+ }
+ }
+ return DEFAULT_USE_COMPOSITION;
+}
+
+} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedPlatformConfig.h b/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedPlatformConfig.h
new file mode 100644
index 00000000000..2d81fa21a4e
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/AnimatedPlatformConfig.h
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include
+
+namespace Microsoft::ReactNative {
+class AnimatedPlatformConfig {
+ public:
+ static bool ShouldUseComposition(const winrt::Microsoft::ReactNative::JSValueObject &config);
+
+ private:
+ static constexpr std::string_view s_platformConfigName{"platformConfig"};
+ static constexpr std::string_view s_useCompositionName{"useComposition"};
+};
+} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/AnimationDriver.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/AnimationDriver.cpp
index 6a694d05ea0..376da7b5876 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/AnimationDriver.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/AnimationDriver.cpp
@@ -4,6 +4,7 @@
#include "pch.h"
#include
+#include "AnimatedPlatformConfig.h"
#include "AnimationDriver.h"
namespace Microsoft::ReactNative {
@@ -20,6 +21,7 @@ AnimationDriver::AnimationDriver(
m_endCallback(endCallback),
m_manager(manager) {
m_iterations = config.find("iterations") == config.end() ? 1 : config["iterations"].AsInt64();
+ m_useComposition = AnimatedPlatformConfig::ShouldUseComposition(config);
}
void AnimationDriver::DoCallback(bool value) {
@@ -36,11 +38,12 @@ void AnimationDriver::DoCallback(bool value) {
}
AnimationDriver::~AnimationDriver() {
- if (m_scopedBatch)
+ if (m_useComposition && m_scopedBatch)
m_scopedBatch.Completed(m_scopedBatchCompletedToken);
}
void AnimationDriver::StartAnimation() {
+ assert(m_useComposition);
m_started = true;
const auto [animation, scopedBatch] = MakeAnimation(m_config);
if (auto const animatedValue = GetAnimatedValue()) {
@@ -76,15 +79,44 @@ void AnimationDriver::StartAnimation() {
}
void AnimationDriver::StopAnimation(bool ignoreCompletedHandlers) {
- if (!m_started) {
- // The animation may have been deferred and never started. In this case,
- // we will never get a scoped batch completion, so we need to fire the
- // callback synchronously.
+ if (m_useComposition && m_started) {
+ if (const auto animatedValue = GetAnimatedValue()) {
+ animatedValue->PropertySet().StopAnimation(ValueAnimatedNode::s_valueName);
+ m_stopped = true;
+ m_ignoreCompletedHandlers = ignoreCompletedHandlers;
+ }
+ } else {
+ // For composition animations, the animation may have been deferred and
+ // never started. In this case, we will never get a scoped batch
+ // completion, so we need to fire the callback synchronously. For rendering
+ // animations, we always fire the callback synchronously.
DoCallback(false);
- } else if (const auto animatedValue = GetAnimatedValue()) {
- animatedValue->PropertySet().StopAnimation(ValueAnimatedNode::s_valueName);
- m_stopped = true;
- m_ignoreCompletedHandlers = ignoreCompletedHandlers;
+ }
+}
+
+void AnimationDriver::RunAnimationStep(winrt::TimeSpan renderingTime) {
+ assert(!m_useComposition);
+ if (m_isComplete) {
+ return;
+ }
+
+ // winrt::TimeSpan ticks are 100 nanoseconds, divide by 10000 to get milliseconds.
+ const auto frameTimeMs = renderingTime.count() / 10000.0;
+ auto restarting = false;
+ if (m_startFrameTimeMs < 0) {
+ m_startFrameTimeMs = frameTimeMs;
+ restarting = true;
+ }
+
+ const auto timeDeltaMs = frameTimeMs - m_startFrameTimeMs;
+ const auto isComplete = Update(timeDeltaMs, restarting);
+
+ if (isComplete) {
+ if (m_iterations == -1 || ++m_iteration < m_iterations) {
+ m_startFrameTimeMs = -1;
+ } else {
+ m_isComplete = true;
+ }
}
}
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/AnimationDriver.h b/vnext/Microsoft.ReactNative/Modules/Animated/AnimationDriver.h
index 6f5b3b477bf..36ff9bbd95c 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/AnimationDriver.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/AnimationDriver.h
@@ -47,11 +47,23 @@ class AnimationDriver : public std::enable_shared_from_this {
};
virtual std::vector Frames() {
+ assert(m_useComposition);
return std::vector();
}
void DoCallback(bool value);
+ bool UseComposition() const noexcept {
+ return m_useComposition;
+ }
+
+ bool IsComplete() {
+ assert(!m_useComposition);
+ return m_isComplete;
+ }
+
+ void RunAnimationStep(winrt::TimeSpan renderingTime);
+
private:
Callback m_endCallback{};
#ifdef DEBUG
@@ -60,13 +72,22 @@ class AnimationDriver : public std::enable_shared_from_this {
protected:
ValueAnimatedNode *GetAnimatedValue();
+ virtual bool Update(double timeDeltaMs, bool restarting) {
+ return true;
+ };
+ bool m_useComposition{};
int64_t m_id{0};
int64_t m_animatedValueTag{};
int64_t m_iterations{0};
winrt::Microsoft::ReactNative::JSValueObject m_config{};
std::weak_ptr m_manager{};
+ bool m_isComplete{false};
+ int64_t m_iteration{0};
+ double m_startFrameTimeMs{-1};
+ std::optional m_originalValue{};
+
comp::CompositionAnimation m_animation{nullptr};
comp::CompositionScopedBatch m_scopedBatch{nullptr};
// auto revoker for scopedBatch.Completed is broken, tracked by internal bug
@@ -75,5 +96,7 @@ class AnimationDriver : public std::enable_shared_from_this {
bool m_started{false};
bool m_stopped{false};
bool m_ignoreCompletedHandlers{false};
+
+ static constexpr double s_frameDurationMs = 1000.0 / 60.0;
};
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/AnimationUtils.h b/vnext/Microsoft.ReactNative/Modules/Animated/AnimationUtils.h
new file mode 100644
index 00000000000..170a8de39dd
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/AnimationUtils.h
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+static constexpr std::string_view ExtrapolateTypeIdentity = "identity";
+static constexpr std::string_view ExtrapolateTypeClamp = "clamp";
+static constexpr std::string_view ExtrapolateTypeExtend = "extend";
+
+static double Interpolate(
+ double value,
+ double inputMin,
+ double inputMax,
+ double outputMin,
+ double outputMax,
+ std::string_view const &extrapolateLeft,
+ std::string_view const &extrapolateRight) {
+ auto result = value;
+
+ // Extrapolate
+ if (result < inputMin) {
+ if (extrapolateLeft == ExtrapolateTypeIdentity) {
+ return result;
+ } else if (extrapolateLeft == ExtrapolateTypeClamp) {
+ result = inputMin;
+ }
+ }
+
+ if (result > inputMax) {
+ if (extrapolateRight == ExtrapolateTypeIdentity) {
+ return result;
+ } else if (extrapolateRight == ExtrapolateTypeClamp) {
+ result = inputMax;
+ }
+ }
+
+ if (inputMin == inputMax) {
+ if (value <= inputMin) {
+ return outputMin;
+ }
+ return outputMax;
+ }
+
+ return outputMin + (outputMax - outputMin) * (result - inputMin) / (inputMax - inputMin);
+}
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/CalculatedAnimationDriver.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/CalculatedAnimationDriver.cpp
index 8e6efb5d69f..08e1fe08ae2 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/CalculatedAnimationDriver.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/CalculatedAnimationDriver.cpp
@@ -10,6 +10,7 @@ namespace Microsoft::ReactNative {
std::tuple CalculatedAnimationDriver::MakeAnimation(
const winrt::Microsoft::ReactNative::JSValueObject & /*config*/) {
+ assert(m_useComposition);
const auto [scopedBatch, animation, easingFunction] = []() {
const auto compositor = Microsoft::ReactNative::GetCompositor();
return std::make_tuple(
@@ -18,7 +19,7 @@ std::tuple CalculatedA
compositor.CreateLinearEasingFunction());
}();
- m_startValue = GetAnimatedValue()->RawValue();
+ m_originalValue = GetAnimatedValue()->RawValue();
std::vector keyFrames = [this]() {
std::vector keyFrames;
bool done = false;
@@ -39,7 +40,7 @@ std::tuple CalculatedA
std::chrono::milliseconds duration(static_cast(keyFrames.size() / 60.0f * 1000.0f));
animation.Duration(duration);
auto normalizedProgress = 0.0f;
- auto fromValue = static_cast(m_startValue);
+ auto fromValue = static_cast(m_originalValue.value());
animation.InsertKeyFrame(normalizedProgress, fromValue, easingFunction);
for (const auto keyFrame : keyFrames) {
normalizedProgress = std::min(normalizedProgress + 1.0f / keyFrames.size(), 1.0f);
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/CalculatedAnimationDriver.h b/vnext/Microsoft.ReactNative/Modules/Animated/CalculatedAnimationDriver.h
index 41e17722113..884f975a693 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/CalculatedAnimationDriver.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/CalculatedAnimationDriver.h
@@ -17,8 +17,6 @@ class CalculatedAnimationDriver : public AnimationDriver {
protected:
virtual std::tuple GetValueAndVelocityForTime(double time) = 0;
-
virtual bool IsAnimationDone(double currentValue, std::optional previousValue, double currentVelocity) = 0;
- double m_startValue{0};
};
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/DecayAnimationDriver.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/DecayAnimationDriver.cpp
index 44b3bfbd299..42a5da62c3d 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/DecayAnimationDriver.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/DecayAnimationDriver.cpp
@@ -20,8 +20,8 @@ DecayAnimationDriver::DecayAnimationDriver(
}
std::tuple DecayAnimationDriver::GetValueAndVelocityForTime(double time) {
- const auto value =
- m_startValue + m_velocity / (1 - m_deceleration) * (1 - std::exp(-(1 - m_deceleration) * (1000 * time)));
+ const auto value = m_originalValue.value() +
+ m_velocity / (1 - m_deceleration) * (1 - std::exp(-(1 - m_deceleration) * (1000 * time)));
return std::make_tuple(static_cast(value),
42.0f); // we don't need the velocity, so set it to a dummy value
}
@@ -33,4 +33,30 @@ bool DecayAnimationDriver::IsAnimationDone(
return previousValue.has_value() && std::abs(currentValue - previousValue.value()) < 0.1;
}
+bool DecayAnimationDriver::Update(double timeDeltaMs, bool restarting) {
+ if (const auto node = GetAnimatedValue()) {
+ if (restarting) {
+ const auto value = node->RawValue();
+ if (!m_originalValue) {
+ // First iteration, assign m_fromValue based on AnimatedValue
+ m_originalValue = value;
+ } else {
+ // Not the first iteration, reset AnimatedValue based on m_originalValue
+ node->RawValue(m_originalValue.value());
+ }
+
+ m_lastValue = value;
+ }
+
+ const auto [value, velocity] = GetValueAndVelocityForTime(timeDeltaMs / 1000.0);
+ if (restarting || IsAnimationDone(value, m_lastValue, 0.0 /* ignored */)) {
+ m_lastValue = value;
+ node->RawValue(value);
+ return false;
+ }
+ }
+
+ return true;
+}
+
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/DecayAnimationDriver.h b/vnext/Microsoft.ReactNative/Modules/Animated/DecayAnimationDriver.h
index 5af9d6f51ee..d67200f319f 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/DecayAnimationDriver.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/DecayAnimationDriver.h
@@ -17,12 +17,14 @@ class DecayAnimationDriver : public CalculatedAnimationDriver {
const std::shared_ptr &manager);
protected:
+ bool Update(double timeDeltaMs, bool restarting) override;
std::tuple GetValueAndVelocityForTime(double time) override;
bool IsAnimationDone(double currentValue, std::optional previousValue, double currentVelocity) override;
private:
double m_velocity{0};
double m_deceleration{0};
+ double m_lastValue{0};
static constexpr std::string_view s_velocityName{"velocity"};
static constexpr std::string_view s_decelerationName{"deceleration"};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/DiffClampAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/DiffClampAnimatedNode.cpp
index 18d6fd4d351..4c8647fa56d 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/DiffClampAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/DiffClampAnimatedNode.cpp
@@ -11,20 +11,35 @@ DiffClampAnimatedNode::DiffClampAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : ValueAnimatedNode(tag, manager) {
+ : ValueAnimatedNode(tag, config, manager) {
m_inputNodeTag = static_cast(config[s_inputName].AsDouble());
+ assert(HasCompatibleAnimationDriver(m_inputNodeTag));
m_min = config[s_minName].AsDouble();
m_max = config[s_maxName].AsDouble();
- m_propertySet.StartAnimation(s_valueName, [node = m_inputNodeTag, min = m_min, max = m_max, manager]() {
- const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
- anim.SetReferenceParameter(s_inputParameterName, manager->GetValueAnimatedNode(node)->PropertySet());
- anim.SetScalarParameter(s_minParameterName, static_cast(min));
- anim.SetScalarParameter(s_maxParameterName, static_cast(max));
- anim.Expression(
- static_cast(L"Clamp(") + s_inputParameterName + L"." + s_valueName + L" + " +
- s_inputParameterName + L"." + s_offsetName + L", " + s_minParameterName + L", " + s_maxParameterName + L")");
- return anim;
- }());
+ if (m_useComposition) {
+ m_propertySet.StartAnimation(s_valueName, [node = m_inputNodeTag, min = m_min, max = m_max, manager]() {
+ const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
+ anim.SetReferenceParameter(s_inputParameterName, manager->GetValueAnimatedNode(node)->PropertySet());
+ anim.SetScalarParameter(s_minParameterName, static_cast(min));
+ anim.SetScalarParameter(s_maxParameterName, static_cast(max));
+ anim.Expression(
+ static_cast(L"Clamp(") + s_inputParameterName + L"." + s_valueName + L" + " +
+ s_inputParameterName + L"." + s_offsetName + L", " + s_minParameterName + L", " + s_maxParameterName + L")");
+ return anim;
+ }());
+ }
+}
+
+void DiffClampAnimatedNode::Update() {
+ assert(!m_useComposition);
+ if (const auto manager = m_manager.lock()) {
+ if (const auto node = manager->GetValueAnimatedNode(m_inputNodeTag)) {
+ const auto value = node->Value();
+ const auto diff = value - m_lastValue;
+ m_lastValue = value;
+ RawValue(std::clamp(Value() + diff, m_min, m_max));
+ }
+ }
}
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/DiffClampAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/DiffClampAnimatedNode.h
index 29948a32e5f..9e5b3569ed8 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/DiffClampAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/DiffClampAnimatedNode.h
@@ -12,10 +12,13 @@ class DiffClampAnimatedNode final : public ValueAnimatedNode {
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager);
+ virtual void Update() override;
+
private:
int64_t m_inputNodeTag{};
double m_min{};
double m_max{};
+ double m_lastValue{};
static constexpr std::string_view s_minName{"min"};
static constexpr std::string_view s_maxName{"max"};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/DivisionAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/DivisionAnimatedNode.cpp
index 644ae8aa074..5b72fe9febc 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/DivisionAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/DivisionAnimatedNode.cpp
@@ -11,30 +11,53 @@ DivisionAnimatedNode::DivisionAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : ValueAnimatedNode(tag, manager) {
+ : ValueAnimatedNode(tag, config, manager) {
for (const auto &inputNode : config[s_inputName].AsArray()) {
+ const auto inputTag = inputNode.AsInt64();
+ assert(HasCompatibleAnimationDriver(inputTag));
if (m_firstInput == s_firstInputUnset) {
- m_firstInput = static_cast(inputNode.AsDouble());
+ m_firstInput = inputTag;
} else {
- m_inputNodes.insert(static_cast(inputNode.AsDouble()));
+ m_inputNodes.insert(inputTag);
}
}
- m_propertySet.StartAnimation(s_valueName, [firstNode = m_firstInput, nodes = m_inputNodes, manager]() {
- const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
-
- anim.Expression([firstNode, nodes, manager, anim]() {
- anim.SetReferenceParameter(s_baseName, manager->GetValueAnimatedNode(firstNode)->PropertySet());
- winrt::hstring expr = static_cast(L"(") + s_baseName + L"." + s_valueName + L" + " + s_baseName +
- L"." + s_offsetName + L")";
- for (const auto tag : nodes) {
- const auto identifier = L"n" + std::to_wstring(tag);
- anim.SetReferenceParameter(identifier, manager->GetValueAnimatedNode(tag)->PropertySet());
- expr = expr + L" / (" + identifier + L"." + s_valueName + L" + " + identifier + L"." + s_offsetName + L")";
- }
- return expr;
+ if (m_useComposition) {
+ m_propertySet.StartAnimation(s_valueName, [firstNode = m_firstInput, nodes = m_inputNodes, manager]() {
+ const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
+
+ anim.Expression([firstNode, nodes, manager, anim]() {
+ anim.SetReferenceParameter(s_baseName, manager->GetValueAnimatedNode(firstNode)->PropertySet());
+ winrt::hstring expr = static_cast(L"(") + s_baseName + L"." + s_valueName + L" + " +
+ s_baseName + L"." + s_offsetName + L")";
+ for (const auto tag : nodes) {
+ const auto identifier = L"n" + std::to_wstring(tag);
+ anim.SetReferenceParameter(identifier, manager->GetValueAnimatedNode(tag)->PropertySet());
+ expr = expr + L" / (" + identifier + L"." + s_valueName + L" + " + identifier + L"." + s_offsetName + L")";
+ }
+ return expr;
+ }());
+ return anim;
}());
- return anim;
- }());
+ }
+}
+
+void DivisionAnimatedNode::Update() {
+ assert(!m_useComposition);
+ if (const auto manager = m_manager.lock()) {
+ auto rawValue = 0.0;
+ if (const auto firstNode = manager->GetValueAnimatedNode(m_firstInput)) {
+ rawValue = firstNode->Value();
+ }
+
+ for (const auto tag : m_inputNodes) {
+ if (const auto node = manager->GetValueAnimatedNode(tag)) {
+ const auto value = node->Value();
+ rawValue /= value;
+ }
+ }
+
+ RawValue(rawValue);
+ }
}
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/DivisionAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/DivisionAnimatedNode.h
index e9edb663671..9e4c89a1dc7 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/DivisionAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/DivisionAnimatedNode.h
@@ -12,6 +12,8 @@ class DivisionAnimatedNode final : public ValueAnimatedNode {
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager);
+ virtual void Update() override;
+
private:
int64_t m_firstInput{s_firstInputUnset};
std::unordered_set m_inputNodes{};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/FrameAnimationDriver.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/FrameAnimationDriver.cpp
index 5dd9b5a7053..a42407f3b3d 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/FrameAnimationDriver.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/FrameAnimationDriver.cpp
@@ -3,6 +3,7 @@
#include "pch.h"
+#include "AnimationUtils.h"
#include "FrameAnimationDriver.h"
#include "Utils/Helpers.h"
@@ -22,6 +23,7 @@ FrameAnimationDriver::FrameAnimationDriver(
std::tuple FrameAnimationDriver::MakeAnimation(
const winrt::Microsoft::ReactNative::JSValueObject & /*config*/) {
+ assert(m_useComposition);
const auto [scopedBatch, animation] = []() {
const auto compositor = Microsoft::ReactNative::GetCompositor();
return std::make_tuple(
@@ -32,7 +34,7 @@ std::tuple FrameAnimat
// Frames contains 60 values per second of duration of the animation, convert
// the size of frames to duration in ms.
- std::chrono::milliseconds duration(static_cast(m_frames.size() * 1000.0 / 60.0));
+ std::chrono::milliseconds duration(static_cast(m_frames.size() * s_frameDurationMs));
animation.Duration(duration);
auto normalizedProgress = 0.0f;
@@ -57,4 +59,39 @@ double FrameAnimationDriver::ToValue() {
return m_toValue;
}
+bool FrameAnimationDriver::Update(double timeDeltaMs, bool restarting) {
+ assert(!m_useComposition);
+ if (const auto node = GetAnimatedValue()) {
+ if (!m_startValue) {
+ m_startValue = node->RawValue();
+ }
+
+ const auto startValue = m_startValue.value();
+ const auto startIndex = static_cast(timeDeltaMs / s_frameDurationMs);
+ assert(startIndex >= 0);
+ const auto nextIndex = startIndex + 1;
+
+ double nextValue;
+ auto isComplete = false;
+ if (nextIndex >= m_frames.size()) {
+ nextValue = m_toValue;
+ isComplete = true;
+ } else {
+ const auto fromInterval = startIndex * s_frameDurationMs;
+ const auto toInterval = nextIndex * s_frameDurationMs;
+ const auto fromValue = m_frames[startIndex];
+ const auto toValue = m_frames[nextIndex];
+ const auto frameOutput = Interpolate(
+ timeDeltaMs, fromInterval, toInterval, fromValue, toValue, ExtrapolateTypeExtend, ExtrapolateTypeExtend);
+ nextValue = Interpolate(frameOutput, 0, 1, startValue, m_toValue, ExtrapolateTypeExtend, ExtrapolateTypeExtend);
+ }
+
+ node->RawValue(nextValue);
+
+ return isComplete;
+ }
+
+ return true;
+}
+
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/FrameAnimationDriver.h b/vnext/Microsoft.ReactNative/Modules/Animated/FrameAnimationDriver.h
index d0ae2fa7b3c..01203085cf0 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/FrameAnimationDriver.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/FrameAnimationDriver.h
@@ -22,11 +22,16 @@ class FrameAnimationDriver : public AnimationDriver {
double ToValue() override;
inline std::vector Frames() override {
+ assert(m_useComposition);
return m_frames;
}
+ protected:
+ bool Update(double timeDeltaMs, bool restarting) override;
+
private:
std::vector m_frames{};
double m_toValue{0};
+ std::optional m_startValue{};
};
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.cpp
index 3b4089a31a4..dfd3db790db 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.cpp
@@ -3,6 +3,7 @@
#include "pch.h"
+#include "AnimationUtils.h"
#include "ExtrapolationType.h"
#include "InterpolationAnimatedNode.h"
#include "NativeAnimatedNodeManager.h"
@@ -12,7 +13,7 @@ InterpolationAnimatedNode::InterpolationAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : ValueAnimatedNode(tag, manager) {
+ : ValueAnimatedNode(tag, config, manager) {
for (const auto &rangeValue : config[s_inputRangeName].AsArray()) {
m_inputRanges.push_back(rangeValue.AsDouble());
}
@@ -24,49 +25,66 @@ InterpolationAnimatedNode::InterpolationAnimatedNode(
m_extrapolateRight = config[s_extrapolateRightName].AsString();
}
-void InterpolationAnimatedNode::Update() {}
+void InterpolationAnimatedNode::Update() {
+ assert(!m_useComposition);
+ if (m_parentTag == s_parentTagUnset) {
+ return;
+ }
+
+ if (const auto manager = m_manager.lock()) {
+ if (const auto node = manager->GetValueAnimatedNode(m_parentTag)) {
+ RawValue(InterpolateValue(node->Value()));
+ }
+ }
+}
void InterpolationAnimatedNode::OnDetachedFromNode([[maybe_unused]] int64_t animatedNodeTag) {
assert(m_parentTag == animatedNodeTag);
m_parentTag = s_parentTagUnset;
- m_propertySet.StopAnimation(s_valueName);
- m_propertySet.StopAnimation(s_offsetName);
- m_rawValueAnimation = nullptr;
- m_offsetAnimation = nullptr;
+
+ if (m_useComposition) {
+ m_propertySet.StopAnimation(s_valueName);
+ m_propertySet.StopAnimation(s_offsetName);
+ m_rawValueAnimation = nullptr;
+ m_offsetAnimation = nullptr;
+ }
}
void InterpolationAnimatedNode::OnAttachToNode(int64_t animatedNodeTag) {
+ assert(HasCompatibleAnimationDriver(animatedNodeTag));
assert(m_parentTag == s_parentTagUnset);
m_parentTag = animatedNodeTag;
- const auto [rawValueAnimation, offsetAnimation] = [this]() {
- if (const auto manager = m_manager.lock()) {
- if (const auto parent = manager->GetValueAnimatedNode(m_parentTag)) {
- const auto compositor = Microsoft::ReactNative::GetCompositor();
-
- const auto rawValueAnimation = CreateExpressionAnimation(compositor, *parent);
- rawValueAnimation.Expression(
- GetExpression(s_parentPropsName + static_cast(L".") + s_valueName));
-
- const auto offsetAnimation = CreateExpressionAnimation(compositor, *parent);
- offsetAnimation.Expression(
- L"(" +
- GetExpression(
- s_parentPropsName + static_cast(L".") + s_offsetName + L" + " + s_parentPropsName +
- L"." + s_valueName) +
- L") - this.target." + s_valueName);
-
- return std::make_tuple(rawValueAnimation, offsetAnimation);
+ if (m_useComposition) {
+ const auto [rawValueAnimation, offsetAnimation] = [this]() {
+ if (const auto manager = m_manager.lock()) {
+ if (const auto parent = manager->GetValueAnimatedNode(m_parentTag)) {
+ const auto compositor = Microsoft::ReactNative::GetCompositor();
+
+ const auto rawValueAnimation = CreateExpressionAnimation(compositor, *parent);
+ rawValueAnimation.Expression(
+ GetExpression(s_parentPropsName + static_cast(L".") + s_valueName));
+
+ const auto offsetAnimation = CreateExpressionAnimation(compositor, *parent);
+ offsetAnimation.Expression(
+ L"(" +
+ GetExpression(
+ s_parentPropsName + static_cast(L".") + s_offsetName + L" + " + s_parentPropsName +
+ L"." + s_valueName) +
+ L") - this.target." + s_valueName);
+
+ return std::make_tuple(rawValueAnimation, offsetAnimation);
+ }
}
- }
- return std::tuple(nullptr, nullptr);
- }();
+ return std::tuple(nullptr, nullptr);
+ }();
- m_propertySet.StartAnimation(s_valueName, rawValueAnimation);
- m_propertySet.StartAnimation(s_offsetName, offsetAnimation);
+ m_propertySet.StartAnimation(s_valueName, rawValueAnimation);
+ m_propertySet.StartAnimation(s_offsetName, offsetAnimation);
- m_rawValueAnimation = rawValueAnimation;
- m_offsetAnimation = offsetAnimation;
+ m_rawValueAnimation = rawValueAnimation;
+ m_offsetAnimation = offsetAnimation;
+ }
}
comp::ExpressionAnimation InterpolationAnimatedNode::CreateExpressionAnimation(
@@ -168,4 +186,24 @@ winrt::hstring InterpolationAnimatedNode::GetRightExpression(
}
}
+double InterpolationAnimatedNode::InterpolateValue(double value) {
+ // Compute range index
+ size_t index = 1;
+ for (; index < m_inputRanges.size() - 1; ++index) {
+ if (m_inputRanges[index] >= value) {
+ break;
+ }
+ }
+ index--;
+
+ return Interpolate(
+ value,
+ m_inputRanges[index],
+ m_inputRanges[index + 1],
+ m_outputRanges[index],
+ m_outputRanges[index + 1],
+ m_extrapolateLeft,
+ m_extrapolateRight);
+}
+
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.h
index dc0d06450ef..84321c4ee91 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.h
@@ -17,9 +17,9 @@ class InterpolationAnimatedNode final : public ValueAnimatedNode {
virtual void OnDetachedFromNode(int64_t animatedNodeTag) override;
virtual void OnAttachToNode(int64_t animatedNodeTag) override;
- static constexpr std::wstring_view ExtrapolateTypeIdentity = L"identity";
- static constexpr std::wstring_view ExtrapolateTypeClamp = L"clamp";
- static constexpr std::wstring_view ExtrapolateTypeExtend = L"extend";
+ static constexpr std::string_view ExtrapolateTypeIdentity = "identity";
+ static constexpr std::string_view ExtrapolateTypeClamp = "clamp";
+ static constexpr std::string_view ExtrapolateTypeExtend = "extend";
private:
comp::ExpressionAnimation CreateExpressionAnimation(const winrt::Compositor &compositor, ValueAnimatedNode &parent);
@@ -34,6 +34,8 @@ class InterpolationAnimatedNode final : public ValueAnimatedNode {
winrt::hstring GetLeftExpression(const winrt::hstring &value, const winrt::hstring &leftInterpolateExpression);
winrt::hstring GetRightExpression(const winrt::hstring &, const winrt::hstring &rightInterpolateExpression);
+ double InterpolateValue(double value);
+
comp::ExpressionAnimation m_rawValueAnimation{nullptr};
comp::ExpressionAnimation m_offsetAnimation{nullptr};
std::vector m_inputRanges;
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/ModulusAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/ModulusAnimatedNode.cpp
index 68475ebd96e..e46aa4df206 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/ModulusAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/ModulusAnimatedNode.cpp
@@ -11,18 +11,30 @@ ModulusAnimatedNode::ModulusAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : ValueAnimatedNode(tag, manager) {
- m_inputNodeTag = static_cast(config[s_inputName].AsDouble());
- m_modulus = static_cast(config[s_modulusName].AsDouble());
+ : ValueAnimatedNode(tag, config, manager) {
+ m_inputNodeTag = config[s_inputName].AsInt64();
+ assert(HasCompatibleAnimationDriver(m_inputNodeTag));
+ m_modulus = config[s_modulusName].AsInt64();
- m_propertySet.StartAnimation(s_valueName, [node = m_inputNodeTag, mod = m_modulus, manager]() {
- const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
- anim.SetReferenceParameter(s_inputParameterName, manager->GetValueAnimatedNode(node)->PropertySet());
- anim.SetScalarParameter(s_modName, static_cast(mod));
- anim.Expression(
- static_cast(L"(") + s_inputParameterName + L"." + s_valueName + L" + " + s_inputParameterName +
- L"." + s_offsetName + L") % " + s_modName);
- return anim;
- }());
+ if (m_useComposition) {
+ m_propertySet.StartAnimation(s_valueName, [node = m_inputNodeTag, mod = m_modulus, manager]() {
+ const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
+ anim.SetReferenceParameter(s_inputParameterName, manager->GetValueAnimatedNode(node)->PropertySet());
+ anim.SetScalarParameter(s_modName, static_cast(mod));
+ anim.Expression(
+ static_cast(L"(") + s_inputParameterName + L"." + s_valueName + L" + " +
+ s_inputParameterName + L"." + s_offsetName + L") % " + s_modName);
+ return anim;
+ }());
+ }
+}
+
+void ModulusAnimatedNode::Update() {
+ assert(!m_useComposition);
+ if (const auto manager = m_manager.lock()) {
+ if (const auto node = manager->GetValueAnimatedNode(m_inputNodeTag)) {
+ RawValue(std::fmod(node->Value(), m_modulus));
+ }
+ }
}
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/ModulusAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/ModulusAnimatedNode.h
index bcb90a33daa..53b96232e45 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/ModulusAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/ModulusAnimatedNode.h
@@ -13,6 +13,8 @@ class ModulusAnimatedNode final : public ValueAnimatedNode {
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager);
+ virtual void Update() override;
+
private:
int64_t m_inputNodeTag{};
int64_t m_modulus{};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/MultiplicationAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/MultiplicationAnimatedNode.cpp
index 84767dc3dfc..c70b02e0f11 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/MultiplicationAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/MultiplicationAnimatedNode.cpp
@@ -11,24 +11,41 @@ MultiplicationAnimatedNode::MultiplicationAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : ValueAnimatedNode(tag, manager) {
+ : ValueAnimatedNode(tag, config, manager) {
for (const auto &inputNode : config[s_inputName].AsArray()) {
- m_inputNodes.insert(static_cast(inputNode.AsDouble()));
+ const auto inputTag = inputNode.AsInt64();
+ assert(HasCompatibleAnimationDriver(inputTag));
+ m_inputNodes.insert(inputTag);
}
- m_propertySet.StartAnimation(s_valueName, [nodes = m_inputNodes, manager]() {
- const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
+ if (m_useComposition) {
+ m_propertySet.StartAnimation(s_valueName, [nodes = m_inputNodes, manager]() {
+ const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
- anim.Expression([nodes, manager, anim]() {
- winrt::hstring expr = L"1";
- for (const auto tag : nodes) {
- auto identifier = L"n" + std::to_wstring(tag);
- anim.SetReferenceParameter(identifier, manager->GetValueAnimatedNode(tag)->PropertySet());
- expr = expr + L" * (" + identifier + L"." + s_valueName + L" + " + identifier + L"." + s_offsetName + L")";
- }
- return expr;
+ anim.Expression([nodes, manager, anim]() {
+ winrt::hstring expr = L"1";
+ for (const auto tag : nodes) {
+ auto identifier = L"n" + std::to_wstring(tag);
+ anim.SetReferenceParameter(identifier, manager->GetValueAnimatedNode(tag)->PropertySet());
+ expr = expr + L" * (" + identifier + L"." + s_valueName + L" + " + identifier + L"." + s_offsetName + L")";
+ }
+ return expr;
+ }());
+ return anim;
}());
- return anim;
- }());
+ }
+}
+
+void MultiplicationAnimatedNode::Update() {
+ assert(!m_useComposition);
+ auto rawValue = 1.0;
+ if (const auto manager = m_manager.lock()) {
+ for (const auto tag : m_inputNodes) {
+ if (const auto node = manager->GetValueAnimatedNode(tag)) {
+ rawValue *= node->Value();
+ }
+ }
+ }
+ RawValue(rawValue);
}
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/MultiplicationAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/MultiplicationAnimatedNode.h
index 5c61227a4db..1c60d95f71c 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/MultiplicationAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/MultiplicationAnimatedNode.h
@@ -12,6 +12,8 @@ class MultiplicationAnimatedNode final : public ValueAnimatedNode {
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager);
+ virtual void Update() override;
+
private:
std::unordered_set m_inputNodes{};
};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp
index ecf11402948..1511744b88c 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp
@@ -54,11 +54,29 @@ void NativeAnimatedModule::getValue(double tag, std::function cons
}
void NativeAnimatedModule::startListeningToAnimatedNodeValue(double tag) noexcept {
- // NYI
+ winrt::Microsoft::ReactNative::implementation::ReactCoreInjection::PostToUIBatchingQueue(
+ m_context.Handle(), [wkThis = std::weak_ptr(this->shared_from_this()), nodeTag = static_cast(tag)]() {
+ if (auto pThis = wkThis.lock()) {
+ pThis->m_nodesManager->StartListeningToAnimatedNodeValue(
+ nodeTag, [context = pThis->m_context, nodeTag](double value) {
+ // This event could be coalesced, however it doesn't appear to be
+ // coalesced on Android or iOS, so leaving it without coalescing.
+ context.EmitJSEvent(
+ L"RCTDeviceEventEmitter",
+ L"onAnimatedValueUpdate",
+ ::React::JSValueObject{{"tag", nodeTag}, {"value", value}});
+ });
+ }
+ });
}
void NativeAnimatedModule::stopListeningToAnimatedNodeValue(double tag) noexcept {
- // NYI
+ winrt::Microsoft::ReactNative::implementation::ReactCoreInjection::PostToUIBatchingQueue(
+ m_context.Handle(), [wkThis = std::weak_ptr(this->shared_from_this()), nodeTag = static_cast(tag)]() {
+ if (auto pThis = wkThis.lock()) {
+ pThis->m_nodesManager->StopListeningToAnimatedNodeValue(nodeTag);
+ }
+ });
}
void NativeAnimatedModule::connectAnimatedNodes(double parentTag, double childTag) noexcept {
@@ -183,13 +201,20 @@ void NativeAnimatedModule::disconnectAnimatedNodeFromView(double nodeTag, double
nodeTag = static_cast(nodeTag),
viewTag = static_cast(viewTag)]() {
if (auto pThis = wkThis.lock()) {
+ pThis->m_nodesManager->RestoreDefaultValues(viewTag);
pThis->m_nodesManager->DisconnectAnimatedNodeToView(nodeTag, viewTag);
}
});
}
void NativeAnimatedModule::restoreDefaultValues(double nodeTag) noexcept {
- // NYI
+ winrt::Microsoft::ReactNative::implementation::ReactCoreInjection::PostToUIBatchingQueue(
+ m_context.Handle(),
+ [wkThis = std::weak_ptr(this->shared_from_this()), nodeTag = static_cast(nodeTag)]() {
+ if (auto pThis = wkThis.lock()) {
+ pThis->m_nodesManager->RestoreDefaultValues(nodeTag);
+ }
+ });
}
void NativeAnimatedModule::dropAnimatedNode(double tag) noexcept {
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedNodeManager.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedNodeManager.cpp
index 3601eeff957..6331e2f1638 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedNodeManager.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedNodeManager.cpp
@@ -25,6 +25,7 @@
#include
#include
#include
+#include
namespace Microsoft::ReactNative {
void NativeAnimatedNodeManager::CreateAnimatedNode(
@@ -103,22 +104,41 @@ void NativeAnimatedNodeManager::GetValue(
}
void NativeAnimatedNodeManager::ConnectAnimatedNodeToView(int64_t propsNodeTag, int64_t viewTag) {
- m_propsNodes.at(propsNodeTag)->ConnectToView(viewTag);
+ const auto &propsNode = m_propsNodes.at(propsNodeTag);
+ propsNode->ConnectToView(viewTag);
+ if (!propsNode->UseComposition()) {
+ m_updatedNodes.insert(propsNodeTag);
+ EnsureRendering();
+ }
}
void NativeAnimatedNodeManager::DisconnectAnimatedNodeToView(int64_t propsNodeTag, int64_t viewTag) {
m_propsNodes.at(propsNodeTag)->DisconnectFromView(viewTag);
}
+void NativeAnimatedNodeManager::RestoreDefaultValues(int64_t tag) {
+ if (const auto propsNode = GetPropsAnimatedNode(tag)) {
+ propsNode->RestoreDefaultValues();
+ }
+}
+
void NativeAnimatedNodeManager::ConnectAnimatedNode(int64_t parentNodeTag, int64_t childNodeTag) {
if (const auto parentNode = GetAnimatedNode(parentNodeTag)) {
parentNode->AddChild(childNodeTag);
+ if (!parentNode->UseComposition()) {
+ m_updatedNodes.insert(childNodeTag);
+ EnsureRendering();
+ }
}
}
void NativeAnimatedNodeManager::DisconnectAnimatedNode(int64_t parentNodeTag, int64_t childNodeTag) {
if (const auto parentNode = GetAnimatedNode(parentNodeTag)) {
parentNode->RemoveChild(childNodeTag);
+ if (!parentNode->UseComposition()) {
+ m_updatedNodes.insert(childNodeTag);
+ EnsureRendering();
+ }
}
}
@@ -127,33 +147,38 @@ void NativeAnimatedNodeManager::StopAnimation(int64_t animationId, bool isTracki
if (const auto animation = m_activeAnimations.at(animationId)) {
animation->StopAnimation(isTrackingAnimation);
- // Insert the animation into the pending completion set to ensure it is
- // not destroyed before the callback occurs. It's safe to assume the
- // scoped batch completion callback has not run, since if it had, the
- // animation would have been removed from the set of active animations.
- m_pendingCompletionAnimations.insert({animationId, animation});
-
- const auto nodeTag = animation->AnimatedValueTag();
- if (nodeTag != -1) {
- const auto deferredAnimation = m_deferredAnimationForValues.find(nodeTag);
- if (deferredAnimation != m_deferredAnimationForValues.end()) {
- // We can assume that the currently deferred animation is the one
- // being stopped given the constraint that only one animation can
- // be active for a given value node.
- assert(deferredAnimation->second == animationId);
- // If the animation is deferred, just remove the deferred animation
- // entry as two animations cannot animate the same value concurrently.
- m_deferredAnimationForValues.erase(nodeTag);
- } else {
- // Since only one animation can be active at a time, there shouldn't
- // be any stopped animations for the value node if the animation has
- // not been deferred.
- assert(!m_valuesWithStoppedAnimation.count(nodeTag));
- // In this case, add the value tag to the set of values with stopped
- // animations. This is used to optimize the lookup when determining
- // if an animation needs to be deferred (rather than iterating over
- // the map of pending completion animations).
- m_valuesWithStoppedAnimation.insert(nodeTag);
+ // We need to update the node manager state for composition animations
+ // to ensure new animations on the same animated value do not start until
+ // the completion callback has fired for the stopped animation.
+ if (animation->UseComposition()) {
+ // Insert the animation into the pending completion set to ensure it is
+ // not destroyed before the callback occurs. It's safe to assume the
+ // scoped batch completion callback has not run, since if it had, the
+ // animation would have been removed from the set of active animations.
+ m_pendingCompletionAnimations.insert({animationId, animation});
+
+ const auto nodeTag = animation->AnimatedValueTag();
+ if (nodeTag != -1) {
+ const auto deferredAnimation = m_deferredAnimationForValues.find(nodeTag);
+ if (deferredAnimation != m_deferredAnimationForValues.end()) {
+ // We can assume that the currently deferred animation is the one
+ // being stopped given the constraint that only one animation can
+ // be active for a given value node.
+ assert(deferredAnimation->second == animationId);
+ // If the animation is deferred, just remove the deferred animation
+ // entry as two animations cannot animate the same value concurrently.
+ m_deferredAnimationForValues.erase(nodeTag);
+ } else {
+ // Since only one animation can be active at a time, there shouldn't
+ // be any stopped animations for the value node if the animation has
+ // not been deferred.
+ assert(!m_valuesWithStoppedAnimation.count(nodeTag));
+ // In this case, add the value tag to the set of values with stopped
+ // animations. This is used to optimize the lookup when determining
+ // if an animation needs to be deferred (rather than iterating over
+ // the map of pending completion animations).
+ m_valuesWithStoppedAnimation.insert(nodeTag);
+ }
}
}
@@ -168,6 +193,7 @@ void NativeAnimatedNodeManager::RestartTrackingAnimatedNode(
const std::shared_ptr &manager) {
if (m_activeAnimations.count(animationId)) {
if (const auto animation = m_activeAnimations.at(animationId).get()) {
+ assert(animation->UseComposition());
auto const animatedValueTag = animation->AnimatedValueTag();
auto const &animationConfig = animation->AnimationConfig();
auto const endCallback = animation->EndCallback();
@@ -273,14 +299,19 @@ void NativeAnimatedNodeManager::StartAnimatingNode(
// If the animated value node has any stopped animations, defer start until
// all stopped animations fire completion callback and have latest values.
if (m_activeAnimations.count(animationId)) {
- if (m_valuesWithStoppedAnimation.count(animatedNodeTag)) {
- // Since only one animation can be active per value at a time, there will
- // not be any other deferred animations for the value node.
- assert(!m_deferredAnimationForValues.count(animatedNodeTag));
- // Add the animation to the deferred animation map for the value tag.
- m_deferredAnimationForValues.insert({animatedNodeTag, animationId});
+ const auto &animation = m_activeAnimations.at(animationId);
+ if (animation->UseComposition()) {
+ if (m_valuesWithStoppedAnimation.count(animatedNodeTag)) {
+ // Since only one animation can be active per value at a time, there will
+ // not be any other deferred animations for the value node.
+ assert(!m_deferredAnimationForValues.count(animatedNodeTag));
+ // Add the animation to the deferred animation map for the value tag.
+ m_deferredAnimationForValues.insert({animatedNodeTag, animationId});
+ } else {
+ StartAnimationAndTrackingNodes(animationId, animatedNodeTag, manager);
+ }
} else {
- StartAnimationAndTrackingNodes(animationId, animatedNodeTag, manager);
+ EnsureRendering();
}
}
}
@@ -290,17 +321,27 @@ void NativeAnimatedNodeManager::DropAnimatedNode(int64_t tag) {
m_propsNodes.erase(tag);
m_styleNodes.erase(tag);
m_transformNodes.erase(tag);
+ m_updatedNodes.erase(tag);
}
void NativeAnimatedNodeManager::SetAnimatedNodeValue(int64_t tag, double value) {
if (const auto valueNode = m_valueNodes.at(tag).get()) {
valueNode->RawValue(static_cast(value));
+ if (!valueNode->UseComposition()) {
+ StopAnimationsForNode(tag);
+ m_updatedNodes.insert(tag);
+ EnsureRendering();
+ }
}
}
void NativeAnimatedNodeManager::SetAnimatedNodeOffset(int64_t tag, double offset) {
if (const auto valueNode = m_valueNodes.at(tag).get()) {
valueNode->Offset(static_cast(offset));
+ if (!valueNode->UseComposition()) {
+ m_updatedNodes.insert(tag);
+ EnsureRendering();
+ }
}
}
@@ -316,6 +357,18 @@ void NativeAnimatedNodeManager::ExtractAnimatedNodeOffset(int64_t tag) {
}
}
+void NativeAnimatedNodeManager::StartListeningToAnimatedNodeValue(int64_t tag, const ValueListenerCallback &callback) {
+ if (const auto valueNode = m_valueNodes.at(tag).get()) {
+ valueNode->ValueListener(callback);
+ }
+}
+
+void NativeAnimatedNodeManager::StopListeningToAnimatedNodeValue(int64_t tag) {
+ if (const auto valueNode = m_valueNodes.at(tag).get()) {
+ valueNode->ValueListener(nullptr);
+ }
+}
+
void NativeAnimatedNodeManager::AddAnimatedEventToView(
int64_t viewTag,
const std::string &eventName,
@@ -374,6 +427,9 @@ void NativeAnimatedNodeManager::ProcessDelayedPropsNodes() {
void NativeAnimatedNodeManager::AddDelayedPropsNode(
int64_t propsNodeTag,
const winrt::Microsoft::ReactNative::ReactContext &context) {
+#if DEBUG
+ assert(m_propsNodes.at(propsNodeTag)->UseComposition());
+#endif
m_delayedPropsNodes.push_back(propsNodeTag);
if (m_delayedPropsNodes.size() <= 1) {
if (const auto uiManger = Microsoft::ReactNative::GetNativeUIManager(context).lock()) {
@@ -478,4 +534,176 @@ void NativeAnimatedNodeManager::StartAnimationAndTrackingNodes(
}
}
}
+
+void NativeAnimatedNodeManager::RunUpdates(winrt::TimeSpan renderingTime) {
+ auto hasFinishedAnimations = false;
+ std::unordered_set updatingNodes{};
+ updatingNodes = std::move(m_updatedNodes);
+
+ // Increment animation drivers
+ for (auto id : m_activeAnimationIds) {
+ auto &animation = m_activeAnimations.at(id);
+ animation->RunAnimationStep(renderingTime);
+ updatingNodes.insert(animation->AnimatedValueTag());
+ if (animation->IsComplete()) {
+ hasFinishedAnimations = true;
+ }
+ }
+
+ UpdateNodes(updatingNodes);
+
+ if (hasFinishedAnimations) {
+ for (auto id : m_activeAnimationIds) {
+ auto &animation = m_activeAnimations.at(id);
+ if (animation->IsComplete()) {
+ animation->DoCallback(true);
+ m_activeAnimations.erase(id);
+ }
+ }
+ }
+}
+
+void NativeAnimatedNodeManager::EnsureRendering() {
+ m_renderingRevoker =
+ xaml::Media::CompositionTarget::Rendering(winrt::auto_revoke, {this, &NativeAnimatedNodeManager::OnRendering});
+}
+
+void NativeAnimatedNodeManager::OnRendering(winrt::IInspectable const &sender, winrt::IInspectable const &args) {
+ // The `UpdateActiveAnimationIds` method only tracks animations where
+ // composition is not used, so if only UI.Composition animations are active,
+ // this rendering callback will not run.
+ UpdateActiveAnimationIds();
+ if (m_activeAnimationIds.size() > 0 || m_updatedNodes.size() > 0) {
+ if (const auto renderingArgs = args.try_as()) {
+ RunUpdates(renderingArgs.RenderingTime());
+ }
+ } else {
+ m_renderingRevoker.revoke();
+ }
+}
+
+void NativeAnimatedNodeManager::StopAnimationsForNode(int64_t tag) {
+ UpdateActiveAnimationIds();
+ for (auto id : m_activeAnimationIds) {
+ auto &animation = m_activeAnimations.at(id);
+ if (tag == animation->AnimatedValueTag()) {
+ animation->DoCallback(false);
+ m_activeAnimations.erase(id);
+ }
+ }
+}
+
+void NativeAnimatedNodeManager::UpdateActiveAnimationIds() {
+ m_activeAnimationIds.clear();
+ for (const auto &pair : m_activeAnimations) {
+ if (!pair.second->UseComposition()) {
+ m_activeAnimationIds.push_back(pair.first);
+ }
+ }
+}
+
+void NativeAnimatedNodeManager::UpdateNodes(std::unordered_set &nodes) {
+ auto activeNodesCount = 0;
+ auto updatedNodesCount = 0;
+
+ // BFS state
+ std::unordered_map bfsColors;
+ std::unordered_map incomingNodeCounts;
+
+ // STEP 1.
+ // BFS over graph of nodes starting from IDs in `nodes` argument and IDs that are attached to
+ // active animations (from `m_activeAnimations)`. Update `incomingNodeCounts` map for each node
+ // during that BFS. Store number of visited nodes in `activeNodesCount`. We "execute" active
+ // animations as a part of this step.
+
+ m_animatedGraphBFSColor++; /* use new color */
+ if (m_animatedGraphBFSColor == 0) {
+ // value "0" is used as an initial color for a new node, using it in BFS may cause some nodes to be skipped.
+ m_animatedGraphBFSColor++;
+ }
+
+ std::queue nodesQueue{};
+ for (auto id : nodes) {
+ if (!bfsColors.count(id) || bfsColors.at(id) != m_animatedGraphBFSColor) {
+ bfsColors[id] = m_animatedGraphBFSColor;
+ activeNodesCount++;
+ nodesQueue.push(id);
+ }
+ }
+
+ while (nodesQueue.size() > 0) {
+ auto id = nodesQueue.front();
+ nodesQueue.pop();
+ if (auto node = GetAnimatedNode(id)) {
+ for (auto &childId : node->Children()) {
+ if (!incomingNodeCounts.count(childId)) {
+ incomingNodeCounts[childId] = 1;
+ } else {
+ incomingNodeCounts.at(childId)++;
+ }
+
+ if (!bfsColors.count(childId) || bfsColors.at(childId) != m_animatedGraphBFSColor) {
+ bfsColors[childId] = m_animatedGraphBFSColor;
+ activeNodesCount++;
+ nodesQueue.push(childId);
+ }
+ }
+ }
+ }
+
+ // STEP 2
+ // BFS over the graph of active nodes in topological order -> visit node only when all its
+ // "predecessors" in the graph have already been visited. It is important to visit nodes in that
+ // order as they may often use values of their predecessors in order to calculate "next state"
+ // of their own. We start by determining the starting set of nodes by looking for nodes with
+ // `incomingNodeCounts[id] = 0` (those can only be the ones that we start BFS in the previous
+ // step). We store number of visited nodes in this step in `updatedNodesCount`
+
+ m_animatedGraphBFSColor++;
+ if (m_animatedGraphBFSColor == 0) {
+ // see reasoning for this check a few lines above
+ m_animatedGraphBFSColor++;
+ }
+
+ // find nodes with zero "incoming nodes", those can be either nodes from `m_updatedNodes` or
+ // ones connected to active animations
+ for (auto id : nodes) {
+ if (!incomingNodeCounts.count(id) ||
+ incomingNodeCounts.at(id) == 0 && bfsColors.at(id) != m_animatedGraphBFSColor) {
+ bfsColors[id] = m_animatedGraphBFSColor;
+ updatedNodesCount++;
+ nodesQueue.push(id);
+ }
+ }
+
+ // Run main "update" loop
+ while (nodesQueue.size() > 0) {
+ auto id = nodesQueue.front();
+ nodesQueue.pop();
+ if (auto node = GetAnimatedNode(id)) {
+ node->Update();
+ if (auto propsNode = GetPropsAnimatedNode(id)) {
+ propsNode->UpdateView();
+ } else if (auto valueNode = GetValueAnimatedNode(id)) {
+ valueNode->OnValueUpdate();
+ }
+
+ for (auto &childId : node->Children()) {
+ auto &incomingCount = incomingNodeCounts.at(childId);
+ auto &bfsColor = bfsColors.at(childId);
+ incomingCount--;
+ if (bfsColor != m_animatedGraphBFSColor && incomingCount == 0) {
+ bfsColor = m_animatedGraphBFSColor;
+ updatedNodesCount++;
+ nodesQueue.push(childId);
+ }
+ }
+ }
+ }
+
+ // Verify that we've visited *all* active nodes. Throw otherwise as this would mean there is a
+ // cycle in animated node graph. We also take advantage of the fact that all active nodes are
+ // visited in the step above so that `incomingNodeCounts` for all node IDs are set to zero
+ assert(activeNodesCount == updatedNodesCount);
+}
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedNodeManager.h b/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedNodeManager.h
index e5198b24de1..3a0d1fcdb1c 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedNodeManager.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedNodeManager.h
@@ -5,6 +5,7 @@
// Licensed under the MIT License.
#include
+#include
#include
#include
#include "AnimatedNode.h"
@@ -30,6 +31,7 @@ namespace Microsoft::ReactNative {
///
typedef std::function EndCallback;
+typedef std::function ValueListenerCallback;
class AnimatedNode;
class StyleAnimatedNode;
@@ -49,6 +51,7 @@ class NativeAnimatedNodeManager {
void GetValue(int64_t animatedNodeTag, std::function const &endCallback);
void ConnectAnimatedNodeToView(int64_t propsNodeTag, int64_t viewTag);
void DisconnectAnimatedNodeToView(int64_t propsNodeTag, int64_t viewTag);
+ void RestoreDefaultValues(int64_t tag);
void ConnectAnimatedNode(int64_t parentNodeTag, int64_t childNodeTag);
void DisconnectAnimatedNode(int64_t parentNodeTag, int64_t childNodeTag);
void StopAnimation(int64_t animationId, bool isTrackingAnimation = false);
@@ -75,6 +78,8 @@ class NativeAnimatedNodeManager {
void SetAnimatedNodeOffset(int64_t tag, double offset);
void FlattenAnimatedNodeOffset(int64_t tag);
void ExtractAnimatedNodeOffset(int64_t tag);
+ void StartListeningToAnimatedNodeValue(int64_t tag, const ValueListenerCallback &callback);
+ void StopListeningToAnimatedNodeValue(int64_t tag);
void AddAnimatedEventToView(
int64_t viewTag,
const std::string &eventName,
@@ -102,6 +107,13 @@ class NativeAnimatedNodeManager {
const std::shared_ptr &manager);
private:
+ void EnsureRendering();
+ void OnRendering(winrt::IInspectable const &sender, winrt::IInspectable const &args);
+ void RunUpdates(winrt::TimeSpan renderingTime);
+ void StopAnimationsForNode(int64_t tag);
+ void UpdateActiveAnimationIds();
+ void UpdateNodes(std::unordered_set &nodes);
+
std::unordered_map> m_valueNodes{};
std::unordered_map> m_propsNodes{};
std::unordered_map> m_styleNodes{};
@@ -116,6 +128,11 @@ class NativeAnimatedNodeManager {
std::vector> m_trackingAndLeadNodeTags{};
std::vector m_delayedPropsNodes{};
+ std::unordered_set m_updatedNodes{};
+ std::vector m_activeAnimationIds{};
+ int64_t m_animatedGraphBFSColor{};
+ xaml::Media::CompositionTarget::Rendering_revoker m_renderingRevoker;
+
static constexpr std::string_view s_toValueIdName{"toValue"};
static constexpr std::string_view s_framesName{"frames"};
static constexpr std::string_view s_dynamicToValuesName{"dynamicToValues"};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.cpp
index a0748d68243..daa0f63f79d 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.cpp
@@ -18,25 +18,29 @@ PropsAnimatedNode::PropsAnimatedNode(
const winrt::Microsoft::ReactNative::JSValueObject &config,
const winrt::Microsoft::ReactNative::ReactContext &context,
const std::shared_ptr &manager)
- : AnimatedNode(tag, manager), m_context(context) {
+ : AnimatedNode(tag, config, manager), m_context(context) {
for (const auto &entry : config["props"].AsObject()) {
- m_propMapping.insert({entry.first, static_cast(entry.second.AsDouble())});
+ const auto inputTag = entry.second.AsInt64();
+ m_propMapping.insert({entry.first, inputTag});
}
- auto compositor = Microsoft::ReactNative::GetCompositor();
- m_subchannelPropertySet = compositor.CreatePropertySet();
- m_subchannelPropertySet.InsertScalar(L"TranslationX", 0.0f);
- m_subchannelPropertySet.InsertScalar(L"TranslationY", 0.0f);
- m_subchannelPropertySet.InsertScalar(L"ScaleX", 1.0f);
- m_subchannelPropertySet.InsertScalar(L"ScaleY", 1.0f);
- m_translationCombined =
- compositor.CreateExpressionAnimation(L"Vector3(subchannels.TranslationX, subchannels.TranslationY, 0.0)");
- m_translationCombined.SetReferenceParameter(L"subchannels", m_subchannelPropertySet);
- m_translationCombined.Target(L"Translation");
+ if (m_useComposition) {
+ auto compositor = Microsoft::ReactNative::GetCompositor();
+ m_subchannelPropertySet = compositor.CreatePropertySet();
+ m_subchannelPropertySet.InsertScalar(L"TranslationX", 0.0f);
+ m_subchannelPropertySet.InsertScalar(L"TranslationY", 0.0f);
+ m_subchannelPropertySet.InsertScalar(L"ScaleX", 1.0f);
+ m_subchannelPropertySet.InsertScalar(L"ScaleY", 1.0f);
- m_scaleCombined = compositor.CreateExpressionAnimation(L"Vector3(subchannels.ScaleX, subchannels.ScaleY, 1.0)");
- m_scaleCombined.SetReferenceParameter(L"subchannels", m_subchannelPropertySet);
- m_scaleCombined.Target(L"Scale");
+ m_translationCombined =
+ compositor.CreateExpressionAnimation(L"Vector3(subchannels.TranslationX, subchannels.TranslationY, 0.0)");
+ m_translationCombined.SetReferenceParameter(L"subchannels", m_subchannelPropertySet);
+ m_translationCombined.Target(L"Translation");
+
+ m_scaleCombined = compositor.CreateExpressionAnimation(L"Vector3(subchannels.ScaleX, subchannels.ScaleY, 1.0)");
+ m_scaleCombined.SetReferenceParameter(L"subchannels", m_subchannelPropertySet);
+ m_scaleCombined.Target(L"Scale");
+ }
}
void PropsAnimatedNode::ConnectToView(int64_t viewTag) {
@@ -46,7 +50,10 @@ void PropsAnimatedNode::ConnectToView(int64_t viewTag) {
return;
}
m_connectedViewTag = viewTag;
- UpdateView();
+
+ if (m_useComposition) {
+ UpdateView();
+ }
}
void PropsAnimatedNode::DisconnectFromView(int64_t viewTag) {
@@ -58,26 +65,36 @@ void PropsAnimatedNode::DisconnectFromView(int64_t viewTag) {
return;
}
- std::vector keys{};
- for (const auto &anim : m_expressionAnimations) {
- keys.push_back(anim.first);
- }
- for (const auto &key : keys) {
- DisposeCompletedAnimation(key);
- }
+ if (m_useComposition) {
+ std::vector keys{};
+ for (const auto &anim : m_expressionAnimations) {
+ keys.push_back(anim.first);
+ }
+ for (const auto &key : keys) {
+ DisposeCompletedAnimation(key);
+ }
- if (m_centerPointAnimation) {
- if (const auto target = GetUIElement()) {
- target.StopAnimation(m_centerPointAnimation);
+ if (m_centerPointAnimation) {
+ if (const auto target = GetUIElement()) {
+ target.StopAnimation(m_centerPointAnimation);
+ }
+ m_centerPointAnimation = nullptr;
}
- m_centerPointAnimation = nullptr;
+ m_needsCenterPointAnimation = false;
}
m_connectedViewTag = s_connectedViewTagUnset;
- m_needsCenterPointAnimation = false;
}
-void PropsAnimatedNode::RestoreDefaultValues() {}
+void PropsAnimatedNode::RestoreDefaultValues() {
+ if (!m_useComposition) {
+ for (const auto &entry : m_props) {
+ m_props[entry.first] = nullptr;
+ }
+
+ CommitProps();
+ }
+}
void PropsAnimatedNode::UpdateView() {
if (m_connectedViewTag == s_connectedViewTagUnset) {
@@ -87,19 +104,31 @@ void PropsAnimatedNode::UpdateView() {
if (const auto manager = std::shared_ptr(m_manager)) {
for (const auto &entry : m_propMapping) {
if (const auto &styleNode = manager->GetStyleAnimatedNode(entry.second)) {
- for (const auto &styleEntry : styleNode->GetMapping()) {
- MakeAnimation(styleEntry.second, styleEntry.first);
+ if (m_useComposition) {
+ for (const auto &styleEntry : styleNode->GetMapping()) {
+ MakeAnimation(styleEntry.second, styleEntry.first);
+ }
+ } else {
+ styleNode->CollectViewUpdates(m_props);
}
} else if (const auto &valueNode = manager->GetValueAnimatedNode(entry.second)) {
- const auto &facade = StringToFacadeType(entry.first);
- if (facade != FacadeType::None) {
- MakeAnimation(entry.second, facade);
+ if (m_useComposition) {
+ const auto &facade = StringToFacadeType(entry.first);
+ if (facade != FacadeType::None) {
+ MakeAnimation(entry.second, facade);
+ }
+ } else {
+ m_props[entry.first] = valueNode->Value();
}
}
}
}
- StartAnimations();
+ if (m_useComposition) {
+ StartAnimations();
+ } else {
+ CommitProps();
+ }
}
static void EnsureUIElementDirtyForRender(xaml::UIElement uiElement) {
@@ -117,6 +146,7 @@ static void EnsureUIElementDirtyForRender(xaml::UIElement uiElement) {
}
void PropsAnimatedNode::StartAnimations() {
+ assert(m_useComposition);
if (m_expressionAnimations.size()) {
if (const auto uiElement = GetUIElement()) {
// Work around for https://github.com/microsoft/microsoft-ui-xaml/issues/2511
@@ -161,6 +191,7 @@ void PropsAnimatedNode::StartAnimations() {
}
void PropsAnimatedNode::DisposeCompletedAnimation(int64_t valueTag) {
+ assert(m_useComposition);
/*
if (m_expressionAnimations.count(valueTag)) {
if (const auto target = GetUIElement()) {
@@ -180,6 +211,7 @@ void PropsAnimatedNode::DisposeCompletedAnimation(int64_t valueTag) {
}
void PropsAnimatedNode::ResumeSuspendedAnimations(int64_t valueTag) {
+ assert(m_useComposition);
/*
const auto iterator =
std::find(m_suspendedExpressionAnimationTags.begin(), m_suspendedExpressionAnimationTags.end(), valueTag);
@@ -286,4 +318,12 @@ xaml::UIElement PropsAnimatedNode::GetUIElement() {
}
return nullptr;
}
+
+void PropsAnimatedNode::CommitProps() {
+ if (const auto node = GetShadowNodeBase()) {
+ if (!node->m_zombie) {
+ node->updateProperties(m_props);
+ }
+ }
+}
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.h
index b88836b8acc..d9cc7bf60aa 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.h
@@ -7,8 +7,8 @@
#include
#include
#include "AnimatedNode.h"
-
#include "FacadeType.h"
+#include "JSValue.h"
namespace Microsoft::ReactNative {
struct ShadowNodeBase;
@@ -31,13 +31,14 @@ class PropsAnimatedNode final : public AnimatedNode {
void ResumeSuspendedAnimations(int64_t valueTag);
private:
+ void CommitProps();
void MakeAnimation(int64_t valueNodeTag, FacadeType facadeType);
Microsoft::ReactNative::ShadowNodeBase *GetShadowNodeBase();
xaml::UIElement GetUIElement();
winrt::Microsoft::ReactNative::ReactContext m_context;
std::map m_propMapping{};
- folly::dynamic m_propMap{};
+ winrt::Microsoft::ReactNative::JSValueObject m_props{};
int64_t m_connectedViewTag{s_connectedViewTagUnset};
std::unordered_map m_expressionAnimations{};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/SpringAnimationDriver.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/SpringAnimationDriver.cpp
index 0cd73f2fd48..b563ab0e42c 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/SpringAnimationDriver.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/SpringAnimationDriver.cpp
@@ -8,6 +8,9 @@
#include "SpringAnimationDriver.h"
namespace Microsoft::ReactNative {
+
+static constexpr auto MAX_DELTA_TIME_MS = 4.0 * 1000.0 / 60.0;
+
SpringAnimationDriver::SpringAnimationDriver(
int64_t id,
int64_t animatedValueTag,
@@ -38,10 +41,13 @@ bool SpringAnimationDriver::IsAnimationDone(
}
std::tuple SpringAnimationDriver::GetValueAndVelocityForTime(double time) {
- const auto toValue = [this, time]() {
- const auto frameFromTime = static_cast(time * 60.0);
- if (frameFromTime < static_cast(m_dynamicToValues.size())) {
- return m_startValue + (m_dynamicToValues[frameFromTime].AsDouble() * (m_endValue - m_startValue));
+ const auto startValue = m_originalValue.value();
+ const auto toValue = [this, startValue, time]() {
+ if (m_useComposition) {
+ const auto frameFromTime = static_cast(time * 60.0);
+ if (frameFromTime < static_cast(m_dynamicToValues.size())) {
+ return startValue + (m_dynamicToValues[frameFromTime].AsDouble() * (m_endValue - startValue));
+ }
}
return m_endValue;
}();
@@ -53,7 +59,7 @@ std::tuple SpringAnimationDriver::GetValueAndVelocityForTime(doub
const auto zeta = c / (2 * std::sqrt(k * m));
const auto omega0 = std::sqrt(k / m);
const auto omega1 = omega0 * std::sqrt(1.0 - (zeta * zeta));
- const auto x0 = toValue - m_startValue;
+ const auto x0 = toValue - startValue;
if (zeta < 1) {
const auto envelope = std::exp(-zeta * omega0 * time);
@@ -72,15 +78,62 @@ std::tuple SpringAnimationDriver::GetValueAndVelocityForTime(doub
}
}
+bool SpringAnimationDriver::Update(double timeDeltaMs, bool restarting) {
+ assert(!m_useComposition);
+ if (const auto node = GetAnimatedValue()) {
+ if (restarting) {
+ if (!m_originalValue) {
+ m_originalValue = node->RawValue();
+ } else {
+ node->RawValue(m_originalValue.value());
+ }
+
+ // Spring animations run a frame behind JS driven animations if we do
+ // not start the first frame at 16ms.
+ m_lastTime = timeDeltaMs - s_frameDurationMs;
+ m_timeAccumulator = 0.0;
+ }
+
+ // clamp the amount of timeDeltaMs to avoid stuttering in the UI.
+ // We should be able to catch up in a subsequent advance if necessary.
+ auto adjustedDeltaTime = timeDeltaMs - m_lastTime;
+ if (adjustedDeltaTime > MAX_DELTA_TIME_MS) {
+ adjustedDeltaTime = MAX_DELTA_TIME_MS;
+ }
+ m_timeAccumulator += adjustedDeltaTime;
+ m_lastTime = timeDeltaMs;
+
+ auto [value, velocity] = GetValueAndVelocityForTime(m_timeAccumulator / 1000.0);
+
+ auto isComplete = false;
+ if (IsAnimationDone(value, std::nullopt, velocity)) {
+ if (m_springStiffness > 0) {
+ value = static_cast(m_endValue);
+ } else {
+ m_endValue = value;
+ }
+
+ isComplete = true;
+ }
+
+ node->RawValue(value);
+
+ return isComplete;
+ }
+
+ return true;
+}
+
bool SpringAnimationDriver::IsAtRest(double currentVelocity, double currentValue, double endValue) {
return std::abs(currentVelocity) <= m_restSpeedThreshold &&
(std::abs(currentValue - endValue) <= m_displacementFromRestThreshold || m_springStiffness == 0);
}
bool SpringAnimationDriver::IsOvershooting(double currentValue) {
+ const auto startValue = m_originalValue.value();
return m_springStiffness > 0 &&
- ((m_startValue < m_endValue && currentValue > m_endValue) ||
- (m_startValue > m_endValue && currentValue < m_endValue));
+ ((startValue < m_endValue && currentValue > m_endValue) ||
+ (startValue > m_endValue && currentValue < m_endValue));
}
double SpringAnimationDriver::ToValue() {
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/SpringAnimationDriver.h b/vnext/Microsoft.ReactNative/Modules/Animated/SpringAnimationDriver.h
index a8e447df1e5..838ac2a69df 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/SpringAnimationDriver.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/SpringAnimationDriver.h
@@ -21,6 +21,7 @@ class SpringAnimationDriver : public CalculatedAnimationDriver {
double ToValue() override;
protected:
+ bool Update(double timeDeltaMs, bool restarting) override;
std::tuple GetValueAndVelocityForTime(double time) override;
bool IsAnimationDone(double currentValue, std::optional previousValue, double currentVelocity) override;
@@ -39,6 +40,9 @@ class SpringAnimationDriver : public CalculatedAnimationDriver {
int m_iterations{0};
winrt::Microsoft::ReactNative::JSValueArray m_dynamicToValues{};
+ double m_lastTime{0};
+ double m_timeAccumulator{0};
+
static constexpr std::string_view s_springStiffnessParameterName{"stiffness"};
static constexpr std::string_view s_springDampingParameterName{"damping"};
static constexpr std::string_view s_springMassParameterName{"mass"};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.cpp
index 78617dfafb5..ef5499166d6 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.cpp
@@ -11,15 +11,30 @@ StyleAnimatedNode::StyleAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : AnimatedNode(tag, manager) {
+ : AnimatedNode(tag, config, manager) {
for (const auto &entry : config[s_styleName].AsObject()) {
- m_propMapping.insert({entry.first, static_cast(entry.second.AsDouble())});
+ const auto inputTag = entry.second.AsInt64();
+ assert(HasCompatibleAnimationDriver(inputTag));
+ m_propMapping.insert({entry.first, inputTag});
}
}
-void StyleAnimatedNode::CollectViewUpdates(const folly::dynamic & /*propsMap*/) {}
+void StyleAnimatedNode::CollectViewUpdates(winrt::Microsoft::ReactNative::JSValueObject &propsMap) {
+ assert(!m_useComposition);
+ auto rawValue = 0.0;
+ for (const auto &propMapping : m_propMapping) {
+ if (const auto manager = m_manager.lock()) {
+ if (const auto transformNode = manager->GetTransformAnimatedNode(propMapping.second)) {
+ transformNode->CollectViewUpdates(propsMap);
+ } else if (const auto node = manager->GetValueAnimatedNode(propMapping.second)) {
+ propsMap[propMapping.first] = node->Value();
+ }
+ }
+ }
+}
std::unordered_map StyleAnimatedNode::GetMapping() {
+ assert(m_useComposition);
std::unordered_map mapping;
for (const auto &prop : m_propMapping) {
if (const auto manager = m_manager.lock()) {
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.h
index 8adc94d60c6..0bd79154ffb 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.h
@@ -5,6 +5,7 @@
#include
#include "AnimatedNode.h"
#include "FacadeType.h"
+#include "JSValue.h"
namespace Microsoft::ReactNative {
class StyleAnimatedNode final : public AnimatedNode {
@@ -13,7 +14,7 @@ class StyleAnimatedNode final : public AnimatedNode {
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager);
- void CollectViewUpdates(const folly::dynamic &propsMap);
+ void CollectViewUpdates(winrt::Microsoft::ReactNative::JSValueObject &propsMap);
std::unordered_map GetMapping();
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/SubtractionAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/SubtractionAnimatedNode.cpp
index 3fef02c348c..51ce24f1680 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/SubtractionAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/SubtractionAnimatedNode.cpp
@@ -11,30 +11,52 @@ SubtractionAnimatedNode::SubtractionAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : ValueAnimatedNode(tag, manager) {
+ : ValueAnimatedNode(tag, config, manager) {
for (const auto &inputNode : config[s_inputName].AsArray()) {
+ const auto inputTag = inputNode.AsInt64();
+ assert(HasCompatibleAnimationDriver(inputTag));
if (m_firstInput == s_firstInputUnset) {
- m_firstInput = static_cast(inputNode.AsDouble());
+ m_firstInput = inputTag;
} else {
- m_inputNodes.insert(static_cast(inputNode.AsDouble()));
+ m_inputNodes.insert(inputTag);
}
}
- m_propertySet.StartAnimation(s_valueName, [firstNode = m_firstInput, nodes = m_inputNodes, manager]() {
- const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
-
- anim.Expression([firstNode, nodes, manager, anim]() {
- anim.SetReferenceParameter(s_baseName, manager->GetValueAnimatedNode(firstNode)->PropertySet());
- winrt::hstring expr = static_cast(L"(") + s_baseName + L"." + s_valueName + L" + " + s_baseName +
- L"." + s_offsetName + L")";
- for (const auto tag : nodes) {
- const auto identifier = L"n" + std::to_wstring(tag);
- anim.SetReferenceParameter(identifier, manager->GetValueAnimatedNode(tag)->PropertySet());
- expr = expr + L" - (" + identifier + L"." + s_valueName + L" + " + identifier + L"." + s_offsetName + L")";
- }
- return expr;
+ if (m_useComposition) {
+ m_propertySet.StartAnimation(s_valueName, [firstNode = m_firstInput, nodes = m_inputNodes, manager]() {
+ const auto anim = Microsoft::ReactNative::GetCompositor().CreateExpressionAnimation();
+
+ anim.Expression([firstNode, nodes, manager, anim]() {
+ anim.SetReferenceParameter(s_baseName, manager->GetValueAnimatedNode(firstNode)->PropertySet());
+ winrt::hstring expr = static_cast(L"(") + s_baseName + L"." + s_valueName + L" + " +
+ s_baseName + L"." + s_offsetName + L")";
+ for (const auto tag : nodes) {
+ const auto identifier = L"n" + std::to_wstring(tag);
+ anim.SetReferenceParameter(identifier, manager->GetValueAnimatedNode(tag)->PropertySet());
+ expr = expr + L" - (" + identifier + L"." + s_valueName + L" + " + identifier + L"." + s_offsetName + L")";
+ }
+ return expr;
+ }());
+ return anim;
}());
- return anim;
- }());
+ }
+}
+
+void SubtractionAnimatedNode::Update() {
+ assert(!m_useComposition);
+ if (const auto manager = m_manager.lock()) {
+ auto rawValue = 0.0;
+ if (const auto firstNode = manager->GetValueAnimatedNode(m_firstInput)) {
+ rawValue = firstNode->Value();
+ }
+
+ for (const auto &tag : m_inputNodes) {
+ if (const auto node = manager->GetValueAnimatedNode(tag)) {
+ rawValue -= node->Value();
+ }
+ }
+
+ RawValue(rawValue);
+ }
}
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/SubtractionAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/SubtractionAnimatedNode.h
index 359cde6dac3..65e8febe1d2 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/SubtractionAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/SubtractionAnimatedNode.h
@@ -13,6 +13,8 @@ class SubtractionAnimatedNode final : public ValueAnimatedNode {
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager);
+ virtual void Update() override;
+
private:
int64_t m_firstInput{s_firstInputUnset};
std::unordered_set m_inputNodes{};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/TrackingAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/TrackingAnimatedNode.cpp
index c0879f3c6a1..3de098e810f 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/TrackingAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/TrackingAnimatedNode.cpp
@@ -11,13 +11,18 @@ TrackingAnimatedNode::TrackingAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : AnimatedNode(tag, manager) {
- m_animationId = static_cast(config[s_animationIdName].AsDouble());
- m_toValueId = static_cast(config[s_toValueIdName].AsDouble());
- m_valueId = static_cast(config[s_valueIdName].AsDouble());
+ : AnimatedNode(tag, config, manager) {
+ m_animationId = config[s_animationIdName].AsInt64();
+ m_toValueId = config[s_toValueIdName].AsInt64();
+ m_valueId = config[s_valueIdName].AsInt64();
m_animationConfig = std::move(config[s_animationConfigName].AsObject().Copy());
+ if (config.count(s_platformConfigName) && !m_animationConfig.count(s_platformConfigName)) {
+ m_animationConfig[s_platformConfigName] = std::move(config[s_platformConfigName].Copy());
+ }
- StartAnimation();
+ if (m_useComposition) {
+ StartAnimation();
+ }
}
void TrackingAnimatedNode::Update() {
@@ -30,10 +35,14 @@ void TrackingAnimatedNode::StartAnimation() {
// In case the animation is already running, we need to stop it to free up the
// animationId key in the active animations map in the animation manager.
strongManager->StopAnimation(m_animationId, true);
- toValueNode->AddActiveTrackingNode(m_tag);
m_animationConfig[s_toValueIdName] = toValueNode->Value();
- strongManager->StartTrackingAnimatedNode(
- m_animationId, m_valueId, m_toValueId, m_animationConfig, nullptr, strongManager);
+ if (m_useComposition) {
+ toValueNode->AddActiveTrackingNode(m_tag);
+ strongManager->StartTrackingAnimatedNode(
+ m_animationId, m_valueId, m_toValueId, m_animationConfig, nullptr, strongManager);
+ } else {
+ strongManager->StartAnimatingNode(m_animationId, m_valueId, m_animationConfig, nullptr, strongManager);
+ }
}
}
}
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/TrackingAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/TrackingAnimatedNode.h
index 609f1a67300..7b88740086e 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/TrackingAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/TrackingAnimatedNode.h
@@ -13,11 +13,10 @@ class TrackingAnimatedNode final : public AnimatedNode {
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager);
- void Update() override;
-
- private:
+ virtual void Update() override;
void StartAnimation();
+ private:
int64_t m_animationId{};
int64_t m_toValueId{};
int64_t m_valueId{};
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/TransformAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/TransformAnimatedNode.cpp
index ac6413b1ceb..4fb2ae95536 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/TransformAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/TransformAnimatedNode.cpp
@@ -4,6 +4,7 @@
#include "pch.h"
#include "FacadeType.h"
+#include "NativeAnimatedNodeManager.h"
#include "TransformAnimatedNode.h"
namespace Microsoft::ReactNative {
@@ -11,12 +12,13 @@ TransformAnimatedNode::TransformAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : AnimatedNode(tag, manager) {
+ : AnimatedNode(tag, config, manager) {
for (const auto &transform : config[s_transformsName].AsArray()) {
const auto property = transform[s_propertyName].AsString();
if (transform[s_typeName].AsString() == s_animatedName) {
- m_transformConfigs.push_back(
- TransformConfig{property, static_cast(transform[s_nodeTagName].AsDouble()), 0});
+ const auto inputTag = transform[s_nodeTagName].AsInt64();
+ assert(HasCompatibleAnimationDriver(inputTag));
+ m_transformConfigs.push_back(TransformConfig{property, inputTag, 0});
} else {
m_transformConfigs.push_back(TransformConfig{property, s_unsetNodeTag, transform[s_valueName].AsDouble()});
}
@@ -24,6 +26,7 @@ TransformAnimatedNode::TransformAnimatedNode(
}
std::unordered_map TransformAnimatedNode::GetMapping() {
+ assert(m_useComposition);
std::unordered_map mapping;
for (const auto &config : m_transformConfigs) {
if (config.nodeTag != s_unsetNodeTag) {
@@ -35,4 +38,29 @@ std::unordered_map TransformAnimatedNode::GetMapping() {
}
return mapping;
}
+
+void TransformAnimatedNode::CollectViewUpdates(winrt::Microsoft::ReactNative::JSValueObject &props) {
+ assert(!m_useComposition);
+ winrt::Microsoft::ReactNative::JSValueArray transforms;
+ if (const auto manager = m_manager.lock()) {
+ for (const auto &transformConfig : m_transformConfigs) {
+ std::optional value;
+ if (transformConfig.nodeTag == s_unsetNodeTag) {
+ value = transformConfig.value;
+ } else {
+ if (const auto node = manager->GetValueAnimatedNode(transformConfig.nodeTag)) {
+ value = node->Value();
+ }
+ }
+
+ if (value) {
+ transforms.emplace_back(
+ winrt::Microsoft::ReactNative::JSValueObject{{transformConfig.property, value.value()}});
+ }
+ }
+ }
+
+ props[s_transformPropName] = std::move(transforms);
+}
+
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/TransformAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/TransformAnimatedNode.h
index 70e37e5462c..ff8fe05bb9c 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/TransformAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/TransformAnimatedNode.h
@@ -5,6 +5,7 @@
#include
#include "AnimatedNode.h"
#include "FacadeType.h"
+#include "JSValue.h"
namespace Microsoft::ReactNative {
struct TransformConfig {
@@ -21,6 +22,7 @@ class TransformAnimatedNode final : public AnimatedNode {
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager);
std::unordered_map GetMapping();
+ void CollectViewUpdates(winrt::Microsoft::ReactNative::JSValueObject &props);
private:
std::vector m_transformConfigs;
@@ -33,5 +35,6 @@ class TransformAnimatedNode final : public AnimatedNode {
static constexpr std::string_view s_animatedName{"animated"};
static constexpr std::string_view s_nodeTagName{"nodeTag"};
static constexpr std::string_view s_valueName{"value"};
+ static constexpr std::string_view s_transformPropName{"transform"};
};
} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.cpp
index 13aee59c5cd..306f2ae1f04 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.cpp
@@ -11,55 +11,70 @@ ValueAnimatedNode::ValueAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager)
- : AnimatedNode(tag, manager) {
- // TODO: Islands - need to get the XamlView associated with this animation in order to
- // use the compositor Microsoft::ReactNative::GetCompositor(xamlView)
- m_propertySet = Microsoft::ReactNative::GetCompositor().CreatePropertySet();
- m_propertySet.InsertScalar(s_valueName, static_cast(config[s_jsValueName].AsDouble()));
- m_propertySet.InsertScalar(s_offsetName, static_cast(config[s_jsOffsetName].AsDouble()));
-}
+ : AnimatedNode(tag, config, manager) {
+ auto value = 0.0;
+ auto offset = 0.0;
+ if (config.count(s_jsValueName) && config.count(s_jsOffsetName)) {
+ value = config[s_jsValueName].AsDouble();
+ offset = config[s_jsOffsetName].AsDouble();
+ }
-ValueAnimatedNode::ValueAnimatedNode(int64_t tag, const std::shared_ptr &manager)
- : AnimatedNode(tag, manager) {
- // TODO: Islands - need to get the XamlView associated with this animation in order to
- // use the compositor Microsoft::ReactNative::GetCompositor(xamlView)
- m_propertySet = Microsoft::ReactNative::GetCompositor().CreatePropertySet();
- m_propertySet.InsertScalar(s_valueName, 0.0);
- m_propertySet.InsertScalar(s_offsetName, 0.0);
+ if (m_useComposition) {
+ // TODO: Islands - need to get the XamlView associated with this animation in order to
+ // use the compositor Microsoft::ReactNative::GetCompositor(xamlView)
+ m_propertySet = Microsoft::ReactNative::GetCompositor().CreatePropertySet();
+ m_propertySet.InsertScalar(s_valueName, static_cast(value));
+ m_propertySet.InsertScalar(s_offsetName, static_cast(offset));
+ } else {
+ m_value = value;
+ m_offset = offset;
+ }
}
double ValueAnimatedNode::RawValue() {
- auto rawValue = 0.0f;
- m_propertySet.TryGetScalar(s_valueName, rawValue);
- return rawValue;
+ if (m_useComposition) {
+ auto rawValue = 0.0f;
+ m_propertySet.TryGetScalar(s_valueName, rawValue);
+ return rawValue;
+ } else {
+ return m_value;
+ }
}
void ValueAnimatedNode::RawValue(double value) {
if (RawValue() != value) {
- m_propertySet.InsertScalar(s_valueName, static_cast(value));
- UpdateTrackingNodes();
+ if (m_useComposition) {
+ m_propertySet.InsertScalar(s_valueName, static_cast(value));
+ UpdateTrackingNodes();
+ } else {
+ m_value = value;
+ }
}
}
double ValueAnimatedNode::Offset() {
- auto offset = 0.0f;
- m_propertySet.TryGetScalar(s_offsetName, offset);
- return offset;
+ if (m_useComposition) {
+ auto offset = 0.0f;
+ m_propertySet.TryGetScalar(s_offsetName, offset);
+ return offset;
+ } else {
+ return m_offset;
+ }
}
void ValueAnimatedNode::Offset(double offset) {
if (Offset() != offset) {
- m_propertySet.InsertScalar(s_offsetName, static_cast(offset));
- UpdateTrackingNodes();
+ if (m_useComposition) {
+ m_propertySet.InsertScalar(s_offsetName, static_cast(offset));
+ UpdateTrackingNodes();
+ } else {
+ m_offset = offset;
+ }
}
}
double ValueAnimatedNode::Value() {
- auto rawValue = 0.0f;
- auto offset = 0.0f;
- m_propertySet.TryGetScalar(s_valueName, rawValue);
- m_propertySet.TryGetScalar(s_offsetName, offset);
- return static_cast(rawValue) + static_cast(offset);
+ return RawValue() + Offset();
}
void ValueAnimatedNode::FlattenOffset() {
@@ -72,15 +87,28 @@ void ValueAnimatedNode::ExtractOffset() {
RawValue(0.0f);
}
+void ValueAnimatedNode::OnValueUpdate() {
+ if (m_valueListener) {
+ m_valueListener(Value());
+ }
+}
+
+void ValueAnimatedNode::ValueListener(const ValueListenerCallback &callback) {
+ m_valueListener = callback;
+}
+
void ValueAnimatedNode::AddDependentPropsNode(int64_t propsNodeTag) {
+ assert(m_useComposition);
m_dependentPropsNodes.insert(propsNodeTag);
}
void ValueAnimatedNode::RemoveDependentPropsNode(int64_t propsNodeTag) {
+ assert(m_useComposition);
m_dependentPropsNodes.erase(propsNodeTag);
}
void ValueAnimatedNode::AddActiveAnimation(int64_t animationTag) {
+ assert(m_useComposition);
m_activeAnimations.insert(animationTag);
if (m_activeAnimations.size() == 1) {
if (const auto manager = m_manager.lock()) {
@@ -93,6 +121,7 @@ void ValueAnimatedNode::AddActiveAnimation(int64_t animationTag) {
}
void ValueAnimatedNode::RemoveActiveAnimation(int64_t animationTag) {
+ assert(m_useComposition);
m_activeAnimations.erase(animationTag);
if (!m_activeAnimations.size()) {
if (const auto manager = m_manager.lock()) {
@@ -105,14 +134,17 @@ void ValueAnimatedNode::RemoveActiveAnimation(int64_t animationTag) {
}
void ValueAnimatedNode::AddActiveTrackingNode(int64_t trackingNodeTag) {
+ assert(m_useComposition);
m_activeTrackingNodes.insert(trackingNodeTag);
}
void ValueAnimatedNode::RemoveActiveTrackingNode(int64_t trackingNodeTag) {
+ assert(m_useComposition);
m_activeTrackingNodes.erase(trackingNodeTag);
}
void ValueAnimatedNode::UpdateTrackingNodes() {
+ assert(m_useComposition);
if (auto const manager = m_manager.lock()) {
for (auto trackingNodeTag : m_activeTrackingNodes) {
if (auto trackingNode = manager->GetTrackingAnimatedNode(trackingNodeTag)) {
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.h
index d73361188c6..6d373641933 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.h
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.h
@@ -12,13 +12,14 @@ using namespace comp;
}
namespace Microsoft::ReactNative {
+typedef std::function ValueListenerCallback;
+
class ValueAnimatedNode : public AnimatedNode {
public:
ValueAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr &manager);
- ValueAnimatedNode(int64_t tag, const std::shared_ptr &manager);
double Value();
double RawValue();
void RawValue(double value);
@@ -26,6 +27,9 @@ class ValueAnimatedNode : public AnimatedNode {
void Offset(double offset);
void FlattenOffset();
void ExtractOffset();
+ void OnValueUpdate();
+ void ValueListener(const ValueListenerCallback &callback);
+
comp::CompositionPropertySet PropertySet() {
return m_propertySet;
};
@@ -42,7 +46,6 @@ class ValueAnimatedNode : public AnimatedNode {
protected:
comp::CompositionPropertySet m_propertySet{nullptr};
-
static constexpr std::string_view s_inputName{"input"};
static constexpr std::string_view s_jsValueName{"value"};
@@ -53,5 +56,8 @@ class ValueAnimatedNode : public AnimatedNode {
std::unordered_set m_dependentPropsNodes{};
std::unordered_set m_activeAnimations{};
std::unordered_set m_activeTrackingNodes{};
+ double m_value{0.0};
+ double m_offset{0.0};
+ ValueListenerCallback m_valueListener{};
};
} // namespace Microsoft::ReactNative