Skip to content

Commit

Permalink
Migrate ContentPainterModifier to implement Modifier.Node. (#2117)
Browse files Browse the repository at this point in the history
  • Loading branch information
colinrtwhite committed Feb 11, 2024
1 parent 5e12570 commit 9832a9e
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 24 deletions.
2 changes: 1 addition & 1 deletion coil-compose-base/src/main/java/coil/compose/AsyncImage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ private fun Content(
.contentDescription(contentDescription)
.run { if (clipToBounds) clipToBounds() else this }
.then(
ContentPainterModifier(
ContentPainterElement(
painter = painter,
alignment = alignment,
contentScale = contentScale,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package coil.compose

import androidx.compose.ui.Alignment
import androidx.compose.ui.draw.DrawModifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.paint
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.isSpecified
Expand All @@ -13,41 +13,83 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.IntrinsicMeasurable
import androidx.compose.ui.layout.IntrinsicMeasureScope
import androidx.compose.ui.layout.LayoutModifier
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.times
import androidx.compose.ui.platform.InspectorValueInfo
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.node.invalidateMeasurement
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import kotlin.math.roundToInt

/**
* A custom [paint] modifier used by [Content].
* A custom [paint] modifier used by [AsyncImage] and [SubcomposeAsyncImage].
*/
internal data class ContentPainterModifier(
internal data class ContentPainterElement(
private val painter: Painter,
private val alignment: Alignment,
private val contentScale: ContentScale,
private val alpha: Float,
private val colorFilter: ColorFilter?,
) : LayoutModifier, DrawModifier, InspectorValueInfo(
debugInspectorInfo {
) : ModifierNodeElement<ContentPainterNode>() {

override fun create(): ContentPainterNode {
return ContentPainterNode(
painter = painter,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
)
}

override fun update(node: ContentPainterNode) {
val intrinsicsChanged = node.painter.intrinsicSize != painter.intrinsicSize

node.painter = painter
node.alignment = alignment
node.contentScale = contentScale
node.alpha = alpha
node.colorFilter = colorFilter

// Only remeasure if intrinsics have changed.
if (intrinsicsChanged) {
node.invalidateMeasurement()
}

// Redraw because one of the node properties has changed.
node.invalidateDraw()
}

override fun InspectorInfo.inspectableProperties() {
name = "content"
properties["painter"] = painter
properties["alignment"] = alignment
properties["contentScale"] = contentScale
properties["alpha"] = alpha
properties["colorFilter"] = colorFilter
}
) {
}

internal class ContentPainterNode(
var painter: Painter,
var alignment: Alignment,
var contentScale: ContentScale,
var alpha: Float,
var colorFilter: ColorFilter?,
) : Modifier.Node(), DrawModifierNode, LayoutModifierNode {

override val shouldAutoInvalidate get() = false

override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
constraints: Constraints,
): MeasureResult {
val placeable = measurable.measure(modifyConstraints(constraints))
return layout(placeable.width, placeable.height) {
Expand All @@ -57,7 +99,7 @@ internal data class ContentPainterModifier(

override fun IntrinsicMeasureScope.minIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int
height: Int,
): Int {
return if (painter.intrinsicSize.isSpecified) {
val constraints = Constraints(maxHeight = height)
Expand All @@ -71,7 +113,7 @@ internal data class ContentPainterModifier(

override fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int
height: Int,
): Int {
return if (painter.intrinsicSize.isSpecified) {
val constraints = Constraints(maxHeight = height)
Expand All @@ -85,11 +127,12 @@ internal data class ContentPainterModifier(

override fun IntrinsicMeasureScope.minIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int
width: Int,
): Int {
return if (painter.intrinsicSize.isSpecified) {
val constraints = Constraints(maxWidth = width)
val layoutHeight = measurable.minIntrinsicHeight(modifyConstraints(constraints).maxWidth)
val layoutHeight =
measurable.minIntrinsicHeight(modifyConstraints(constraints).maxWidth)
val scaledSize = calculateScaledSize(Size(width.toFloat(), layoutHeight.toFloat()))
maxOf(scaledSize.height.roundToInt(), layoutHeight)
} else {
Expand All @@ -99,11 +142,12 @@ internal data class ContentPainterModifier(

override fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int
width: Int,
): Int {
return if (painter.intrinsicSize.isSpecified) {
val constraints = Constraints(maxWidth = width)
val layoutHeight = measurable.maxIntrinsicHeight(modifyConstraints(constraints).maxWidth)
val layoutHeight =
measurable.maxIntrinsicHeight(modifyConstraints(constraints).maxWidth)
val scaledSize = calculateScaledSize(Size(width.toFloat(), layoutHeight.toFloat()))
maxOf(scaledSize.height.roundToInt(), layoutHeight)
} else {
Expand All @@ -123,7 +167,7 @@ internal data class ContentPainterModifier(

val srcSize = Size(
width = intrinsicSize.width.takeOrElse { dstSize.width },
height = intrinsicSize.height.takeOrElse { dstSize.height }
height = intrinsicSize.height.takeOrElse { dstSize.height },
)
val scaleFactor = contentScale.computeScaleFactor(srcSize, dstSize)
if (!scaleFactor.scaleX.isFinite() || !scaleFactor.scaleY.isFinite()) {
Expand All @@ -148,7 +192,7 @@ internal data class ContentPainterModifier(
if (hasBoundedSize) {
return constraints.copy(
minWidth = constraints.maxWidth,
minHeight = constraints.maxHeight
minHeight = constraints.maxHeight,
)
} else {
return constraints
Expand Down Expand Up @@ -179,7 +223,7 @@ internal data class ContentPainterModifier(
val (scaledWidth, scaledHeight) = calculateScaledSize(Size(dstWidth, dstHeight))
return constraints.copy(
minWidth = constraints.constrainWidth(scaledWidth.roundToInt()),
minHeight = constraints.constrainHeight(scaledHeight.roundToInt())
minHeight = constraints.constrainHeight(scaledHeight.roundToInt()),
)
}

Expand All @@ -188,7 +232,7 @@ internal data class ContentPainterModifier(
val (dx, dy) = alignment.align(
size = scaledSize.toIntSize(),
space = size.toIntSize(),
layoutDirection = layoutDirection
layoutDirection = layoutDirection,
)

// Draw the painter.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,13 +338,13 @@ fun SubcomposeAsyncImageScope.SubcomposeAsyncImageContent(
.contentDescription(contentDescription)
.run { if (clipToBounds) clipToBounds() else this }
.then(
ContentPainterModifier(
ContentPainterElement(
painter = painter,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter
)
colorFilter = colorFilter,
),
),
measurePolicy = { _, constraints ->
layout(constraints.minWidth, constraints.minHeight) {}
Expand Down

0 comments on commit 9832a9e

Please sign in to comment.