Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Box(
.snowfall(
color = Color.White,
alpha = 0.3f,
fadeThreshold = 0.6f,
fadeThresholdSpread = 0.3f,
strokeWidth = 1f,
drawPosition = SnowfallDrawPosition.Ahead,
snowflakeMinSize = 10.dp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import io.github.skeptick.snowfall.compose.snowfall
fun RootView() {
var color by remember { mutableStateOf(Color.White) }
var alpha by remember { mutableFloatStateOf(0.5f) }
var fadeThreshold by remember { mutableFloatStateOf(0.6f) }
var fadeThresholdSpread by remember { mutableFloatStateOf(0.3f) }
var strokeWidth by remember { mutableFloatStateOf(1f) }
var drawPosition by remember { mutableStateOf(SnowfallDrawPosition.Ahead) }
var snowflakeMinSize by remember { mutableStateOf(10.dp) }
Expand All @@ -63,6 +65,8 @@ fun RootView() {
modifier = modifier,
color = color,
alpha = alpha,
fadeThreshold = fadeThreshold,
fadeThresholdSpread = fadeThresholdSpread,
strokeWidth = strokeWidth,
drawPosition = drawPosition,
snowflakeMinSize = snowflakeMinSize,
Expand All @@ -80,6 +84,8 @@ fun RootView() {
modifier = modifier,
color = color,
alpha = alpha,
fadeThreshold = fadeThreshold,
fadeThresholdSpread = fadeThresholdSpread,
strokeWidth = strokeWidth,
drawPosition = drawPosition,
snowflakeMinSize = snowflakeMinSize,
Expand All @@ -89,6 +95,8 @@ fun RootView() {
snowflakeDensity = snowflakeDensity,
onColorChange = { color = it },
onAlphaChange = { alpha = it },
onFadeThresholdChange = { fadeThreshold = it },
onFadeThresholdSpreadChange = { fadeThresholdSpread = it },
onStrokeChange = { strokeWidth = it },
onDrawPositionChange = { drawPosition = it },
onSnowflakeSizeChange = { min, max ->
Expand Down Expand Up @@ -133,6 +141,8 @@ fun RootView() {
private fun Preview(
color: Color,
alpha: Float,
fadeThreshold: Float,
fadeThresholdSpread: Float,
strokeWidth: Float,
drawPosition: SnowfallDrawPosition,
snowflakeMinSize: Dp,
Expand All @@ -148,6 +158,8 @@ private fun Preview(
.snowfall(
color = color,
alpha = alpha,
fadeThreshold = fadeThreshold,
fadeThresholdSpread = fadeThresholdSpread,
strokeWidth = strokeWidth,
drawPosition = drawPosition,
snowflakeMinSize = snowflakeMinSize,
Expand All @@ -172,6 +184,8 @@ private fun Preview(
private fun Settings(
color: Color,
alpha: Float,
fadeThreshold: Float,
fadeThresholdSpread: Float,
strokeWidth: Float,
drawPosition: SnowfallDrawPosition,
snowflakeMinSize: Dp,
Expand All @@ -181,6 +195,8 @@ private fun Settings(
snowflakeDensity: Float,
onColorChange: (Color) -> Unit,
onAlphaChange: (Float) -> Unit,
onFadeThresholdChange: (Float) -> Unit,
onFadeThresholdSpreadChange: (Float) -> Unit,
onStrokeChange: (Float) -> Unit,
onDrawPositionChange: (SnowfallDrawPosition) -> Unit,
onSnowflakeSizeChange: (Dp, Dp) -> Unit,
Expand All @@ -207,6 +223,20 @@ private fun Settings(
onValueChange = onAlphaChange
)

SliderSelector(
title = "Fade threshold",
selectedValue = fadeThreshold,
valueRange = 0f..1f,
onValueChange = onFadeThresholdChange
)

SliderSelector(
title = "Fade threshold spread",
selectedValue = fadeThresholdSpread,
valueRange = 0f..1f,
onValueChange = onFadeThresholdSpreadChange
)

SliderSelector(
title = "Stroke Width",
selectedValue = strokeWidth,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public enum class SnowfallDrawPosition {
public fun Modifier.snowfall(
color: Color = Color.White,
alpha: Float = 0.3f,
fadeThreshold: Float = 1f,
fadeThresholdSpread: Float = 0f,
strokeWidth: Float = 1f,
drawPosition: SnowfallDrawPosition = SnowfallDrawPosition.Ahead,
snowflakes: List<Path> = DefaultSnowflakes,
Expand All @@ -59,6 +61,8 @@ public fun Modifier.snowfall(
this then SnowfallElement(
color = color,
alpha = alpha,
fadeThreshold = fadeThreshold,
fadeThresholdSpread = fadeThresholdSpread,
strokeWidth = strokeWidth,
drawPosition = drawPosition,
snowflakes = snowflakes,
Expand All @@ -77,6 +81,8 @@ public fun Modifier.snowfall(
private data class SnowfallElement(
val color: Color,
val alpha: Float,
val fadeThreshold: Float,
val fadeThresholdSpread: Float,
val strokeWidth: Float,
val drawPosition: SnowfallDrawPosition,
val snowflakes: List<Path>,
Expand All @@ -92,13 +98,16 @@ private data class SnowfallElement(
return Snowfall(
color = color,
alpha = alpha,
fadeThreshold = fadeThreshold,
fadeThresholdSpread = fadeThresholdSpread,
stroke = Stroke(strokeWidth),
drawPosition = drawPosition,
snowflakes = ArrayList(snowflakes),
snowflakeSize = snowflakeMinSize..snowflakeMaxSize,
snowflakeSpeed = snowflakeMinSpeed..snowflakeMaxSpeed,
snowflakeDensity = snowflakeDensity,
snowfallState = snowfallState
snowfallState = snowfallState,

)
}

Expand All @@ -111,6 +120,8 @@ private data class SnowfallElement(
val speedInvalidationRequired = speedInvalidationRequired(node)
node.color = color
node.alpha = alpha
node.fadeThreshold = fadeThreshold
node.fadeThresholdSpread = fadeThresholdSpread
node.stroke = Stroke(strokeWidth)
node.drawPosition = drawPosition
node.snowflakes = ArrayList(snowflakes)
Expand Down Expand Up @@ -150,14 +161,16 @@ private data class SnowfallElement(
private class Snowfall(
var color: Color,
var alpha: Float,
var fadeThreshold: Float,
var fadeThresholdSpread: Float,
var stroke: Stroke,
var drawPosition: SnowfallDrawPosition,
var snowflakes: List<Path>,
var snowflakeSize: ClosedRange<Float>,
var snowflakeSpeed: ClosedRange<Float>,
var snowflakeDensity: Float,
var snowfallState: SnowfallState,
var pathSizes: FloatArray = snowflakes.pathSizes
var pathSizes: FloatArray = snowflakes.pathSizes,
) : DrawModifierNode, LayoutAwareModifierNode, Modifier.Node() {

private var canvasSize = Size.Zero
Expand All @@ -182,8 +195,11 @@ private class Snowfall(

override fun ContentDrawScope.draw() {
if (drawPosition == SnowfallDrawPosition.Ahead) drawContent()
val canvasHeight = (fadeThreshold + fadeThresholdSpread).coerceIn(0f, 1f) * size.height

clipRect {
clipRect(
bottom = canvasHeight,
) {
snowfallState.snowflakes.fastForEachIndexed { index, flake ->
val scale = flake.scale
val path = snowflakes[index % snowflakes.size]
Expand All @@ -196,7 +212,7 @@ private class Snowfall(
scale(scale, offset)
translate(offset.x, offset.y)
}) {
drawPath(path, color, alpha, stroke)
drawPath(path, color, (alpha * flake.alpha), stroke)
}
}
}
Expand Down Expand Up @@ -245,6 +261,14 @@ private class Snowfall(
x = (x + speed * cos(angle)).coerceIn(-flakeSize, canvasSize.width + flakeSize)
y = (y + speed * sin(angle)).coerceIn(-canvasSize.height, canvasSize.height + flakeSize)
angle += Random.nextFloat() * AddableAngleRange

val yPos = (y / canvasSize.height)

alpha = when {
yPos < fadeThreshold -> 1f
yPos > (fadeThreshold + fadeThresholdSpread) -> 0f
else -> (1 - (yPos - fadeThreshold) / (fadeThresholdSpread * alphaOffset)).coerceIn(0f, 1f)
}
if (y == canvasSize.height + flakeSize) recycle(index)
}

Expand All @@ -253,6 +277,7 @@ private class Snowfall(
scaleRatio = Random.nextFloat()
speedRatio = Random.nextFloat()
angle = Random.nextFloat() * SourceAngleRange
alpha = 1f
scale = scaleRatio * snowflakeSize / pathSize
speed = speedRatio * snowflakeSpeed
x = Random.nextFloat() * canvasSize.width
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.skeptick.snowfall.compose.internal
import androidx.compose.ui.graphics.Path

internal val SourceAngleRange = 1.45f..1.55f
internal val AlphaOffsetRange = 0.5f..1f
internal val AddableAngleRange = -0.0025f..0.0025f

internal operator fun Float.times(range: ClosedRange<Float>) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ internal class SnowfallState(
) {
companion object StateSaver : Saver<SnowfallState, Any> by listSaver(
save = { state ->
buildList(state.snowflakes.size * 7) {
buildList(state.snowflakes.size * 8) {
state.snowflakes.fastForEach { snowflake ->
add(snowflake.x)
add(snowflake.y)
add(snowflake.alphaOffset)
add(snowflake.angle)
add(snowflake.scale)
add(snowflake.speed)
Expand All @@ -25,15 +26,16 @@ internal class SnowfallState(
},
restore = { list: List<Float> ->
SnowfallState(
snowflakes = List((list.size) / 7) { index ->
snowflakes = List((list.size) / 8) { index ->
SnowflakeState(
x = list[index * 7 + 0],
y = list[index * 7 + 1],
angle = list[index * 7 + 2],
scale = list[index * 7 + 3],
speed = list[index * 7 + 4],
scaleRatio = list[index * 7 + 5],
speedRatio = list[index * 7 + 6],
alphaOffset = list[index * 7 + 2],
angle = list[index * 7 + 3],
scale = list[index * 7 + 4],
speed = list[index * 7 + 5],
scaleRatio = list[index * 7 + 6],
speedRatio = list[index * 7 + 7],
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlin.random.Random
internal class SnowflakeState(
x: Float,
y: Float,
alphaOffset: Float,
angle: Float,
scale: Float,
speed: Float,
Expand All @@ -19,6 +20,8 @@ internal class SnowflakeState(
) {
var x by mutableFloatStateOf(x)
var y by mutableFloatStateOf(y)
var alpha by mutableFloatStateOf(1f)
var alphaOffset by mutableFloatStateOf(alphaOffset)
var angle by mutableFloatStateOf(angle)
var scale by mutableFloatStateOf(scale)
var speed by mutableFloatStateOf(speed)
Expand All @@ -28,13 +31,14 @@ internal fun SnowflakeState(
canvasSize: Size,
pathSize: Float,
size: ClosedRange<Float>,
speed: ClosedRange<Float>
speed: ClosedRange<Float>,
): SnowflakeState {
val scaleRatio = Random.nextFloat()
val speedRatio = Random.nextFloat()
return SnowflakeState(
x = Random.nextFloat() * canvasSize.width,
y = Random.nextFloat() * canvasSize.height * -1f,
alphaOffset = Random.nextFloat() * AlphaOffsetRange,
angle = Random.nextFloat() * SourceAngleRange,
scale = scaleRatio * size / pathSize,
speed = speedRatio * speed,
Expand Down