Nicolai
11/15/2024, 5:38 PMNicolai
11/15/2024, 5:38 PM@Composable
fun Table(
modifier: Modifier = Modifier,
rows: Int,
columns: Int,
extraHeaders: Int,
rowHeaders: @Composable (Int) -> Unit,
columnHeaders: @Composable (Int) -> Unit,
tableData: @Composable RowScope.(Int, Int) -> Unit,
columnTitle: @Composable () -> Unit? = { null },
rowExtraHeaders: @Composable () -> Unit? = { null },
) {
val scrollState = rememberScrollState()
SubcomposeLayout(
modifier = modifier
) { constraints ->
// Measure table data for max width and height
val measurablesTableAll = subcompose(TableSlots.TableDataMeasurePass) {
(0 until rows).forEach { rowIndex ->
(0 until columns).forEach { columnIndex ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.horizontalScroll(scrollState)
) {
tableData(rowIndex, columnIndex)
}
}
}
}
val contentMaxHeight = measurablesTableAll.maxOf { it.maxIntrinsicHeight(Int.MAX_VALUE) }
val contentMaxWidth = constraints.maxWidth
// Measure Row Headers
val measurablesHeaders = subcompose(TableSlots.RowHeaders) {
(0 until rows).forEach { rowIndex -> rowHeaders(rowIndex) }
}
val headerMaxWidth = measurablesHeaders.maxOf { it.maxIntrinsicWidth(Int.MAX_VALUE) }
val maxRowHeight = contentMaxHeight
val headerPlaceables = measurablesHeaders.map {
it.measure(Constraints.fixed(width = headerMaxWidth, height = contentMaxHeight))
}
// Measure Column Headers
val measurablesColumnHeaders = subcompose(TableSlots.ColumHeaders) {
(0 until columns).forEach { columnIndex -> columnHeaders(columnIndex) }
}
val headerColumnMaxWidth =
measurablesColumnHeaders.maxOf { it.maxIntrinsicWidth(Int.MAX_VALUE) }
val headerColumnMaxHeight =
measurablesColumnHeaders.maxOf { it.maxIntrinsicHeight(headerMaxWidth) }
val columnMaxWidth = max(headerColumnMaxWidth, contentMaxWidth)
// Measure Column Title
val columnTitlePlaceable = subcompose(TableSlots.ColumnTitle) {
columnTitle()
}.first().measure(Constraints.fixed(width = columnMaxWidth, height = maxRowHeight))
// Measure Extra Headers
val measurablesExtraHeaders = subcompose(TableSlots.ExtraHeaders) {
(0 until extraHeaders).forEach { _ -> rowExtraHeaders() }
}
val extraHeadersMaxWidth =
measurablesExtraHeaders.maxOf { it.maxIntrinsicWidth(Int.MAX_VALUE) }
val extraHeadersMaxHeight =
measurablesExtraHeaders.maxOf { it.maxIntrinsicHeight(Int.MAX_VALUE) }
// Measure Table Data
val measurablesTableData = subcompose(TableSlots.TableData) {
(0 until rows).forEach { rowIndex ->
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.horizontalScroll(scrollState)
.fillMaxWidth()
.background(Color.Red)
) {
(0 until columns).forEach { columnIndex -> tableData(rowIndex, columnIndex) }
}
}
}
val dataMaxWidth = measurablesTableData.maxOf { it.maxIntrinsicWidth(Int.MAX_VALUE) }
val headerWidth = maxOf(dataMaxWidth, columnMaxWidth, extraHeadersMaxWidth)
val measurablesTableDataPlaceable = measurablesTableData.map {
it.measure(Constraints.fixed(width = headerWidth, height = maxRowHeight))
}
// Measure Divider
val divider = subcompose(TableSlots.Divider) {
repeat(11) { PublicDivider() }
}.map {
it.measure(Constraints.fixed(width = contentMaxWidth, height = 1.dp.roundToPx()))
}
// Measure Column Header Placeables
val headerColumnPlaceables = measurablesColumnHeaders.map {
it.measure(Constraints.fixed(width = headerWidth / 2, height = headerColumnMaxHeight))
}
// Measure Extra Headers Placeables
val extraHeadersPlaceable = measurablesExtraHeaders.map {
it.measure(Constraints.fixed(width = headerWidth / 2, height = extraHeadersMaxHeight))
}
// Calculate Overall Height
val height = headerColumnMaxHeight +
headerPlaceables.sumOf { it.height } +
columnTitlePlaceable.height +
extraHeadersPlaceable.sumOf { it.height }
layout(width = constraints.maxWidth, height = height) {
val dividerPositions = mutableListOf<Int>() // List to hold y-positions for dividers
// Place column headers below the title
var x = headerMaxWidth
headerColumnPlaceables.forEach {
// Position each column header horizontally
it.placeRelative(x, 0)
x += it.width
}
// Place column title at the top-left corner
columnTitlePlaceable.placeRelative(0, 0)
// Place extra headers below column title and above table data
x = headerMaxWidth
extraHeadersPlaceable.forEach { extraHeaderPlaceable ->
// Position extra headers horizontally starting after the row headers
extraHeaderPlaceable.placeRelative(
x,
headerColumnMaxHeight + headerColumnMaxHeight / 2
)
x += extraHeaderPlaceable.width
}
// Add positions for dividers below extra headers
dividerPositions.add(headerColumnMaxHeight + 8.dp.roundToPx())
dividerPositions.add(extraHeadersMaxHeight + headerColumnMaxHeight + 8.dp.roundToPx())
// Calculate initial y-position for placing row headers
val y: Int = headerColumnMaxHeight + extraHeadersMaxHeight + headerColumnMaxHeight / 2
// Place row headers
headerPlaceables.forEachIndexed { index, headerPlaceable ->
// Position each row header vertically
headerPlaceable.placeRelative(0, y + index * maxRowHeight)
if (index > 0) {
// Add divider position after each row header
dividerPositions.add(y + index * maxRowHeight)
}
}
// Place data rows corresponding to row headers
measurablesTableDataPlaceable.forEachIndexed { index, dataPlaceable ->
x = headerMaxWidth // Start placing data after the row headers
dataPlaceable.placeRelative(x, y + index * maxRowHeight)
}
// Place dividers at calculated positions
dividerPositions.forEachIndexed { index, position ->
// Position each divider at the calculated y-coordinate
divider[index].placeRelative(0, position)
}
}
}
}
private enum class TableSlots {
RowHeaders, TableData, TableDataMeasurePass, ColumHeaders, ColumnTitle, ExtraHeaders, Divider
}
Stylianos Gakis
11/15/2024, 5:43 PMNicolai
11/15/2024, 5:50 PMStylianos Gakis
11/15/2024, 6:47 PMNicolai
11/15/2024, 7:16 PM