Skip to content

Commit 27eb9b0

Browse files
Fix: propagate elements metrics to their shadow nodes (#2802)
# Summary Fixes: #2796 Fixes: #2083 Previously, in the new architecture, shadow nodes for elements such as `Circle`, `Rect` and other `SVG` components were initialized with zero dimensions. This caused issues with press interactions during move action. Yoga was unable to locate the corresponding shadow node resulting in the press event being canceled. This PR updates the implementation to propagate each element’s actual native layout metrics to its shadow node. By ensuring the shadow nodes reflect accurate geometry, Yoga can now correctly resolve touch targets fixing the issue where press actions were being incorrectly canceled. ## Test Plan - Examples -> TouchEvents - run example from issue: #2796
1 parent f36e4f9 commit 27eb9b0

16 files changed

+254
-59
lines changed

android/src/main/java/com/horcrux/svg/GroupView.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
import android.graphics.Region;
2020
import android.os.Build;
2121
import android.view.View;
22+
import com.facebook.react.bridge.Arguments;
2223
import com.facebook.react.bridge.Dynamic;
2324
import com.facebook.react.bridge.ReactContext;
2425
import com.facebook.react.bridge.ReadableMap;
2526
import com.facebook.react.bridge.ReadableType;
27+
import com.facebook.react.bridge.WritableMap;
2628
import java.util.ArrayList;
2729
import javax.annotation.Nullable;
2830

@@ -340,4 +342,20 @@ void resetProperties() {
340342
}
341343
}
342344
}
345+
346+
@Override
347+
public void updateShadowNodeMetrics() {
348+
SvgView svgView = this.getSvgView();
349+
350+
if (this.stateWrapper == null || svgView == null) {
351+
return;
352+
}
353+
354+
WritableMap map = Arguments.createMap();
355+
map.putDouble("x", 0);
356+
map.putDouble("y", 0);
357+
map.putDouble("width", svgView.getCanvasWidth() / mScale);
358+
map.putDouble("height", svgView.getCanvasHeight() / mScale);
359+
this.stateWrapper.updateState(map);
360+
}
343361
}

android/src/main/java/com/horcrux/svg/RenderableView.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
import com.facebook.react.bridge.ReadableType;
3434
import com.facebook.react.bridge.WritableMap;
3535
import com.facebook.react.touch.ReactHitSlopView;
36-
import com.facebook.react.uimanager.events.RCTEventEmitter;
3736
import com.facebook.react.uimanager.PointerEvents;
37+
import com.facebook.react.uimanager.events.RCTEventEmitter;
3838
import java.lang.reflect.Field;
3939
import java.util.ArrayList;
4040
import java.util.regex.Matcher;
@@ -103,10 +103,8 @@ public abstract class RenderableView extends VirtualView implements ReactHitSlop
103103

104104
public void onReceiveNativeEvent() {
105105
WritableMap event = Arguments.createMap();
106-
ReactContext reactContext = (ReactContext)getContext();
107-
reactContext
108-
.getJSModule(RCTEventEmitter.class)
109-
.receiveEvent(getId(), "topSvgLayout", event);
106+
ReactContext reactContext = (ReactContext) getContext();
107+
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topSvgLayout", event);
110108
}
111109

112110
@Nullable String mFilter;
@@ -500,6 +498,8 @@ void render(Canvas canvas, Paint paint, float opacity) {
500498
} else {
501499
draw(canvas, paint, opacity);
502500
}
501+
502+
this.updateShadowNodeMetrics();
503503
}
504504

505505
@Override
@@ -823,4 +823,19 @@ void resetProperties() {
823823
private boolean hasOwnProperty(String propName) {
824824
return mAttributeList != null && mAttributeList.contains(propName);
825825
}
826+
827+
protected void updateShadowNodeMetrics() {
828+
RectF clientRect = this.getClientRect();
829+
830+
if (this.stateWrapper == null || clientRect == null) {
831+
return;
832+
}
833+
834+
WritableMap map = Arguments.createMap();
835+
map.putDouble("x", clientRect.left / mScale);
836+
map.putDouble("y", clientRect.top / mScale);
837+
map.putDouble("width", clientRect.width() / mScale);
838+
map.putDouble("height", clientRect.height() / mScale);
839+
this.stateWrapper.updateState(map);
840+
}
826841
}

android/src/main/java/com/horcrux/svg/RenderableViewManager.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
import android.util.SparseArray;
6868
import android.view.View;
6969
import android.view.ViewGroup;
70+
71+
import androidx.annotation.NonNull;
72+
7073
import com.facebook.react.bridge.Dynamic;
7174
import com.facebook.react.bridge.JavaOnlyMap;
7275
import com.facebook.react.bridge.ReadableArray;
@@ -78,6 +81,8 @@
7881
import com.facebook.react.uimanager.MatrixMathHelper;
7982
import com.facebook.react.uimanager.PixelUtil;
8083
import com.facebook.react.uimanager.PointerEvents;
84+
import com.facebook.react.uimanager.ReactStylesDiffMap;
85+
import com.facebook.react.uimanager.StateWrapper;
8186
import com.facebook.react.uimanager.ThemedReactContext;
8287
import com.facebook.react.uimanager.TransformHelper;
8388
import com.facebook.react.uimanager.ViewGroupManager;
@@ -166,6 +171,12 @@ protected ViewManagerDelegate getDelegate() {
166171
return mDelegate;
167172
}
168173

174+
@Override
175+
public Object updateState(@NonNull VirtualView view, ReactStylesDiffMap props, StateWrapper stateWrapper) {
176+
view.setStateWrapper(stateWrapper);
177+
return super.updateState(view, props, stateWrapper);
178+
}
179+
169180
static class RenderableShadowNode extends LayoutShadowNode {
170181

171182
@SuppressWarnings({"unused", "EmptyMethod"})

android/src/main/java/com/horcrux/svg/VirtualView.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
import com.facebook.react.common.ReactConstants;
2222
import com.facebook.react.uimanager.DisplayMetricsHolder;
2323
import com.facebook.react.uimanager.PointerEvents;
24+
import com.facebook.react.uimanager.StateWrapper;
2425
import com.facebook.react.uimanager.UIManagerHelper;
2526
import com.facebook.react.uimanager.events.EventDispatcher;
2627
import com.facebook.react.views.view.ReactViewGroup;
2728
import com.horcrux.svg.events.SvgOnLayoutEvent;
28-
2929
import java.util.ArrayList;
3030
import javax.annotation.Nullable;
3131

@@ -84,6 +84,7 @@ public abstract class VirtualView extends ReactViewGroup {
8484
private float canvasHeight = -1;
8585
private float canvasWidth = -1;
8686
private GlyphContext glyphContext;
87+
protected @Nullable StateWrapper stateWrapper = null;
8788

8889
Path mPath;
8990
Path mFillPath;
@@ -106,6 +107,10 @@ public void setPointerEvents(PointerEvents pointerEvents) {
106107
mPointerEvents = pointerEvents;
107108
}
108109

110+
public void setStateWrapper(@Nullable StateWrapper stateWrapper) {
111+
this.stateWrapper = stateWrapper;
112+
}
113+
109114
@Override
110115
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
111116
super.onInitializeAccessibilityNodeInfo(info);
@@ -249,7 +254,7 @@ int saveAndSetupCanvas(Canvas canvas, Matrix ctm) {
249254
int count = canvas.save();
250255
mCTM.set(mMatrix);
251256
canvas.concat(mCTM);
252-
mCTM.preConcat(ctm);
257+
mCTM.postConcat(ctm);
253258
mCTMInvertible = mCTM.invert(mInvCTM);
254259
return count;
255260
}
@@ -437,15 +442,19 @@ public SvgView getSvgView() {
437442

438443
double relativeOnWidth(SVGLength length) {
439444
SvgView svg = getSvgView();
440-
if (length.unit == SVGLength.UnitType.PERCENTAGE && svg != null && svg.getViewBox().width() != 0) {
445+
if (length.unit == SVGLength.UnitType.PERCENTAGE
446+
&& svg != null
447+
&& svg.getViewBox().width() != 0) {
441448
return relativeOn(length, svg.getViewBox().width());
442449
}
443450
return relativeOn(length, getCanvasWidth());
444451
}
445452

446453
double relativeOnHeight(SVGLength length) {
447454
SvgView svg = getSvgView();
448-
if (length.unit == SVGLength.UnitType.PERCENTAGE && svg != null && svg.getViewBox().height() != 0) {
455+
if (length.unit == SVGLength.UnitType.PERCENTAGE
456+
&& svg != null
457+
&& svg.getViewBox().height() != 0) {
449458
return relativeOn(length, svg.getViewBox().height());
450459
}
451460
return relativeOn(length, getCanvasHeight());

apple/Elements/RNSVGGroup.mm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#import <react/renderer/components/view/conversions.h>
1717
#import <rnsvg/RNSVGComponentDescriptors.h>
1818
#import "RNSVGFabricConversions.h"
19+
#import "RNSVGRenderUtils.h"
1920
#endif // RCT_NEW_ARCH_ENABLED
2021

2122
@implementation RNSVGGroup {
@@ -308,6 +309,16 @@ - (void)resetProperties
308309
}];
309310
}
310311

312+
#ifdef RCT_NEW_ARCH_ENABLED
313+
- (void)updateShadowNodeMetrics
314+
{
315+
auto statePtr = [self state];
316+
if (statePtr) {
317+
statePtr->updateState(RNSVGLayoutableState(0, 0, [self getCanvasWidth], [self getCanvasHeight]));
318+
}
319+
}
320+
#endif
321+
311322
@end
312323

313324
#ifdef RCT_NEW_ARCH_ENABLED

apple/RNSVGRenderable.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
#import "RNSVGNode.h"
1717
#import "RNSVGVectorEffect.h"
1818

19+
#ifdef RCT_NEW_ARCH_ENABLED
20+
#import <rnsvg/RNSVGComponentDescriptors.h>
21+
22+
using namespace facebook::react;
23+
#endif
24+
1925
@interface RNSVGRenderable : RNSVGNode
2026

2127
@property (class) RNSVGRenderable *contextElement;
@@ -36,6 +42,10 @@
3642
@property (nonatomic, assign) CGPathRef hitArea;
3743
@property (nonatomic, strong) NSString *filter;
3844

45+
#ifdef RCT_NEW_ARCH_ENABLED
46+
- (facebook::react::RNSVGRenderableShadowNode::ConcreteState::Shared)state;
47+
#endif
48+
3949
- (void)setColor:(RNSVGColor *)color;
4050

4151
- (void)setHitArea:(CGPathRef)path;
@@ -48,4 +58,8 @@
4858

4959
- (CGColor *)getCurrentColor;
5060

61+
#ifdef RCT_NEW_ARCH_ENABLED
62+
- (void)updateShadowNodeMetrics;
63+
#endif
64+
5165
@end

apple/RNSVGRenderable.mm

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ @implementation RNSVGRenderable {
2626
CGFloat *_strokeDashArrayData;
2727
CGPathRef _srcHitPath;
2828
RNSVGRenderable *_caller;
29+
30+
#ifdef RCT_NEW_ARCH_ENABLED
31+
RNSVGRenderableShadowNode::ConcreteState::Shared _state;
32+
#endif
2933
}
3034

3135
static RNSVGRenderable *_contextElement;
@@ -243,6 +247,11 @@ - (void)prepareForRecycle
243247
_filter = nil;
244248
_caller = nil;
245249
}
250+
251+
- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState
252+
{
253+
_state = std::static_pointer_cast<const RNSVGRenderableShadowNode::ConcreteState>(state);
254+
}
246255
#endif // RCT_NEW_ARCH_ENABLED
247256

248257
UInt32 saturate(CGFloat value)
@@ -432,6 +441,9 @@ - (void)renderTo:(CGContextRef)context rect:(CGRect)rect
432441
CGContextRestoreGState(context);
433442

434443
[self renderMarkers:context path:self.path rect:&rect];
444+
#ifdef RCT_NEW_ARCH_ENABLED
445+
[self updateShadowNodeMetrics];
446+
#endif
435447
}
436448

437449
- (void)prepareStrokeDash:(NSUInteger)count strokeDasharray:(NSArray<RNSVGLength *> *)strokeDasharray
@@ -773,4 +785,17 @@ - (CGColor *)getCurrentColor
773785
return nil;
774786
}
775787

788+
#ifdef RCT_NEW_ARCH_ENABLED
789+
- (void)updateShadowNodeMetrics
790+
{
791+
if (_state) {
792+
_state->updateState(RNSVGLayoutableState(self.clientRect.origin.x, self.clientRect.origin.y, self.clientRect.size.width, self.clientRect.size.height));
793+
}
794+
}
795+
796+
- (RNSVGRenderableShadowNode::ConcreteState::Shared)state {
797+
return _state;
798+
}
799+
#endif
800+
776801
@end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RNSVGComponentDescriptor.h
2+
#pragma once
3+
#include <react/debug/react_native_assert.h>
4+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
5+
#include "RNSVGShadowNodes.h"
6+
7+
namespace facebook::react {
8+
9+
template <typename ShadowNodeT>
10+
class RNSVGComponentDescriptor final : public ConcreteComponentDescriptor<ShadowNodeT> {
11+
public:
12+
using ConcreteComponentDescriptor<ShadowNodeT>::ConcreteComponentDescriptor;
13+
void adopt(ShadowNode &shadowNode) const override {
14+
react_native_assert(dynamic_cast<RNSVGLayoutableShadowNode *>(&shadowNode));
15+
16+
auto layoutableShadowNode = dynamic_cast<RNSVGLayoutableShadowNode *>(&shadowNode);
17+
auto state = std::static_pointer_cast<const typename ShadowNodeT::ConcreteState>(shadowNode.getState());
18+
auto stateData = state->getData();
19+
20+
if (stateData.getWidth() != 0 && stateData.getHeight() != 0) {
21+
layoutableShadowNode->setSize(Size{stateData.getWidth(), stateData.getHeight()});
22+
layoutableShadowNode->setShadowNodePosition(stateData.getX(), stateData.getY());
23+
}
24+
25+
ConcreteComponentDescriptor<ShadowNodeT>::adopt(shadowNode);
26+
}
27+
};
28+
29+
} // namespace facebook::react

0 commit comments

Comments
 (0)