[go: nahoru, domu]

blob: 5afd41e1ec71cd0a406203b231b0241d69e2d32f [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.ui.core
import androidx.ui.geometry.Size
import androidx.ui.graphics.ColorFilter
import androidx.ui.graphics.DefaultAlpha
import androidx.ui.graphics.drawscope.translate
import androidx.ui.graphics.painter.Painter
import androidx.ui.unit.IntSize
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.roundToInt
/**
* Paint the content using [painter].
*
* @param sizeToIntrinsics `true` to size the element relative to [Painter.intrinsicSize]
* @param alignment specifies alignment of the [painter] relative to content
* @param contentScale strategy for scaling [painter] if its size does not match the content size
* @param alpha opacity of [painter]
* @param colorFilter optional [ColorFilter] to apply to [painter]
*
* @sample androidx.ui.core.samples.PainterModifierSample
*/
fun Modifier.paint(
painter: Painter,
sizeToIntrinsics: Boolean = true,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Inside,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null
) = this + PainterModifier(
painter = painter,
sizeToIntrinsics = sizeToIntrinsics,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter
)
/**
* [DrawModifier] used to draw the provided [Painter] followed by the contents
* of the component itself
*/
private data class PainterModifier(
val painter: Painter,
val sizeToIntrinsics: Boolean,
val alignment: Alignment = Alignment.Center,
val contentScale: ContentScale = ContentScale.Inside,
val alpha: Float = DefaultAlpha,
val colorFilter: ColorFilter? = null
) : LayoutModifier, DrawModifier {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints,
layoutDirection: LayoutDirection
): MeasureScope.MeasureResult {
val placeable = measurable.measure(modifyConstraints(constraints))
return layout(placeable.width, placeable.height) {
placeable.place(0, 0)
}
}
override fun IntrinsicMeasureScope.minIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int,
layoutDirection: LayoutDirection
): Int {
return if (sizeToIntrinsics) {
val constraints = Constraints(maxHeight = height)
val layoutWidth =
measurable.minIntrinsicWidth(modifyConstraints(constraints).maxHeight)
val scaledSize = calculateScaledSize(Size(layoutWidth.toFloat(), height.toFloat()))
max(scaledSize.width.roundToInt(), layoutWidth)
} else {
measurable.minIntrinsicWidth(height)
}
}
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int,
layoutDirection: LayoutDirection
): Int {
return if (sizeToIntrinsics) {
val constraints = Constraints(maxHeight = height)
val layoutWidth =
measurable.maxIntrinsicWidth(modifyConstraints(constraints).maxHeight)
val scaledSize = calculateScaledSize(Size(layoutWidth.toFloat(), height.toFloat()))
max(scaledSize.width.roundToInt(), layoutWidth)
} else {
measurable.maxIntrinsicWidth(height)
}
}
override fun IntrinsicMeasureScope.minIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int,
layoutDirection: LayoutDirection
): Int {
return if (sizeToIntrinsics) {
val constraints = Constraints(maxWidth = width)
val layoutHeight =
measurable.minIntrinsicHeight(modifyConstraints(constraints).maxWidth)
val scaledSize = calculateScaledSize(Size(width.toFloat(), layoutHeight.toFloat()))
max(scaledSize.height.roundToInt(), layoutHeight)
} else {
measurable.minIntrinsicHeight(width)
}
}
override fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int,
layoutDirection: LayoutDirection
): Int {
return if (sizeToIntrinsics) {
val constraints = Constraints(maxWidth = width)
val layoutHeight =
measurable.maxIntrinsicHeight(modifyConstraints(constraints).maxWidth)
val scaledSize = calculateScaledSize(Size(width.toFloat(), layoutHeight.toFloat()))
max(scaledSize.height.roundToInt(), layoutHeight)
} else {
measurable.maxIntrinsicHeight(width)
}
}
private fun calculateScaledSize(dstSize: Size): Size {
return if (!sizeToIntrinsics) {
dstSize
} else {
val intrinsicWidth = painter.intrinsicSize.width
val intrinsicHeight = painter.intrinsicSize.height
val srcWidth = if (intrinsicWidth == Float.POSITIVE_INFINITY) {
dstSize.width
} else {
intrinsicWidth
}
val srcHeight = if (intrinsicHeight == Float.POSITIVE_INFINITY) {
dstSize.height
} else {
intrinsicHeight
}
val srcSize = Size(srcWidth, srcHeight)
srcSize * contentScale.scale(srcSize, dstSize)
}
}
private fun modifyConstraints(constraints: Constraints): Constraints {
if (!sizeToIntrinsics || (constraints.hasFixedWidth && constraints.hasFixedHeight)) {
// If we have fixed constraints or we are not attempting to size the
// composable based on the size of the Painter, do not attempt to
// modify them. Otherwise rely on Alignment and ContentScale
// to determine how to position the drawing contents of the Painter within
// the provided bounds
return constraints
}
val intrinsicSize = painter.intrinsicSize
val intrinsicWidth =
if (intrinsicSize.width != Float.POSITIVE_INFINITY) {
intrinsicSize.width.roundToInt()
} else {
constraints.minWidth
}
val intrinsicHeight =
if (intrinsicSize.height != Float.POSITIVE_INFINITY) {
intrinsicSize.height.roundToInt()
} else {
constraints.minHeight
}
// Scale the width and height appropriately based on the given constraints
// and ContentScale
val constrainedWidth = constraints.constrainWidth(intrinsicWidth)
val constrainedHeight = constraints.constrainHeight(intrinsicHeight)
val scaledSize = calculateScaledSize(
Size(constrainedWidth.toFloat(), constrainedHeight.toFloat())
)
val minWidth = if (constraints.hasFixedWidth) {
constraints.minWidth
} else {
scaledSize.width.roundToInt()
}
val minHeight = if (constraints.hasFixedHeight) {
constraints.minHeight
} else {
scaledSize.height.roundToInt()
}
return constraints.copy(minWidth = minWidth, minHeight = minHeight)
}
override fun ContentDrawScope.draw() {
val intrinsicSize = painter.intrinsicSize
val srcWidth = if (intrinsicSize.width != Float.POSITIVE_INFINITY) {
intrinsicSize.width
} else {
size.width
}
val srcHeight = if (intrinsicSize.height != Float.POSITIVE_INFINITY) {
intrinsicSize.height
} else {
size.height
}
val srcSize = Size(srcWidth, srcHeight)
val scale = contentScale.scale(srcSize, size)
// Compute the offset to translate the content based on the given alignment
// and size to draw based on the ContentScale parameter
val scaledSize = srcSize * scale
val alignedPosition = alignment.align(
IntSize(
ceil(size.width - (scaledSize.width)).toInt(),
ceil(size.height - (scaledSize.height)).toInt()
)
)
val dx = alignedPosition.x.toFloat()
val dy = alignedPosition.y.toFloat()
// Only translate the current drawing position while delegating the Painter to draw
// with scaled size.
// Individual Painter implementations should be responsible for scaling their drawing
// content accordingly to fit within the drawing area.
translate(dx, dy) {
with(painter) {
draw(size = scaledSize, alpha = alpha, colorFilter = colorFilter)
}
}
}
}