Skip to content

Commit

Permalink
fix!: #1247 Animated view translation inside Svg tag
Browse files Browse the repository at this point in the history
Fixes performance regressions on ios
Place elements inside ForeignObject to use v10 / standard behaviour
ForeignObject should behave unchanged from v10 / v11

Possibly fixes #1258 as well

BREAKING CHANGE: Behavior of native elements is reverted to pre v10
  • Loading branch information
msand committed Mar 4, 2020
1 parent 80b5064 commit 0288d95
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 30 deletions.
65 changes: 65 additions & 0 deletions android/src/main/java/com/horcrux/svg/ForeignObjectView.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
package com.horcrux.svg;

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;

import androidx.annotation.NonNull;
Expand Down Expand Up @@ -72,4 +74,67 @@ public void setHeight(Dynamic height) {
mH = SVGLength.from(height);
invalidate();
}

void drawGroup(final Canvas canvas, final Paint paint, final float opacity) {
pushGlyphContext();
final SvgView svg = getSvgView();
final GroupView self = this;
final RectF groupRect = new RectF();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child instanceof MaskView) {
continue;
}
if (child instanceof VirtualView) {
VirtualView node = ((VirtualView)child);
if ("none".equals(node.mDisplay)) {
continue;
}
if (node instanceof RenderableView) {
((RenderableView)node).mergeProperties(self);
}

int count = node.saveAndSetupCanvas(canvas, mCTM);
node.render(canvas, paint, opacity * mOpacity);
RectF r = node.getClientRect();
if (r != null) {
groupRect.union(r);
}

node.restoreCanvas(canvas, count);

if (node instanceof RenderableView) {
((RenderableView)node).resetProperties();
}

if (node.isResponsible()) {
svg.enableTouchEvents();
}
} else if (child instanceof SvgView) {
SvgView svgView = (SvgView)child;
svgView.drawChildren(canvas);
if (svgView.isResponsible()) {
svg.enableTouchEvents();
}
} else {
// Enable rendering other native ancestor views in e.g. masks
child.draw(canvas);
}
}
this.setClientRect(groupRect);
popGlyphContext();
}

// Enable rendering other native ancestor views in e.g. masks, but don't render them another time
Bitmap fakeBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
Canvas fake = new Canvas(fakeBitmap);

@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(fake);
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return super.drawChild(fake, child, drawingTime);
}
}
3 changes: 0 additions & 3 deletions android/src/main/java/com/horcrux/svg/GroupView.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,6 @@ void drawGroup(final Canvas canvas, final Paint paint, final float opacity) {
if (svgView.isResponsible()) {
svg.enableTouchEvents();
}
} else {
// Enable rendering other native ancestor views in e.g. masks
child.draw(canvas);
}
}
this.setClientRect(groupRect);
Expand Down
19 changes: 0 additions & 19 deletions android/src/main/java/com/horcrux/svg/SvgView.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,25 +96,6 @@ public void invalidate() {
mBitmap = null;
}

// Enable rendering other native ancestor views in e.g. masks, but don't render them another time
Bitmap fakeBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
Canvas fake = new Canvas(fakeBitmap);

@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(fake);
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return super.drawChild(fake, child, drawingTime);
}

@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
super.onDescendantInvalidated(child, target);
invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
if (getParent() instanceof VirtualView) {
Expand Down
82 changes: 82 additions & 0 deletions ios/Elements/RNSVGForeignObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* LICENSE file in the root directory of this source tree.
*/
#import "RNSVGForeignObject.h"
#import "RNSVGClipPath.h"
#import "RNSVGMask.h"
#import "RNSVGNode.h"

@implementation RNSVGForeignObject
Expand Down Expand Up @@ -34,6 +36,86 @@ - (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect
[super renderLayerTo:context rect:rect];
}

- (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect
{
[self pushGlyphContext];

__block CGRect bounds = CGRectNull;

[self traverseSubviews:^(UIView *node) {
if ([node isKindOfClass:[RNSVGMask class]] || [node isKindOfClass:[RNSVGClipPath class]]) {
// no-op
} else if ([node isKindOfClass:[RNSVGNode class]]) {
RNSVGNode* svgNode = (RNSVGNode*)node;
if (svgNode.display && [@"none" isEqualToString:svgNode.display]) {
return YES;
}
if (svgNode.responsible && !self.svgView.responsible) {
self.svgView.responsible = YES;
}

if ([node isKindOfClass:[RNSVGRenderable class]]) {
[(RNSVGRenderable*)node mergeProperties:self];
}

[svgNode renderTo:context rect:rect];

CGRect nodeRect = svgNode.clientRect;
if (!CGRectIsEmpty(nodeRect)) {
bounds = CGRectUnion(bounds, nodeRect);
}

if ([node isKindOfClass:[RNSVGRenderable class]]) {
[(RNSVGRenderable*)node resetProperties];
}
} else if ([node isKindOfClass:[RNSVGSvgView class]]) {
RNSVGSvgView* svgView = (RNSVGSvgView*)node;
CGFloat width = [self relativeOnWidth:svgView.bbWidth];
CGFloat height = [self relativeOnHeight:svgView.bbHeight];
CGRect rect = CGRectMake(0, 0, width, height);
CGContextClipToRect(context, rect);
[svgView drawToContext:context withRect:rect];
} else {
node.hidden = false;
[node.layer renderInContext:context];
node.hidden = true;
}

return YES;
}];
CGPathRef path = [self getPath:context];
[self setHitArea:path];
if (!CGRectEqualToRect(bounds, CGRectNull)) {
self.clientRect = bounds;
self.fillBounds = CGPathGetBoundingBox(path);
self.strokeBounds = CGPathGetBoundingBox(self.strokePath);
self.pathBounds = CGRectUnion(self.fillBounds, self.strokeBounds);

CGAffineTransform current = CGContextGetCTM(context);
CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM);

self.ctm = svgToClientTransform;
self.screenCTM = current;

CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms);
CGPoint mid = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint center = CGPointApplyAffineTransform(mid, transform);

self.bounds = bounds;
if (!isnan(center.x) && !isnan(center.y)) {
self.center = center;
}
self.frame = bounds;
}

[self popGlyphContext];
}

- (void)drawRect:(CGRect)rect
{
[self invalidate];
}

- (void)setX:(RNSVGLength *)x
{
if ([x isEqualTo:_x]) {
Expand Down
9 changes: 1 addition & 8 deletions ios/Elements/RNSVGGroup.m
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ - (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect
CGContextClipToRect(context, rect);
[svgView drawToContext:context withRect:rect];
} else {
node.hidden = false;
[node.layer renderInContext:context];
node.hidden = true;
[node drawRect:rect];
}

return YES;
Expand Down Expand Up @@ -107,11 +105,6 @@ - (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect
[self popGlyphContext];
}

- (void)drawRect:(CGRect)rect
{
[self invalidate];
}

- (void)setupGlyphContext:(CGContextRef)context
{
CGRect clipBounds = CGContextGetClipBoundingBox(context);
Expand Down

0 comments on commit 0288d95

Please sign in to comment.