From 4972cabaa5f12a1633c74fca1be901d01f16faf9 Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Fri, 1 Jan 2016 09:32:59 -0800 Subject: [PATCH] Add shadow support Summary: Add three new TextStylePropTypes for \ - textShadowOffset - textShadowRadius - textShadowColor Closes https://github.com/facebook/react-native/pull/4975 Reviewed By: svcscm Differential Revision: D2796278 Pulled By: nicklockwood fb-gh-sync-id: f8c3fa210e664428b029b9fba8eca4a8eb81c08d --- Examples/UIExplorer/TextExample.android.js | 5 ++ Examples/UIExplorer/TextExample.ios.js | 11 ++++ .../View/ReactNativeStyleAttributes.js | 1 + Libraries/Text/RCTShadowText.h | 3 ++ Libraries/Text/RCTShadowText.m | 22 ++++++-- Libraries/Text/RCTTextManager.m | 3 ++ Libraries/Text/TextStylePropTypes.js | 5 ++ .../react/views/text/ReactTextShadowNode.java | 52 ++++++++++++++++++- .../react/views/text/ShadowStyleSpan.java | 31 +++++++++++ 9 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java diff --git a/Examples/UIExplorer/TextExample.android.js b/Examples/UIExplorer/TextExample.android.js index 18defae27..7c0c7868f 100644 --- a/Examples/UIExplorer/TextExample.android.js +++ b/Examples/UIExplorer/TextExample.android.js @@ -365,6 +365,11 @@ var TextExample = React.createClass({ This text contains an inline image . Neat, huh? + + + Demo text shadow + + ); } diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js index 57a2f032c..fbf434cde 100644 --- a/Examples/UIExplorer/TextExample.ios.js +++ b/Examples/UIExplorer/TextExample.ios.js @@ -424,6 +424,17 @@ exports.examples = [ ); }, +}, { + title: 'Text shadow', + render: function() { + return ( + + + Demo text shadow + + + ); + }, }]; var styles = StyleSheet.create({ diff --git a/Libraries/Components/View/ReactNativeStyleAttributes.js b/Libraries/Components/View/ReactNativeStyleAttributes.js index ffeff5420..d26e01615 100644 --- a/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -46,5 +46,6 @@ ReactNativeStyleAttributes.color = colorAttributes; ReactNativeStyleAttributes.shadowColor = colorAttributes; ReactNativeStyleAttributes.textDecorationColor = colorAttributes; ReactNativeStyleAttributes.tintColor = colorAttributes; +ReactNativeStyleAttributes.textShadowColor = colorAttributes; module.exports = ReactNativeStyleAttributes; diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h index 1ded47c93..5d793916b 100644 --- a/Libraries/Text/RCTShadowText.h +++ b/Libraries/Text/RCTShadowText.h @@ -33,6 +33,9 @@ extern NSString *const RCTReactTagAttributeName; @property (nonatomic, assign) CGFloat fontSizeMultiplier; @property (nonatomic, assign) BOOL allowFontScaling; @property (nonatomic, assign) CGFloat opacity; +@property (nonatomic, assign) CGSize textShadowOffset; +@property (nonatomic, assign) CGFloat textShadowRadius; +@property (nonatomic, strong) UIColor *textShadowColor; - (void)recomputeText; diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index 9139c187d..4d4380d42 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -305,22 +305,31 @@ static css_dim_t RCTMeasure(void *context, float width, float height) } // Text decoration - if(_textDecorationLine == RCTTextDecorationLineTypeUnderline || - _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) { + if (_textDecorationLine == RCTTextDecorationLineTypeUnderline || + _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) { [self _addAttribute:NSUnderlineStyleAttributeName withValue:@(_textDecorationStyle) toAttributedString:attributedString]; } - if(_textDecorationLine == RCTTextDecorationLineTypeStrikethrough || - _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough){ + if (_textDecorationLine == RCTTextDecorationLineTypeStrikethrough || + _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough){ [self _addAttribute:NSStrikethroughStyleAttributeName withValue:@(_textDecorationStyle) toAttributedString:attributedString]; } - if(_textDecorationColor) { + if (_textDecorationColor) { [self _addAttribute:NSStrikethroughColorAttributeName withValue:_textDecorationColor toAttributedString:attributedString]; [self _addAttribute:NSUnderlineColorAttributeName withValue:_textDecorationColor toAttributedString:attributedString]; } + + // Text shadow + if (!CGSizeEqualToSize(_textShadowOffset, CGSizeZero)) { + NSShadow *shadow = [NSShadow new]; + shadow.shadowOffset = _textShadowOffset; + shadow.shadowBlurRadius = _textShadowRadius; + shadow.shadowColor = _textShadowColor; + [self _addAttribute:NSShadowAttributeName withValue:shadow toAttributedString:attributedString]; + } } - (void)fillCSSNode:(css_node_t *)node @@ -371,6 +380,9 @@ RCT_TEXT_PROPERTY(TextDecorationLine, _textDecorationLine, RCTTextDecorationLine RCT_TEXT_PROPERTY(TextDecorationStyle, _textDecorationStyle, NSUnderlineStyle); RCT_TEXT_PROPERTY(WritingDirection, _writingDirection, NSWritingDirection) RCT_TEXT_PROPERTY(Opacity, _opacity, CGFloat) +RCT_TEXT_PROPERTY(TextShadowOffset, _textShadowOffset, CGSize); +RCT_TEXT_PROPERTY(TextShadowRadius, _textShadowRadius, CGFloat); +RCT_TEXT_PROPERTY(TextShadowColor, _textShadowColor, UIColor *); - (void)setAllowFontScaling:(BOOL)allowFontScaling { diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index 5496b3b80..7a28ab928 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -59,6 +59,9 @@ RCT_EXPORT_SHADOW_PROPERTY(textDecorationLine, RCTTextDecorationLineType) RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection) RCT_EXPORT_SHADOW_PROPERTY(allowFontScaling, BOOL) RCT_EXPORT_SHADOW_PROPERTY(opacity, CGFloat) +RCT_EXPORT_SHADOW_PROPERTY(textShadowOffset, CGSize) +RCT_EXPORT_SHADOW_PROPERTY(textShadowRadius, CGFloat) +RCT_EXPORT_SHADOW_PROPERTY(textShadowColor, UIColor) - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary *)shadowViewRegistry { diff --git a/Libraries/Text/TextStylePropTypes.js b/Libraries/Text/TextStylePropTypes.js index ce1044277..edcf42839 100644 --- a/Libraries/Text/TextStylePropTypes.js +++ b/Libraries/Text/TextStylePropTypes.js @@ -30,6 +30,11 @@ var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), { ['normal' /*default*/, 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'] ), + textShadowOffset: ReactPropTypes.shape( + {width: ReactPropTypes.number, height: ReactPropTypes.number} + ), + textShadowRadius: ReactPropTypes.number, + textShadowColor: ColorPropType, /** * @platform ios */ diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index e6e19ad8c..315052660 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -32,6 +32,7 @@ import com.facebook.csslayout.CSSConstants; import com.facebook.csslayout.CSSNode; import com.facebook.csslayout.MeasureOutput; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.LayoutShadowNode; @@ -63,6 +64,11 @@ public class ReactTextShadowNode extends LayoutShadowNode { @VisibleForTesting public static final String PROP_TEXT = "text"; + public static final String PROP_SHADOW_OFFSET = "textShadowOffset"; + public static final String PROP_SHADOW_RADIUS = "textShadowRadius"; + public static final String PROP_SHADOW_COLOR = "textShadowColor"; + public static final int DEFAULT_TEXT_SHADOW_COLOR = 0x55000000; + private static final TextPaint sTextPaintInstance = new TextPaint(); static { @@ -114,8 +120,7 @@ public class ReactTextShadowNode extends LayoutShadowNode { ops.add(new SetSpanOperation(start, end, new ForegroundColorSpan(textCSSNode.mColor))); } if (textCSSNode.mIsBackgroundColorSet) { - ops.add( - new SetSpanOperation( + ops.add(new SetSpanOperation( start, end, new BackgroundColorSpan(textCSSNode.mBackgroundColor))); @@ -135,6 +140,16 @@ public class ReactTextShadowNode extends LayoutShadowNode { textCSSNode.mFontFamily, textCSSNode.getThemedContext().getAssets()))); } + if (textCSSNode.mTextShadowOffsetDx != 0 || textCSSNode.mTextShadowOffsetDy != 0) { + ops.add(new SetSpanOperation( + start, + end, + new ShadowStyleSpan( + textCSSNode.mTextShadowOffsetDx, + textCSSNode.mTextShadowOffsetDy, + textCSSNode.mTextShadowRadius, + textCSSNode.mTextShadowColor))); + } ops.add(new SetSpanOperation(start, end, new ReactTagSpan(textCSSNode.getReactTag()))); } } @@ -279,6 +294,11 @@ public class ReactTextShadowNode extends LayoutShadowNode { protected int mNumberOfLines = UNSET; protected int mFontSize = UNSET; + private float mTextShadowOffsetDx = 0; + private float mTextShadowOffsetDy = 0; + private float mTextShadowRadius = 1; + private int mTextShadowColor = DEFAULT_TEXT_SHADOW_COLOR; + /** * mFontStyle can be {@link Typeface#NORMAL} or {@link Typeface#ITALIC}. * mFontWeight can be {@link Typeface#NORMAL} or {@link Typeface#BOLD}. @@ -413,6 +433,34 @@ public class ReactTextShadowNode extends LayoutShadowNode { } } + @ReactProp(name = PROP_SHADOW_OFFSET) + public void setTextShadowOffset(ReadableMap offsetMap) { + if (offsetMap == null) { + mTextShadowOffsetDx = 0; + mTextShadowOffsetDy = 0; + } else { + mTextShadowOffsetDx = PixelUtil.toPixelFromDIP(offsetMap.getDouble("width")); + mTextShadowOffsetDy = PixelUtil.toPixelFromDIP(offsetMap.getDouble("height")); + } + markUpdated(); + } + + @ReactProp(name = PROP_SHADOW_RADIUS, defaultInt = 1) + public void setTextShadowRadius(float textShadowRadius) { + if (textShadowRadius != mTextShadowRadius) { + mTextShadowRadius = textShadowRadius; + markUpdated(); + } + } + + @ReactProp(name = PROP_SHADOW_COLOR, defaultInt = DEFAULT_TEXT_SHADOW_COLOR, customType = "Color") + public void setTextShadowColor(int textShadowColor) { + if (textShadowColor != mTextShadowColor) { + mTextShadowColor = textShadowColor; + markUpdated(); + } + } + @Override public boolean isVirtualAnchor() { return !mIsVirtual; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java new file mode 100644 index 000000000..9e030f838 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.text; + + +import android.text.TextPaint; +import android.text.style.CharacterStyle; + +public class ShadowStyleSpan extends CharacterStyle { + private final float mDx, mDy, mRadius; + private final int mColor; + + public ShadowStyleSpan(float dx, float dy, float radius, int color) { + mDx = dx; + mDy = dy; + mRadius = radius; + mColor = color; + } + + @Override + public void updateDrawState(TextPaint textPaint) { + textPaint.setShadowLayer(mRadius, mDx, mDy, mColor); + } +}