net.peanuuutz.fork.ui.foundation.draw.Paint.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fork-ui Show documentation
Show all versions of fork-ui Show documentation
Comprehensive API designed for Minecraft modders
The newest version!
/*
* Copyright 2020 The Android Open Source Project
* Modifications Copyright 2022 Peanuuutz
*
* 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 net.peanuuutz.fork.ui.foundation.draw
import androidx.compose.runtime.Stable
import net.peanuuutz.fork.ui.inspection.InspectInfo
import net.peanuuutz.fork.ui.ui.draw.ContentDrawScope
import net.peanuuutz.fork.ui.ui.draw.ContentScale
import net.peanuuutz.fork.ui.ui.draw.Painter
import net.peanuuutz.fork.ui.ui.draw.inset
import net.peanuuutz.fork.ui.ui.draw.times
import net.peanuuutz.fork.ui.ui.layout.Alignment
import net.peanuuutz.fork.ui.ui.layout.Constraints
import net.peanuuutz.fork.ui.ui.layout.Measurable
import net.peanuuutz.fork.ui.ui.layout.MeasureResult
import net.peanuuutz.fork.ui.ui.layout.constrainHeight
import net.peanuuutz.fork.ui.ui.layout.constrainWidth
import net.peanuuutz.fork.ui.ui.modifier.Modifier
import net.peanuuutz.fork.ui.ui.modifier.ModifierNodeElement
import net.peanuuutz.fork.ui.ui.node.DrawModifierNode
import net.peanuuutz.fork.ui.ui.node.LayoutModifierNode
import net.peanuuutz.fork.ui.ui.node.ModifierNode
import net.peanuuutz.fork.ui.ui.unit.FloatOffset
import net.peanuuutz.fork.ui.ui.unit.FloatSize
import net.peanuuutz.fork.ui.ui.unit.IntOffset
import net.peanuuutz.fork.ui.ui.unit.isZero
import net.peanuuutz.fork.ui.ui.unit.toIntSize
import kotlin.math.roundToInt
@Stable
fun Modifier.paint(
painter: Painter,
alignment: Alignment = Alignment.CenterOnPlane,
contentScale: ContentScale = ContentScale.Inside
): Modifier {
val element = PainterModifier(
painter = painter,
alignment = alignment,
contentScale = contentScale
)
return this then element
}
// ======== Internal ========
private data class PainterModifier(
val painter: Painter,
val alignment: Alignment,
val contentScale: ContentScale
) : ModifierNodeElement() {
override fun create(): PainterModifierNode {
return PainterModifierNode(
painter = painter,
alignment = alignment,
contentScale = contentScale
)
}
override fun update(node: PainterModifierNode) {
node.painter = painter
node.alignment = alignment
node.contentScale = contentScale
}
override fun InspectInfo.inspect() {
set("painter", painter)
set("alignment", alignment)
set("contentScale", contentScale)
}
}
private class PainterModifierNode(
painter: Painter,
alignment: Alignment = Alignment.CenterOnPlane,
contentScale: ContentScale = ContentScale.Inside
) : ModifierNode(),
LayoutModifierNode,
DrawModifierNode
{
var painter: Painter = painter
set(value) {
if (field != value) {
field = value
shouldRebuildCache = true
}
}
var alignment: Alignment = alignment
set(value) {
if (field != value) {
field = value
shouldRebuildCache = true
}
}
var contentScale: ContentScale = contentScale
set(value) {
if (field != value) {
field = value
shouldRebuildCache = true
}
}
private var shouldRebuildCache: Boolean = true
private var cachedResultSize: FloatSize = FloatSize.Zero
private var cachedPosition: FloatOffset = FloatOffset.Zero
override fun measure(measurable: Measurable, constraints: Constraints): MeasureResult {
val contentConstraints = calculateContentConstraints(constraints)
val placeable = measurable.measure(contentConstraints)
return MeasureResult(placeable.width, placeable.height) {
placeable.place(IntOffset.Zero)
}
}
private fun calculateContentConstraints(incoming: Constraints): Constraints {
if (incoming.hasFixedWidth && incoming.hasFixedHeight) {
return incoming
}
val painterSize = painter.size
val dstWidth = if (painterSize.width.isFinite()){
painterSize.width.roundToInt()
} else{
incoming.minWidth
}
val dstHeight = if (painterSize.height.isFinite()){
painterSize.height.roundToInt()
} else{
incoming.minHeight
}
val dstSize = FloatSize(dstWidth, dstHeight)
val (resultWidth, resultHeight) = calculateResultSize(dstSize)
val minWidth = incoming.constrainWidth(resultWidth.roundToInt())
val minHeight = incoming.constrainHeight(resultHeight.roundToInt())
return incoming.copy(minWidth = minWidth, minHeight = minHeight)
}
override fun ContentDrawScope.draw() {
if (shouldRebuildCache) {
shouldRebuildCache = false
rebuildCache(size)
}
inset(
topLeft = cachedPosition,
size = cachedResultSize
) {
with(painter) {
onDraw()
}
}
drawContent()
}
override fun onSizeChanged(size: FloatSize) {
shouldRebuildCache = true
}
private fun rebuildCache(dstSize: FloatSize) {
val resultSize = calculateResultSize(dstSize)
cachedResultSize = resultSize
cachedPosition = FloatOffset(
intOffset = alignment.align(
contentSize = resultSize.toIntSize(),
availableSpace = dstSize.toIntSize()
)
)
}
private fun calculateResultSize(dstSize: FloatSize): FloatSize {
if (dstSize.isZero()) {
return FloatSize.Zero
}
val painterSize = painter.size
val srcWidth = if (painterSize.width.isFinite()){
painterSize.width
} else{
dstSize.width
}
val srcHeight = if (painterSize.height.isFinite()){
painterSize.height
} else{
dstSize.height
}
val srcSize = FloatSize(srcWidth, srcHeight)
return if (dstSize.width != 0.0f && dstSize.height != 0.0f) {
srcSize * contentScale.calculateScaleFactor(srcSize, dstSize)
} else {
FloatSize.Zero
}
}
}