mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
make the gradient endless and darken it with scroll
This commit is contained in:
@@ -58,6 +58,7 @@ import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.layout.positionInParent
|
||||
@@ -79,6 +80,7 @@ import com.subtracks.ui.theme.ArtworkColors
|
||||
import com.subtracks.ui.theme.ArtworkTheme
|
||||
import com.subtracks.ui.theme.HeroGradient
|
||||
import com.subtracks.ui.theme.gradientColorAt
|
||||
import com.subtracks.ui.theme.heroDarken
|
||||
import com.subtracks.ui.theme.rememberArtworkColors
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
@@ -165,17 +167,18 @@ fun AlbumDetailScreen(
|
||||
label = "gradientAlpha",
|
||||
)
|
||||
val barColor =
|
||||
artwork?.gradientColorAt(((scrollPx + barHeightPx / 2f) / gradientHeightPx).coerceIn(0f, 1f))
|
||||
?: Color.Black
|
||||
artwork?.let {
|
||||
lerp(
|
||||
it.gradientColorAt((scrollPx + barHeightPx / 2f) / gradientHeightPx),
|
||||
Color.Black,
|
||||
heroDarken(scrollPx, with(density) { maxHeight.toPx() }),
|
||||
)
|
||||
} ?: Color.Black
|
||||
|
||||
HeroGradient(
|
||||
colors = artwork,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.requiredHeight(gradientHeight)
|
||||
.offset { IntOffset(0, -scrollPx.roundToInt()) }
|
||||
.alpha(gradientAlpha),
|
||||
scrollPx = { scrollPx },
|
||||
modifier = Modifier.fillMaxSize().alpha(gradientAlpha),
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.TileMode
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.palette.graphics.Palette
|
||||
@@ -31,9 +32,12 @@ import kotlin.math.abs
|
||||
import kotlin.random.Random
|
||||
|
||||
private const val SEED_ART_SIZE_PX = 128
|
||||
private const val GRADIENT_FADE_START = 1.3f / 2.0f
|
||||
private const val PERIOD_SCREENS = 2f
|
||||
private const val BLOB_ZONE = 0.62f
|
||||
private const val BLOB_COUNT = 4
|
||||
private const val ACCENT_MAX_LUMINANCE = 0.34f
|
||||
private const val HERO_DARKEN_MAX = 0.8f
|
||||
private const val HERO_DARKEN_SCREENS = 1.5f
|
||||
|
||||
data class ArtworkColors(
|
||||
val scheme: ColorScheme,
|
||||
@@ -129,10 +133,13 @@ private fun Color.withMaxLuminance(max: Float): Color {
|
||||
}
|
||||
|
||||
fun ArtworkColors.gradientColorAt(fraction: Float): Color {
|
||||
val t = fraction.coerceIn(0f, 1f)
|
||||
val base = lerp(gradientHigh, gradientLow, t)
|
||||
val fade = ((t - GRADIENT_FADE_START) / (1f - GRADIENT_FADE_START)).coerceIn(0f, 1f)
|
||||
return lerp(base, Color.Black, fade)
|
||||
if (fraction.isNaN()) return gradientHigh
|
||||
val f = fraction.mod(1f)
|
||||
return if (f < 0.5f) {
|
||||
lerp(gradientHigh, gradientLow, f * 2f)
|
||||
} else {
|
||||
lerp(gradientLow, gradientHigh, (f - 0.5f) * 2f)
|
||||
}
|
||||
}
|
||||
|
||||
private fun blendHue(
|
||||
@@ -231,6 +238,7 @@ fun ArtworkTheme(
|
||||
@Composable
|
||||
fun HeroGradient(
|
||||
colors: ArtworkColors?,
|
||||
scrollPx: () -> Float,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val high = colors?.gradientHigh ?: MaterialTheme.colorScheme.surfaceContainerHigh
|
||||
@@ -239,50 +247,49 @@ fun HeroGradient(
|
||||
val seed = colors?.blobSeed ?: 0
|
||||
|
||||
Canvas(modifier) {
|
||||
val period = size.height * PERIOD_SCREENS
|
||||
if (period <= 0f) return@Canvas
|
||||
val phase = scrollPx().mod(period)
|
||||
drawRect(
|
||||
brush =
|
||||
Brush.linearGradient(
|
||||
colors = listOf(high, low),
|
||||
start = Offset(size.width * 0.2f, 0f),
|
||||
end = Offset(size.width * 0.8f, size.height),
|
||||
Brush.verticalGradient(
|
||||
colorStops = arrayOf(0f to high, 0.5f to low, 1f to high),
|
||||
startY = -phase,
|
||||
endY = -phase + period,
|
||||
tileMode = TileMode.Repeated,
|
||||
),
|
||||
)
|
||||
if (accents.isNotEmpty()) {
|
||||
val random = Random(seed)
|
||||
val zone = size.height * BLOB_ZONE
|
||||
repeat(4) { index ->
|
||||
val center =
|
||||
Offset(
|
||||
size.width * (0.10f + random.nextFloat() * 0.80f),
|
||||
zone * random.nextFloat(),
|
||||
)
|
||||
repeat(BLOB_COUNT) { index ->
|
||||
val centerX = size.width * (0.10f + random.nextFloat() * 0.80f)
|
||||
val baseY = period * BLOB_ZONE * random.nextFloat()
|
||||
val radius = size.width * (0.45f + random.nextFloat() * 0.35f)
|
||||
val alpha = 0.50f + random.nextFloat() * 0.32f
|
||||
val accent = accents[index % accents.size]
|
||||
drawRect(
|
||||
brush =
|
||||
Brush.radialGradient(
|
||||
colors = listOf(accent.copy(alpha = alpha), accent.copy(alpha = 0f)),
|
||||
center = center,
|
||||
radius = radius,
|
||||
),
|
||||
)
|
||||
for (shift in -1..1) {
|
||||
val centerY = baseY + shift * period - phase
|
||||
if (centerY + radius < 0f || centerY - radius > size.height) continue
|
||||
drawRect(
|
||||
brush =
|
||||
Brush.radialGradient(
|
||||
colors = listOf(accent.copy(alpha = alpha), accent.copy(alpha = 0f)),
|
||||
center = Offset(centerX, centerY),
|
||||
radius = radius,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
drawRect(
|
||||
brush =
|
||||
Brush.verticalGradient(
|
||||
colorStops =
|
||||
arrayOf(
|
||||
0f to Color.Transparent,
|
||||
GRADIENT_FADE_START to Color.Transparent,
|
||||
1f to Color.Black,
|
||||
),
|
||||
),
|
||||
)
|
||||
drawRect(color = Color.Black.copy(alpha = heroDarken(scrollPx(), size.height)))
|
||||
}
|
||||
}
|
||||
|
||||
fun heroDarken(
|
||||
scrollPx: Float,
|
||||
screenHeightPx: Float,
|
||||
): Float = (scrollPx / (screenHeightPx * HERO_DARKEN_SCREENS)).coerceIn(0f, 1f) * HERO_DARKEN_MAX
|
||||
|
||||
private fun Color.toHsl(): Triple<Float, Float, Float> {
|
||||
val max = maxOf(red, green, blue)
|
||||
val min = minOf(red, green, blue)
|
||||
|
||||
Reference in New Issue
Block a user