From 672e5c9505279c10f938f6e619eda5f2fab8ece0 Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Tue, 14 Oct 2025 16:07:50 +0200 Subject: [PATCH 1/6] [WIP] Introduce shadow nodes state --- apps/common/example/examples/TouchEvents.tsx | 31 ++++++-- apps/common/index.tsx | 2 +- apps/common/test/ColorTest.tsx | 75 ++++++++----------- apps/fabric-example/ios/Podfile.lock | 8 +- .../rnsvg/RNSVGConcreteShadowNode.h | 5 +- .../rnsvg/RNSVGLayoutableShadowNode.cpp | 8 +- .../components/rnsvg/RNSVGLayoutableState.cpp | 24 ++++++ .../components/rnsvg/RNSVGLayoutableState.h | 40 ++++++++++ 8 files changed, 135 insertions(+), 58 deletions(-) create mode 100644 common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.cpp create mode 100644 common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h diff --git a/apps/common/example/examples/TouchEvents.tsx b/apps/common/example/examples/TouchEvents.tsx index c822d3a50..425941000 100644 --- a/apps/common/example/examples/TouchEvents.tsx +++ b/apps/common/example/examples/TouchEvents.tsx @@ -69,10 +69,10 @@ HoverExample.title = 'Hover the svg path'; function GroupExample() { return ( - - Alert.alert('Pressed on G')} scale="1.4"> - - + {/* Alert.alert('Pressed on G')} collapsable={false}> */} + {/* Alert.alert('Pressed on Text')}> H - - - + */} + Alert.alert('Pressed on Circle')} + /> + Alert.alert('Pressed on Rect')} + /> + {/* */} ); } diff --git a/apps/common/index.tsx b/apps/common/index.tsx index fe90837e6..2bbaade62 100644 --- a/apps/common/index.tsx +++ b/apps/common/index.tsx @@ -1,3 +1,3 @@ -import App from './example'; +import App from './test'; export default App; diff --git a/apps/common/test/ColorTest.tsx b/apps/common/test/ColorTest.tsx index c9eb63fd2..ad8a99b7a 100644 --- a/apps/common/test/ColorTest.tsx +++ b/apps/common/test/ColorTest.tsx @@ -1,50 +1,41 @@ -import React from 'react'; -import {PlatformColor, Platform, Button, DynamicColorIOS} from 'react-native'; -import {Svg, Circle, Rect, Text, TSpan} from 'react-native-svg'; - -const color = - Platform.OS !== 'web' - ? PlatformColor( - Platform.select({ - ios: 'systemTealColor', - android: '@android:color/holo_blue_bright', - default: 'black', - }), - ) - : 'black'; - -// const customContrastDynamicTextColor = DynamicColorIOS({ -// dark: 'hsla(360, 40%, 30%, 1.0)', -// light: '#ff00ff55', -// highContrastDark: 'black', -// highContrastLight: 'white', -// }); +import {Alert, View} from 'react-native'; +import {Circle, Rect, Svg} from 'react-native-svg'; export default () => { - const [test, setTest] = React.useState(50); - return ( - <> - - + + + {/* Alert.alert('Pressed on G')} collapsable={false}> */} + {/* Alert.alert('Pressed on Text')}> + H + */} + Alert.alert('Pressed on Circle')} + /> Alert.alert('Pressed on Rect')} /> + {/* */} - - - - Testing word-wrap... Testing word-wrap... Testing word-wrap... - Testing word-wrap... - - - - ); }; diff --git a/apps/fabric-example/ios/Podfile.lock b/apps/fabric-example/ios/Podfile.lock index 8ecc27467..d8e2aa4ee 100644 --- a/apps/fabric-example/ios/Podfile.lock +++ b/apps/fabric-example/ios/Podfile.lock @@ -2606,7 +2606,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNSVG (15.13.0): + - RNSVG (15.14.0): - boost - DoubleConversion - fast_float @@ -2632,10 +2632,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNSVG/common (= 15.13.0) + - RNSVG/common (= 15.14.0) - SocketRocket - Yoga - - RNSVG/common (15.13.0): + - RNSVG/common (15.14.0): - boost - DoubleConversion - fast_float @@ -2988,7 +2988,7 @@ SPEC CHECKSUMS: RNGestureHandler: 4f7cc97a71d4fe0fcba38c94acdd969f5f17c91c RNReanimated: d3ecb5fd98f70b736cb8880cc95776298ff761c7 RNScreens: 74985ca8e102294a60cec7513fa84c936fa0b20b - RNSVG: eca4f6bb09bcaf582c4f082f3989a6b8513b3e93 + RNSVG: 4a4f7d4897abbfdc6dfe30be5d6cc5ffee23ea43 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: a3ed390a19db0459bd6839823a6ac6d9c6db198d diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGConcreteShadowNode.h b/common/cpp/react/renderer/components/rnsvg/RNSVGConcreteShadowNode.h index 0ab02986c..477d24e87 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGConcreteShadowNode.h +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGConcreteShadowNode.h @@ -4,6 +4,7 @@ #include #include #include "RNSVGLayoutableShadowNode.h" +#include "RNSVGLayoutableState.h" namespace facebook::react { @@ -13,14 +14,14 @@ class RNSVGConcreteShadowNode : public ConcreteShadowNode< RNSVGLayoutableShadowNode, PropsT, ViewEventEmitter, - StateData> { + RNSVGLayoutableState> { public: using BaseShadowNode = ConcreteShadowNode< concreteComponentName, RNSVGLayoutableShadowNode, PropsT, ViewEventEmitter, - StateData>; + RNSVGLayoutableState>; using ConcreteViewProps = PropsT; diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp index 11718dd86..9d0f14447 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp @@ -27,9 +27,13 @@ void RNSVGLayoutableShadowNode::setZeroDimensions() { // the Yoga layout. Setting the dimensions to 0 eliminates randomly positioned // views in the layout inspector when Yoga attempts to interpret SVG // properties like width when viewBox scale is set. + +// const auto &stateData = getStateData(); auto style = yogaNode_.style(); - style.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(0)); - style.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(0)); + style.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::percent(100.0)); + style.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::percent(100.0)); +// style.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(0)); +// style.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(0)); yogaNode_.setStyle(style); } diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.cpp b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.cpp new file mode 100644 index 000000000..4828d47be --- /dev/null +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.cpp @@ -0,0 +1,24 @@ +#pragma once + +#include "RNSVGLayoutableState.h" + +namespace facebook::react { + + +float RNSVGLayoutableState::getX() const { + return x_; +} + +float RNSVGLayoutableState::getY() const { + return y_; +} + +float RNSVGLayoutableState::getWidth() const { + return width_; +} + +float RNSVGLayoutableState::getHeight() const { + return height_; +} + +} // namespace facebook::react diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h new file mode 100644 index 000000000..a302b86c9 --- /dev/null +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h @@ -0,0 +1,40 @@ +#pragma once + +#ifdef ANDROID +#include +#endif + +namespace facebook::react { + +class RNSVGLayoutableState { +public: + RNSVGLayoutableState() + : x_(0), y_(0), width_(0), height_(0) {} + + RNSVGLayoutableState(float x, float y, float width, float height) + : x_(x), y_(y), width_(width), height_(height) {} + +#ifdef ANDROID + RNSVGLayoutableState(RNSVGLayoutableState const &previousState, folly::dynamic data) + : x_((float)data["x"].getDouble()), + y_((float)data["y"].getDouble()), + width_((float)data["width"].getDouble()), + height_((float)data["height"].getDouble()){}; + folly::dynamic getDynamic() const { + return {}; + }; +#endif + + float getX() const; + float getY() const; + float getWidth() const; + float getHeight() const; + +private: + const float x_{}; + const float y_{}; + const float width_{}; + const float height_{}; +}; + +} // namespace facebook::react From c952a63b254050c3a3b0b4eaaa13c7f4ad9c0f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 17 Oct 2025 12:24:00 +0200 Subject: [PATCH 2/6] fix: propagate elements metrics to their shadow nodes on iOS --- apple/RNSVGRenderable.mm | 18 ++++++ apps/common/test/ColorTest.tsx | 55 ++++++++++++------ .../rnsvg/RNSVGComponentDescriptor.h | 30 ++++++++++ .../rnsvg/RNSVGComponentDescriptors.h | 56 +++++++++---------- .../rnsvg/RNSVGLayoutableShadowNode.cpp | 32 ++++------- .../rnsvg/RNSVGLayoutableShadowNode.h | 5 +- .../components/rnsvg/RNSVGLayoutableState.h | 17 +++--- .../components/rnsvg/RNSVGShadowNodes.cpp | 1 + .../components/rnsvg/RNSVGShadowNodes.h | 8 +++ 9 files changed, 145 insertions(+), 77 deletions(-) create mode 100644 common/cpp/react/renderer/components/rnsvg/RNSVGComponentDescriptor.h diff --git a/apple/RNSVGRenderable.mm b/apple/RNSVGRenderable.mm index 754d29be8..a55ee73a8 100644 --- a/apple/RNSVGRenderable.mm +++ b/apple/RNSVGRenderable.mm @@ -18,6 +18,12 @@ #import "RNSVGVectorEffect.h" #import "RNSVGViewBox.h" +#ifdef RCT_NEW_ARCH_ENABLED +#import + +using namespace facebook::react; +#endif + @implementation RNSVGRenderable { NSMutableDictionary *_originProperties; NSArray *_lastMergedList; @@ -26,6 +32,10 @@ @implementation RNSVGRenderable { CGFloat *_strokeDashArrayData; CGPathRef _srcHitPath; RNSVGRenderable *_caller; + +#ifdef RCT_NEW_ARCH_ENABLED + RNSVGRenderableShadowNode::ConcreteState::Shared _state; +#endif } static RNSVGRenderable *_contextElement; @@ -243,6 +253,11 @@ - (void)prepareForRecycle _filter = nil; _caller = nil; } + +- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState +{ + _state = std::static_pointer_cast(state); +} #endif // RCT_NEW_ARCH_ENABLED UInt32 saturate(CGFloat value) @@ -432,6 +447,9 @@ - (void)renderTo:(CGContextRef)context rect:(CGRect)rect CGContextRestoreGState(context); [self renderMarkers:context path:self.path rect:&rect]; +#ifdef RCT_NEW_ARCH_ENABLED + _state->updateState(RNSVGLayoutableState(self.clientRect.origin.x, self.clientRect.origin.y, self.clientRect.size.width, self.clientRect.size.height)); +#endif } - (void)prepareStrokeDash:(NSUInteger)count strokeDasharray:(NSArray *)strokeDasharray diff --git a/apps/common/test/ColorTest.tsx b/apps/common/test/ColorTest.tsx index ad8a99b7a..7c5166a4f 100644 --- a/apps/common/test/ColorTest.tsx +++ b/apps/common/test/ColorTest.tsx @@ -1,34 +1,57 @@ import {Alert, View} from 'react-native'; -import {Circle, Rect, Svg} from 'react-native-svg'; +import {Circle, G, Rect, Svg, Text} from 'react-native-svg'; export default () => { return ( - + Alert.alert('Pressed on SVG')}> {/* Alert.alert('Pressed on G')} collapsable={false}> */} - {/* Alert.alert('Pressed on Text')}> - H - */} + + + Alert.alert('Pressed on Circle')} + /> + + Alert.alert('Pressed on Text')}> + H + + Alert.alert('Pressed on Circle')} /> + + Alert.alert('Pressed on Circle')} + /> + +#include +#include "RNSVGShadowNodes.h" + +namespace facebook::react { + +template +class RNSVGComponentDescriptor final +: public ConcreteComponentDescriptor { +public: + using ConcreteComponentDescriptor::ConcreteComponentDescriptor; + void adopt(ShadowNode &shadowNode) const override { + react_native_assert(dynamic_cast(&shadowNode)); + auto layoutableShadowNode = dynamic_cast(&shadowNode); + + auto state = std::static_pointer_cast(shadowNode.getState()); + auto stateData = state->getData(); + + if (stateData.getWidth() != 0 && stateData.getHeight() != 0 && std::strcmp(this->getComponentName(), "RNSVGGroup") != 0) { + layoutableShadowNode->setSize(Size{stateData.getWidth(), stateData.getHeight()}); + layoutableShadowNode->setShadowNodePosition(stateData.getX(), stateData.getY()); + } + + ConcreteComponentDescriptor::adopt(shadowNode); + } +}; + +} // namespace facebook::react diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGComponentDescriptors.h b/common/cpp/react/renderer/components/rnsvg/RNSVGComponentDescriptors.h index 61c44e7a6..5711d319f 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGComponentDescriptors.h +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGComponentDescriptors.h @@ -1,64 +1,64 @@ #pragma once #include -#include +#include "RNSVGComponentDescriptor.h" #include "RNSVGShadowNodes.h" namespace facebook::react { using RNSVGCircleComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGClipPathComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGDefsComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGEllipseComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGFeBlendComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGFeColorMatrixComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGFeCompositeComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGFeFloodComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGFeGaussianBlurComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGFeMergeComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGFeOffsetComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGFilterComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGForeignObjectComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGGroupComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGLinearGradientComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGLineComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGMarkerComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGMaskComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGPathComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGPatternComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGRadialGradientComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGRectComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGSymbolComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGTextComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGTextPathComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGTSpanComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; using RNSVGUseComponentDescriptor = - ConcreteComponentDescriptor; + RNSVGComponentDescriptor; } // namespace facebook::react diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp index 9d0f14447..4b3e46e1b 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp @@ -7,34 +7,22 @@ RNSVGLayoutableShadowNode::RNSVGLayoutableShadowNode( const ShadowNodeFragment &fragment, const ShadowNodeFamily::Shared &family, ShadowNodeTraits traits) - : YogaLayoutableShadowNode(fragment, family, traits) { - if (std::strcmp(this->getComponentName(), "RNSVGGroup") != 0) { - setZeroDimensions(); - } -} + : YogaLayoutableShadowNode(fragment, family, traits) {} RNSVGLayoutableShadowNode::RNSVGLayoutableShadowNode( const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment) - : YogaLayoutableShadowNode(sourceShadowNode, fragment) { - if (std::strcmp(this->getComponentName(), "RNSVGGroup") != 0) { - setZeroDimensions(); - } -} + : YogaLayoutableShadowNode(sourceShadowNode, fragment) {} -void RNSVGLayoutableShadowNode::setZeroDimensions() { - // SVG handles its layout manually on the native side and does not depend on - // the Yoga layout. Setting the dimensions to 0 eliminates randomly positioned - // views in the layout inspector when Yoga attempts to interpret SVG - // properties like width when viewBox scale is set. +void RNSVGLayoutableShadowNode::setShadowNodePosition(float x, float y) { + auto style = yogaNode_.style(); -// const auto &stateData = getStateData(); - auto style = yogaNode_.style(); - style.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::percent(100.0)); - style.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::percent(100.0)); -// style.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(0)); -// style.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(0)); - yogaNode_.setStyle(style); + style.setPositionType(yoga::PositionType::Absolute); + style.setPosition(yoga::Edge::Left, yoga::StyleLength::points(x)); + style.setPosition(yoga::Edge::Top, yoga::StyleLength::points(y)); +// style.setMargin(yoga::Edge::Left, yoga::StyleLength::points(x)); +// style.setMargin(yoga::Edge::Top, yoga::StyleLength::points(y)); + yogaNode_.setStyle(style); } void RNSVGLayoutableShadowNode::layout(LayoutContext layoutContext) { diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.h b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.h index c93c3c1eb..f1572ea02 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.h +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.h @@ -1,4 +1,5 @@ #include +#include "RNSVGLayoutableState.h" namespace facebook::react { @@ -14,9 +15,7 @@ class RNSVGLayoutableShadowNode : public YogaLayoutableShadowNode { const ShadowNodeFragment &fragment); void layout(LayoutContext layoutContext) override; - - private: - void setZeroDimensions(); + void setShadowNodePosition(float x, float y); }; } // namespace facebook::react diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h index a302b86c9..f208a5e1d 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h @@ -17,11 +17,12 @@ class RNSVGLayoutableState { #ifdef ANDROID RNSVGLayoutableState(RNSVGLayoutableState const &previousState, folly::dynamic data) : x_((float)data["x"].getDouble()), - y_((float)data["y"].getDouble()), - width_((float)data["width"].getDouble()), - height_((float)data["height"].getDouble()){}; + y_((float)data["y"].getDouble()), + width_((float)data["width"].getDouble()), + height_((float)data["height"].getDouble()){}; + folly::dynamic getDynamic() const { - return {}; + return folly::dynamic::object("x", x_)("y", y_)("width", width_)("height", height_) }; #endif @@ -31,10 +32,10 @@ class RNSVGLayoutableState { float getHeight() const; private: - const float x_{}; - const float y_{}; - const float width_{}; - const float height_{}; + float x_{}; + float y_{}; + float width_{}; + float height_{}; }; } // namespace facebook::react diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.cpp b/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.cpp index 70f1abd43..b5f1ad1ea 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.cpp +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.cpp @@ -24,6 +24,7 @@ extern const char RNSVGPathComponentName[] = "RNSVGPath"; extern const char RNSVGPatternComponentName[] = "RNSVGPattern"; extern const char RNSVGRadialGradientComponentName[] = "RNSVGRadialGradient"; extern const char RNSVGRectComponentName[] = "RNSVGRect"; +extern const char RNSVGRenderableComponentName[] = "RNSVGRenderable"; extern const char RNSVGSymbolComponentName[] = "RNSVGSymbol"; extern const char RNSVGTextComponentName[] = "RNSVGText"; extern const char RNSVGTextPathComponentName[] = "RNSVGTextPath"; diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.h b/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.h index 71addc842..a7a2a8e2d 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.h +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.h @@ -190,6 +190,14 @@ JSI_EXPORT extern const char RNSVGRectComponentName[]; using RNSVGRectShadowNode = RNSVGConcreteShadowNode; +JSI_EXPORT extern const char RNSVGRenderableComponentName[]; + +/* + * `ShadowNode` for component. + */ +using RNSVGRenderableShadowNode = + RNSVGConcreteShadowNode; + JSI_EXPORT extern const char RNSVGSymbolComponentName[]; /* From 665409e4b5c5a7e1d270e8c975a7b68a1dce5175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 17 Oct 2025 14:57:41 +0200 Subject: [PATCH 3/6] fix: propagate elements metrics to their shadow nodes on Android --- .../java/com/horcrux/svg/RenderableView.java | 18 +++++++++++++----- .../com/horcrux/svg/RenderableViewManager.java | 11 +++++++++++ .../main/java/com/horcrux/svg/VirtualView.java | 6 ++++++ .../rnsvg/RNSVGLayoutableShadowNode.cpp | 3 +-- .../components/rnsvg/RNSVGLayoutableState.cpp | 2 -- .../components/rnsvg/RNSVGLayoutableState.h | 2 +- 6 files changed, 32 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/RenderableView.java b/android/src/main/java/com/horcrux/svg/RenderableView.java index 57d6e8197..6f271c5d1 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableView.java +++ b/android/src/main/java/com/horcrux/svg/RenderableView.java @@ -33,8 +33,8 @@ import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.WritableMap; import com.facebook.react.touch.ReactHitSlopView; -import com.facebook.react.uimanager.events.RCTEventEmitter; import com.facebook.react.uimanager.PointerEvents; +import com.facebook.react.uimanager.events.RCTEventEmitter; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.regex.Matcher; @@ -103,10 +103,8 @@ public abstract class RenderableView extends VirtualView implements ReactHitSlop public void onReceiveNativeEvent() { WritableMap event = Arguments.createMap(); - ReactContext reactContext = (ReactContext)getContext(); - reactContext - .getJSModule(RCTEventEmitter.class) - .receiveEvent(getId(), "topSvgLayout", event); + ReactContext reactContext = (ReactContext) getContext(); + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topSvgLayout", event); } @Nullable String mFilter; @@ -500,6 +498,16 @@ void render(Canvas canvas, Paint paint, float opacity) { } else { draw(canvas, paint, opacity); } + + if (this.stateWrapper != null) { + WritableMap map = Arguments.createMap(); + RectF clientRect = this.getClientRect(); + map.putDouble("x", clientRect.left / mScale); + map.putDouble("y", clientRect.top / mScale); + map.putDouble("width", clientRect.width() / mScale); + map.putDouble("height", clientRect.height() / mScale); + this.stateWrapper.updateState(map); + } } @Override diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index 06095e45f..26e186a8b 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -67,6 +67,9 @@ import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; + +import androidx.annotation.NonNull; + import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.ReadableArray; @@ -78,6 +81,8 @@ import com.facebook.react.uimanager.MatrixMathHelper; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.PointerEvents; +import com.facebook.react.uimanager.ReactStylesDiffMap; +import com.facebook.react.uimanager.StateWrapper; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.TransformHelper; import com.facebook.react.uimanager.ViewGroupManager; @@ -166,6 +171,12 @@ protected ViewManagerDelegate getDelegate() { return mDelegate; } + @Override + public Object updateState(@NonNull VirtualView view, ReactStylesDiffMap props, StateWrapper stateWrapper) { + view.setStateWrapper(stateWrapper); + return super.updateState(view, props, stateWrapper); + } + static class RenderableShadowNode extends LayoutShadowNode { @SuppressWarnings({"unused", "EmptyMethod"}) diff --git a/android/src/main/java/com/horcrux/svg/VirtualView.java b/android/src/main/java/com/horcrux/svg/VirtualView.java index 3f8b0ac35..be4ce86e7 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualView.java +++ b/android/src/main/java/com/horcrux/svg/VirtualView.java @@ -21,6 +21,7 @@ import com.facebook.react.common.ReactConstants; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.PointerEvents; +import com.facebook.react.uimanager.StateWrapper; import com.facebook.react.uimanager.UIManagerHelper; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.views.view.ReactViewGroup; @@ -85,6 +86,7 @@ public abstract class VirtualView extends ReactViewGroup { private float canvasHeight = -1; private float canvasWidth = -1; private GlyphContext glyphContext; + protected @Nullable StateWrapper stateWrapper = null; Path mPath; Path mFillPath; @@ -107,6 +109,10 @@ public void setPointerEvents(PointerEvents pointerEvents) { mPointerEvents = pointerEvents; } + public void setStateWrapper(@Nullable StateWrapper stateWrapper) { + this.stateWrapper = stateWrapper; + } + @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp index 4b3e46e1b..f66a68b35 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp @@ -20,8 +20,7 @@ void RNSVGLayoutableShadowNode::setShadowNodePosition(float x, float y) { style.setPositionType(yoga::PositionType::Absolute); style.setPosition(yoga::Edge::Left, yoga::StyleLength::points(x)); style.setPosition(yoga::Edge::Top, yoga::StyleLength::points(y)); -// style.setMargin(yoga::Edge::Left, yoga::StyleLength::points(x)); -// style.setMargin(yoga::Edge::Top, yoga::StyleLength::points(y)); + yogaNode_.setStyle(style); } diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.cpp b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.cpp index 4828d47be..6f993caf9 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.cpp +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.cpp @@ -1,5 +1,3 @@ -#pragma once - #include "RNSVGLayoutableState.h" namespace facebook::react { diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h index f208a5e1d..503e90c4f 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableState.h @@ -22,7 +22,7 @@ class RNSVGLayoutableState { height_((float)data["height"].getDouble()){}; folly::dynamic getDynamic() const { - return folly::dynamic::object("x", x_)("y", y_)("width", width_)("height", height_) + return folly::dynamic::object("x", x_)("y", y_)("width", width_)("height", height_); }; #endif From b15b15a50a052b63c653c94d4572893cbf7a4402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 17 Oct 2025 15:05:52 +0200 Subject: [PATCH 4/6] fix: revert example changes --- apps/common/example/examples/TouchEvents.tsx | 31 ++----- apps/common/index.tsx | 2 +- apps/common/test/ColorTest.tsx | 96 +++++++++----------- 3 files changed, 49 insertions(+), 80 deletions(-) diff --git a/apps/common/example/examples/TouchEvents.tsx b/apps/common/example/examples/TouchEvents.tsx index 425941000..c822d3a50 100644 --- a/apps/common/example/examples/TouchEvents.tsx +++ b/apps/common/example/examples/TouchEvents.tsx @@ -69,10 +69,10 @@ HoverExample.title = 'Hover the svg path'; function GroupExample() { return ( - - {/* Alert.alert('Pressed on G')} collapsable={false}> */} - {/* + Alert.alert('Pressed on G')} scale="1.4"> + + Alert.alert('Pressed on Text')}> H - */} - Alert.alert('Pressed on Circle')} - /> - Alert.alert('Pressed on Rect')} - /> - {/* */} + + + ); } diff --git a/apps/common/index.tsx b/apps/common/index.tsx index 2bbaade62..fe90837e6 100644 --- a/apps/common/index.tsx +++ b/apps/common/index.tsx @@ -1,3 +1,3 @@ -import App from './test'; +import App from './example'; export default App; diff --git a/apps/common/test/ColorTest.tsx b/apps/common/test/ColorTest.tsx index 7c5166a4f..c9eb63fd2 100644 --- a/apps/common/test/ColorTest.tsx +++ b/apps/common/test/ColorTest.tsx @@ -1,64 +1,50 @@ -import {Alert, View} from 'react-native'; -import {Circle, G, Rect, Svg, Text} from 'react-native-svg'; +import React from 'react'; +import {PlatformColor, Platform, Button, DynamicColorIOS} from 'react-native'; +import {Svg, Circle, Rect, Text, TSpan} from 'react-native-svg'; -export default () => { - return ( - - Alert.alert('Pressed on SVG')}> - {/* Alert.alert('Pressed on G')} collapsable={false}> */} +const color = + Platform.OS !== 'web' + ? PlatformColor( + Platform.select({ + ios: 'systemTealColor', + android: '@android:color/holo_blue_bright', + default: 'black', + }), + ) + : 'black'; - - Alert.alert('Pressed on Circle')} - /> - - Alert.alert('Pressed on Text')}> - H - - - Alert.alert('Pressed on Circle')} - /> +// const customContrastDynamicTextColor = DynamicColorIOS({ +// dark: 'hsla(360, 40%, 30%, 1.0)', +// light: '#ff00ff55', +// highContrastDark: 'black', +// highContrastLight: 'white', +// }); - Alert.alert('Pressed on Circle')} - /> +export default () => { + const [test, setTest] = React.useState(50); + return ( + <> + + Alert.alert('Pressed on Rect')} + x="15" + y="15" + width="70" + height="70" + stroke="currentColor" + strokeWidth="5" /> - {/* */} - + + + + Testing word-wrap... Testing word-wrap... Testing word-wrap... + Testing word-wrap... + + + +