All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.peanuuutz.fork.ui.foundation.draw.Paint.kt Maven / Gradle / Ivy

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
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy