Skip to content

Commit 93890de

Browse files
timothy-huynhFlewp
authored andcommitted
update Text to support gradient colors
1 parent f701402 commit 93890de

File tree

7 files changed

+101
-3
lines changed

7 files changed

+101
-3
lines changed

packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ - (RCTShadowView *)shadowView
2828
// Color
2929
RCT_REMAP_SHADOW_PROPERTY(color, textAttributes.foregroundColor, UIColor)
3030
RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textAttributes.backgroundColor, UIColor)
31+
RCT_REMAP_SHADOW_PROPERTY(gradientColors, textAttributes.gradientColors, NSArray)
3132
RCT_REMAP_SHADOW_PROPERTY(opacity, textAttributes.opacity, CGFloat)
3233
// Font
3334
RCT_REMAP_SHADOW_PROPERTY(fontFamily, textAttributes.fontFamily, NSString)

packages/react-native/Libraries/Text/RCTTextAttributes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern NSString *const RCTTextAttributesTagAttributeName;
2626
// Color
2727
@property (nonatomic, strong, nullable) UIColor *foregroundColor;
2828
@property (nonatomic, strong, nullable) UIColor *backgroundColor;
29+
@property (nonatomic, copy, nullable) NSArray *gradientColors;
2930
@property (nonatomic, assign) CGFloat opacity;
3031
// Font
3132
@property (nonatomic, copy, nullable) NSString *fontFamily;

packages/react-native/Libraries/Text/RCTTextAttributes.mm

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
4646
// Color
4747
_foregroundColor = textAttributes->_foregroundColor ?: _foregroundColor;
4848
_backgroundColor = textAttributes->_backgroundColor ?: _backgroundColor;
49+
_gradientColors = textAttributes->_gradientColors ?: _gradientColors;
4950
_opacity =
5051
!isnan(textAttributes->_opacity) ? (isnan(_opacity) ? 1.0 : _opacity) * textAttributes->_opacity : _opacity;
5152

@@ -294,6 +295,34 @@ - (UIColor *)effectiveForegroundColor
294295
{
295296
UIColor *effectiveForegroundColor = _foregroundColor ?: [UIColor blackColor];
296297

298+
if (_gradientColors != nil) {
299+
NSMutableArray *cgColors = [NSMutableArray array];
300+
for (NSNumber *rawColor in _gradientColors) {
301+
if (rawColor != nil) {
302+
UIColor *color = [RCTConvert UIColor:@((0xFF << 24) | [rawColor integerValue])];
303+
[cgColors addObject:(id)color.CGColor];
304+
}
305+
}
306+
307+
if([cgColors count] > 0) {
308+
CAGradientLayer *gradient = [CAGradientLayer layer];
309+
// this pattern width corresponds roughly to desktop's pattern width
310+
int patternWidth = 100;
311+
CGFloat height = _lineHeight * self.effectiveFontSizeMultiplier;
312+
gradient.frame = CGRectMake(0, 0, patternWidth, height);
313+
gradient.colors = cgColors;
314+
gradient.startPoint = CGPointMake(0.0, 0.5);
315+
gradient.endPoint = CGPointMake(1.0, 0.5);
316+
317+
UIGraphicsBeginImageContextWithOptions(gradient.frame.size, NO, 0.0);
318+
[gradient renderInContext:UIGraphicsGetCurrentContext()];
319+
UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext();
320+
UIGraphicsEndImageContext();
321+
322+
effectiveForegroundColor = [UIColor colorWithPatternImage:gradientImage];
323+
}
324+
}
325+
297326
if (!isnan(_opacity)) {
298327
effectiveForegroundColor =
299328
[effectiveForegroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveForegroundColor.CGColor) * _opacity];

packages/react-native/Libraries/Text/Text.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ export interface TextProps
214214
* Controls how touch events are handled. Similar to `View`'s `pointerEvents`.
215215
*/
216216
pointerEvents?: ViewStyle['pointerEvents'] | undefined;
217+
218+
/**
219+
* Adds a horizontal gradient using the int based color values.
220+
*/
221+
gradientColors?: number[] | undefined;
217222
}
218223

219224
/**

packages/react-native/Libraries/Text/TextNativeComponent.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const textViewConfig = {
4848
dataDetectorType: true,
4949
android_hyphenationFrequency: true,
5050
lineBreakStrategyIOS: true,
51+
gradientColors: true,
5152
},
5253
directEventTypes: {
5354
topTextLayout: {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.facebook.infer.annotation.Assertions;
2121
import com.facebook.react.bridge.ReadableArray;
2222
import com.facebook.react.bridge.ReadableMap;
23+
import com.facebook.react.bridge.ReadableType;
2324
import com.facebook.react.common.ReactConstants;
2425
import com.facebook.react.uimanager.IllegalViewOperationException;
2526
import com.facebook.react.uimanager.LayoutShadowNode;
@@ -34,6 +35,7 @@
3435
import com.facebook.react.views.text.internal.span.CustomLetterSpacingSpan;
3536
import com.facebook.react.views.text.internal.span.CustomLineHeightSpan;
3637
import com.facebook.react.views.text.internal.span.CustomStyleSpan;
38+
import com.facebook.react.views.text.internal.span.LinearGradientSpan;
3739
import com.facebook.react.views.text.internal.span.ReactAbsoluteSizeSpan;
3840
import com.facebook.react.views.text.internal.span.ReactBackgroundColorSpan;
3941
import com.facebook.react.views.text.internal.span.ReactClickableSpan;
@@ -160,9 +162,15 @@ private static void buildSpannedFromShadowNode(
160162
}
161163
int end = sb.length();
162164
if (end >= start) {
163-
if (textShadowNode.mIsColorSet) {
164-
ops.add(
165-
new SetSpanOperation(start, end, new ReactForegroundColorSpan(textShadowNode.mColor)));
165+
if (textShadowNode.mIsColorSet || textShadowNode.mGradientColors != null) {
166+
if (textShadowNode.mGradientColors != null && textShadowNode.mGradientColors.length >= 2) {
167+
int effectiveFontSize = textAttributes.getEffectiveFontSize();
168+
ops.add(
169+
new SetSpanOperation(start, end, new LinearGradientSpan(start * effectiveFontSize, textShadowNode.mGradientColors)));
170+
} else {
171+
ops.add(
172+
new SetSpanOperation(start, end, new ReactForegroundColorSpan(textShadowNode.mColor)));
173+
}
166174
}
167175
if (textShadowNode.mIsBackgroundColorSet) {
168176
ops.add(
@@ -319,6 +327,8 @@ protected Spannable spannedFromShadowNode(
319327
protected boolean mIsBackgroundColorSet = false;
320328
protected int mBackgroundColor;
321329

330+
protected @Nullable int[] mGradientColors = null;
331+
322332
protected @Nullable AccessibilityRole mAccessibilityRole = null;
323333
protected @Nullable Role mRole = null;
324334

@@ -479,6 +489,30 @@ public void setColor(@Nullable Integer color) {
479489
markUpdated();
480490
}
481491

492+
@ReactProp(name = "gradientColors")
493+
public void setGradientColors(@Nullable ReadableArray gradientColors) {
494+
if (gradientColors != null) {
495+
ArrayList<Integer> colors = new ArrayList<Integer>();
496+
497+
for (int i = 0; i < gradientColors.size(); i++) {
498+
if (!gradientColors.isNull(i) && gradientColors.getType(i) == ReadableType.Number) {
499+
int color = gradientColors.getInt(i);
500+
colors.add(color);
501+
}
502+
}
503+
504+
int colorsSize = colors.size();
505+
if (colorsSize >= 2) {
506+
int[] colorsAsList = new int[colorsSize];
507+
for (int i = 0; i < colorsSize; i++) {
508+
colorsAsList[i] = colors.get(i);
509+
}
510+
511+
mGradientColors = colorsAsList;
512+
}
513+
}
514+
}
515+
482516
@ReactProp(name = ViewProps.BACKGROUND_COLOR, customType = "Color")
483517
public void setBackgroundColor(@Nullable Integer color) {
484518
// Background color needs to be handled here for virtual nodes so it can be incorporated into
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.facebook.react.views.text.internal.span
2+
3+
import android.graphics.LinearGradient
4+
import android.graphics.Shader
5+
import android.text.TextPaint
6+
import android.text.style.CharacterStyle
7+
import android.text.style.UpdateAppearance
8+
9+
public class LinearGradientSpan(
10+
private val start: Float,
11+
private val colors: IntArray,
12+
) : CharacterStyle(), ReactSpan,
13+
UpdateAppearance {
14+
public override fun updateDrawState(tp: TextPaint) {
15+
val textShader: Shader =
16+
LinearGradient(
17+
start,
18+
0f,
19+
start + 150.0f,
20+
0f,
21+
colors,
22+
null,
23+
Shader.TileMode.MIRROR,
24+
)
25+
tp.setShader(textShader)
26+
}
27+
}

0 commit comments

Comments
 (0)