feat: linear gradient px and transition hint syntax support#48410
feat: linear gradient px and transition hint syntax support#48410intergalacticspacehighway wants to merge 8 commits into
px and transition hint syntax support#48410Conversation
|
@intergalacticspacehighway Thanks for the PR! I tagged the people that worked on the feature. Thanks for the patience in this slower time, due to holidays and end-of-the-year celebrations. |
| const positions = colorStop.positions; | ||
| // Color transition hint syntax (red, 20%, blue) | ||
| if ( | ||
| colorStop.color == null && | ||
| Array.isArray(positions) && | ||
| positions.length === 1 | ||
| ) { | ||
| const position = positions[0]; | ||
| if (typeof position === 'string' && position.endsWith('%')) { | ||
| processedColorStops.push({ | ||
| color: null, | ||
| position: parseFloat(position) / 100, | ||
| }); | ||
| } else { | ||
| // If a position is invalid, return an empty array and do not apply gradient. Same as web. | ||
| return []; |
There was a problem hiding this comment.
I think we can change this to share some code better. Do transition hints have to be percentages? Regardless we are doing a lot of the same things, can you try to get rid of the top level if/else here and share some of this logic?
There was a problem hiding this comment.
Do transition hints have to be percentages?
They are actually length-percentage in the spec. But i only added % support for now in color stops. Linear gradient support mixing % and px units which require us to know the dimension, for this we'll have to move color stop parsing to native side. I wanted to avoid that change until the new css parser.
can you try to get rid of the top level if/else here and share some of this logic?
i kinda prefer the explicit checks, also each branch has a tiny comment on top, but i will take another look!
| direction: LinearGradientDirection, | ||
| colorStops: $ReadOnlyArray<{ | ||
| color: ProcessedColorValue, | ||
| color: ColorStopColor, |
There was a problem hiding this comment.
I would comment in what scenarios we can have a null color, or otherwise change the typing to make that super clear like
$ReadOnlyArray<{
colorStop | transitionHint
}>
I think they are similar enough to just roll with the comment but it should be clear that that syntax is supported
| ColorStop colorStop; | ||
| colorStop.position = (Float)(positionIt->second); | ||
| fromRawValue(context, colorIt->second, colorStop.color); | ||
| if (colorIt->second.hasValue()) { |
There was a problem hiding this comment.
what is the default value of the color in ColorStop? Will we be able to detect that this is a transition hint vs something like the color black?
There was a problem hiding this comment.
here if JS color is null, that only happens if user passed transition hint. Then the color in ColorStop gets default SharedColor value i.e. HostPlatformColor::UndefinedColor. So for black color it should be alright!
| auto &colorStop = colorStops[i]; | ||
| // Skip if not a color hint | ||
| if (colorStop.color) { | ||
| continue; |
There was a problem hiding this comment.
again, curious if a color like black breaks this? Regardless I feel like it would be more future proof and more obvious if you just changed the color in ColorStop to be a std::optional or such
There was a problem hiding this comment.
color in ColorStop is SharedColor instance which defaults to HostPlatformColor::UndefinedColor and it has bool overload to check for undefined color. So i think black or such colors should be fine?
| {color: null, position: 0.4}, | ||
| {color: processColor('blue'), position: 1}, | ||
| ]); | ||
| }); |
There was a problem hiding this comment.
can you add some more? Hints that are invalid (> 100%, non percents, multiple in a row, etc). Some longer ones with more stops and more hints just to stress test things a bit, etc. The code has a lot of branching logic due to the complexity of the syntax we are allowing, so I think we should err on the side of excessive testing to raise our confidence level here
* linear gradient px support * fix test cases * handle invalid transition hint * fix multiple transition hint * final fixes
|
@joevilches i added the |
px and transition hint syntax support
|
@joevilches has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
| private enum class UnitType { | ||
| Point, | ||
| Percent, | ||
| Undefined | ||
| } | ||
|
|
||
| private data class ValueUnit( | ||
| val value: Float = 0.0f, | ||
| val unit: UnitType = UnitType.Undefined | ||
| ) | ||
|
|
||
| private data class ColorStop( | ||
| var color: Int? = null, | ||
| val position: ValueUnit | ||
| ) |
There was a problem hiding this comment.
@intergalacticspacehighway got some internal comments mentioning we should use LengthPercentage here for position's type: https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LengthPercentage.kt
| private val _value: Float, | ||
| public val type: LengthPercentageType, | ||
| ) { | ||
| public val value: Float | ||
| get() = _value |
There was a problem hiding this comment.
I think we can just change value to be public. We don't have to worry about the setter since its a val
| if (newPosition == null) { | ||
| // Step 1: | ||
| // If the first color stop does not have a position, | ||
| // set its position to 0%. If the last color stop does not have a position, | ||
| // set its position to 100%. | ||
| when (i) { | ||
| 0 -> newPosition = ValueUnit(0f, UnitType.Point) | ||
| colorStops.size - 1 -> newPosition = ValueUnit(1f, UnitType.Point) | ||
| 0 -> newPosition = 0f | ||
| colorStops.size - 1 -> newPosition = 1f |
There was a problem hiding this comment.
nit:
newPosition = newPosition ?: when (i) {
0 -> 0f
colorStops.size - 1 -> 1f
}
|
@joevilches has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
joevilches
left a comment
There was a problem hiding this comment.
A few more things with the new length percentage!
| return when (position.type) { | ||
| LengthPercentageType.POINT -> PixelUtil.toPixelFromDIP(position.value) / gradientLineLength | ||
| LengthPercentageType.PERCENT -> position.value / 100 | ||
| else -> null |
There was a problem hiding this comment.
this switch is exhaustive so this else does nothing, can you remove (this is also an internal linting error)
|
|
||
| public data class LengthPercentage( | ||
| private val value: Float, | ||
| public val value: Float, |
There was a problem hiding this comment.
Ideally we do not make this public. Reading the value of a percent has a high chance of being a bug (should resolve it first). I see the current resolve is a bit specific to corner radius. Mind adding a resolve that
- takes a float as the referenceLength
- returns points as is and percents as a percent of said reference length
and use that resolve and make this private again?
| LengthPercentageType.POINT -> PixelUtil.toPixelFromDIP(position.value) / gradientLineLength | ||
| LengthPercentageType.PERCENT -> position.value / 100 |
There was a problem hiding this comment.
are percents eval'ed relative to the gradientLineLength?
There was a problem hiding this comment.
actually, px are eval'ed relative to gradientLineLength 😅. android and iOS linear gradient APIs expect [0-1] value for color stop position. So we need this kind of resolve . e.g. if user passes 50% we just want to resolve it to 0.5 and If user passes 50px then we need to resolve it relative to gradientLineLength like 50 / gradientLineLength.
Lengths are measured along the gradient line from the starting point in the direction of the ending point.
From the color stop syntax spec
tldr; percentage are relative to gradient line length but we do not need to evaluate it as native APIs handle it. For px we need to eval.
|
@joevilches has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
|
@joevilches merged this pull request in cc89ddd. |
|
This pull request was successfully merged by @intergalacticspacehighway in cc89ddd When will my fix make it into a release? | How to file a pick request? |
Summary:
linear-gradient(red, 20%, green)pxsupport. Combination ofpxand%also works.processColorTransitionHintandgetFixedColorStopsis moved to native code so it can support combination ofpxand%units as it requires gradient line length, which is derived from view dimensions and gradient line angle.Changelog:
[GENERAL] [ADDED] - Linear gradient color transition hint syntax and
pxunit support.Test Plan:
Added testcase in processBackgroundImage-test.ts and example in LinearGradientExample.js
Todo
Add testcases for
getFixedColorStopsandprocessColorTransitionHintin native code for both platforms. That's the only downside of moving it out of JS 🤦