diff --git a/change/react-native-windows-b9691dda-2b02-4670-a043-8ecd90525929.json b/change/react-native-windows-b9691dda-2b02-4670-a043-8ecd90525929.json new file mode 100644 index 00000000000..525839af5bc --- /dev/null +++ b/change/react-native-windows-b9691dda-2b02-4670-a043-8ecd90525929.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "[Fabric] Add support for PlatformColor", + "packageName": "react-native-windows", + "email": "30809111+acoates-ms@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative/Fabric/ActivityIndicatorComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/ActivityIndicatorComponentView.cpp index 6e414fd702e..8dcf4987337 100644 --- a/vnext/Microsoft.ReactNative/Fabric/ActivityIndicatorComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/ActivityIndicatorComponentView.cpp @@ -46,7 +46,7 @@ void ActivityIndicatorComponentView::updateProps( } if (oldActivityProps.color != newActivityProps.color) { - m_element.Foreground(SolidColorBrushFrom(newActivityProps.color)); + m_element.Foreground(newActivityProps.color.AsWindowsBrush()); } m_props = std::static_pointer_cast(props); diff --git a/vnext/Microsoft.ReactNative/Fabric/ImageComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/ImageComponentView.cpp index eb6d1ac3d81..7994a1cb948 100644 --- a/vnext/Microsoft.ReactNative/Fabric/ImageComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/ImageComponentView.cpp @@ -87,7 +87,7 @@ void ImageComponentView::updateProps( if (oldImageProps.tintColor != newImageProps.tintColor) { if (newImageProps.tintColor) { - m_element->TintColor(ColorFromNumber(*(newImageProps.tintColor))); + m_element->TintColor(newImageProps.tintColor.AsWindowsColor()); } else { m_element->TintColor(winrt::Colors::Transparent()); } diff --git a/vnext/Microsoft.ReactNative/Fabric/ParagraphComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/ParagraphComponentView.cpp index 987a71936e0..625dae9ddda 100644 --- a/vnext/Microsoft.ReactNative/Fabric/ParagraphComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/ParagraphComponentView.cpp @@ -48,7 +48,7 @@ void ParagraphComponentView::updateProps( if (oldViewProps.textAttributes.foregroundColor != newViewProps.textAttributes.foregroundColor) { if (newViewProps.textAttributes.foregroundColor) - m_element.Foreground(SolidColorBrushFrom(newViewProps.textAttributes.foregroundColor)); + m_element.Foreground(newViewProps.textAttributes.foregroundColor.AsWindowsBrush()); else m_element.ClearValue(::xaml::Controls::TextBlock::ForegroundProperty()); } diff --git a/vnext/Microsoft.ReactNative/Fabric/ViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/ViewComponentView.cpp index d6f55479caf..d93f017f928 100644 --- a/vnext/Microsoft.ReactNative/Fabric/ViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/ViewComponentView.cpp @@ -48,7 +48,7 @@ void ViewComponentView::updateProps( auto color = *newViewProps.backgroundColor; if (newViewProps.backgroundColor) { - m_panel.ViewBackground(SolidColorBrushFrom(newViewProps.backgroundColor)); + m_panel.ViewBackground(newViewProps.backgroundColor.AsWindowsBrush()); } else { m_panel.ClearValue(winrt::Microsoft::ReactNative::ViewPanel::ViewBackgroundProperty()); } @@ -56,7 +56,7 @@ void ViewComponentView::updateProps( if (oldViewProps.borderColors != newViewProps.borderColors) { if (newViewProps.borderColors.all) { - m_panel.BorderBrush(SolidColorBrushFrom(*newViewProps.borderColors.all)); + m_panel.BorderBrush(newViewProps.borderColors.all->AsWindowsBrush()); } else { m_panel.ClearValue(winrt::Microsoft::ReactNative::ViewPanel::BorderBrushProperty()); } diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/Color.cpp b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/Color.cpp new file mode 100644 index 00000000000..94b5488f594 --- /dev/null +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/Color.cpp @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "Color.h" +#include + +namespace facebook { +namespace react { + +xaml::Media::Brush SharedColor::AsWindowsBrush() const { + if (!m_color) + return nullptr; + if (!m_color->m_platformColor.empty()) { + return Microsoft::ReactNative::BrushFromColorObject(winrt::to_hstring(m_color->m_platformColor)); + } + return xaml::Media::SolidColorBrush(m_color->m_color); +} + +SharedColor colorFromComponents(ColorComponents components) { + float ratio = 255; + return SharedColor(ui::ColorHelper::FromArgb( + (int)round(components.alpha * ratio) & 0xff, + (int)round(components.red * ratio) & 0xff, + (int)round(components.green * ratio) & 0xff, + (int)round(components.blue * ratio) & 0xff)); +} + +ColorComponents colorComponentsFromColor(SharedColor sharedColor) { + float ratio = 255; + auto color = sharedColor.AsWindowsColor(); + return ColorComponents{ + (float)color.R / ratio, (float)color.G / ratio, (float)color.B / ratio, (float)color.A / ratio}; +} + +SharedColor clearColor() { + static SharedColor color = colorFromComponents(ColorComponents{0, 0, 0, 0}); + return color; +} + +SharedColor blackColor() { + static SharedColor color = colorFromComponents(ColorComponents{0, 0, 0, 1}); + return color; +} + +SharedColor whiteColor() { + static SharedColor color = colorFromComponents(ColorComponents{1, 1, 1, 1}); + return color; +} + +} // namespace react +} // namespace facebook diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/Color.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/Color.h new file mode 100644 index 00000000000..bf77acec34a --- /dev/null +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/Color.h @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// RN has some weird include directory redirections..this forwards the include to the right place +#include \ No newline at end of file diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/Float.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/Float.h new file mode 100644 index 00000000000..a82411c486a --- /dev/null +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/Float.h @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// RN has some weird include directory redirections..this forwards the include to the right place +#include \ No newline at end of file diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/conversions.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/conversions.h new file mode 100644 index 00000000000..aed766fa209 --- /dev/null +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/conversions.h @@ -0,0 +1,256 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +#pragma mark - Color + +inline void fromRawValue(const RawValue &value, SharedColor &result) { + float red = 0; + float green = 0; + float blue = 0; + float alpha = 0; + + if (value.hasType()) { + auto argb = (int64_t)value; + auto ratio = 255.f; + alpha = ((argb >> 24) & 0xFF) / ratio; + red = ((argb >> 16) & 0xFF) / ratio; + green = ((argb >> 8) & 0xFF) / ratio; + blue = (argb & 0xFF) / ratio; + } else if (value.hasType>()) { + auto items = (std::vector)value; + auto length = items.size(); + react_native_assert(length == 3 || length == 4); + red = items.at(0); + green = items.at(1); + blue = items.at(2); + alpha = length == 4 ? items.at(3) : 1.0f; + // [Windows - Add support for windowsBrush colors (PlatformColor) + } else if (value.hasType>()) { + auto map = (better::map)value; + for (const auto &pair : map) { + if (pair.first == "windowsbrush") { + result = SharedColor(std::string(pair.second)); + return; + } + } + } + // Windows] + + result = colorFromComponents({red, green, blue, alpha}); +} + +#ifdef ANDROID + +inline folly::dynamic toDynamic(const SharedColor &color) { + ColorComponents components = colorComponentsFromColor(color); + auto ratio = 255.f; + return ( + ((int)round(components.alpha * ratio) & 0xff) << 24 | ((int)round(components.red * ratio) & 0xff) << 16 | + ((int)round(components.green * ratio) & 0xff) << 8 | ((int)round(components.blue * ratio) & 0xff)); +} + +inline int toMapBuffer(const SharedColor &color) { + ColorComponents components = colorComponentsFromColor(color); + auto ratio = 255.f; + return ( + ((int)round(components.alpha * ratio) & 0xff) << 24 | ((int)round(components.red * ratio) & 0xff) << 16 | + ((int)round(components.green * ratio) & 0xff) << 8 | ((int)round(components.blue * ratio) & 0xff)); +} + +#endif + +inline std::string toString(const SharedColor &value) { + ColorComponents components = colorComponentsFromColor(value); + auto ratio = 255.f; + return "rgba(" + folly::to(round(components.red * ratio)) + ", " + + folly::to(round(components.green * ratio)) + ", " + + folly::to(round(components.blue * ratio)) + ", " + + folly::to(round(components.alpha * ratio)) + ")"; +} + +#pragma mark - Geometry + +inline void fromRawValue(const RawValue &value, Point &result) { + if (value.hasType>()) { + auto map = (better::map)value; + for (const auto &pair : map) { + if (pair.first == "x") { + result.x = pair.second; + } else if (pair.first == "y") { + result.y = pair.second; + } + } + return; + } + + react_native_assert(value.hasType>()); + if (value.hasType>()) { + auto array = (std::vector)value; + react_native_assert(array.size() == 2); + if (array.size() >= 2) { + result = {array.at(0), array.at(1)}; + } else { + result = {0, 0}; + LOG(ERROR) << "Unsupported Point vector size: " << array.size(); + } + } else { + LOG(ERROR) << "Unsupported Point type"; + } +} + +inline void fromRawValue(const RawValue &value, Size &result) { + if (value.hasType>()) { + auto map = (better::map)value; + for (const auto &pair : map) { + if (pair.first == "width") { + result.width = pair.second; + } else if (pair.first == "height") { + result.height = pair.second; + } else { + LOG(ERROR) << "Unsupported Size map key: " << pair.first; + react_native_assert(false); + } + } + return; + } + + react_native_assert(value.hasType>()); + if (value.hasType>()) { + auto array = (std::vector)value; + react_native_assert(array.size() == 2); + if (array.size() >= 2) { + result = {array.at(0), array.at(1)}; + } else { + result = {0, 0}; + LOG(ERROR) << "Unsupported Size vector size: " << array.size(); + } + } else { + LOG(ERROR) << "Unsupported Size type"; + } +} + +inline void fromRawValue(const RawValue &value, EdgeInsets &result) { + if (value.hasType()) { + auto number = (Float)value; + result = {number, number, number, number}; + } + + if (value.hasType>()) { + auto map = (better::map)value; + for (const auto &pair : map) { + if (pair.first == "top") { + result.top = pair.second; + } else if (pair.first == "left") { + result.left = pair.second; + } else if (pair.first == "bottom") { + result.bottom = pair.second; + } else if (pair.first == "right") { + result.right = pair.second; + } else { + LOG(ERROR) << "Unsupported EdgeInsets map key: " << pair.first; + react_native_assert(false); + } + } + return; + } + + react_native_assert(value.hasType>()); + if (value.hasType>()) { + auto array = (std::vector)value; + react_native_assert(array.size() == 4); + if (array.size() >= 4) { + result = {array.at(0), array.at(1), array.at(2), array.at(3)}; + } else { + result = {0, 0, 0, 0}; + LOG(ERROR) << "Unsupported EdgeInsets vector size: " << array.size(); + } + } else { + LOG(ERROR) << "Unsupported EdgeInsets type"; + } +} + +inline void fromRawValue(const RawValue &value, CornerInsets &result) { + if (value.hasType()) { + auto number = (Float)value; + result = {number, number, number, number}; + return; + } + + if (value.hasType>()) { + auto map = (better::map)value; + for (const auto &pair : map) { + if (pair.first == "topLeft") { + result.topLeft = pair.second; + } else if (pair.first == "topRight") { + result.topRight = pair.second; + } else if (pair.first == "bottomLeft") { + result.bottomLeft = pair.second; + } else if (pair.first == "bottomRight") { + result.bottomRight = pair.second; + } else { + LOG(ERROR) << "Unsupported CornerInsets map key: " << pair.first; + react_native_assert(false); + } + } + return; + } + + react_native_assert(value.hasType>()); + if (value.hasType>()) { + auto array = (std::vector)value; + react_native_assert(array.size() == 4); + if (array.size() >= 4) { + result = {array.at(0), array.at(1), array.at(2), array.at(3)}; + } else { + LOG(ERROR) << "Unsupported CornerInsets vector size: " << array.size(); + } + } + + // Error case - we should only here if all other supported cases fail + // In dev we would crash on assert before this point + result = {0, 0, 0, 0}; + LOG(ERROR) << "Unsupported CornerInsets type"; +} + +inline std::string toString(const Point &point) { + return "{" + folly::to(point.x) + ", " + folly::to(point.y) + "}"; +} + +inline std::string toString(const Size &size) { + return "{" + folly::to(size.width) + ", " + folly::to(size.height) + "}"; +} + +inline std::string toString(const Rect &rect) { + return "{" + toString(rect.origin) + ", " + toString(rect.size) + "}"; +} + +inline std::string toString(const EdgeInsets &edgeInsets) { + return "{" + folly::to(edgeInsets.left) + ", " + folly::to(edgeInsets.top) + ", " + + folly::to(edgeInsets.right) + ", " + folly::to(edgeInsets.bottom) + "}"; +} + +inline std::string toString(const CornerInsets &cornerInsets) { + return "{" + folly::to(cornerInsets.topLeft) + ", " + folly::to(cornerInsets.topRight) + + ", " + folly::to(cornerInsets.bottomLeft) + ", " + folly::to(cornerInsets.bottomRight) + + "}"; +} + +} // namespace react +} // namespace facebook diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/platform/cxx/react/renderer/graphics/Color.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/platform/cxx/react/renderer/graphics/Color.h new file mode 100644 index 00000000000..2f67104221f --- /dev/null +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/platform/cxx/react/renderer/graphics/Color.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace facebook { +namespace react { + +struct Color { + bool operator==(const Color &otherColor) const { + return m_color == otherColor.m_color && m_platformColor == otherColor.m_platformColor; + } + bool operator!=(const Color &otherColor) const { + return m_color != otherColor.m_color || m_platformColor != otherColor.m_platformColor; + } + + winrt::Windows::UI::Color m_color; + std::string m_platformColor; +}; + +/* + * On Android, a color can be represented as 32 bits integer, so there is no + * need to instantiate complex color objects and then pass them as shared + * pointers. Hense instead of using shared_ptr, we use a simple wrapper class + * which provides a pointer-like interface. + */ +class SharedColor { + friend std::hash; + + public: + SharedColor() : m_color(nullptr) {} + + SharedColor(const SharedColor &sharedColor) : m_color(sharedColor.m_color) {} + + SharedColor(winrt::Windows::UI::Color color) { + m_color = std::make_shared(); + m_color->m_color = color; + } + + SharedColor(std::string &&windowsBrush) { + m_color = std::make_shared(); + m_color->m_platformColor = std::move(windowsBrush); + } + + SharedColor &operator=(const SharedColor &sharedColor) { + m_color = sharedColor.m_color; + return *this; + } + + Color operator*() const { + return *m_color; + } + + bool operator==(const SharedColor &otherColor) const { + if (!m_color && !otherColor.m_color) + return true; + if (!m_color || !otherColor.m_color) + return false; + return *m_color == *otherColor.m_color; + } + + bool operator!=(const SharedColor &otherColor) const { + if (!m_color && !otherColor.m_color) + return false; + if (!m_color || !otherColor.m_color) + return true; + return *m_color != *otherColor.m_color; + } + + operator bool() const { + return m_color != nullptr; + } + + winrt::Windows::UI::Color AsWindowsColor() const { + return m_color->m_color; + } + + xaml::Media::Brush AsWindowsBrush() const; + + private: + std::shared_ptr m_color; +}; + +SharedColor colorFromComponents(ColorComponents components); +ColorComponents colorComponentsFromColor(SharedColor color); + +SharedColor clearColor(); +SharedColor blackColor(); +SharedColor whiteColor(); + +} // namespace react +} // namespace facebook + +namespace std { +template <> +struct hash { + size_t operator()(const facebook::react::SharedColor &sharedColor) const { + size_t h = sharedColor.m_color->m_color.A + (sharedColor.m_color->m_color.B << 8) + + (sharedColor.m_color->m_color.G << 16) + (sharedColor.m_color->m_color.R << 24); + return h ^ hashm_platformColor)>{}(sharedColor.m_color->m_platformColor); + } +}; +} // namespace std diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index 38fc23b767c..2898b84f84f 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -110,7 +110,6 @@ $(ReactNativeWindowsDir)Shared; $(ReactNativeWindowsDir)Shared\tracing; $(ReactNativeWindowsDir)include\Shared; - $(ReactNativeDir)\ReactCommon\react\renderer\graphics\platform\cxx; $(YogaDir); $(GeneratedFilesDir); %(AdditionalIncludeDirectories) @@ -441,7 +440,6 @@ - @@ -481,6 +479,7 @@ + diff --git a/vnext/Microsoft.ReactNative/Utils/ValueUtils.cpp b/vnext/Microsoft.ReactNative/Utils/ValueUtils.cpp index 1eecee1eb85..1985a1bd53f 100644 --- a/vnext/Microsoft.ReactNative/Utils/ValueUtils.cpp +++ b/vnext/Microsoft.ReactNative/Utils/ValueUtils.cpp @@ -197,15 +197,6 @@ SolidColorBrushFrom(const winrt::Microsoft::ReactNative::JSValue &v) { return SolidBrushFromColor(ColorFrom(v)); } -xaml::Media::SolidColorBrush SolidColorBrushFrom(facebook::react::Color argb) noexcept { - return SolidBrushFromColor( - winrt::ColorHelper::FromArgb(GetAFromArgb(argb), GetRFromArgb(argb), GetGFromArgb(argb), GetBFromArgb(argb))); -} - -xaml::Media::SolidColorBrush SolidColorBrushFrom(facebook::react::SharedColor color) noexcept { - return SolidColorBrushFrom(*color); -} - REACTWINDOWS_API_(xaml::Media::SolidColorBrush) SolidColorBrushFrom(const folly::dynamic &d) { if (d.isObject()) { diff --git a/vnext/Microsoft.ReactNative/Utils/ValueUtils.h b/vnext/Microsoft.ReactNative/Utils/ValueUtils.h index 9c95894cd28..915b4f2651f 100644 --- a/vnext/Microsoft.ReactNative/Utils/ValueUtils.h +++ b/vnext/Microsoft.ReactNative/Utils/ValueUtils.h @@ -20,6 +20,7 @@ struct JSValue; namespace Microsoft::ReactNative { +xaml::Media::Brush BrushFromColorObject(winrt::hstring resourceName); xaml::Media::Brush BrushFromColorObject(const folly::dynamic &d); xaml::Media::Brush BrushFromColorObject(const winrt::Microsoft::ReactNative::JSValue &v); xaml::Media::SolidColorBrush SolidBrushFromColor(winrt::Windows::UI::Color color); @@ -59,6 +60,4 @@ TimeSpanFromMs(double ms); winrt::Uri UriTryCreate(winrt::param::hstring const &uri); winrt::Windows::UI::Color ColorFromNumber(DWORD argb) noexcept; -xaml::Media::SolidColorBrush SolidColorBrushFrom(facebook::react::Color) noexcept; -xaml::Media::SolidColorBrush SolidColorBrushFrom(facebook::react::SharedColor) noexcept; } // namespace Microsoft::ReactNative diff --git a/vnext/overrides.json b/vnext/overrides.json index 69df3eb2284..3710c19ee9f 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -14,6 +14,27 @@ "baseFile": ".flowconfig", "baseHash": "da1871d982a9d96c7f8235bb6c6efbb790f17cd1" }, + { + "type": "patch", + "file": "Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/Color.cpp", + "baseFile": "ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/Color.cpp", + "baseHash": "72f43acae8d5d34ed0607636008ff2618b5c734f", + "issue": 7821 + }, + { + "type": "patch", + "file": "Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/conversions.h", + "baseFile": "ReactCommon/react/renderer/graphics/conversions.h", + "baseHash": "a77dfb26acdaeaf0b7cd1df893843cd176364b17", + "issue": 7821 + }, + { + "type": "patch", + "file": "Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/platform/cxx/react/renderer/graphics/Color.h", + "baseFile": "ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/Color.h", + "baseHash": "93c4d57329747bec83201898ba807d0a23700570", + "issue": 7821 + }, { "type": "patch", "file": "ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/leakchecker/LeakChecker.cpp",