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