Skip to content
Closed
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 @@ -24,6 +24,7 @@ import com.facebook.react.uimanager.PixelUtil.pxToDp
import com.facebook.react.uimanager.common.UIManagerType
import com.facebook.react.uimanager.common.ViewUtil
import com.facebook.react.uimanager.drawable.BackgroundDrawable
import com.facebook.react.uimanager.drawable.BackgroundImageDrawable
import com.facebook.react.uimanager.drawable.BorderDrawable
import com.facebook.react.uimanager.drawable.CompositeBackgroundDrawable
import com.facebook.react.uimanager.drawable.InsetBoxShadowDrawable
Expand All @@ -32,6 +33,9 @@ import com.facebook.react.uimanager.drawable.MIN_OUTSET_BOX_SHADOW_SDK_VERSION
import com.facebook.react.uimanager.drawable.OutlineDrawable
import com.facebook.react.uimanager.drawable.OutsetBoxShadowDrawable
import com.facebook.react.uimanager.style.BackgroundImageLayer
import com.facebook.react.uimanager.style.BackgroundPosition
import com.facebook.react.uimanager.style.BackgroundRepeat
import com.facebook.react.uimanager.style.BackgroundSize
import com.facebook.react.uimanager.style.BorderInsets
import com.facebook.react.uimanager.style.BorderRadiusProp
import com.facebook.react.uimanager.style.BorderRadiusStyle
Expand Down Expand Up @@ -65,7 +69,31 @@ public object BackgroundStyleApplicator {
view: View,
backgroundImageLayers: List<BackgroundImageLayer>?,
): Unit {
ensureBackgroundDrawable(view).backgroundImageLayers = backgroundImageLayers
ensureBackgroundImageDrawable(view).backgroundImageLayers = backgroundImageLayers
}

@JvmStatic
public fun setBackgroundSize(
view: View,
backgroundSizes: List<BackgroundSize>?
): Unit {
ensureBackgroundImageDrawable(view).backgroundSize = backgroundSizes
}

@JvmStatic
public fun setBackgroundPosition(
view: View,
backgroundPositions: List<BackgroundPosition>?
): Unit {
ensureBackgroundImageDrawable(view).backgroundPosition = backgroundPositions
}

@JvmStatic
public fun setBackgroundRepeat(
view: View,
backgroundRepeats: List<BackgroundRepeat>?
): Unit {
ensureBackgroundImageDrawable(view).backgroundRepeat = backgroundRepeats
}

@JvmStatic
Expand All @@ -82,9 +110,11 @@ public object BackgroundStyleApplicator {

ensureBorderDrawable(view).setBorderWidth(edge.toSpacingType(), width?.dpToPx() ?: Float.NaN)
composite.background?.borderInsets = composite.borderInsets
composite.backgroundImage?.borderInsets = composite.borderInsets
composite.border?.borderInsets = composite.borderInsets

composite.background?.invalidateSelf()
composite.backgroundImage?.invalidateSelf()
composite.border?.invalidateSelf()

composite.borderInsets = composite.borderInsets ?: BorderInsets()
Expand Down Expand Up @@ -133,9 +163,11 @@ public object BackgroundStyleApplicator {
ensureBackgroundDrawable(view)
}
compositeBackgroundDrawable.background?.borderRadius = compositeBackgroundDrawable.borderRadius
compositeBackgroundDrawable.backgroundImage?.borderRadius = compositeBackgroundDrawable.borderRadius
compositeBackgroundDrawable.border?.borderRadius = compositeBackgroundDrawable.borderRadius

compositeBackgroundDrawable.background?.invalidateSelf()
compositeBackgroundDrawable.backgroundImage?.invalidateSelf()
compositeBackgroundDrawable.border?.invalidateSelf()

if (Build.VERSION.SDK_INT >= MIN_OUTSET_BOX_SHADOW_SDK_VERSION) {
Expand Down Expand Up @@ -381,6 +413,27 @@ public object BackgroundStyleApplicator {
private fun getBackground(view: View): BackgroundDrawable? =
getCompositeBackgroundDrawable(view)?.background

private fun ensureBackgroundImageDrawable(view: View): BackgroundImageDrawable {
val compositeBackgroundDrawable = ensureCompositeBackgroundDrawable(view)
var backgroundImage = compositeBackgroundDrawable.backgroundImage

return if (backgroundImage != null) {
backgroundImage
} else {
backgroundImage =
BackgroundImageDrawable(
view.context,
compositeBackgroundDrawable.borderRadius,
compositeBackgroundDrawable.borderInsets,
)
view.background = compositeBackgroundDrawable.withNewBackgroundImage(backgroundImage)
backgroundImage
}
}

private fun getBackgroundImage(view: View): BackgroundImageDrawable? =
getCompositeBackgroundDrawable(view)?.backgroundImage

private fun getBorder(view: View): BorderDrawable? = getCompositeBackgroundDrawable(view)?.border

private fun ensureBorderDrawable(view: View): BorderDrawable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,24 @@ public data class LengthPercentage(
) {
public companion object {
@JvmStatic
public fun setFromDynamic(dynamic: Dynamic): LengthPercentage? {
public fun setFromDynamic(dynamic: Dynamic, allowNegative: Boolean = false): LengthPercentage? {

@intergalacticspacehighway intergalacticspacehighway Jun 26, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Did not like this change, but we need to support negative values for background-position. lmk if I should take some other approach!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is fine.. I'll think about it a bit

return when (dynamic.type) {
ReadableType.Number -> {
val value = dynamic.asDouble()
if (value >= 0f) {
LengthPercentage(value.toFloat(), LengthPercentageType.POINT)
} else {
null
if (value < 0 && !allowNegative) {
return null
}
LengthPercentage(value.toFloat(), LengthPercentageType.POINT)
}
ReadableType.String -> {
val s = dynamic.asString()
if (s != null && s.endsWith("%")) {
try {
val value = s.substring(0, s.length - 1).toFloat()
if (value >= 0f) {
LengthPercentage(value, LengthPercentageType.PERCENT)
} else {
null
if (value < 0 && !allowNegative) {
return null
}
LengthPercentage(value, LengthPercentageType.PERCENT)
} catch (e: NumberFormatException) {
FLog.w(ReactConstants.TAG, "Invalid percentage format: $s")
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ public object ViewProps {
public const val ENABLED: String = "enabled"
public const val BACKGROUND_COLOR: String = "backgroundColor"
public const val BACKGROUND_IMAGE: String = "experimental_backgroundImage"
public const val BACKGROUND_SIZE: String = "experimental_backgroundSize"
public const val BACKGROUND_POSITION: String = "experimental_backgroundPosition"
public const val BACKGROUND_REPEAT: String = "experimental_backgroundRepeat"
public const val FOREGROUND_COLOR: String = "foregroundColor"
public const val COLOR: String = "color"
public const val FONT_SIZE: String = "fontSize"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import android.graphics.Shader
import android.graphics.drawable.Drawable
import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.facebook.react.uimanager.PixelUtil.pxToDp
import com.facebook.react.uimanager.style.BackgroundImageLayer
import com.facebook.react.uimanager.style.BorderInsets
import com.facebook.react.uimanager.style.BorderRadiusStyle
import com.facebook.react.uimanager.style.ComputedBorderRadius
Expand Down Expand Up @@ -59,14 +58,6 @@ internal class BackgroundDrawable(
private var backgroundRect: RectF = RectF()
private var backgroundRenderPath: Path? = null

var backgroundImageLayers: List<BackgroundImageLayer>? = null
set(value) {
if (field != value) {
field = value
invalidateSelf()
}
}

private val backgroundPaint: Paint =
Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
Expand Down Expand Up @@ -111,8 +102,8 @@ internal class BackgroundDrawable(
if (backgroundPaint.alpha != 0) {
if (computedBorderRadius?.isUniform() == true && borderRadius?.hasRoundedBorders() == true) {
canvas.drawRoundRect(
backgroundRect,
computedBorderRadius?.topLeft?.horizontal?.dpToPx() ?: 0f,
backgroundRect,
computedBorderRadius?.topLeft?.horizontal?.dpToPx() ?: 0f,
computedBorderRadius?.topLeft?.vertical?.dpToPx() ?: 0f,
backgroundPaint,
)
Expand All @@ -123,24 +114,6 @@ internal class BackgroundDrawable(
}
}

backgroundPaint.alpha = 255
if (backgroundImageLayers != null && backgroundImageLayers?.isNotEmpty() == true) {
backgroundPaint.setShader(getBackgroundImageShader())
if (computedBorderRadius?.isUniform() == true && borderRadius?.hasRoundedBorders() == true) {
canvas.drawRoundRect(
backgroundRect,
computedBorderRadius?.topLeft?.horizontal?.dpToPx() ?: 0f,
computedBorderRadius?.topLeft?.vertical?.dpToPx() ?: 0f,
backgroundPaint,
)
} else if (borderRadius?.hasRoundedBorders() != true) {
canvas.drawRect(backgroundRect, backgroundPaint)
} else {
canvas.drawPath(checkNotNull(backgroundRenderPath), backgroundPaint)
}
backgroundPaint.setShader(null)
}
backgroundPaint.alpha = Color.alpha(backgroundColor)
canvas.restore()
}

Expand All @@ -152,24 +125,6 @@ internal class BackgroundDrawable(
it?.right?.dpToPx() ?: 0f,
it?.bottom?.dpToPx() ?: 0f,
)
}

private fun getBackgroundImageShader(): Shader? {
backgroundImageLayers?.let { layers ->
var compositeShader: Shader? = null
for (backgroundImageLayer in layers) {
val currentShader = backgroundImageLayer.getShader(bounds)

compositeShader =
if (compositeShader == null) {
currentShader
} else {
ComposeShader(currentShader, compositeShader, PorterDuff.Mode.SRC_OVER)
}
}
return compositeShader
}
return null
}

private fun updatePath() {
Expand Down
Loading
Loading