Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ - (RCTShadowView *)shadowView
// Color
RCT_REMAP_SHADOW_PROPERTY(color, textAttributes.foregroundColor, UIColor)
RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textAttributes.backgroundColor, UIColor)
RCT_REMAP_SHADOW_PROPERTY(gradientColors, textAttributes.gradientColors, NSArray)
RCT_REMAP_SHADOW_PROPERTY(opacity, textAttributes.opacity, CGFloat)
// Font
RCT_REMAP_SHADOW_PROPERTY(fontFamily, textAttributes.fontFamily, NSString)
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/Libraries/Text/RCTTextAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extern NSString *const RCTTextAttributesTagAttributeName;
// Color
@property (nonatomic, strong, nullable) UIColor *foregroundColor;
@property (nonatomic, strong, nullable) UIColor *backgroundColor;
@property (nonatomic, copy, nullable) NSArray *gradientColors;
@property (nonatomic, assign) CGFloat opacity;
// Font
@property (nonatomic, copy, nullable) NSString *fontFamily;
Expand Down
29 changes: 29 additions & 0 deletions packages/react-native/Libraries/Text/RCTTextAttributes.mm
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
// Color
_foregroundColor = textAttributes->_foregroundColor ?: _foregroundColor;
_backgroundColor = textAttributes->_backgroundColor ?: _backgroundColor;
_gradientColors = textAttributes->_gradientColors ?: _gradientColors;
_opacity =
!isnan(textAttributes->_opacity) ? (isnan(_opacity) ? 1.0 : _opacity) * textAttributes->_opacity : _opacity;

Expand Down Expand Up @@ -294,6 +295,34 @@ - (UIColor *)effectiveForegroundColor
{
UIColor *effectiveForegroundColor = _foregroundColor ?: [UIColor blackColor];

if (_gradientColors != nil) {
NSMutableArray *cgColors = [NSMutableArray array];
for (NSNumber *rawColor in _gradientColors) {
if (rawColor != nil) {
UIColor *color = [RCTConvert UIColor:@((0xFF << 24) | [rawColor integerValue])];
[cgColors addObject:(id)color.CGColor];
}
}

if([cgColors count] > 0) {
CAGradientLayer *gradient = [CAGradientLayer layer];
// this pattern width corresponds roughly to desktop's pattern width
int patternWidth = 100;
CGFloat height = _lineHeight * self.effectiveFontSizeMultiplier;
gradient.frame = CGRectMake(0, 0, patternWidth, height);
gradient.colors = cgColors;
gradient.startPoint = CGPointMake(0.0, 0.5);
gradient.endPoint = CGPointMake(1.0, 0.5);

UIGraphicsBeginImageContextWithOptions(gradient.frame.size, NO, 0.0);
[gradient renderInContext:UIGraphicsGetCurrentContext()];
UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

effectiveForegroundColor = [UIColor colorWithPatternImage:gradientImage];
}
}

if (!isnan(_opacity)) {
effectiveForegroundColor =
[effectiveForegroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveForegroundColor.CGColor) * _opacity];
Expand Down
5 changes: 5 additions & 0 deletions packages/react-native/Libraries/Text/Text.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ export interface TextProps
* Specifies smallest possible scale a font can reach when adjustsFontSizeToFit is enabled. (values 0.01-1.0).
*/
minimumFontScale?: number | undefined;

/**
* Adds a horizontal gradient using the int based color values.
*/
gradientColors?: number[] | undefined;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's a fork but maybe doc on the expected format, eg: what type of "int based color" is expected here and also presumably its a horizontal gradient only.

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import {createViewConfig} from '../NativeComponent/ViewConfig';
import UIManager from '../ReactNative/UIManager';
import createReactNativeComponentClass from '../Renderer/shims/createReactNativeComponentClass';
import Platform from '../Utilities/Platform';

Check warning on line 19 in packages/react-native/Libraries/Text/TextNativeComponent.js

View workflow job for this annotation

GitHub Actions / test_js (20)

'Platform' is defined but never used

Check warning on line 19 in packages/react-native/Libraries/Text/TextNativeComponent.js

View workflow job for this annotation

GitHub Actions / test_js (18)

'Platform' is defined but never used

export type NativeTextProps = $ReadOnly<{
...TextProps,
Expand Down Expand Up @@ -49,6 +49,7 @@
dataDetectorType: true,
android_hyphenationFrequency: true,
lineBreakStrategyIOS: true,
gradientColors: true,
},
directEventTypes: {
topTextLayout: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.LayoutShadowNode;
Expand All @@ -34,6 +35,7 @@
import com.facebook.react.views.text.internal.span.CustomLetterSpacingSpan;
import com.facebook.react.views.text.internal.span.CustomLineHeightSpan;
import com.facebook.react.views.text.internal.span.CustomStyleSpan;
import com.facebook.react.views.text.internal.span.LinearGradientSpan;
import com.facebook.react.views.text.internal.span.ReactAbsoluteSizeSpan;
import com.facebook.react.views.text.internal.span.ReactBackgroundColorSpan;
import com.facebook.react.views.text.internal.span.ReactClickableSpan;
Expand Down Expand Up @@ -160,9 +162,15 @@ private static void buildSpannedFromShadowNode(
}
int end = sb.length();
if (end >= start) {
if (textShadowNode.mIsColorSet) {
ops.add(
new SetSpanOperation(start, end, new ReactForegroundColorSpan(textShadowNode.mColor)));
if (textShadowNode.mIsColorSet || textShadowNode.mGradientColors != null) {
if (textShadowNode.mGradientColors != null && textShadowNode.mGradientColors.length >= 2) {
int effectiveFontSize = textAttributes.getEffectiveFontSize();
ops.add(
new SetSpanOperation(start, end, new LinearGradientSpan(start * effectiveFontSize, textShadowNode.mGradientColors)));
} else {
ops.add(
new SetSpanOperation(start, end, new ReactForegroundColorSpan(textShadowNode.mColor)));
}
}
if (textShadowNode.mIsBackgroundColorSet) {
ops.add(
Expand Down Expand Up @@ -319,6 +327,8 @@ protected Spannable spannedFromShadowNode(
protected boolean mIsBackgroundColorSet = false;
protected int mBackgroundColor;

protected @Nullable int[] mGradientColors = null;

protected @Nullable AccessibilityRole mAccessibilityRole = null;
protected @Nullable Role mRole = null;

Expand Down Expand Up @@ -479,6 +489,30 @@ public void setColor(@Nullable Integer color) {
markUpdated();
}

@ReactProp(name = "gradientColors")
public void setGradientColors(@Nullable ReadableArray gradientColors) {
if (gradientColors != null) {
ArrayList<Integer> colors = new ArrayList<Integer>();

for (int i = 0; i < gradientColors.size(); i++) {
if (!gradientColors.isNull(i) && gradientColors.getType(i) == ReadableType.Number) {
int color = gradientColors.getInt(i);
colors.add(color);
}
}

int colorsSize = colors.size();
if (colorsSize >= 2) {
int[] colorsAsList = new int[colorsSize];
for (int i = 0; i < colorsSize; i++) {
colorsAsList[i] = colors.get(i);
}

mGradientColors = colorsAsList;
}
}
}

@ReactProp(name = ViewProps.BACKGROUND_COLOR, customType = "Color")
public void setBackgroundColor(@Nullable Integer color) {
// Background color needs to be handled here for virtual nodes so it can be incorporated into
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.facebook.react.views.text.internal.span

import android.graphics.LinearGradient
import android.graphics.Shader
import android.text.TextPaint
import android.text.style.CharacterStyle
import android.text.style.UpdateAppearance

public class LinearGradientSpan(
private val start: Float,
private val colors: IntArray,
) : CharacterStyle(), ReactSpan,
UpdateAppearance {
public override fun updateDrawState(tp: TextPaint) {
val textShader: Shader =
LinearGradient(
start,
0f,
start + 150.0f,
0f,
colors,
null,
Shader.TileMode.MIRROR,
)
tp.setShader(textShader)
}
}
Loading