![JAR search and dependency download from the Maven repository](/logo.png)
net.peanuuutz.fork.ui.foundation.layout.Box.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.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