Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds color animated nodes to tick-driven animations #12886

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Adds color animated nodes to tick-driven animations",
"packageName": "react-native-windows",
"email": "[email protected]",
"dependentChangeType": "patch"
}
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@
<ClInclude Include="Modules\Animated\AnimationType.h" />
<ClInclude Include="Modules\Animated\AnimationUtils.h" />
<ClInclude Include="Modules\Animated\CalculatedAnimationDriver.h" />
<ClInclude Include="Modules\Animated\ColorAnimatedNode.h" />
<ClInclude Include="Modules\Animated\DecayAnimationDriver.h" />
<ClInclude Include="Modules\Animated\DiffClampAnimatedNode.h" />
<ClInclude Include="Modules\Animated\DivisionAnimatedNode.h" />
Expand Down Expand Up @@ -437,6 +438,7 @@
<ClCompile Include="Modules\Animated\AnimatedPlatformConfig.cpp" />
<ClCompile Include="Modules\Animated\AnimationDriver.cpp" />
<ClCompile Include="Modules\Animated\CalculatedAnimationDriver.cpp" />
<ClCompile Include="Modules\Animated\ColorAnimatedNode.cpp" />
<ClCompile Include="Modules\Animated\DecayAnimationDriver.cpp" />
<ClCompile Include="Modules\Animated\DiffClampAnimatedNode.cpp" />
<ClCompile Include="Modules\Animated\DivisionAnimatedNode.cpp" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
<ClCompile Include="Modules\Animated\CalculatedAnimationDriver.cpp">
<Filter>Modules\Animated</Filter>
</ClCompile>
<ClCompile Include="Modules\Animated\ColorAnimatedNode.cpp">
<Filter>Modules\Animated</Filter>
</ClCompile>
<ClCompile Include="Modules\Animated\DecayAnimationDriver.cpp">
<Filter>Modules\Animated</Filter>
</ClCompile>
Expand Down Expand Up @@ -383,6 +386,9 @@
<ClInclude Include="Modules\Animated\CalculatedAnimationDriver.h">
<Filter>Modules\Animated</Filter>
</ClInclude>
<ClInclude Include="Modules\Animated\ColorAnimatedNode.h">
<Filter>Modules\Animated</Filter>
</ClInclude>
<ClInclude Include="Modules\Animated\DecayAnimationDriver.h">
<Filter>Modules\Animated</Filter>
</ClInclude>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum class AnimatedNodeType {
Diffclamp,
Transform,
Tracking,
Color,
};

static AnimatedNodeType AnimatedNodeTypeFromString(const std::string &string) {
Expand All @@ -43,7 +44,8 @@ static AnimatedNodeType AnimatedNodeTypeFromString(const std::string &string) {
return AnimatedNodeType::Diffclamp;
if (string == "transform")
return AnimatedNodeType::Transform;

assert(string == "tracking");
return AnimatedNodeType::Tracking;
if (string == "tracking")
return AnimatedNodeType::Tracking;
assert(string == "color");
return AnimatedNodeType::Color;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#include "pch.h"

#include <UI.Composition.h>
#include <Utils/ValueUtils.h>
#include "ColorAnimatedNode.h"
#include "NativeAnimatedNodeManager.h"

namespace Microsoft::ReactNative {
ColorAnimatedNode::ColorAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr<NativeAnimatedNodeManager> &manager)
: AnimatedNode(tag, config, manager) {
m_rNodeId = config[s_rNodeName].AsInt32();
m_gNodeId = config[s_gNodeName].AsInt32();
m_bNodeId = config[s_bNodeName].AsInt32();
m_aNodeId = config[s_aNodeName].AsInt32();
m_nativeColor = config[s_nativeColorName].Copy();

if (!m_useComposition) {
TryApplyNativeColor();
} else {
assert(false && "ColorAnimatedNode not supported");
}
}

uint32_t ColorAnimatedNode::GetColor() {
uint32_t r = 0;
uint32_t g = 0;
uint32_t b = 0;
uint32_t a = 0;

if (const auto manager = m_manager.lock()) {
if (const auto rNode = manager->GetValueAnimatedNode(m_rNodeId)) {
r = std::clamp(static_cast<uint32_t>(std::round(rNode->Value())), 0u, 255u);
}
if (const auto gNode = manager->GetValueAnimatedNode(m_gNodeId)) {
g = std::clamp(static_cast<uint32_t>(std::round(gNode->Value())), 0u, 255u);
}
if (const auto bNode = manager->GetValueAnimatedNode(m_bNodeId)) {
b = std::clamp(static_cast<uint32_t>(std::round(bNode->Value())), 0u, 255u);
}
if (const auto aNode = manager->GetValueAnimatedNode(m_aNodeId)) {
a = std::clamp(static_cast<uint32_t>(std::round(aNode->Value() * 255)), 0u, 255u);
}
}

const auto result = (a << 24) | (r << 16) | (g << 8) | b;
return result;
}

void ColorAnimatedNode::TryApplyNativeColor() {
if (m_nativeColor.IsNull()) {
return;
}

const auto brush = BrushFromColorObject(m_nativeColor).try_as<xaml::Media::SolidColorBrush>();
if (!brush) {
return;
}

if (const auto manager = m_manager.lock()) {
if (const auto rNode = manager->GetValueAnimatedNode(m_rNodeId)) {
rNode->RawValue(brush.Color().R);
}
if (const auto gNode = manager->GetValueAnimatedNode(m_gNodeId)) {
gNode->RawValue(brush.Color().G);
}
if (const auto bNode = manager->GetValueAnimatedNode(m_bNodeId)) {
bNode->RawValue(brush.Color().B);
}
if (const auto aNode = manager->GetValueAnimatedNode(m_aNodeId)) {
aNode->RawValue(static_cast<double>(brush.Color().A) / 255);
}
}
}

} // namespace Microsoft::ReactNative
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once
#include "AnimatedNode.h"

namespace Microsoft::ReactNative {
class ColorAnimatedNode final : public AnimatedNode {
public:
ColorAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
const std::shared_ptr<NativeAnimatedNodeManager> &manager);

uint32_t GetColor();

private:
void TryApplyNativeColor();

int32_t m_rNodeId{};
int32_t m_gNodeId{};
int32_t m_bNodeId{};
int32_t m_aNodeId{};
winrt::Microsoft::ReactNative::JSValue m_nativeColor{};

static constexpr std::string_view s_rNodeName{"r"};
static constexpr std::string_view s_gNodeName{"g"};
static constexpr std::string_view s_bNodeName{"b"};
static constexpr std::string_view s_aNodeName{"a"};
static constexpr std::string_view s_nativeColorName{"nativeColor"};
};
} // namespace Microsoft::ReactNative
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,21 @@
#include "ExtrapolationType.h"
#include "InterpolationAnimatedNode.h"
#include "NativeAnimatedNodeManager.h"
#include "Utils/ValueUtils.h"

namespace Microsoft::ReactNative {

inline int32_t ColorToInt(winrt::Windows::UI::Color color) {
return static_cast<uint8_t>(color.A) << 24 | static_cast<uint8_t>(color.R) << 16 |
static_cast<uint8_t>(color.G) << 8 | static_cast<uint8_t>(color.B);
}

inline uint8_t ScaleByte(uint8_t min, uint8_t max, double ratio) {
const auto scaledValue = min + (max - min) * ratio;
const auto clampedValue = std::clamp(static_cast<uint32_t>(std::round(scaledValue)), 0u, 255u);
return static_cast<uint8_t>(clampedValue);
}

InterpolationAnimatedNode::InterpolationAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
Expand All @@ -17,8 +30,18 @@ InterpolationAnimatedNode::InterpolationAnimatedNode(
for (const auto &rangeValue : config[s_inputRangeName].AsArray()) {
m_inputRanges.push_back(rangeValue.AsDouble());
}
for (const auto &rangeValue : config[s_outputRangeName].AsArray()) {
m_outputRanges.push_back(rangeValue.AsDouble());

const auto isColorOutput = config[s_outputTypeName].AsString() == s_colorOutputType;
if (!m_useComposition && isColorOutput) {
m_isColorOutput = true;
for (const auto &rangeValue : config[s_outputRangeName].AsArray()) {
m_colorOutputRanges.push_back(ColorFrom(rangeValue));
}
} else {
assert(!isColorOutput && "Color interpolation not supported");
for (const auto &rangeValue : config[s_outputRangeName].AsArray()) {
m_defaultOutputRanges.push_back(rangeValue.AsDouble());
}
}

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

if (const auto manager = m_manager.lock()) {
if (const auto node = manager->GetValueAnimatedNode(m_parentTag)) {
RawValue(InterpolateValue(node->Value()));
if (m_isColorOutput) {
RawValue(InterpolateColor(node->Value()));
} else {
RawValue(InterpolateValue(node->Value()));
}
}
}
}
Expand Down Expand Up @@ -95,8 +122,9 @@ comp::ExpressionAnimation InterpolationAnimatedNode::CreateExpressionAnimation(
for (size_t i = 0; i < m_inputRanges.size(); i++) {
animation.SetScalarParameter(s_inputName.data() + std::to_wstring(i), static_cast<float>(m_inputRanges[i]));
}
for (size_t i = 0; i < m_outputRanges.size(); i++) {
animation.SetScalarParameter(s_outputName.data() + std::to_wstring(i), static_cast<float>(m_outputRanges[i]));
for (size_t i = 0; i < m_defaultOutputRanges.size(); i++) {
animation.SetScalarParameter(
s_outputName.data() + std::to_wstring(i), static_cast<float>(m_defaultOutputRanges[i]));
}
return animation;
}
Expand Down Expand Up @@ -173,7 +201,7 @@ winrt::hstring InterpolationAnimatedNode::GetRightExpression(
const winrt::hstring &value,
const winrt::hstring &rightInterpolateExpression) {
const auto lastInput = s_inputName.data() + std::to_wstring(m_inputRanges.size() - 1);
const auto lastOutput = s_outputName.data() + std::to_wstring(m_outputRanges.size() - 1);
const auto lastOutput = s_outputName.data() + std::to_wstring(m_defaultOutputRanges.size() - 1);
switch (ExtrapolationTypeFromString(m_extrapolateRight)) {
case ExtrapolationType::Clamp:
return value + L" > " + lastInput + L" ? " + lastOutput + L" : ";
Expand All @@ -200,10 +228,49 @@ double InterpolationAnimatedNode::InterpolateValue(double value) {
value,
m_inputRanges[index],
m_inputRanges[index + 1],
m_outputRanges[index],
m_outputRanges[index + 1],
m_defaultOutputRanges[index],
m_defaultOutputRanges[index + 1],
m_extrapolateLeft,
m_extrapolateRight);
}

double InterpolationAnimatedNode::InterpolateColor(double value) {
// Compute range index
size_t index = 1;
for (; index < m_inputRanges.size() - 1; ++index) {
if (m_inputRanges[index] >= value) {
break;
}
}
index--;

double result;
const auto outputMin = m_colorOutputRanges[index];
const auto outputMax = m_colorOutputRanges[index + 1];
const auto outputMinInt = ColorToInt(outputMin);
const auto outputMaxInt = ColorToInt(outputMax);
if (outputMin == outputMax) {
memcpy(&result, &outputMinInt, sizeof(int32_t));
return result;
}

const auto inputMin = m_inputRanges[index];
const auto inputMax = m_inputRanges[index + 1];
if (inputMin == inputMax) {
if (value <= inputMin) {
memcpy(&result, &outputMinInt, sizeof(int32_t));
} else {
memcpy(&result, &outputMaxInt, sizeof(int32_t));
}
return result;
}

const auto ratio = (value - inputMin) / (inputMax - inputMin);
const auto interpolatedColor = ScaleByte(outputMin.A, outputMax.A, ratio) << 24 |
ScaleByte(outputMin.R, outputMax.R, ratio) << 16 | ScaleByte(outputMin.G, outputMax.G, ratio) << 8 |
ScaleByte(outputMin.B, outputMax.B, ratio);
memcpy(&result, &interpolatedColor, sizeof(int32_t));
return result;
}

} // namespace Microsoft::ReactNative
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class InterpolationAnimatedNode final : public ValueAnimatedNode {
virtual void OnDetachedFromNode(int64_t animatedNodeTag) override;
virtual void OnAttachToNode(int64_t animatedNodeTag) override;

bool IsColorValue() override {
return m_isColorOutput;
}

static constexpr std::string_view ExtrapolateTypeIdentity = "identity";
static constexpr std::string_view ExtrapolateTypeClamp = "clamp";
static constexpr std::string_view ExtrapolateTypeExtend = "extend";
Expand All @@ -35,11 +39,14 @@ class InterpolationAnimatedNode final : public ValueAnimatedNode {
winrt::hstring GetRightExpression(const winrt::hstring &, const winrt::hstring &rightInterpolateExpression);

double InterpolateValue(double value);
double InterpolateColor(double value);

comp::ExpressionAnimation m_rawValueAnimation{nullptr};
comp::ExpressionAnimation m_offsetAnimation{nullptr};
bool m_isColorOutput{false};
std::vector<double> m_inputRanges;
std::vector<double> m_outputRanges;
std::vector<double> m_defaultOutputRanges;
std::vector<winrt::Windows::UI::Color> m_colorOutputRanges;
std::string m_extrapolateLeft;
std::string m_extrapolateRight;

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

static constexpr std::string_view s_inputRangeName{"inputRange"};
static constexpr std::string_view s_outputRangeName{"outputRange"};
static constexpr std::string_view s_outputTypeName{"outputType"};
static constexpr std::string_view s_extrapolateLeftName{"extrapolateLeft"};
static constexpr std::string_view s_extrapolateRightName{"extrapolateRight"};

static constexpr std::string_view s_colorOutputType{"color"};

static constexpr std::wstring_view s_parentPropsName{L"p"};
static constexpr std::wstring_view s_inputName{L"i"};
static constexpr std::wstring_view s_outputName{L"o"};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ void NativeAnimatedNodeManager::CreateAnimatedNode(
m_trackingNodes.emplace(tag, std::make_unique<TrackingAnimatedNode>(tag, config, manager));
break;
}
case AnimatedNodeType::Color: {
m_colorNodes.emplace(tag, std::make_unique<ColorAnimatedNode>(tag, config, manager));
break;
}
default: {
assert(false);
break;
Expand Down Expand Up @@ -483,6 +487,9 @@ AnimatedNode *NativeAnimatedNodeManager::GetAnimatedNode(int64_t tag) {
if (m_trackingNodes.count(tag)) {
return m_trackingNodes.at(tag).get();
}
if (m_colorNodes.count(tag)) {
return m_colorNodes.at(tag).get();
}
return static_cast<AnimatedNode *>(nullptr);
}

Expand Down Expand Up @@ -521,6 +528,13 @@ TrackingAnimatedNode *NativeAnimatedNodeManager::GetTrackingAnimatedNode(int64_t
return nullptr;
}

ColorAnimatedNode *NativeAnimatedNodeManager::GetColorAnimatedNode(int64_t tag) {
if (m_colorNodes.count(tag)) {
return m_colorNodes.at(tag).get();
}
return nullptr;
}

void NativeAnimatedNodeManager::RemoveActiveAnimation(int64_t tag) {
m_activeAnimations.erase(tag);
}
Expand Down
Loading
Loading