Skip to content
This repository has been archived by the owner on Oct 8, 2021. It is now read-only.

Commit

Permalink
Shape and Text Shadows (#45)
Browse files Browse the repository at this point in the history
* shadows on both android and ios

* update docs

* linter fix

* fix flow typings

* linter fixes

* forgot to include opacity

* update comment

* update typscript definitions
  • Loading branch information
adkenyon authored and Esemesek committed Dec 19, 2019
1 parent 2007634 commit c622f7c
Show file tree
Hide file tree
Showing 20 changed files with 205 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ protected boolean setupStrokePaint(Paint paint, float opacity) {
if (mStrokeDash != null && mStrokeDash.length > 0) {
paint.setPathEffect(new DashPathEffect(mStrokeDash, 0));
}
if (mShadowOpacity > 0) {
paint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mShadowColor);
}
return true;
}

Expand Down Expand Up @@ -231,6 +234,9 @@ protected boolean setupFillPaint(Paint paint, float opacity) {
default:
FLog.w(ReactConstants.TAG, "ART: Color type " + colorType + " not supported!");
}
if (mShadowOpacity > 0) {
paint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mShadowColor);
}
return true;
}
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ public void draw(Canvas canvas, Paint paint, float opacity) {
canvas.drawTextOnPath(text, mPath, 0, 0, paint);
}
}
if (mShadowOpacity > 0) {
paint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mShadowColor);
}
restoreCanvas(canvas);
markUpdateSeen();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
package com.reactnativecommunity.art;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.support.v4.graphics.ColorUtils;

import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.DisplayMetricsHolder;
Expand All @@ -30,6 +33,11 @@ public abstract class ARTVirtualNode extends ReactShadowNodeImpl {

protected float mOpacity = 1f;
private @Nullable Matrix mMatrix = new Matrix();
protected int mShadowColor = 0;
protected float mShadowOpacity = 1;
protected float mShadowRadius = 0;
protected float mShadowOffsetX = 0;
protected float mShadowOffsetY = 0;

protected final float mScale;

Expand Down Expand Up @@ -91,6 +99,32 @@ public void setTransform(@Nullable ReadableArray transformArray) {
markUpdated();
}

@ReactProp(name = "shadow")
public void setShadow(@Nullable ReadableArray shadowArray) {
if (shadowArray != null) {
mShadowOpacity = (float)shadowArray.getDouble(1);
mShadowRadius = (float)shadowArray.getDouble(2);
mShadowOffsetX = (float)shadowArray.getDouble(3);
mShadowOffsetY = (float)shadowArray.getDouble(4);

int color = shadowArray.getInt(0);

if (mShadowOpacity < 1) {
color = ColorUtils.setAlphaComponent(color, (int)(mShadowOpacity * 255));
}

mShadowColor = color;

} else {
mShadowColor = 0;
mShadowOpacity = 0;
mShadowRadius = 0;
mShadowOffsetX = 0;
mShadowOffsetY = 0;
}
markUpdated();
}

protected void setupMatrix() {
sRawMatrix[0] = sMatrixData[0];
sRawMatrix[1] = sMatrixData[2];
Expand Down
23 changes: 21 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Container to combine shapes or other groups into hierarchies that can be transfo
| :---------------: | :-----------------------------------: | :-----: |
| ...opacityProps | [`OpacityProps`](###OpacityProps) | --- |
| ...transformProps | [`TransformProps`](###TransformProps) | --- |
| ...shadowProps | [`ShadowProps`](###ShadowProps) | --- |
| children | `React.Node` | --- |

```jsx
Expand All @@ -53,7 +54,8 @@ Used to draw arbitrary vector shapes from Path. Shape implements Transform as a
| :---------------: | :-----------------------------------: | :-------: |
| ...opacityProps | [`OpacityProps`](###OpacityProps) | --- |
| ...transformProps | [`TransformProps`](###TransformProps) | --- |
| fill | `string \| Brush` | --- |
| ...shadowProps | [`ShadowProps`](###ShadowProps) | --- |
| fill | `string \| Brush` | --- |
| stroke | `string` | --- |
| strokeCap | `'butt' \| 'square' \| 'round'` | `'round'` |
| strokeDash | `Array<number>` | --- |
Expand Down Expand Up @@ -84,7 +86,8 @@ Text component creates a shape based on text content using native text rendering
| :---------------: | :-----------------------------------: | :-------: |
| ...opacityProps | [`OpacityProps`](###OpacityProps) | --- |
| ...transformProps | [`TransformProps`](###TransformProps) | --- |
| fill | `string \| Brush` | --- |
| ...shadowProps | [`ShadowProps`](###ShadowProps) | --- |
| fill | `string \| Brush` | --- |
| stroke | `string` | --- |
| strokeCap | `'butt' \| 'square' \| 'round'` | `'round'` |
| strokeDash | `Array<number>` | --- |
Expand Down Expand Up @@ -354,6 +357,22 @@ function Component() {
| originY | `number` | --- |
| transform | `TransformObject` | --- |

### ShadowProps

| Prop | Type | Default |
| :-----------: | :------------------: | :------: |
| shadowOpacity | `number` | `1` |
| shadowColor | `string` | `black` |
| shadowRadius | `number` | `0` |
| shadowOffset | `ShadowOffsetObject` | --- |

### ShadowOffsetObject

| Prop | Type | Default |
| :--: | :------: | :-----: |
| y | `number` | `0` |
| x | `number` | `0` |

### Font

| Prop | Type | Default |
Expand Down
12 changes: 11 additions & 1 deletion example/components/CustomText.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@ import React from 'react';
import {Dimensions, StyleSheet} from 'react-native';
import {Surface, Text, Group} from '@react-native-community/art';

const SHADOW = {
shadowOpacity: 0.5,
shadowColor: 'blue',
shadowRadius: 10,
shadowOffset: {x: 4, y: 4},
};

export default function CustomText() {
const surfaceWidth = Dimensions.get('window').width;
const surfaceHeight = surfaceWidth / 3;

return (
<Surface width={surfaceWidth} height={surfaceHeight} style={styles.surface}>
<Group x={surfaceWidth / 2 - 100} y={surfaceHeight / 2}>
<Text font={'18px "Helvetica Neue", "Helvetica", Arial'} fill="#000000">
<Text
font={'18px "Helvetica Neue", "Helvetica", Arial'}
fill="#000000"
{...SHADOW}>
React Native Community
</Text>
</Group>
Expand Down
8 changes: 8 additions & 0 deletions example/components/Heart.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import {
const HEART_SHAPE =
'M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z';

const SHADOW = {
shadowOpacity: 1,
shadowColor: 'red',
shadowRadius: 8,
shadowOffset: {x: 0, y: 0},
};

export default function Heart() {
const surfaceDimensions = Dimensions.get('window').width;
const gradient = new RadialGradient(
Expand Down Expand Up @@ -42,6 +49,7 @@ export default function Heart() {
stroke={'#00ff00'}
fill={gradient}
visible={true}
{...SHADOW}
/>
</Group>
</Surface>
Expand Down
7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ declare module '@react-native-community/art' {
x?: number;
y?: number;
visible?: boolean;
shadowOpacity?: number;
shadowColor?: string | number;
shadowRadius?: number;
shadowOffset?: {
x: number,
y: number,
}
}

export interface ARTGroupProps extends ARTNodeMixin {
Expand Down
3 changes: 3 additions & 0 deletions ios/ART.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
09966EAF23996E3900E9C452 /* ARTShadow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ARTShadow.h; sourceTree = "<group>"; };
0CF68AC11AF0540F00FF9E5C /* libART.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libART.a; sourceTree = BUILT_PRODUCTS_DIR; };
0CF68ADB1AF0549300FF9E5C /* ARTCGFloatArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTCGFloatArray.h; sourceTree = "<group>"; };
0CF68ADC1AF0549300FF9E5C /* ARTContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTContainer.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -131,6 +132,7 @@
0CF68AB81AF0540F00FF9E5C = {
isa = PBXGroup;
children = (
09966EAF23996E3900E9C452 /* ARTShadow.h */,
0CF68AEA1AF0549300FF9E5C /* Brushes */,
0CF68AF81AF0549300FF9E5C /* ViewManagers */,
0CF68ADB1AF0549300FF9E5C /* ARTCGFloatArray.h */,
Expand Down Expand Up @@ -261,6 +263,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 0CF68AB81AF0540F00FF9E5C;
Expand Down
3 changes: 2 additions & 1 deletion ios/ARTNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

#import <React/UIView+React.h>

#import "ARTShadow.h"
/**
* ART nodes are implemented as empty UIViews but this is just an implementation detail to fit
* into the existing view management. They should also be shadow views and painted on a background
Expand All @@ -16,6 +16,7 @@
@interface ARTNode : UIView

@property (nonatomic, assign) CGFloat opacity;
@property (nonatomic, assign) ARTShadow shadow;

- (void)invalidate;
- (void)renderTo:(CGContextRef)context;
Expand Down
22 changes: 16 additions & 6 deletions ios/ARTNode.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ - (void)setTransform:(CGAffineTransform)transform
super.transform = transform;
}

- (void)setShadow:(ARTShadow)shadow
{
[self invalidate];
_shadow = shadow;
}

- (void)invalidate
{
id<ARTContainer> container = (id<ARTContainer>)self.superview;
Expand All @@ -55,23 +61,27 @@ - (void)renderTo:(CGContextRef)context
}
if (self.opacity >= 1) {
// Just paint at full opacity
CGContextSaveGState(context);
CGContextConcatCTM(context, self.transform);
CGContextSetAlpha(context, 1);
[self renderContentTo:context];
[self renderLayerTo:context];
CGContextRestoreGState(context);
return;
}

// This needs to be painted on a layer before being composited.
CGContextSaveGState(context);
CGContextConcatCTM(context, self.transform);
CGContextSetAlpha(context, self.opacity);
[self renderContentTo:context];
CGContextBeginTransparencyLayer(context, NULL);
[self renderLayerTo:context];
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
}

- (void)renderContentTo:(CGContextRef)context {
CGContextSaveGState(context);
CGContextConcatCTM(context, self.transform);
CGContextSetAlpha(context, self.opacity);
CGContextSetShadowWithColor(context, self.shadow.offset, self.shadow.blur, self.shadow.color.CGColor);
}

- (void)renderLayerTo:(CGContextRef)context
{
// abstract
Expand Down
12 changes: 12 additions & 0 deletions ios/ARTShadow.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* 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.
*/

typedef struct {
CGSize offset;
CGFloat blur;
UIColor* color;
} ARTShadow;
2 changes: 2 additions & 0 deletions ios/RCTConvert+ART.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import <React/RCTConvert.h>

#import "ARTBrush.h"
#import "ARTShadow.h"
#import "ARTCGFloatArray.h"
#import "ARTTextFrame.h"

Expand All @@ -20,6 +21,7 @@
+ (ARTTextFrame)ARTTextFrame:(id)json;
+ (ARTCGFloatArray)ARTCGFloatArray:(id)json;
+ (ARTBrush *)ARTBrush:(id)json;
+ (ARTShadow)ARTShadow:(id)json;

+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset;
+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset;
Expand Down
17 changes: 17 additions & 0 deletions ios/RCTConvert+ART.m
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,23 @@ + (ARTBrush *)ARTBrush:(id)json
}
}

+ (ARTShadow)ARTShadow:(id)json
{
NSArray *arr = [self NSArray:json];

UIColor *color = [UIColor colorWithCGColor:[self CGColor:[arr objectAtIndex:0]]];
color = [color colorWithAlphaComponent:[[arr objectAtIndex:1] floatValue]];

return (ARTShadow){
.color = color,
.blur = [[arr objectAtIndex:2] floatValue],
.offset = (CGSize){
.width = [[arr objectAtIndex:3] floatValue],
.height = [[arr objectAtIndex:4] floatValue]
},
};
}

+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset
{
NSArray *arr = [self NSArray:json];
Expand Down
1 change: 1 addition & 0 deletions ios/ViewManagers/ARTNodeManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ - (RCTShadowView *)shadowView

RCT_EXPORT_VIEW_PROPERTY(opacity, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(transform, CGAffineTransform)
RCT_EXPORT_VIEW_PROPERTY(shadow, ARTShadow)

@end
23 changes: 13 additions & 10 deletions lib/ClippingRectangle.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@

import * as React from 'react';
import merge from 'react-native/Libraries/vendor/core/merge';
import {extractOpacity, extractTransform} from './helpers';
import {extractOpacity, extractTransform, extractShadow} from './helpers';
import {NativeGroup} from './nativeComponents';
import type {OpacityProps} from './types';
import type {OpacityProps, TransformProps, ShadowProps} from './types';

type ClippingRectangleProps = OpacityProps & {
x: number,
y: number,
width: number,
height: number,
children?: React.Node,
};
type ClippingRectangleProps = OpacityProps &
TransformProps &
ShadowProps & {
x: number,
y: number,
width: number,
height: number,
children?: React.Node,
};

export default class ClippingRectangle extends React.Component<ClippingRectangleProps> {
static defaultProps = {
Expand All @@ -44,7 +46,8 @@ export default class ClippingRectangle extends React.Component<ClippingRectangle
<NativeGroup
clipping={clipping}
opacity={extractOpacity(this.props)}
transform={extractTransform(propsExcludingXAndY)}>
transform={extractTransform(propsExcludingXAndY)}
shadow={extractShadow(this.props)}>
{this.props.children}
</NativeGroup>
);
Expand Down
Loading

0 comments on commit c622f7c

Please sign in to comment.