Skip to content

Commit 20dcae8

Browse files
committed
Adds color value interpolation handling to tick-driven NativeAnimated driver
To avoid overcomplicating the base ValueAnimatedNode, this change uses memcpy to stuff the interpolated color value into the double `value` field in the ValueAnimatedNode, and reads it out as a color type if the output type is set.
1 parent 0d9d42c commit 20dcae8

6 files changed

+104
-11
lines changed

vnext/Microsoft.ReactNative/Modules/Animated/ColorAnimatedNode.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
#include "pch.h"
55

66
#include <UI.Composition.h>
7+
#include <Utils/ValueUtils.h>
78
#include "ColorAnimatedNode.h"
89
#include "NativeAnimatedNodeManager.h"
9-
#include <Utils/ValueUtils.h>
1010

1111
namespace Microsoft::ReactNative {
1212
ColorAnimatedNode::ColorAnimatedNode(

vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.cpp

+75-8
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,21 @@
77
#include "ExtrapolationType.h"
88
#include "InterpolationAnimatedNode.h"
99
#include "NativeAnimatedNodeManager.h"
10+
#include "Utils/ValueUtils.h"
1011

1112
namespace Microsoft::ReactNative {
13+
14+
inline int32_t ColorToInt(winrt::Windows::UI::Color color) {
15+
return static_cast<uint8_t>(color.A) << 24 | static_cast<uint8_t>(color.R) << 16 |
16+
static_cast<uint8_t>(color.G) << 8 | static_cast<uint8_t>(color.B);
17+
}
18+
19+
inline uint8_t ScaleByte(uint8_t min, uint8_t max, double ratio) {
20+
const auto scaledValue = min + (max - min) * ratio;
21+
const auto clampedValue = std::clamp(static_cast<uint32_t>(std::round(scaledValue)), 0u, 255u);
22+
return static_cast<uint8_t>(clampedValue);
23+
}
24+
1225
InterpolationAnimatedNode::InterpolationAnimatedNode(
1326
int64_t tag,
1427
const winrt::Microsoft::ReactNative::JSValueObject &config,
@@ -17,8 +30,18 @@ InterpolationAnimatedNode::InterpolationAnimatedNode(
1730
for (const auto &rangeValue : config[s_inputRangeName].AsArray()) {
1831
m_inputRanges.push_back(rangeValue.AsDouble());
1932
}
20-
for (const auto &rangeValue : config[s_outputRangeName].AsArray()) {
21-
m_outputRanges.push_back(rangeValue.AsDouble());
33+
34+
const auto isColorOutput = config[s_outputTypeName].AsString() == s_colorOutputType;
35+
if (!m_useComposition && isColorOutput) {
36+
m_isColorOutput = true;
37+
for (const auto &rangeValue : config[s_outputRangeName].AsArray()) {
38+
m_colorOutputRanges.push_back(ColorFrom(rangeValue));
39+
}
40+
} else {
41+
assert(!isColorOutput && "Color interpolation not supported");
42+
for (const auto &rangeValue : config[s_outputRangeName].AsArray()) {
43+
m_defaultOutputRanges.push_back(rangeValue.AsDouble());
44+
}
2245
}
2346

2447
m_extrapolateLeft = config[s_extrapolateLeftName].AsString();
@@ -33,7 +56,11 @@ void InterpolationAnimatedNode::Update() {
3356

3457
if (const auto manager = m_manager.lock()) {
3558
if (const auto node = manager->GetValueAnimatedNode(m_parentTag)) {
36-
RawValue(InterpolateValue(node->Value()));
59+
if (m_isColorOutput) {
60+
RawValue(InterpolateColor(node->Value()));
61+
} else {
62+
RawValue(InterpolateValue(node->Value()));
63+
}
3764
}
3865
}
3966
}
@@ -95,8 +122,9 @@ comp::ExpressionAnimation InterpolationAnimatedNode::CreateExpressionAnimation(
95122
for (size_t i = 0; i < m_inputRanges.size(); i++) {
96123
animation.SetScalarParameter(s_inputName.data() + std::to_wstring(i), static_cast<float>(m_inputRanges[i]));
97124
}
98-
for (size_t i = 0; i < m_outputRanges.size(); i++) {
99-
animation.SetScalarParameter(s_outputName.data() + std::to_wstring(i), static_cast<float>(m_outputRanges[i]));
125+
for (size_t i = 0; i < m_defaultOutputRanges.size(); i++) {
126+
animation.SetScalarParameter(
127+
s_outputName.data() + std::to_wstring(i), static_cast<float>(m_defaultOutputRanges[i]));
100128
}
101129
return animation;
102130
}
@@ -173,7 +201,7 @@ winrt::hstring InterpolationAnimatedNode::GetRightExpression(
173201
const winrt::hstring &value,
174202
const winrt::hstring &rightInterpolateExpression) {
175203
const auto lastInput = s_inputName.data() + std::to_wstring(m_inputRanges.size() - 1);
176-
const auto lastOutput = s_outputName.data() + std::to_wstring(m_outputRanges.size() - 1);
204+
const auto lastOutput = s_outputName.data() + std::to_wstring(m_defaultOutputRanges.size() - 1);
177205
switch (ExtrapolationTypeFromString(m_extrapolateRight)) {
178206
case ExtrapolationType::Clamp:
179207
return value + L" > " + lastInput + L" ? " + lastOutput + L" : ";
@@ -200,10 +228,49 @@ double InterpolationAnimatedNode::InterpolateValue(double value) {
200228
value,
201229
m_inputRanges[index],
202230
m_inputRanges[index + 1],
203-
m_outputRanges[index],
204-
m_outputRanges[index + 1],
231+
m_defaultOutputRanges[index],
232+
m_defaultOutputRanges[index + 1],
205233
m_extrapolateLeft,
206234
m_extrapolateRight);
207235
}
208236

237+
double InterpolationAnimatedNode::InterpolateColor(double value) {
238+
// Compute range index
239+
size_t index = 1;
240+
for (; index < m_inputRanges.size() - 1; ++index) {
241+
if (m_inputRanges[index] >= value) {
242+
break;
243+
}
244+
}
245+
index--;
246+
247+
double result;
248+
const auto outputMin = m_colorOutputRanges[index];
249+
const auto outputMax = m_colorOutputRanges[index + 1];
250+
const auto outputMinInt = ColorToInt(outputMin);
251+
const auto outputMaxInt = ColorToInt(outputMax);
252+
if (outputMin == outputMax) {
253+
memcpy(&result, &outputMinInt, sizeof(int32_t));
254+
return result;
255+
}
256+
257+
const auto inputMin = m_inputRanges[index];
258+
const auto inputMax = m_inputRanges[index + 1];
259+
if (inputMin == inputMax) {
260+
if (value <= inputMin) {
261+
memcpy(&result, &outputMinInt, sizeof(int32_t));
262+
} else {
263+
memcpy(&result, &outputMaxInt, sizeof(int32_t));
264+
}
265+
return result;
266+
}
267+
268+
const auto ratio = (value - inputMin) / (inputMax - inputMin);
269+
const auto interpolatedColor = ScaleByte(outputMin.A, outputMax.A, ratio) << 24 |
270+
ScaleByte(outputMin.R, outputMax.R, ratio) << 16 | ScaleByte(outputMin.G, outputMax.G, ratio) << 8 |
271+
ScaleByte(outputMin.B, outputMax.B, ratio);
272+
memcpy(&result, &interpolatedColor, sizeof(int32_t));
273+
return result;
274+
}
275+
209276
} // namespace Microsoft::ReactNative

vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ class InterpolationAnimatedNode final : public ValueAnimatedNode {
1717
virtual void OnDetachedFromNode(int64_t animatedNodeTag) override;
1818
virtual void OnAttachToNode(int64_t animatedNodeTag) override;
1919

20+
bool IsColorValue() override {
21+
return m_isColorOutput;
22+
}
23+
2024
static constexpr std::string_view ExtrapolateTypeIdentity = "identity";
2125
static constexpr std::string_view ExtrapolateTypeClamp = "clamp";
2226
static constexpr std::string_view ExtrapolateTypeExtend = "extend";
@@ -35,11 +39,14 @@ class InterpolationAnimatedNode final : public ValueAnimatedNode {
3539
winrt::hstring GetRightExpression(const winrt::hstring &, const winrt::hstring &rightInterpolateExpression);
3640

3741
double InterpolateValue(double value);
42+
double InterpolateColor(double value);
3843

3944
comp::ExpressionAnimation m_rawValueAnimation{nullptr};
4045
comp::ExpressionAnimation m_offsetAnimation{nullptr};
46+
bool m_isColorOutput{false};
4147
std::vector<double> m_inputRanges;
42-
std::vector<double> m_outputRanges;
48+
std::vector<double> m_defaultOutputRanges;
49+
std::vector<winrt::Windows::UI::Color> m_colorOutputRanges;
4350
std::string m_extrapolateLeft;
4451
std::string m_extrapolateRight;
4552

@@ -49,9 +56,12 @@ class InterpolationAnimatedNode final : public ValueAnimatedNode {
4956

5057
static constexpr std::string_view s_inputRangeName{"inputRange"};
5158
static constexpr std::string_view s_outputRangeName{"outputRange"};
59+
static constexpr std::string_view s_outputTypeName{"outputType"};
5260
static constexpr std::string_view s_extrapolateLeftName{"extrapolateLeft"};
5361
static constexpr std::string_view s_extrapolateRightName{"extrapolateRight"};
5462

63+
static constexpr std::string_view s_colorOutputType{"color"};
64+
5565
static constexpr std::wstring_view s_parentPropsName{L"p"};
5666
static constexpr std::wstring_view s_inputName{L"i"};
5767
static constexpr std::wstring_view s_outputName{L"o"};

vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ void PropsAnimatedNode::UpdateView() {
120120
for (const auto &styleEntry : styleNode->GetMapping()) {
121121
MakeAnimation(styleEntry.second, styleEntry.first);
122122
}
123+
} else if (valueNode->IsColorValue()) {
124+
const auto value = valueNode->Value();
125+
int32_t color;
126+
memcpy(&color, &value, sizeof(int32_t));
127+
m_props[entry.first] = color;
123128
} else {
124129
styleNode->CollectViewUpdates(m_props);
125130
}

vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.cpp

+8-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,14 @@ void StyleAnimatedNode::CollectViewUpdates(winrt::Microsoft::ReactNative::JSValu
2727
if (const auto transformNode = manager->GetTransformAnimatedNode(propMapping.second)) {
2828
transformNode->CollectViewUpdates(propsMap);
2929
} else if (const auto node = manager->GetValueAnimatedNode(propMapping.second)) {
30-
propsMap[propMapping.first] = node->Value();
30+
if (node->IsColorValue()) {
31+
const auto value = node->Value();
32+
int32_t color;
33+
memcpy(&color, &value, sizeof(int32_t));
34+
propsMap[propMapping.first] = color;
35+
} else {
36+
propsMap[propMapping.first] = node->Value();
37+
}
3138
} else if (const auto node = manager->GetColorAnimatedNode(propMapping.second)) {
3239
propsMap[propMapping.first] = node->GetColor();
3340
}

vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.h

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ class ValueAnimatedNode : public AnimatedNode {
3131
void OnValueUpdate();
3232
void ValueListener(const ValueListenerCallback &callback);
3333

34+
virtual bool IsColorValue() {
35+
return false;
36+
}
37+
3438
comp::CompositionPropertySet PropertySet() {
3539
return m_propertySet;
3640
};

0 commit comments

Comments
 (0)