diff --git a/commonUI/src/main/kotlin/ch/dissem/yaep/ui/common/App.kt b/commonUI/src/main/kotlin/ch/dissem/yaep/ui/common/App.kt index 6a75278..ead7de9 100644 --- a/commonUI/src/main/kotlin/ch/dissem/yaep/ui/common/App.kt +++ b/commonUI/src/main/kotlin/ch/dissem/yaep/ui/common/App.kt @@ -1,15 +1,19 @@ package ch.dissem.yaep.ui.common +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* 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.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.shadow import androidx.compose.ui.unit.dp import ch.dissem.yaep.domain.* import org.jetbrains.compose.resources.painterResource @@ -18,16 +22,31 @@ import yaep.commonui.generated.resources.neighbour import yaep.commonui.generated.resources.order class DisplayClue(val clue: C) { - var isActive by mutableStateOf(true) + var isActive: Boolean by mutableStateOf(true) + + var isViolated: Boolean by mutableStateOf(false) + + fun update(grid: Grid) { + isViolated = !clue.isValid(grid) + if (isViolated) { + isActive = true + } + } } @Composable -fun App(modifier: Modifier = Modifier) { - val game = remember { generateGame() } +fun App(modifier: Modifier = Modifier, game: Game = remember { generateGame() }) { val horizontalClues = remember { game.horizontalClues.map { DisplayClue(it) } } val verticalClues = remember { game.verticalClues.map { DisplayClue(it) } } Row(modifier = modifier) { - PuzzleGrid(modifier = Modifier.aspectRatio(1f).weight(0.6f).fillMaxHeight(), game.grid) + PuzzleGrid( + modifier = Modifier.aspectRatio(1f).weight(0.6f).fillMaxHeight(), + grid = game.grid, + onUpdate = { + horizontalClues.forEach { it.update(game.grid) } + verticalClues.forEach { it.update(game.grid) } + } + ) PuzzleClues( modifier = Modifier.padding(start = 16.dp).weight(0.4f).fillMaxHeight(), horizontalClues, @@ -39,7 +58,8 @@ fun App(modifier: Modifier = Modifier) { @Composable fun PuzzleGrid( modifier: Modifier = Modifier, - grid: Grid + grid: Grid, + onUpdate: () -> Unit ) { Column(modifier = modifier) { for (row in grid) { @@ -60,6 +80,7 @@ fun PuzzleGrid( } item.selectionChangedListeners.add { selection = it + onUpdate() } } Selector( @@ -94,10 +115,9 @@ fun PuzzleClues( item { HorizontalClue( modifier = Modifier - .alpha(if (clue.isActive) 1f else 0.2f) - .padding(8.dp) - .clickable { clue.isActive = false }, - clue = clue.clue + .forClue(clue), + clue = clue.clue, + isClueViolated = clue.isViolated ) } } @@ -111,11 +131,10 @@ fun PuzzleClues( item { VerticalClue( modifier = Modifier - .alpha(if (clue.isActive) 1f else 0.2f) - .padding(8.dp) - .aspectRatio(0.33333334f) - .clickable { clue.isActive = false }, - clue = clue.clue + .forClue(clue) + .aspectRatio(0.33333334f), + clue = clue.clue, + isClueViolated = clue.isViolated ) } } @@ -123,9 +142,17 @@ fun PuzzleClues( } } +private fun Modifier.forClue(clue: DisplayClue) = this + .alpha(if (clue.isActive) 1f else 0.2f) + .padding(8.dp) + .clickable { clue.isActive = false } + @Composable -fun HorizontalClue(modifier: Modifier = Modifier, clue: HorizontalClue) { - OutlinedCard(modifier) { +fun HorizontalClue(modifier: Modifier = Modifier, clue: HorizontalClue, isClueViolated: Boolean) { + ClueCard( + modifier = modifier, + isClueViolated = isClueViolated + ) { Row { when (clue) { is NeighbourClue<*, *> -> { @@ -159,11 +186,72 @@ fun HorizontalClue(modifier: Modifier = Modifier, clue: HorizontalClue) { } @Composable -fun VerticalClue(modifier: Modifier = Modifier, clue: SameColumnClue<*, *>) { - OutlinedCard(modifier.aspectRatio(0.5f)) { +fun VerticalClue(modifier: Modifier = Modifier, clue: SameColumnClue<*, *>, isClueViolated: Boolean = false) { + ClueCard( + modifier = modifier.aspectRatio(0.5f), + isClueViolated = isClueViolated + ) { Column { DrawItem(modifier = Modifier.weight(1f), clue.a) DrawItem(modifier = Modifier.weight(1f), clue.b) } } } + +@Composable +fun ClueCard(modifier: Modifier = Modifier, isClueViolated: Boolean, content: @Composable () -> Unit) { + val colors = MaterialTheme.colorScheme + OutlinedCard( + modifier = if (isClueViolated) { + modifier.shadow(8.dp, shape = CardDefaults.outlinedShape, ambientColor = colors.error, spotColor = colors.error) + } else { + modifier + }, + border = if (isClueViolated) { + remember { BorderStroke(1.0.dp, colors.error) } + } else { + CardDefaults.outlinedCardBorder() + } + ) { + content() + } +} + +//fun Modifier.advanceShadow( +// color: Color = Color.Black, +// borderRadius: Dp = 16.dp, +// blurRadius: Dp = 16.dp, +// offsetY: Dp = 0.dp, +// offsetX: Dp = 0.dp, +// spread: Float = 1f, +//) = drawBehind { +// this.drawIntoCanvas { +// val paint = Paint() +// val frameworkPaint = paint.asFrameworkPaint() +// val spreadPixel = spread.dp.toPx() +// val leftPixel = (0f - spreadPixel) + offsetX.toPx() +// val topPixel = (0f - spreadPixel) + offsetY.toPx() +// val rightPixel = (this.size.width) +// val bottomPixel = (this.size.height + spreadPixel) +// +// if (blurRadius != 0.dp) { +// /* +// The feature maskFilter used below to apply the blur effect only works +// with hardware acceleration disabled. +// */ +// frameworkPaint.maskFilter = +// BlurEffect(blurRadius.toPx(), blurRadius.toPx()) +// } +// +// frameworkPaint.color = color.toArgb() +// it.drawRoundRect( +// left = leftPixel, +// top = topPixel, +// right = rightPixel, +// bottom = bottomPixel, +// radiusX = borderRadius.toPx(), +// radiusY = borderRadius.toPx(), +// paint +// ) +// } +//}