Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
commonMain.Table.kt Maven / Gradle / Ivy
package com.zakgof
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.ceil
@Composable
fun Table(
modifier: Modifier = Modifier,
showGridLines: Boolean = true,
lineWidth: Dp = 1.dp,
lineColor: Color = Color.DarkGray,
content: TableScope.() -> Unit
) {
var horizontalLines by remember { mutableStateOf>>(mapOf()) }
var verticalLines by remember { mutableStateOf>>(mapOf()) }
SubcomposeLayout(modifier = modifier.drawBehind {
val lineWidthFloatPix: Float = LocalDensity.run { lineWidth.toPx() }
horizontalLines.forEach { entry ->
entry.value.forEach {
drawLine(
color = lineColor,
strokeWidth = lineWidthFloatPix,
start = Offset(it.key, entry.key),
end = Offset(it.value, entry.key)
)
}
}
verticalLines.forEach { entry ->
entry.value.forEach {
drawLine(
color = lineColor,
strokeWidth = lineWidth.toPx(),
start = Offset(entry.key, it.key),
end = Offset(entry.key, it.value)
)
}
}
}) { constraints ->
val lineWidthFloatPix: Float =
if (showGridLines) LocalDensity.run { lineWidth.toPx() } else 0f
val lineWidthPix = if (showGridLines) ceil(lineWidthFloatPix).toInt() else 0
val rows = mutableListOf<@Composable TableRowScope.() -> Unit>()
content(object : TableScope {
override fun Row(rowContent: @Composable TableRowScope.() -> Unit) {
rows += rowContent
}
})
val cells = mutableListOf()
val chessBoard = mutableSetOf>()
val noxline = mutableSetOf>()
val noyline = mutableSetOf>()
var x = 0
var y = 0
rows.forEachIndexed { rowIndex, row ->
val firstScan = subcompose(rowIndex to 0) { TableRowScopeImpl.row() }.map {
it.measure(Constraints())
}
subcompose(rowIndex to 1) { TableRowScopeImpl.row() }.forEachIndexed { index, it ->
val min = firstScan[index]
val max = it.measure(
Constraints(
maxWidth = constraints.maxWidth,
maxHeight = constraints.maxHeight
)
)
val columnSpan = (it.parentData as? ColumnSpanParentData)?.columnSpan ?: 1
val rowSpan = (it.parentData as? RowSpanParentData)?.rowSpan ?: 1
while (chessBoard.contains(x to y)) {
x++
}
cells.add(Cell(min, max, x, x + columnSpan - 1, y, y + rowSpan - 1))
for (xx in x until x + columnSpan) {
for (yy in y until y + rowSpan) {
chessBoard.add(xx to yy)
}
}
for (xx in x until x + columnSpan) {
for (yy in y + 1 until y + rowSpan) {
noxline.add(xx to yy)
}
}
for (xx in x + 1 until x + columnSpan) {
for (yy in y until y + rowSpan) {
noyline.add(yy to xx)
}
}
x += columnSpan
}
y++
x = 0
}
val mins = measureCells(cells, lineWidthPix) { it.min }
val maxs = measureCells(cells, lineWidthPix) { it.max }
val actualTotalWidth = mins.first.sum()
val desiredTotalWidth = maxs.first.sum()
val columnCount = chessBoard.maxOf { it.first } + 1
val rowCount = chessBoard.maxOf { it.second } + 1
val effectiveAvailableWidth = constraints.maxWidth - (columnCount + 1) * lineWidthPix
val finalWidths =
if (actualTotalWidth > effectiveAvailableWidth) {
mins.first.map { it * effectiveAvailableWidth / actualTotalWidth }
} else if (desiredTotalWidth > effectiveAvailableWidth) {
val deficit = desiredTotalWidth - effectiveAvailableWidth
val space = desiredTotalWidth - actualTotalWidth
maxs.first.mapIndexed { index, it ->
it - (it - mins.first[index]) * deficit / space
}
} else maxs.first
val actualTotalHeight = mins.second.sum()
val desiredTotalHeight = maxs.second.sum()
val effectiveAvailableHeight = constraints.maxHeight - (rowCount + 1) * lineWidthPix
val finalHeights =
if (actualTotalHeight > effectiveAvailableHeight) {
mins.second.map { it * effectiveAvailableHeight / actualTotalHeight }
} else if (desiredTotalHeight > effectiveAvailableHeight) {
val deficit = desiredTotalHeight - effectiveAvailableHeight
val space = desiredTotalHeight - actualTotalHeight
maxs.second.mapIndexed { index, it ->
it - (it - mins.second[index]) * deficit / space
}
} else maxs.second
var i = 0
val triples = rows.flatMapIndexed { rowIndex, row ->
subcompose(rowIndex to 2) { TableRowScopeImpl.row() }.map { measurable ->
val cell = cells[i++]
val width = (cell.columnStart..cell.columnEnd).sumOf { finalWidths[it] } +
(cell.columnEnd - cell.columnStart) * lineWidthPix
val height = (cell.rowStart..cell.rowEnd).sumOf { finalHeights[it] } +
(cell.rowEnd - cell.rowStart) * lineWidthPix
val placeable = measurable.measure(
Constraints(
minWidth = width,
minHeight = height,
maxWidth = width,
maxHeight = height
)
)
Triple(placeable, cell.columnStart, cell.rowStart)
}
}
val accumWidths = finalWidths.accumulated(lineWidthPix)
val accumHeights = finalHeights.accumulated(lineWidthPix)
if (showGridLines) {
horizontalLines = calculateLines(accumWidths, accumHeights, noxline, lineWidthFloatPix)
verticalLines = calculateLines(accumHeights, accumWidths, noyline, lineWidthFloatPix)
}
layout(
width = accumWidths.last(),
height = accumHeights.last()
) {
triples.map {
it.first.placeRelative(accumWidths[it.second], accumHeights[it.third])
}
}
}
}
fun calculateLines(
accumWidths: List,
accumHeights: List,
noxline: Set>,
lineWidthFloatPix: Float
): Map> {
return accumHeights.mapIndexed { yy, y ->
val m = mutableMapOf()
var startIndex = 0
while (startIndex < accumWidths.size) {
while (noxline.contains(startIndex to yy) && startIndex < accumWidths.size - 1) {
startIndex++
}
var endIndex = startIndex
while (!noxline.contains(endIndex to yy) && endIndex < accumWidths.size - 1) {
endIndex++
}
val so = if (startIndex == 0) -lineWidthFloatPix else -lineWidthFloatPix * 0.5f
val eo = if (endIndex == accumWidths.size - 1) 0f else -lineWidthFloatPix * 0.5f
m[accumWidths[startIndex].toFloat() + so] = accumWidths[endIndex].toFloat() + eo
startIndex = endIndex + 1
}
y.toFloat() - lineWidthFloatPix * 0.5f to m
}.associate { it }
}
private fun measureCells(
cells: List,
lineWidth: Int,
getter: (Cell) -> Placeable,
): Pair, List> {
val columnWidths = mutableMapOf()
val rowHeights = mutableMapOf()
cells.forEach { cell ->
val placeable = getter(cell)
val currentWidth = (cell.columnStart..cell.columnEnd).sumOf {
columnWidths[it] ?: 0
} + lineWidth * (cell.columnEnd - cell.columnStart)
val currentHeight = (cell.rowStart..cell.rowEnd).sumOf {
rowHeights[it] ?: 0
} + lineWidth * (cell.rowEnd - cell.rowStart)
if (placeable.width > currentWidth) {
val surplus = (placeable.width - currentWidth) / (cell.columnEnd - cell.columnStart + 1)
(cell.columnStart..cell.columnEnd).forEach {
columnWidths[it] = (columnWidths[it] ?: 0) + surplus
}
}
if (placeable.height > currentHeight) {
val surplus = (placeable.height - currentHeight) / (cell.rowEnd - cell.rowStart + 1)
(cell.rowStart..cell.rowEnd).forEach {
rowHeights[it] = (rowHeights[it] ?: 0) + surplus
}
}
}
val widthsList = (0 until columnWidths.size).map { columnWidths[it]!! }
val heightsList = (0 until rowHeights.size).map { rowHeights[it]!! }
return widthsList to heightsList
}
private fun List.accumulated(width: Int) =
fold(listOf(width)) { acc, el -> acc + (acc.last() + el + width) }
private class Cell(
val min: Placeable, val max: Placeable,
val columnStart: Int, val columnEnd: Int,
val rowStart: Int, val rowEnd: Int
)
| |