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

net.peanuuutz.fork.ui.foundation.layout.Box.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.layout

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import net.peanuuutz.fork.ui.inspection.InspectInfo
import net.peanuuutz.fork.ui.ui.layout.Alignment
import net.peanuuutz.fork.ui.ui.layout.Constraints
import net.peanuuutz.fork.ui.ui.layout.EmptyPlacement
import net.peanuuutz.fork.ui.ui.layout.Layout
import net.peanuuutz.fork.ui.ui.layout.MeasurePolicy
import net.peanuuutz.fork.ui.ui.layout.MeasureResult
import net.peanuuutz.fork.ui.ui.layout.Placeable
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.ModifierNode
import net.peanuuutz.fork.ui.ui.node.MutableParentData
import net.peanuuutz.fork.ui.ui.node.ParentData
import net.peanuuutz.fork.ui.ui.node.ParentDataContainer
import net.peanuuutz.fork.ui.ui.node.ParentDataModifierNode
import net.peanuuutz.fork.ui.ui.node.getOrPut
import net.peanuuutz.fork.ui.ui.unit.IntSize
import net.peanuuutz.fork.util.common.fastForEach
import net.peanuuutz.fork.util.common.fastForEachIndexed
import kotlin.math.max

@Composable
inline fun Box(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopLeft,
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxScope.() -> Unit
) {
    val measurePolicy = rememberBoxMeasurePolicy(
        contentAlignment = contentAlignment,
        propagateMinConstraints = propagateMinConstraints
    )

    Layout(
        content = { BoxScopeImpl.content() },
        modifier = modifier,
        measurePolicy = measurePolicy
    )
}

@Composable
fun Box(modifier: Modifier) {
    Layout(modifier = modifier, measurePolicy = EmptyBoxMeasurePolicy)
}

@LayoutDsl
@Immutable
interface BoxScope {
    fun Modifier.align(alignment: Alignment): Modifier

    fun Modifier.matchParentSize(): Modifier
}

// ======== Internal ========

@PublishedApi
internal object BoxScopeImpl : BoxScope {
    override fun Modifier.align(alignment: Alignment): Modifier {
        return this then BoxChildModifier(alignment = alignment)
    }

    override fun Modifier.matchParentSize(): Modifier {
        return this then BoxChildModifier(matchParentSize = true)
    }
}

@Stable
private val EmptyBoxMeasurePolicy: MeasurePolicy = MeasurePolicy { _, constraints ->
    MeasureResult(constraints.minWidth, constraints.minHeight, EmptyPlacement)
}

@PublishedApi
@Composable
internal fun rememberBoxMeasurePolicy(
    contentAlignment: Alignment,
    propagateMinConstraints: Boolean
): MeasurePolicy {
    return remember(contentAlignment, propagateMinConstraints) {
        if (contentAlignment == Alignment.TopLeft && !propagateMinConstraints) {
            DefaultBoxMeasurePolicy
        } else {
            boxMeasurePolicy(contentAlignment, propagateMinConstraints)
        }
    }
}

private val DefaultBoxMeasurePolicy: MeasurePolicy = boxMeasurePolicy(
    contentAlignment = Alignment.TopLeft,
    propagateMinConstraints = false
)

private fun boxMeasurePolicy(
    contentAlignment: Alignment,
    propagateMinConstraints: Boolean
): MeasurePolicy {
    return MeasurePolicy { measurables, constraints ->
        if (measurables.isEmpty()) {
            return@MeasurePolicy MeasureResult(constraints.minWidth, constraints.minHeight, EmptyPlacement)
        }
        val contentConstraints = if (!propagateMinConstraints) {
            constraints.copy(minWidth = 0, minHeight = 0)
        } else {
            constraints
        }
        if (measurables.size == 1) {
            val measurable = measurables[0]
            val placeable: Placeable
            val boxWidth: Int
            val boxHeight: Int
            if (measurable.matchParentSize != true) {
                placeable = measurable.measure(contentConstraints)
                boxWidth = constraints.constrainWidth(placeable.width)
                boxHeight = constraints.constrainHeight(placeable.height)
            } else {
                boxWidth = constraints.minWidth
                boxHeight = constraints.minHeight
                placeable = measurable.measure(Constraints.fixed(boxWidth, boxHeight))
            }
            return@MeasurePolicy MeasureResult(boxWidth, boxHeight) {
                val childAlignment = placeable.alignment ?: contentAlignment
                val position = childAlignment.align(
                    contentSize = IntSize(placeable.width, placeable.height),
                    availableSpace = IntSize(boxWidth, boxHeight)
                )
                placeable.place(position)
            }
        }
        val placeables = arrayOfNulls(measurables.size)
        var boxWidth = constraints.minWidth
        var boxHeight = constraints.minHeight
        var hasMatchParentSizeChild = false
        measurables.fastForEachIndexed { index, measurable ->
            if (measurable.matchParentSize != true) {
                val placeable = measurable.measure(contentConstraints)
                placeables[index] = placeable
                boxWidth = max(boxWidth, placeable.width)
                boxHeight = max(boxHeight, placeable.height)
            } else {
                hasMatchParentSizeChild = true
            }
        }
        boxWidth = boxWidth.coerceAtMost(constraints.maxWidth)
        boxHeight = boxHeight.coerceAtMost(constraints.maxHeight)
        if (hasMatchParentSizeChild) {
            val matchParentSizeConstraints = Constraints.fixed(boxWidth, boxHeight)
            measurables.fastForEachIndexed { index, measurable ->
                if (measurable.matchParentSize == true) {
                    placeables[index] = measurable.measure(matchParentSizeConstraints)
                }
            }
        }
        MeasureResult(boxWidth, boxHeight) {
            val boxSize = IntSize(boxWidth, boxHeight)
            placeables.fastForEach { placeable ->
                placeable!!
                val childAlignment = placeable.alignment ?: contentAlignment
                val position = childAlignment.align(
                    contentSize = IntSize(placeable.width, placeable.height),
                    availableSpace = boxSize
                )
                placeable.place(position)
            }
        }
    }
}

private data class BoxChildModifier(
    val alignment: Alignment? = null,
    val matchParentSize: Boolean? = null
) : ModifierNodeElement() {
    override fun create(): BoxChildModifierNode {
        return BoxChildModifierNode(
            alignment = alignment,
            matchParentSize = matchParentSize
        )
    }

    override fun update(node: BoxChildModifierNode) {
        node.alignment = alignment
        node.matchParentSize = matchParentSize
    }

    override fun InspectInfo.inspect() {
        set("alignment", alignment)
        set("matchParentSize", matchParentSize)
    }
}

private val AlignmentParentData = ParentData.Key()

private inline val ParentDataContainer.alignment: Alignment?
    get() = parentData[AlignmentParentData]

private val MatchParentSizeParentData = ParentData.Key()

private inline val ParentDataContainer.matchParentSize: Boolean?
    get() = parentData[MatchParentSizeParentData]

private class BoxChildModifierNode(
    var alignment: Alignment?,
    var matchParentSize: Boolean?
) : ModifierNode(), ParentDataModifierNode {
    override fun modifyParentData(parentData: MutableParentData) {
        parentData.getOrPut(AlignmentParentData) { alignment }
        parentData.getOrPut(MatchParentSizeParentData) { matchParentSize }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy