Adaptive Layout (WIP)
This commit is contained in:
@@ -50,9 +50,6 @@ android {
|
||||
merges += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = libs.versions.jdk.get()
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
|
||||
@@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.aspectRatio
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
@@ -33,7 +31,6 @@ import ch.dissem.yaep.domain.Clue
|
||||
import ch.dissem.yaep.domain.Game
|
||||
import ch.dissem.yaep.domain.Grid
|
||||
import ch.dissem.yaep.domain.HorizontalClue
|
||||
import ch.dissem.yaep.domain.ItemClass
|
||||
import ch.dissem.yaep.domain.NeighbourClue
|
||||
import ch.dissem.yaep.domain.OrderClue
|
||||
import ch.dissem.yaep.domain.SameColumnClue
|
||||
@@ -216,48 +213,6 @@ fun PuzzleGrid(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HorizontalClues(
|
||||
modifier: Modifier = Modifier,
|
||||
clues: List<DisplayClue<HorizontalClue>>
|
||||
) {
|
||||
LazyVerticalGrid(
|
||||
modifier = modifier,
|
||||
columns = GridCells.Fixed(4)
|
||||
) {
|
||||
for (clue in clues) {
|
||||
item {
|
||||
HorizontalClue(
|
||||
modifier = Modifier.forClue(clue),
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.forClue(clue: DisplayClue<out Clue>): Modifier = this
|
||||
.alpha(if (clue.isActive) 1f else 0.2f)
|
||||
.padding(8.dp)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ch.dissem.yaep.ui.common
|
||||
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.Layout
|
||||
@@ -7,6 +8,7 @@ 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.fixed
|
||||
import androidx.compose.ui.unit.Constraints.Companion.fixedWidth
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ch.dissem.yaep.ui.common.AspectRatio.LANDSCAPE
|
||||
import ch.dissem.yaep.ui.common.AspectRatio.PORTRAIT
|
||||
@@ -41,10 +43,11 @@ fun AdaptiveGameLayout(
|
||||
grid: @Composable () -> Unit,
|
||||
horizontalClues: @Composable () -> Unit,
|
||||
verticalClues: @Composable () -> Unit,
|
||||
time: @Composable () -> Unit
|
||||
time: @Composable () -> Unit,
|
||||
divider: @Composable () -> Unit = { HorizontalDivider() }
|
||||
) {
|
||||
Layout(
|
||||
contents = listOf(grid, horizontalClues, verticalClues, time),
|
||||
contents = listOf(grid, horizontalClues, verticalClues, time, divider),
|
||||
modifier = modifier
|
||||
) { measurables, constraints ->
|
||||
layout(width = constraints.maxWidth, height = constraints.maxHeight) {
|
||||
@@ -54,6 +57,7 @@ fun AdaptiveGameLayout(
|
||||
val horizontalCluesMeasurables = measurables[1]
|
||||
val verticalCluesMeasurables = measurables[2]
|
||||
val timeMeasurable = measurables[3][0]
|
||||
val dividerMeasurable = measurables[4][0]
|
||||
|
||||
val spacingPx = spacing.roundToPx()
|
||||
|
||||
@@ -65,7 +69,8 @@ fun AdaptiveGameLayout(
|
||||
gridMeasurable,
|
||||
horizontalCluesMeasurables,
|
||||
verticalCluesMeasurables,
|
||||
timeMeasurable
|
||||
timeMeasurable,
|
||||
dividerMeasurable
|
||||
)
|
||||
}
|
||||
|
||||
@@ -87,7 +92,8 @@ fun AdaptiveGameLayout(
|
||||
gridMeasurable,
|
||||
horizontalCluesMeasurables,
|
||||
verticalCluesMeasurables,
|
||||
timeMeasurable
|
||||
timeMeasurable,
|
||||
dividerMeasurable
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -101,7 +107,8 @@ private fun Placeable.PlacementScope.portrait(
|
||||
gridMeasurable: Measurable,
|
||||
horizontalCluesMeasurables: List<Measurable>,
|
||||
verticalCluesMeasurables: List<Measurable>,
|
||||
timeMeasurable: Measurable
|
||||
timeMeasurable: Measurable,
|
||||
dividerMeasurable: Measurable
|
||||
) {
|
||||
val gridSize = constraints.maxWidth
|
||||
val bottomBarHeight = constraints.maxHeight - gridSize - spacingPx
|
||||
@@ -112,6 +119,7 @@ private fun Placeable.PlacementScope.portrait(
|
||||
val verticalCluesConstraints =
|
||||
fixed(gridSize - timeWidth - spacingPx, bottomBarHeight / 2)
|
||||
val timeConstraints = fixed(timeWidth, bottomBarHeight / 2)
|
||||
val dividerConstraints = fixedWidth(gridSize)
|
||||
|
||||
val gridPlaceable = gridMeasurable.measure(gridConstraints)
|
||||
val horizontalCluesPlaceables = horizontalCluesMeasurables.map {
|
||||
@@ -121,15 +129,29 @@ private fun Placeable.PlacementScope.portrait(
|
||||
it.measure(verticalCluesConstraints)
|
||||
}
|
||||
val timePlaceable = timeMeasurable.measure(timeConstraints)
|
||||
val dividerPlaceable = dividerMeasurable.measure(dividerConstraints)
|
||||
|
||||
// Position the grid
|
||||
gridPlaceable.place(0, 0)
|
||||
|
||||
// Position the horizontal clues
|
||||
// TODO horizontalCluesPlaceable.place(0, gridPlaceable.height + spacingPx)
|
||||
val offsetY = placeClues(
|
||||
placeables = horizontalCluesPlaceables,
|
||||
offsetX = 0,
|
||||
offsetY = gridSize + 2 * spacingPx,
|
||||
maxWidth = gridSize
|
||||
)
|
||||
|
||||
// Add divider in between
|
||||
dividerPlaceable.place(gridSize + 3 * spacingPx, offsetY + spacingPx)
|
||||
|
||||
// Position the vertical clues
|
||||
// TODO verticalCluesPlaceable.place(0, gridPlaceable.height + horizontalCluesPlaceable.height + 2 * spacingPx)
|
||||
placeClues(
|
||||
placeables = verticalCluesPlaceables,
|
||||
offsetX = 0,
|
||||
offsetY = offsetY + 2 * spacingPx + dividerPlaceable.height,
|
||||
maxWidth = gridSize
|
||||
)
|
||||
|
||||
// Position the time
|
||||
timePlaceable.place(
|
||||
@@ -146,14 +168,20 @@ private fun Placeable.PlacementScope.squarish(
|
||||
verticalCluesMeasurables: List<Measurable>,
|
||||
timeMeasurable: Measurable
|
||||
) {
|
||||
val gridSize = (8 * min(constraints.maxWidth, constraints.maxHeight)) / 10
|
||||
val gridSize = (7 * min(constraints.maxWidth, constraints.maxHeight)) / 10
|
||||
val gridItemSize = (gridSize - 12 * spacingPx) / 18
|
||||
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 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()
|
||||
|
||||
val gridPlaceable = gridMeasurable.measure(gridConstraints)
|
||||
val horizontalCluesPlaceables = horizontalCluesMeasurables.map {
|
||||
@@ -168,10 +196,20 @@ private fun Placeable.PlacementScope.squarish(
|
||||
gridPlaceable.place(0, 0)
|
||||
|
||||
// Position the horizontal clues
|
||||
// TODO horizontalCluesPlaceable.place(gridPlaceable.width + spacingPx, 0)
|
||||
placeClues(
|
||||
placeables = horizontalCluesPlaceables,
|
||||
offsetX = gridSize + 2 * spacingPx,
|
||||
offsetY = 0,
|
||||
maxWidth = rightBarWidth
|
||||
)
|
||||
|
||||
// Position the vertical clues
|
||||
// TODO verticalCluesPlaceable.place(0, gridPlaceable.height + spacingPx)
|
||||
placeClues(
|
||||
placeables = verticalCluesPlaceables,
|
||||
offsetX = 0,
|
||||
offsetY = gridSize + 2 * spacingPx,
|
||||
maxWidth = gridSize
|
||||
)
|
||||
|
||||
// Position the time
|
||||
timePlaceable.place(
|
||||
@@ -186,11 +224,12 @@ private fun Placeable.PlacementScope.landscape(
|
||||
gridMeasurable: Measurable,
|
||||
horizontalCluesMeasurables: List<Measurable>,
|
||||
verticalCluesMeasurables: List<Measurable>,
|
||||
timeMeasurable: Measurable
|
||||
timeMeasurable: Measurable,
|
||||
dividerMeasurable: Measurable
|
||||
) {
|
||||
val gridSize = constraints.maxHeight
|
||||
val gridItemSize = (gridSize - 12 * spacingPx) / 18
|
||||
val rightBarWidth = constraints.maxWidth - gridSize - spacingPx
|
||||
val rightBarWidth = constraints.maxWidth - gridSize - 2 * spacingPx
|
||||
|
||||
val gridConstraints = fixed(gridSize, gridSize)
|
||||
val baseSpace = gridSize - 2 * spacingPx
|
||||
@@ -203,6 +242,7 @@ private fun Placeable.PlacementScope.landscape(
|
||||
height = 3 * gridItemSize + 2 * spacingPx
|
||||
)
|
||||
val timeConstraints = Constraints.fixedHeight(baseSpace / 10)
|
||||
val dividerConstraints = fixedWidth(rightBarWidth - 2 * spacingPx)
|
||||
|
||||
val gridPlaceable = gridMeasurable.measure(gridConstraints)
|
||||
val horizontalCluesPlaceables = horizontalCluesMeasurables.map {
|
||||
@@ -212,6 +252,7 @@ private fun Placeable.PlacementScope.landscape(
|
||||
it.measure(verticalCluesConstraints)
|
||||
}
|
||||
val timePlaceable = timeMeasurable.measure(timeConstraints)
|
||||
val dividerPlaceable = dividerMeasurable.measure(dividerConstraints)
|
||||
|
||||
// Position the grid
|
||||
gridPlaceable.place(0, 0)
|
||||
@@ -219,18 +260,19 @@ private fun Placeable.PlacementScope.landscape(
|
||||
// Position the horizontal clues
|
||||
val offsetY = placeClues(
|
||||
placeables = horizontalCluesPlaceables,
|
||||
offsetX = gridPlaceable.width + spacingPx,
|
||||
offsetX = gridSize + 2 * spacingPx,
|
||||
offsetY = 0,
|
||||
maxWidth = rightBarWidth
|
||||
)
|
||||
|
||||
// TODO: add spacer in between
|
||||
// Add divider in between
|
||||
dividerPlaceable.place(gridSize + 3 * spacingPx, offsetY + spacingPx)
|
||||
|
||||
// Position the vertical clues
|
||||
placeClues(
|
||||
placeables = verticalCluesPlaceables,
|
||||
offsetX = gridPlaceable.width + spacingPx,
|
||||
offsetY = offsetY + spacingPx,
|
||||
offsetX = gridSize + 2 * spacingPx,
|
||||
offsetY = offsetY + 2 * spacingPx + dividerPlaceable.height,
|
||||
maxWidth = rightBarWidth
|
||||
)
|
||||
|
||||
@@ -253,7 +295,7 @@ private fun Placeable.PlacementScope.placeClues(
|
||||
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))
|
||||
val spacing = if (columns == 1) 0 else (maxWidth - columns * itemWidth) / (columns - 1)
|
||||
var currentX = offsetX
|
||||
var currentY = offsetY
|
||||
var i = 0
|
||||
@@ -261,7 +303,7 @@ private fun Placeable.PlacementScope.placeClues(
|
||||
placeable.place(currentX, currentY)
|
||||
currentX += itemWidth + spacing
|
||||
i++
|
||||
if (i % columns == 0) {
|
||||
if (i % columns == 0 && i < placeables.size) {
|
||||
currentX = offsetX
|
||||
currentY += itemHeight
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ kotlin {
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "MainKt"
|
||||
mainClass = "ch.dissem.yaep.ui.desktop.MainKt"
|
||||
|
||||
nativeDistributions {
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
|
||||
@@ -94,16 +94,16 @@ fun AppBar(
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
if (useDarkMode) {
|
||||
CRes.drawable.sun
|
||||
} else {
|
||||
CRes.drawable.moon
|
||||
} else {
|
||||
CRes.drawable.sun
|
||||
}
|
||||
),
|
||||
contentDescription = stringResource(
|
||||
if (useDarkMode) {
|
||||
CRes.string.use_light_mode
|
||||
} else {
|
||||
CRes.string.use_dark_mode
|
||||
} else {
|
||||
CRes.string.use_light_mode
|
||||
}
|
||||
),
|
||||
modifier = Modifier.size(SwitchDefaults.IconSize),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
package ch.dissem.yaep.ui.desktop
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -18,8 +20,6 @@ import ch.dissem.yaep.domain.Game
|
||||
import ch.dissem.yaep.domain.generateGame
|
||||
import ch.dissem.yaep.ui.common.App
|
||||
import ch.dissem.yaep.ui.common.theme.emojiFontFamily
|
||||
import ch.dissem.yaep.ui.desktop.AppBar
|
||||
import ch.dissem.yaep.ui.desktop.DesktopWindow
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import yaep.commonui.generated.resources.app_name
|
||||
@@ -27,7 +27,7 @@ import yaep.desktop.generated.resources.ic_launcher
|
||||
import yaep.commonui.generated.resources.Res as CRes
|
||||
import yaep.desktop.generated.resources.Res as DRes
|
||||
|
||||
fun main() = application {
|
||||
fun main(): Unit = application {
|
||||
emojiFontFamily = FontFamily(
|
||||
Font(
|
||||
resource = "NotoColorEmoji-Regular.ttf",
|
||||
@@ -37,7 +37,7 @@ fun main() = application {
|
||||
)
|
||||
val windowState = rememberWindowState(
|
||||
placement = WindowPlacement.Floating,
|
||||
size = DpSize(1200.dp, 800.dp)
|
||||
size = DpSize(1250.dp, 800.dp)
|
||||
)
|
||||
var game by remember { mutableStateOf<Game>(generateGame()) }
|
||||
|
||||
@@ -58,7 +58,8 @@ fun main() = application {
|
||||
setDarkMode = { useDarkMode = it },
|
||||
onCloseRequest = ::exitApplication,
|
||||
onRestart = {
|
||||
do while (game.grid.undo());
|
||||
@Suppress("ControlFlowWithEmptyBody")
|
||||
do /* nothing */ while (game.grid.undo())
|
||||
resetCluesBeacon = Any()
|
||||
},
|
||||
windowState = windowState,
|
||||
|
||||
Reference in New Issue
Block a user