blob: a2f5e42864a9e6f9d90b727cef50e319ad4f5297 [file]
/*
* Copyright 2024 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.compose.foundation.layout
import androidx.collection.IntIntPair
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.Placeable
import kotlin.math.max
@OptIn(ExperimentalLayoutApi::class)
internal class FlowLayoutBuildingBlocks(
private val maxItemsInMainAxis: Int,
private val overflow: FlowLayoutOverflowState,
private val constraints: OrientationIndependentConstraints,
private val maxLines: Int,
private val mainAxisSpacing: Int,
private val crossAxisSpacing: Int,
) {
class WrapInfo(
val isLastItemInLine: Boolean = false,
val isLastItemInContainer: Boolean = false,
)
class WrapEllipsisInfo(
val ellipsis: Measurable,
val placeable: Placeable?,
val ellipsisSize: IntIntPair,
var placeEllipsisOnLastContentLine: Boolean = true,
)
fun getWrapEllipsisInfo(
wrapInfo: WrapInfo,
hasNext: Boolean,
lastContentLineIndex: Int,
totalCrossAxisSize: Int,
leftOverMainAxis: Int,
nextIndexInLine: Int,
): WrapEllipsisInfo? {
if (!wrapInfo.isLastItemInContainer) return null
val ellipsisInfo =
overflow.ellipsisInfo(hasNext, lastContentLineIndex, totalCrossAxisSize) ?: return null
val canFitLine =
lastContentLineIndex >= 0 &&
(nextIndexInLine == 0 ||
!(leftOverMainAxis - ellipsisInfo.ellipsisSize.first < 0 ||
nextIndexInLine >= maxItemsInMainAxis))
ellipsisInfo.placeEllipsisOnLastContentLine = canFitLine
return ellipsisInfo
}
@Suppress("DEPRECATION")
fun getWrapInfo(
nextItemHasNext: Boolean,
nextIndexInLine: Int,
leftOver: IntIntPair,
nextSize: IntIntPair?,
lineIndex: Int,
totalCrossAxisSize: Int,
currentLineCrossAxisSize: Int,
isWrappingRound: Boolean,
isEllipsisWrap: Boolean,
): WrapInfo {
var totalContainerCrossAxisSize = totalCrossAxisSize + currentLineCrossAxisSize
if (nextSize == null) {
return WrapInfo(isLastItemInLine = true, isLastItemInContainer = true)
}
val willOverflowCrossAxis =
when {
overflow.type == FlowLayoutOverflow.OverflowType.Visible -> false
lineIndex >= maxLines -> true
leftOver.second - nextSize.second < 0 -> true
else -> false
}
if (willOverflowCrossAxis) {
return WrapInfo(isLastItemInLine = true, isLastItemInContainer = true)
}
val shouldWrapItem =
when {
nextIndexInLine == 0 -> false
nextIndexInLine >= maxItemsInMainAxis -> true
leftOver.first - nextSize.first < 0 -> true
else -> false
}
if (shouldWrapItem) {
if (isWrappingRound) {
return WrapInfo(isLastItemInLine = true, isLastItemInContainer = true)
}
val wrapInfo =
getWrapInfo(
nextItemHasNext,
nextIndexInLine = 0,
leftOver =
IntIntPair(
constraints.mainAxisMax,
leftOver.second - crossAxisSpacing - currentLineCrossAxisSize,
),
// remove the mainAxisSpacing added to 2nd position or more indexed items.
IntIntPair(
first = nextSize.first.minus(mainAxisSpacing),
second = nextSize.second,
),
lineIndex = lineIndex + 1,
totalCrossAxisSize = totalContainerCrossAxisSize,
currentLineCrossAxisSize = 0,
isWrappingRound = true,
isEllipsisWrap = false,
)
return WrapInfo(
isLastItemInLine = true,
isLastItemInContainer = wrapInfo.isLastItemInContainer,
)
}
totalContainerCrossAxisSize =
totalCrossAxisSize + max(currentLineCrossAxisSize, nextSize.second)
val ellipsis =
if (isEllipsisWrap) {
null
} else {
overflow.ellipsisSize(nextItemHasNext, lineIndex, totalContainerCrossAxisSize)
}
val shouldWrapEllipsis =
ellipsis?.run {
when {
nextIndexInLine + 1 >= maxItemsInMainAxis -> true
leftOver.first - nextSize.first - mainAxisSpacing - ellipsis.first < 0 -> true
else -> false
}
} ?: false
if (shouldWrapEllipsis) {
if (isEllipsisWrap) {
return WrapInfo(isLastItemInLine = true, isLastItemInContainer = true)
}
val wrapInfo =
getWrapInfo(
nextItemHasNext = false,
nextIndexInLine = 0,
IntIntPair(
constraints.mainAxisMax,
leftOver.second -
crossAxisSpacing -
max(currentLineCrossAxisSize, nextSize.second),
),
ellipsis,
lineIndex = lineIndex + 1,
totalCrossAxisSize = totalContainerCrossAxisSize,
currentLineCrossAxisSize = 0,
isWrappingRound = true,
isEllipsisWrap = true,
)
return WrapInfo(
isLastItemInLine = wrapInfo.isLastItemInContainer,
isLastItemInContainer = wrapInfo.isLastItemInContainer,
)
}
return WrapInfo(isLastItemInLine = false, isLastItemInContainer = false)
}
}