Skip to content
Draft
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 @@ -26,12 +26,8 @@ import kotlin.math.roundToInt

internal class BackgroundDrawable(
private val context: Context,
/*
* We assume borderRadius & borderInsets to be shared across multiple drawables
* therefore we should manually invalidate this drawable when changing either of them
*/
var borderRadius: BorderRadiusStyle? = null,
var borderInsets: BorderInsets? = null,
borderRadius: BorderRadiusStyle? = null,
borderInsets: BorderInsets? = null,
) : Drawable() {
/**
* There is a small gap between the edges of adjacent paths, such as between its Border and its
Expand All @@ -43,6 +39,22 @@ internal class BackgroundDrawable(
private var computedBorderRadius: ComputedBorderRadius? = null
private var needUpdatePath = true

/*
* We assume borderRadius & borderInsets to be shared across multiple drawables
* therefore user should assign them to this drawable again when changing either of them
*/
var borderRadius: BorderRadiusStyle? = borderRadius
set(value) {
field = value
needUpdatePath = true
}

var borderInsets: BorderInsets? = borderInsets
set(value) {
field = value
needUpdatePath = true
}

var backgroundColor: Int = Color.TRANSPARENT
set(value) {
if (field != value) {
Expand All @@ -61,14 +73,14 @@ internal class BackgroundDrawable(
color = backgroundColor
}

override fun invalidateSelf() {
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
needUpdatePath = true
super.invalidateSelf()
}

override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
needUpdatePath = true
return true
}

override fun setAlpha(alpha: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,30 @@ import kotlin.math.round

internal class BackgroundImageDrawable(
private val context: Context,
/*
* We assume borderRadius & borderInsets to be shared across multiple drawables
* therefore we should manually invalidate this drawable when changing either of them
*/
var borderRadius: BorderRadiusStyle? = null,
var borderInsets: BorderInsets? = null,
borderRadius: BorderRadiusStyle? = null,
borderInsets: BorderInsets? = null,
) : Drawable() {
private var needUpdatePath = true
private var backgroundImageClipPath: Path? = null
private var backgroundPositioningArea: RectF? = null
private var backgroundPaintingArea: RectF? = null

/*
* We assume borderRadius & borderInsets to be shared across multiple drawables
* therefore user should assign them to this drawable again when changing either of them
*/
var borderRadius: BorderRadiusStyle? = borderRadius
set(value) {
field = value
needUpdatePath = true
}

var borderInsets: BorderInsets? = borderInsets
set(value) {
field = value
needUpdatePath = true
}

var backgroundImageLayers: List<BackgroundImageLayer>? = null
set(value) {
if (field != value) {
Expand Down Expand Up @@ -81,14 +93,14 @@ internal class BackgroundImageDrawable(
private val backgroundPaint: Paint =
Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }

override fun invalidateSelf() {
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
needUpdatePath = true
super.invalidateSelf()
}

override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
needUpdatePath = true
return true
}

override fun setAlpha(alpha: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@ import android.graphics.Region
import android.graphics.drawable.Drawable
import android.os.Build
import com.facebook.react.uimanager.FloatUtil.floatsEqual
import com.facebook.react.uimanager.LengthPercentage
import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.facebook.react.uimanager.PixelUtil.pxToDp
import com.facebook.react.uimanager.Spacing
import com.facebook.react.uimanager.style.BorderColors
import com.facebook.react.uimanager.style.BorderInsets
import com.facebook.react.uimanager.style.BorderRadiusProp
import com.facebook.react.uimanager.style.BorderRadiusStyle
import com.facebook.react.uimanager.style.BorderStyle
import com.facebook.react.uimanager.style.ColorEdges
Expand All @@ -52,30 +50,42 @@ private const val ALL_BITS_UNSET = 0
internal class BorderDrawable(
private val context: Context,
val borderWidth: Spacing?,
/*
* We assume borderRadius & borderInsets to be shared across multiple drawables
* therefore user should invalidate this drawable when changing either of them
*/
var borderRadius: BorderRadiusStyle?,
var borderInsets: BorderInsets?,
borderRadius: BorderRadiusStyle?,
borderInsets: BorderInsets?,
borderStyle: BorderStyle?,
) : Drawable() {
private fun <T> invalidatingAndPathChange(initialValue: T): ReadWriteProperty<Any?, T> =
private fun <T> invalidatingChange(initialValue: T): ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
if (oldValue != newValue) {
needUpdatePath = true
invalidateSelf()
}
}
}

/*
* We assume borderRadius & borderInsets to be shared across multiple drawables
* therefore user should assign them to this drawable again when changing either of them
*/
var borderRadius: BorderRadiusStyle? = borderRadius
set(value) {
field = value
needUpdatePath = true
}

var borderInsets: BorderInsets? = borderInsets
set(value) {
field = value
needUpdatePath = true
}

/** Border Properties */
var borderStyle: BorderStyle? by invalidatingAndPathChange(borderStyle)
var borderStyle: BorderStyle? by invalidatingChange(borderStyle)

private var borderColors: BorderColors? = null
private var computedBorderColors: ColorEdges = ColorEdges()
private var computedBorderRadius: ComputedBorderRadius? = null
private var computedBorderInsets: RectF = RectF()
private var borderAlpha: Int = 255

/**
Expand All @@ -90,8 +100,10 @@ internal class BorderDrawable(

private var needUpdatePath: Boolean = true

/** The inner clip is inset by the border widths only while an edge's color is visible. */
private var pathInsetForVisibleBorder: Boolean = false

private var pathForSingleBorder: Path? = null
private var pathForOutline: Path? = null

private var centerDrawPath: Path? = null
private var outerClipPathForBorderRadius: Path? = null
Expand All @@ -111,14 +123,14 @@ internal class BorderDrawable(
private var outerClipTempRectForBorderRadius: RectF? = null
private var tempRectForCenterDrawPath: RectF? = null

override fun invalidateSelf() {
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
needUpdatePath = true
super.invalidateSelf()
}

override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
needUpdatePath = true
return true
}

override fun setAlpha(alpha: Int) {
Expand Down Expand Up @@ -171,6 +183,7 @@ internal class BorderDrawable(
override fun draw(canvas: Canvas) {
updatePathEffect()
computedBorderColors = borderColors?.resolve(layoutDirection, context) ?: computedBorderColors
updatePath()
if (borderRadius?.hasRoundedBorders() == true) {
drawRoundedBorders(canvas)
} else {
Expand Down Expand Up @@ -202,26 +215,15 @@ internal class BorderDrawable(
}
}

fun setBorderRadius(property: BorderRadiusProp, radius: LengthPercentage?) {
if (radius != borderRadius?.get(property)) {
borderRadius?.set(property, radius)
needUpdatePath = true
invalidateSelf()
}
}

fun setBorderStyle(style: String?) {
val borderStyle = if (style == null) null else BorderStyle.valueOf(style.uppercase())
this.borderStyle = borderStyle
needUpdatePath = true
invalidateSelf()
}

fun setBorderColor(position: LogicalEdge, color: Int?) {
borderColors = borderColors ?: BorderColors()

borderColors?.edgeColors?.set(position.ordinal, color)
needUpdatePath = true
invalidateSelf()
}

Expand All @@ -230,7 +232,7 @@ internal class BorderDrawable(
}

private fun drawRectangularBorders(canvas: Canvas) {
val borderWidth = computeBorderInsets()
val borderWidth = computedBorderInsets
val borderLeft = borderWidth.left.roundToInt()
val borderTop = borderWidth.top.roundToInt()
val borderRight = borderWidth.right.roundToInt()
Expand Down Expand Up @@ -261,7 +263,7 @@ internal class BorderDrawable(
val bottom = bounds.bottom
borderPaint.color = multiplyColorAlpha(fastBorderColor, borderAlpha)
borderPaint.style = Paint.Style.STROKE
pathForSingleBorder = Path()
pathForSingleBorder = pathForSingleBorder ?: Path()
if (borderLeft > 0) {
pathForSingleBorder?.reset()
val width = borderWidth.left.roundToInt()
Expand Down Expand Up @@ -360,13 +362,12 @@ internal class BorderDrawable(
}

private fun drawRoundedBorders(canvas: Canvas) {
updatePath()
canvas.save()

// Clip outer border
canvas.clipPath(checkNotNull(outerClipPathForBorderRadius))

val borderWidth = computeBorderInsets()
val borderWidth = computedBorderInsets
if (
borderWidth.top > 0 ||
borderWidth.bottom > 0 ||
Expand Down Expand Up @@ -664,17 +665,29 @@ internal class BorderDrawable(
}
}

private fun hasVisibleBorderColor(): Boolean =
Color.alpha(computedBorderColors.left) != 0 ||
Color.alpha(computedBorderColors.top) != 0 ||
Color.alpha(computedBorderColors.right) != 0 ||
Color.alpha(computedBorderColors.bottom) != 0

private fun updatePath() {
if (!needUpdatePath) {
val insetForVisibleBorder = hasVisibleBorderColor()
if (!needUpdatePath && insetForVisibleBorder == pathInsetForVisibleBorder) {
return
}

needUpdatePath = false
pathInsetForVisibleBorder = insetForVisibleBorder
computedBorderInsets = computeBorderInsets()

if (borderRadius?.hasRoundedBorders() != true) {
return
}

// Path
innerClipPathForBorderRadius = innerClipPathForBorderRadius ?: Path()
outerClipPathForBorderRadius = outerClipPathForBorderRadius ?: Path()
pathForOutline = Path()

// RectF
innerClipTempRectForBorderRadius = innerClipTempRectForBorderRadius ?: RectF()
Expand All @@ -688,15 +701,10 @@ internal class BorderDrawable(
outerClipTempRectForBorderRadius?.set(bounds)
tempRectForCenterDrawPath?.set(bounds)

val borderWidth = computeBorderInsets()
val borderWidth = computedBorderInsets

// Clip border ONLY if at least one edge is non-transparent
if (
Color.alpha(computedBorderColors.left) != 0 ||
Color.alpha(computedBorderColors.top) != 0 ||
Color.alpha(computedBorderColors.right) != 0 ||
Color.alpha(computedBorderColors.bottom) != 0
) {
if (insetForVisibleBorder) {
innerClipTempRectForBorderRadius?.top =
innerClipTempRectForBorderRadius?.top?.plus(borderWidth.top) ?: 0f
innerClipTempRectForBorderRadius?.bottom =
Expand Down Expand Up @@ -779,27 +787,6 @@ internal class BorderDrawable(
)
}

var extraRadiusForOutline = 0f

if (this.borderWidth != null) {
extraRadiusForOutline = this.borderWidth[Spacing.ALL] / 2f
}

pathForOutline?.addRoundRect(
RectF(bounds),
floatArrayOf(
topLeftRadius.horizontal + extraRadiusForOutline,
topLeftRadius.vertical + extraRadiusForOutline,
topRightRadius.horizontal + extraRadiusForOutline,
topRightRadius.vertical + extraRadiusForOutline,
bottomRightRadius.horizontal + extraRadiusForOutline,
bottomRightRadius.vertical + extraRadiusForOutline,
bottomLeftRadius.horizontal + extraRadiusForOutline,
bottomLeftRadius.vertical + extraRadiusForOutline,
),
Path.Direction.CW,
)

if (computedBorderRadius?.isUniform() != true) {
centerDrawPath = centerDrawPath ?: Path()
centerDrawPath?.reset()
Expand Down
Loading
Loading