Adaptive Layout (WIP)
This commit is contained in:
@@ -116,10 +116,6 @@ fun App(
|
||||
modifier = Modifier.blurOnFinished(isSolved),
|
||||
grid = {
|
||||
PuzzleGrid(
|
||||
// modifier = Modifier
|
||||
// .aspectRatio(1f)
|
||||
// .fillMaxWidth()
|
||||
// .align(Alignment.CenterHorizontally),
|
||||
grid = game.grid,
|
||||
onUpdate = {
|
||||
horizontalClues.forEach { it.update(game.grid) }
|
||||
@@ -128,19 +124,29 @@ fun App(
|
||||
)
|
||||
},
|
||||
horizontalClues = {
|
||||
HorizontalClues(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||
clues = horizontalClues
|
||||
)
|
||||
for (clue in horizontalClues) {
|
||||
HorizontalClue(
|
||||
modifier = Modifier.forClue(clue),
|
||||
clue = clue.clue,
|
||||
isClueViolated = clue.isViolated
|
||||
)
|
||||
}
|
||||
},
|
||||
verticalClues = {
|
||||
VerticalClues(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||
clues = verticalClues
|
||||
)
|
||||
for (clue in verticalClues) {
|
||||
VerticalClue(
|
||||
modifier = Modifier.forClue(clue),
|
||||
clue = clue.clue,
|
||||
isClueViolated = clue.isViolated
|
||||
)
|
||||
}
|
||||
},
|
||||
time = {
|
||||
Text(time, fontSize = TextUnit(4f, TextUnitType.Companion.Em), textAlign = TextAlign.End)
|
||||
Text(
|
||||
time,
|
||||
fontSize = TextUnit(4f, TextUnitType.Companion.Em),
|
||||
textAlign = TextAlign.End
|
||||
)
|
||||
}
|
||||
)
|
||||
EndOfGame(isSolved = isSolved, time = time, onRestart = onNewGame)
|
||||
|
||||
@@ -3,13 +3,15 @@ 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.layout.Measurable
|
||||
import androidx.compose.ui.layout.Placeable
|
||||
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.max
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
@@ -48,98 +50,222 @@ fun AdaptiveGameLayout(
|
||||
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 gridMeasurable = measurables[0][0]
|
||||
val horizontalCluesMeasurables = measurables[1]
|
||||
val verticalCluesMeasurables = measurables[2]
|
||||
val timeMeasurable = measurables[3][0]
|
||||
|
||||
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
|
||||
portrait(
|
||||
constraints,
|
||||
spacingPx,
|
||||
gridMeasurable,
|
||||
horizontalCluesMeasurables,
|
||||
verticalCluesMeasurables,
|
||||
timeMeasurable
|
||||
)
|
||||
}
|
||||
|
||||
SQUARISH -> {
|
||||
verticalCluesPlaceable.place(0, gridPlaceable.height + spacingPx)
|
||||
squarish(
|
||||
constraints,
|
||||
spacingPx,
|
||||
gridMeasurable,
|
||||
horizontalCluesMeasurables,
|
||||
verticalCluesMeasurables,
|
||||
timeMeasurable
|
||||
)
|
||||
}
|
||||
|
||||
LANDSCAPE -> {
|
||||
verticalCluesPlaceable.place(
|
||||
gridPlaceable.width + spacingPx,
|
||||
horizontalCluesPlaceable.height + spacingPx
|
||||
landscape(
|
||||
constraints,
|
||||
spacingPx,
|
||||
gridMeasurable,
|
||||
horizontalCluesMeasurables,
|
||||
verticalCluesMeasurables,
|
||||
timeMeasurable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Position the time
|
||||
timePlaceable.place(
|
||||
x = constraints.maxWidth - timePlaceable.width - spacingPx,
|
||||
y = constraints.maxHeight - timePlaceable.height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun Placeable.PlacementScope.portrait(
|
||||
constraints: Constraints,
|
||||
spacingPx: Int,
|
||||
gridMeasurable: Measurable,
|
||||
horizontalCluesMeasurables: List<Measurable>,
|
||||
verticalCluesMeasurables: List<Measurable>,
|
||||
timeMeasurable: Measurable
|
||||
) {
|
||||
val gridSize = constraints.maxWidth
|
||||
val bottomBarHeight = constraints.maxHeight - gridSize - spacingPx
|
||||
val timeWidth = constraints.maxWidth / 10
|
||||
|
||||
val gridConstraints = fixed(gridSize, gridSize)
|
||||
val horizontalCluesConstraints = fixed(gridSize, bottomBarHeight / 2)
|
||||
val verticalCluesConstraints =
|
||||
fixed(gridSize - timeWidth - spacingPx, bottomBarHeight / 2)
|
||||
val timeConstraints = fixed(timeWidth, bottomBarHeight / 2)
|
||||
|
||||
val gridPlaceable = gridMeasurable.measure(gridConstraints)
|
||||
val horizontalCluesPlaceables = horizontalCluesMeasurables.map {
|
||||
it.measure(horizontalCluesConstraints)
|
||||
}
|
||||
val verticalCluesPlaceables = verticalCluesMeasurables.map {
|
||||
it.measure(verticalCluesConstraints)
|
||||
}
|
||||
val timePlaceable = timeMeasurable.measure(timeConstraints)
|
||||
|
||||
// Position the grid
|
||||
gridPlaceable.place(0, 0)
|
||||
|
||||
// Position the horizontal clues
|
||||
// TODO horizontalCluesPlaceable.place(0, gridPlaceable.height + spacingPx)
|
||||
|
||||
// Position the vertical clues
|
||||
// TODO verticalCluesPlaceable.place(0, gridPlaceable.height + horizontalCluesPlaceable.height + 2 * spacingPx)
|
||||
|
||||
// Position the time
|
||||
timePlaceable.place(
|
||||
x = constraints.maxWidth - timePlaceable.width - spacingPx,
|
||||
y = constraints.maxHeight - timePlaceable.height
|
||||
)
|
||||
}
|
||||
|
||||
private fun Placeable.PlacementScope.squarish(
|
||||
constraints: Constraints,
|
||||
spacingPx: Int,
|
||||
gridMeasurable: Measurable,
|
||||
horizontalCluesMeasurables: List<Measurable>,
|
||||
verticalCluesMeasurables: List<Measurable>,
|
||||
timeMeasurable: Measurable
|
||||
) {
|
||||
val gridSize = (8 * min(constraints.maxWidth, constraints.maxHeight)) / 10
|
||||
val rightBarWidth = constraints.maxWidth - gridSize - spacingPx
|
||||
val bottomBarHeight = constraints.maxHeight - gridSize - spacingPx
|
||||
|
||||
val gridConstraints = fixed(gridSize, gridSize)
|
||||
val horizontalCluesConstraints = fixed(rightBarWidth, gridSize)
|
||||
val verticalCluesConstraints = fixed(gridSize, bottomBarHeight)
|
||||
val timeConstraints = fixed(rightBarWidth, bottomBarHeight)
|
||||
|
||||
val gridPlaceable = gridMeasurable.measure(gridConstraints)
|
||||
val horizontalCluesPlaceables = horizontalCluesMeasurables.map {
|
||||
it.measure(horizontalCluesConstraints)
|
||||
}
|
||||
val verticalCluesPlaceables = verticalCluesMeasurables.map {
|
||||
it.measure(verticalCluesConstraints)
|
||||
}
|
||||
val timePlaceable = timeMeasurable.measure(timeConstraints)
|
||||
|
||||
// Position the grid
|
||||
gridPlaceable.place(0, 0)
|
||||
|
||||
// Position the horizontal clues
|
||||
// TODO horizontalCluesPlaceable.place(gridPlaceable.width + spacingPx, 0)
|
||||
|
||||
// Position the vertical clues
|
||||
// TODO verticalCluesPlaceable.place(0, gridPlaceable.height + spacingPx)
|
||||
|
||||
// Position the time
|
||||
timePlaceable.place(
|
||||
x = constraints.maxWidth - timePlaceable.width - spacingPx,
|
||||
y = constraints.maxHeight - timePlaceable.height
|
||||
)
|
||||
}
|
||||
|
||||
private fun Placeable.PlacementScope.landscape(
|
||||
constraints: Constraints,
|
||||
spacingPx: Int,
|
||||
gridMeasurable: Measurable,
|
||||
horizontalCluesMeasurables: List<Measurable>,
|
||||
verticalCluesMeasurables: List<Measurable>,
|
||||
timeMeasurable: Measurable
|
||||
) {
|
||||
val gridSize = constraints.maxHeight
|
||||
val gridItemSize = (gridSize - 12 * spacingPx) / 18
|
||||
val rightBarWidth = constraints.maxWidth - gridSize - spacingPx
|
||||
|
||||
val gridConstraints = fixed(gridSize, gridSize)
|
||||
val baseSpace = gridSize - 2 * spacingPx
|
||||
val horizontalCluesConstraints = fixed(
|
||||
width = 3 * gridItemSize + 2 * spacingPx,
|
||||
height = gridItemSize + 2 * spacingPx
|
||||
)
|
||||
val verticalCluesConstraints = fixed(
|
||||
width = gridItemSize + 2 * spacingPx,
|
||||
height = 3 * gridItemSize + 2 * spacingPx
|
||||
)
|
||||
val timeConstraints = Constraints.fixedHeight(baseSpace / 10)
|
||||
|
||||
val gridPlaceable = gridMeasurable.measure(gridConstraints)
|
||||
val horizontalCluesPlaceables = horizontalCluesMeasurables.map {
|
||||
it.measure(horizontalCluesConstraints)
|
||||
}
|
||||
val verticalCluesPlaceables = verticalCluesMeasurables.map {
|
||||
it.measure(verticalCluesConstraints)
|
||||
}
|
||||
val timePlaceable = timeMeasurable.measure(timeConstraints)
|
||||
|
||||
// Position the grid
|
||||
gridPlaceable.place(0, 0)
|
||||
|
||||
// Position the horizontal clues
|
||||
val offsetY = placeClues(
|
||||
placeables = horizontalCluesPlaceables,
|
||||
offsetX = gridPlaceable.width + spacingPx,
|
||||
offsetY = 0,
|
||||
maxWidth = rightBarWidth
|
||||
)
|
||||
|
||||
// TODO: add spacer in between
|
||||
|
||||
// Position the vertical clues
|
||||
placeClues(
|
||||
placeables = verticalCluesPlaceables,
|
||||
offsetX = gridPlaceable.width + spacingPx,
|
||||
offsetY = offsetY + spacingPx,
|
||||
maxWidth = rightBarWidth
|
||||
)
|
||||
|
||||
// Position the time
|
||||
timePlaceable.place(
|
||||
x = constraints.maxWidth - timePlaceable.width - spacingPx,
|
||||
y = constraints.maxHeight - timePlaceable.height
|
||||
)
|
||||
}
|
||||
|
||||
private fun Placeable.PlacementScope.placeClues(
|
||||
placeables: List<Placeable>,
|
||||
offsetX: Int,
|
||||
offsetY: Int,
|
||||
maxWidth: Int,
|
||||
minSpacing: Int = 0
|
||||
): Int {
|
||||
if (placeables.isEmpty()) return offsetY
|
||||
|
||||
val itemWidth = placeables.first().width
|
||||
val itemHeight = placeables.first().height
|
||||
val columns = max(1, maxWidth / itemWidth)
|
||||
val spacing = max(minSpacing, (maxWidth - columns * itemWidth) / (columns - 1))
|
||||
var currentX = offsetX
|
||||
var currentY = offsetY
|
||||
var i = 0
|
||||
for (placeable in placeables) {
|
||||
placeable.place(currentX, currentY)
|
||||
currentX += itemWidth + spacing
|
||||
i++
|
||||
if (i % columns == 0) {
|
||||
currentX = offsetX
|
||||
currentY += itemHeight
|
||||
}
|
||||
}
|
||||
return currentY + itemHeight
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user