diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt index a5986934..5020f5c3 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt @@ -1,11 +1,26 @@ package de.mm20.launcher2.ui.component import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material3.LocalAbsoluteTonalElevation +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import de.mm20.launcher2.ui.locals.LocalCardStyle @@ -29,4 +44,149 @@ fun LauncherCard( shadowElevation = if (backgroundOpacity == 1f) elevation else 0.dp, tonalElevation = elevation ) +} + +@Composable +fun PartialLauncherCard( + modifier: Modifier = Modifier, + isTop: Boolean = false, + isBottom: Boolean = false, + elevation: Dp = 2.dp, + backgroundOpacity: Float = LocalCardStyle.current.opacity, + content: @Composable () -> Unit +) { + + if (isTop && isBottom) { + LauncherCard(modifier = modifier, content = content) + } else if (!isTop && !isBottom) { + CardMiddlePiece(modifier = modifier, elevation = elevation, content = content) + } else { + CardEndPiece( + modifier = modifier, + isTop = isTop, + isBottom = isBottom, + elevation = elevation, + backgroundOpacity = backgroundOpacity, + content = content + ) + } +} + +@Composable +private fun CardMiddlePiece( + modifier: Modifier, + elevation: Dp, + backgroundOpacity: Float = LocalCardStyle.current.opacity, + content: @Composable () -> Unit +) { + val borderWidth = LocalCardStyle.current.borderWidth.dp + val borderColor = MaterialTheme.colorScheme.surface + + val absoluteElevation = LocalAbsoluteTonalElevation.current + elevation + Box( + modifier = modifier + .shadow(elevation, RectangleShape, true) + .background( + if (backgroundOpacity == 1f) { + MaterialTheme.colorScheme.surfaceColorAtElevation(absoluteElevation) + } else { + MaterialTheme.colorScheme.surface.copy( + alpha = backgroundOpacity.coerceIn( + 0f, + 1f + ) + ) + } + ) + .drawBehind { + if (borderWidth == 0.dp) return@drawBehind + val border = borderWidth.toPx() + drawRect( + color = borderColor, + topLeft = Offset.Zero, + size = size.copy(width = border) + ) + drawRect( + color = borderColor, + topLeft = Offset(size.width - border, 0f), + size = size.copy(width = border) + ) + }, + ) { + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colorScheme.onSurface, + LocalAbsoluteTonalElevation provides absoluteElevation, + ) { + content() + } + } +} + +@Composable +private fun CardEndPiece( + modifier: Modifier = Modifier, + isTop: Boolean, + isBottom: Boolean, + elevation: Dp, + backgroundOpacity: Float, + content: @Composable () -> Unit, +) { + val shape = when { + isTop -> MaterialTheme.shapes.medium.copy( + bottomEnd = CornerSize(0), + bottomStart = CornerSize(0), + ) + isBottom -> MaterialTheme.shapes.medium.copy( + topEnd = CornerSize(0), + topStart = CornerSize(0), + ) + else -> RectangleShape + } + + val borderWidth = LocalCardStyle.current.borderWidth.dp + val borderColor = MaterialTheme.colorScheme.surface + + val absoluteElevation = LocalAbsoluteTonalElevation.current + elevation + Box( + modifier = modifier + .shadow(elevation, shape, true) + .background( + if (backgroundOpacity == 1f) { + MaterialTheme.colorScheme.surfaceColorAtElevation(absoluteElevation) + } else { + MaterialTheme.colorScheme.surface.copy( + alpha = backgroundOpacity.coerceIn( + 0f, + 1f + ) + ) + } + ) + .drawWithCache { + val border = borderWidth.toPx() + val outline = shape.createOutline( + size.copy(height = size.height + border), + layoutDirection, + Density(density, fontScale) + ) + onDrawBehind { + withTransform({ + translate(0f, if (isBottom) -border else 0f) + }) { + drawOutline( + outline, + borderColor, + style = Stroke(width = border * 2) + ) + } + } + }, + ) { + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colorScheme.onSurface, + LocalAbsoluteTonalElevation provides absoluteElevation, + ) { + content() + } + } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt index 11d097b1..e5dab99a 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.ui.component.LauncherCard +import de.mm20.launcher2.ui.component.PartialLauncherCard import de.mm20.launcher2.ui.launcher.search.calculator.CalculatorItem import de.mm20.launcher2.ui.launcher.search.common.grid.GridItem import de.mm20.launcher2.ui.launcher.search.common.list.ListItem @@ -127,25 +128,15 @@ fun GridRow( modifier = modifier .clipToBounds() ) { - LauncherCard( + PartialLauncherCard( modifier = Modifier.padding( start = 8.dp, end = 8.dp, top = if (isFirst) 4.dp else 0.dp, bottom = if (isLast) 4.dp else 0.dp, ), - shape = when { - isFirst && isLast -> MaterialTheme.shapes.medium - isFirst -> MaterialTheme.shapes.medium.copy( - bottomEnd = CornerSize(0), - bottomStart = CornerSize(0), - ) - isLast -> MaterialTheme.shapes.medium.copy( - topEnd = CornerSize(0), - topStart = CornerSize(0), - ) - else -> RectangleShape - } + isTop = isFirst, + isBottom = isLast, ) { Row { for (item in items) { @@ -190,25 +181,15 @@ fun ListRow( modifier = modifier .clipToBounds() ) { - LauncherCard( + PartialLauncherCard( modifier = Modifier.padding( start = 8.dp, end = 8.dp, top = if (isFirst) 4.dp else 0.dp, bottom = if (isLast) 4.dp else 0.dp, ), - shape = when { - isFirst && isLast -> MaterialTheme.shapes.medium - isFirst -> MaterialTheme.shapes.medium.copy( - bottomEnd = CornerSize(0), - bottomStart = CornerSize(0), - ) - isLast -> MaterialTheme.shapes.medium.copy( - topEnd = CornerSize(0), - topStart = CornerSize(0), - ) - else -> RectangleShape - } + isTop = isFirst, + isBottom = isLast, ) { Box( modifier = Modifier.padding(