Adaptive Layout (WIP)
This commit is contained in:
@@ -6,14 +6,12 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Text
|
||||
@@ -27,6 +25,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -128,40 +127,26 @@ fun App(
|
||||
}
|
||||
)
|
||||
},
|
||||
clues = {
|
||||
PuzzleClues(
|
||||
horizontalClues = horizontalClues,
|
||||
verticalClues = verticalClues
|
||||
horizontalClues = {
|
||||
HorizontalClues(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||
clues = horizontalClues
|
||||
)
|
||||
},
|
||||
time = time
|
||||
verticalClues = {
|
||||
VerticalClues(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||
clues = verticalClues
|
||||
)
|
||||
},
|
||||
time = {
|
||||
Text(time, fontSize = TextUnit(4f, TextUnitType.Companion.Em), textAlign = TextAlign.End)
|
||||
}
|
||||
)
|
||||
EndOfGame(isSolved = isSolved, time = time, onRestart = onNewGame)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AdaptiveGameLayout(
|
||||
modifier: Modifier = Modifier,
|
||||
grid: @Composable () -> Unit,
|
||||
clues: @Composable () -> Unit,
|
||||
time: String = "00:00"
|
||||
) {
|
||||
// TODO: actually use an adaptive layout
|
||||
Row(modifier = modifier) {
|
||||
Column(modifier = Modifier.weight(0.6f)) {
|
||||
grid()
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 16.dp).weight(0.4f).fillMaxHeight()
|
||||
) {
|
||||
clues()
|
||||
|
||||
Text(time, fontSize = TextUnit(4f, TextUnitType.Em))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PuzzleGrid(
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -226,42 +211,42 @@ fun PuzzleGrid(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PuzzleClues(
|
||||
fun HorizontalClues(
|
||||
modifier: Modifier = Modifier,
|
||||
horizontalClues: List<DisplayClue<HorizontalClue>>,
|
||||
verticalClues: List<DisplayClue<SameColumnClue<ItemClass<*>, ItemClass<*>>>>
|
||||
clues: List<DisplayClue<HorizontalClue>>
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
LazyVerticalGrid(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||
columns = GridCells.Fixed(4)
|
||||
) {
|
||||
for (clue in horizontalClues) {
|
||||
item {
|
||||
HorizontalClue(
|
||||
modifier = Modifier
|
||||
.forClue(clue),
|
||||
clue = clue.clue,
|
||||
isClueViolated = clue.isViolated
|
||||
)
|
||||
}
|
||||
LazyVerticalGrid(
|
||||
modifier = modifier,
|
||||
columns = GridCells.Fixed(4)
|
||||
) {
|
||||
for (clue in clues) {
|
||||
item {
|
||||
HorizontalClue(
|
||||
modifier = Modifier.forClue(clue),
|
||||
clue = clue.clue,
|
||||
isClueViolated = clue.isViolated
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalDivider()
|
||||
LazyVerticalGrid(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||
columns = GridCells.Fixed(8)
|
||||
) {
|
||||
for (clue in verticalClues) {
|
||||
item {
|
||||
VerticalClue(
|
||||
modifier = Modifier
|
||||
.forClue(clue)
|
||||
.aspectRatio(0.33333334f),
|
||||
clue = clue.clue,
|
||||
isClueViolated = clue.isViolated
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VerticalClues(
|
||||
modifier: Modifier = Modifier,
|
||||
clues: List<DisplayClue<SameColumnClue<ItemClass<*>, ItemClass<*>>>>
|
||||
) {
|
||||
LazyVerticalGrid(
|
||||
modifier = modifier,
|
||||
columns = GridCells.Fixed(8)
|
||||
) {
|
||||
for (clue in clues) {
|
||||
item {
|
||||
VerticalClue(
|
||||
modifier = Modifier.forClue(clue),
|
||||
clue = clue.clue,
|
||||
isClueViolated = clue.isViolated
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package ch.dissem.yaep.ui.common
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.Constraints.Companion.fitPrioritizingWidth
|
||||
import androidx.compose.ui.unit.Constraints.Companion.fixed
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ch.dissem.yaep.ui.common.AspectRatio.LANDSCAPE
|
||||
import ch.dissem.yaep.ui.common.AspectRatio.PORTRAIT
|
||||
import ch.dissem.yaep.ui.common.AspectRatio.SQUARISH
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
internal val spacing = 8.dp
|
||||
|
||||
private enum class AspectRatio {
|
||||
PORTRAIT, LANDSCAPE, SQUARISH;
|
||||
|
||||
companion object {
|
||||
private const val landscapeRatio = 1.4f
|
||||
private const val portraitRatio = 1 / landscapeRatio
|
||||
|
||||
fun from(constraints: Constraints): AspectRatio {
|
||||
val ratio = constraints.maxWidth.toFloat() / constraints.maxHeight.toFloat()
|
||||
return when {
|
||||
ratio < portraitRatio -> PORTRAIT
|
||||
ratio > landscapeRatio -> LANDSCAPE
|
||||
else -> SQUARISH
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AdaptiveGameLayout(
|
||||
modifier: Modifier = Modifier,
|
||||
grid: @Composable () -> Unit,
|
||||
horizontalClues: @Composable () -> Unit,
|
||||
verticalClues: @Composable () -> Unit,
|
||||
time: @Composable () -> Unit
|
||||
) {
|
||||
Layout(
|
||||
contents = listOf(grid, horizontalClues, verticalClues, time),
|
||||
modifier = modifier
|
||||
) { measurables, constraints ->
|
||||
layout(width = constraints.maxWidth, height = constraints.maxHeight) {
|
||||
val aspectRatio = AspectRatio.from(constraints)
|
||||
|
||||
val gridConstraints: Constraints
|
||||
val horizontalCluesConstraints: Constraints
|
||||
val verticalCluesConstraints: Constraints
|
||||
val timeConstraints: Constraints
|
||||
val spacingPx = spacing.roundToPx()
|
||||
when (aspectRatio) {
|
||||
PORTRAIT -> {
|
||||
val gridSize = constraints.maxWidth
|
||||
val bottomBarHeight = constraints.maxHeight - gridSize - spacingPx
|
||||
val timeWidth = constraints.maxWidth / 10
|
||||
|
||||
gridConstraints = fixed(gridSize, gridSize)
|
||||
horizontalCluesConstraints = fixed(gridSize, bottomBarHeight / 2)
|
||||
verticalCluesConstraints =
|
||||
fixed(gridSize - timeWidth - spacingPx, bottomBarHeight / 2)
|
||||
timeConstraints = fixed(timeWidth, bottomBarHeight / 2)
|
||||
}
|
||||
|
||||
SQUARISH -> {
|
||||
val gridSize = (8 * min(constraints.maxWidth, constraints.maxHeight)) / 10
|
||||
val rightBarWidth = constraints.maxWidth - gridSize - spacingPx
|
||||
val bottomBarHeight = constraints.maxHeight - gridSize - spacingPx
|
||||
|
||||
gridConstraints = fixed(gridSize, gridSize)
|
||||
horizontalCluesConstraints = fixed(rightBarWidth, gridSize)
|
||||
verticalCluesConstraints = fixed(gridSize, bottomBarHeight)
|
||||
timeConstraints = fixed(rightBarWidth, bottomBarHeight)
|
||||
}
|
||||
|
||||
LANDSCAPE -> {
|
||||
val gridSize = constraints.maxHeight
|
||||
val rightBarWidth = constraints.maxWidth - gridSize - spacingPx
|
||||
|
||||
gridConstraints = fixed(gridSize, gridSize)
|
||||
val baseSpace = gridSize - 2 * spacingPx
|
||||
horizontalCluesConstraints = fitPrioritizingWidth(rightBarWidth, rightBarWidth, 0, baseSpace)
|
||||
verticalCluesConstraints = fitPrioritizingWidth(rightBarWidth, rightBarWidth, 0, baseSpace)
|
||||
timeConstraints = Constraints.fixedHeight(baseSpace / 10)
|
||||
}
|
||||
}
|
||||
|
||||
// The grid takes up most of the space
|
||||
// Place the clues to the right for landscape and below for portrait. If the aspect ratio is close to 1, we place the horizontal clues to the right and the vertical clues to the left.
|
||||
// The time is placed at the bottom right corner.
|
||||
val gridPlaceable = measurables[0][0].measure(gridConstraints)
|
||||
val horizontalCluesPlaceable =
|
||||
measurables[1][0].measure(horizontalCluesConstraints)
|
||||
val verticalCluesPlaceable = measurables[2][0].measure(verticalCluesConstraints)
|
||||
val timePlaceable = measurables[3][0].apply {}.measure(timeConstraints)
|
||||
|
||||
// Position the grid
|
||||
gridPlaceable.place(0, 0)
|
||||
|
||||
// Position the horizontal clues
|
||||
when (aspectRatio) {
|
||||
PORTRAIT -> {
|
||||
horizontalCluesPlaceable.place(0, gridPlaceable.height + spacingPx)
|
||||
}
|
||||
|
||||
SQUARISH, LANDSCAPE -> {
|
||||
horizontalCluesPlaceable.place(gridPlaceable.width + spacingPx, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Position the vertical clues
|
||||
when (aspectRatio) {
|
||||
PORTRAIT -> {
|
||||
verticalCluesPlaceable.place(
|
||||
0,
|
||||
gridPlaceable.height + horizontalCluesPlaceable.height + 2 * spacingPx
|
||||
)
|
||||
}
|
||||
|
||||
SQUARISH -> {
|
||||
verticalCluesPlaceable.place(0, gridPlaceable.height + spacingPx)
|
||||
}
|
||||
|
||||
LANDSCAPE -> {
|
||||
verticalCluesPlaceable.place(
|
||||
gridPlaceable.width + spacingPx,
|
||||
horizontalCluesPlaceable.height + spacingPx
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Position the time
|
||||
timePlaceable.place(
|
||||
x = constraints.maxWidth - timePlaceable.width - spacingPx,
|
||||
y = constraints.maxHeight - timePlaceable.height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user