From 081259129fd59dbf646ca72b9674246ac3725e52 Mon Sep 17 00:00:00 2001 From: "Ian G. Clifton" Date: Thu, 29 Aug 2024 14:53:38 -0700 Subject: [PATCH 1/6] Added a "solar flare" shader and modifier The Kotlin side of this is very similar to the existing yellow striped modifier, but the shader is entirely different. Right now this isn't used anywhere but can be tested by swapping the yellowBackground with solarFlareShaderBackground. --- .../jetlagged/backgrounds/ShaderBackground.kt | 2 +- .../backgrounds/SolarFlareShaderBackground.kt | 133 ++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/ShaderBackground.kt b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/ShaderBackground.kt index 4add69d96f..0480455197 100644 --- a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/ShaderBackground.kt +++ b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/ShaderBackground.kt @@ -90,7 +90,7 @@ fun Modifier.yellowBackground(): Modifier = } @Language("AGSL") -val SHADER = """ +private val SHADER = """ uniform float2 resolution; uniform float time; uniform float waves; diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt new file mode 100644 index 0000000000..ade5704886 --- /dev/null +++ b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.jetlagged.backgrounds + +import android.graphics.RuntimeShader +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.animation.core.withInfiniteAnimationFrameMillis +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import com.example.jetlagged.ui.theme.White +import com.example.jetlagged.ui.theme.Yellow +import kotlinx.coroutines.launch +import org.intellij.lang.annotations.Language + +/** + * Background modifier that displays a custom shader for Android T and above and a linear gradient + * for older versions of Android + */ +fun Modifier.solarFlareShaderBackground(): Modifier = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + this.then(SolarFlareShaderBackgroundElement) + } else { + drawWithCache { + val gradientBrush = Brush.verticalGradient(listOf(Yellow, White)) + onDrawBehind { + drawRect(gradientBrush, alpha = 1f) + } + } + } + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +private data object SolarFlareShaderBackgroundElement : + ModifierNodeElement() { + override fun create() = SolarFlairShaderBackgroundNode() + override fun update(node: SolarFlairShaderBackgroundNode) { + } +} + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +private class SolarFlairShaderBackgroundNode : DrawModifierNode, Modifier.Node() { + private val shader = RuntimeShader(SHADER) + private val shaderBrush = ShaderBrush(shader) + private val time = mutableFloatStateOf(0f) + + init { + shader.setColorUniform( + "baseColor", + android.graphics.Color.valueOf(Yellow.red, Yellow.green, Yellow.blue, Yellow.alpha) + ) + } + + override fun ContentDrawScope.draw() { + shader.setFloatUniform("resolution", size.width, size.height) + shader.setFloatUniform("time", time.floatValue) + + drawRect(shaderBrush) + drawContent() + } + + override fun onAttach() { + coroutineScope.launch { + while (isAttached) { + withInfiniteAnimationFrameMillis { + time.floatValue = it / 1000f + } + } + } + } +} + +@Language("AGSL") +private val SHADER = """ + uniform float2 resolution; + uniform float time; + layout(color) uniform half4 baseColor; + + const int ITERATIONS = 2; + const float INTENSITY = 100.0; + const float TIME_MULTIPLIER = 0.25; + + float4 main(in float2 fragCoord) { + // Slow down the animation to be more soothing + float calculatedTime = time * TIME_MULTIPLIER; + + // Coords + float2 uv = fragCoord / resolution.xy; + float2 uvCalc = mod(uv * 5.0, 5.0) - 200.0; + + // Values to adjust per iteration + float2 iterationChange = float2(uvCalc); + float colorPart = 1.0; + + for (int i = 0; i < ITERATIONS; i++) { + iterationChange = uvCalc + float2( + cos(calculatedTime + iterationChange.x) + + sin(calculatedTime - iterationChange.y), + cos(calculatedTime - iterationChange.x) + + sin(calculatedTime + iterationChange.y) + ); + colorPart += 0.8 / length( + float2(uvCalc.x / (cos(iterationChange.x + calculatedTime) * INTENSITY), + uvCalc.y / (sin(iterationChange.y + calculatedTime) * INTENSITY) + ) + ); + } + colorPart = 1.6 - (colorPart / float(ITERATIONS)); + + float alpha = 1.0 - (uv.y * uv.y); + float4 color = float4(colorPart * baseColor.r, colorPart * baseColor.g, colorPart * baseColor.b, alpha); + return clamp(color, 0.0, 1.0); + } +""".trimIndent() \ No newline at end of file From 3808c73683fdb45e2afd051c1e19da5232417884 Mon Sep 17 00:00:00 2001 From: "Ian G. Clifton" Date: Thu, 29 Aug 2024 15:06:47 -0700 Subject: [PATCH 2/6] Empty line to feed the formatter and make it happy --- .../example/jetlagged/backgrounds/SolarFlareShaderBackground.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt index ade5704886..8d53c056db 100644 --- a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt +++ b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt @@ -130,4 +130,4 @@ private val SHADER = """ float4 color = float4(colorPart * baseColor.r, colorPart * baseColor.g, colorPart * baseColor.b, alpha); return clamp(color, 0.0, 1.0); } -""".trimIndent() \ No newline at end of file +""".trimIndent() From e42e436f46edf82d6022694f85ebe033276489fd Mon Sep 17 00:00:00 2001 From: "Ian G. Clifton" Date: Tue, 3 Sep 2024 15:17:40 -0700 Subject: [PATCH 3/6] Renamed ShaderBackground/yellow to stripes Minor tweaks to both shaders and split out the fallback for older Android OSes to use the same SimpleGradientBackground. --- .../com/example/jetlagged/JetLaggedScreen.kt | 4 +-- .../backgrounds/SimpleGradientBackground.kt | 32 +++++++++++++++++++ .../backgrounds/SolarFlareShaderBackground.kt | 15 +++------ ...ckground.kt => StripesShaderBackground.kt} | 30 ++++++----------- 4 files changed, 48 insertions(+), 33 deletions(-) create mode 100644 JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SimpleGradientBackground.kt rename JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/{ShaderBackground.kt => StripesShaderBackground.kt} (78%) diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/JetLaggedScreen.kt b/JetLagged/app/src/main/java/com/example/jetlagged/JetLaggedScreen.kt index ae7ac23aec..6bc5382021 100644 --- a/JetLagged/app/src/main/java/com/example/jetlagged/JetLaggedScreen.kt +++ b/JetLagged/app/src/main/java/com/example/jetlagged/JetLaggedScreen.kt @@ -40,7 +40,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel -import com.example.jetlagged.backgrounds.yellowBackground +import com.example.jetlagged.backgrounds.stripesBackground import com.example.jetlagged.data.JetLaggedHomeScreenViewModel import com.example.jetlagged.heartrate.HeartRateCard import com.example.jetlagged.sleep.JetLaggedHeader @@ -63,7 +63,7 @@ fun JetLaggedScreen( .verticalScroll(rememberScrollState()) .background(Color.White) ) { - Column(modifier = Modifier.yellowBackground()) { + Column(modifier = Modifier.stripesBackground()) { JetLaggedHeader( modifier = Modifier.fillMaxWidth(), onDrawerClicked = onDrawerClicked diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SimpleGradientBackground.kt b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SimpleGradientBackground.kt new file mode 100644 index 0000000000..55d6d04996 --- /dev/null +++ b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SimpleGradientBackground.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.jetlagged.backgrounds + +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.Brush +import com.example.jetlagged.ui.theme.White +import com.example.jetlagged.ui.theme.Yellow +import com.example.jetlagged.ui.theme.YellowVariant + +fun Modifier.simpleGradient(): Modifier = + drawWithCache { + val gradientBrush = Brush.verticalGradient(listOf(Yellow, YellowVariant, White)) + onDrawBehind { + drawRect(gradientBrush, alpha = 1f) + } + } \ No newline at end of file diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt index 8d53c056db..6000dba7dc 100644 --- a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt +++ b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SolarFlareShaderBackground.kt @@ -22,13 +22,10 @@ import androidx.annotation.RequiresApi import androidx.compose.animation.core.withInfiniteAnimationFrameMillis import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithCache -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.node.ModifierNodeElement -import com.example.jetlagged.ui.theme.White import com.example.jetlagged.ui.theme.Yellow import kotlinx.coroutines.launch import org.intellij.lang.annotations.Language @@ -41,12 +38,7 @@ fun Modifier.solarFlareShaderBackground(): Modifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { this.then(SolarFlareShaderBackgroundElement) } else { - drawWithCache { - val gradientBrush = Brush.verticalGradient(listOf(Yellow, White)) - onDrawBehind { - drawRect(gradientBrush, alpha = 1f) - } - } + this.then(Modifier.simpleGradient()) } @RequiresApi(Build.VERSION_CODES.TIRAMISU) @@ -105,7 +97,7 @@ private val SHADER = """ // Coords float2 uv = fragCoord / resolution.xy; - float2 uvCalc = mod(uv * 5.0, 5.0) - 200.0; + float2 uvCalc = (uv * 5.0) - (INTENSITY * 2.0); // Values to adjust per iteration float2 iterationChange = float2(uvCalc); @@ -126,8 +118,11 @@ private val SHADER = """ } colorPart = 1.6 - (colorPart / float(ITERATIONS)); + // Fade out the bottom on a curve float alpha = 1.0 - (uv.y * uv.y); + // Mix calculated color with the incoming base color float4 color = float4(colorPart * baseColor.r, colorPart * baseColor.g, colorPart * baseColor.b, alpha); + // Keep all channels within valid bounds of 0.0 and 1.0 return clamp(color, 0.0, 1.0); } """.trimIndent() diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/ShaderBackground.kt b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt similarity index 78% rename from JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/ShaderBackground.kt rename to JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt index 0480455197..ac3b6983f6 100644 --- a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/ShaderBackground.kt +++ b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt @@ -23,27 +23,23 @@ import androidx.annotation.RequiresApi import androidx.compose.animation.core.withInfiniteAnimationFrameMillis import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithCache -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.node.ModifierNodeElement -import com.example.jetlagged.ui.theme.White import com.example.jetlagged.ui.theme.Yellow -import com.example.jetlagged.ui.theme.YellowVariant import kotlinx.coroutines.launch import org.intellij.lang.annotations.Language -private data object YellowBackgroundElement : ModifierNodeElement() { +private data object StripesBackgroundElement : ModifierNodeElement() { @RequiresApi(Build.VERSION_CODES.TIRAMISU) - override fun create() = YellowBackgroundNode() - override fun update(node: YellowBackgroundNode) { + override fun create() = StripesBackgroundNode() + override fun update(node: StripesBackgroundNode) { } } @RequiresApi(Build.VERSION_CODES.TIRAMISU) -private class YellowBackgroundNode : DrawModifierNode, Modifier.Node() { +private class StripesBackgroundNode : DrawModifierNode, Modifier.Node() { private val shader = RuntimeShader(SHADER) private val shaderBrush = ShaderBrush(shader) @@ -59,7 +55,6 @@ private class YellowBackgroundNode : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { shader.setFloatUniform("resolution", size.width, size.height) shader.setFloatUniform("time", time.floatValue) - shader.setFloatUniform("waves", 7f) drawRect(shaderBrush) @@ -77,23 +72,17 @@ private class YellowBackgroundNode : DrawModifierNode, Modifier.Node() { } } -fun Modifier.yellowBackground(): Modifier = +fun Modifier.stripesBackground(): Modifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - this.then(YellowBackgroundElement) + this.then(StripesBackgroundElement) } else { - drawWithCache { - val gradientBrush = Brush.verticalGradient(listOf(Yellow, YellowVariant, White)) - onDrawBehind { - drawRect(gradientBrush, alpha = 1f) - } - } + this.then(Modifier.simpleGradient()) } @Language("AGSL") private val SHADER = """ uniform float2 resolution; uniform float time; - uniform float waves; layout(color) uniform half4 color; float calculateColorMultiplier(float yCoord, float factor) { @@ -104,7 +93,7 @@ private val SHADER = """ // Config values const float speedMultiplier = 1.5; const float waveDensity = 1.0; - //const float loops = iWaves; + const float waves = 7.0; const float energy = 0.6; // Calculated values @@ -114,8 +103,7 @@ private val SHADER = """ float hAdjustment = uv.x * 4.3; float3 loopColor = vec3(1.0 - rgbColor.r, 1.0 - rgbColor.g, 1.0 - rgbColor.b) / waves; - for (float i = 1.0; i <= 100; i += 1.0) { - if (i > waves) break; + for (float i = 1.0; i <= waves; i += 1.0) { float loopFactor = i * 0.1; float sinInput = (timeOffset + hAdjustment) * energy; float curve = sin(sinInput) * (1.0 - loopFactor) * 0.05; From 577cc2fc02810284609ea54942177510ca7dc477 Mon Sep 17 00:00:00 2001 From: "Ian G. Clifton" Date: Tue, 3 Sep 2024 15:22:30 -0700 Subject: [PATCH 4/6] Feeding the formatter and rubbing its belly to make it happy --- .../example/jetlagged/backgrounds/SimpleGradientBackground.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SimpleGradientBackground.kt b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SimpleGradientBackground.kt index 55d6d04996..57352fae3f 100644 --- a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SimpleGradientBackground.kt +++ b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/SimpleGradientBackground.kt @@ -29,4 +29,4 @@ fun Modifier.simpleGradient(): Modifier = onDrawBehind { drawRect(gradientBrush, alpha = 1f) } - } \ No newline at end of file + } From adbc3d638bb4a8f0cecfbf0cbd15c24bfd6c862a Mon Sep 17 00:00:00 2001 From: "Ian G. Clifton" Date: Wed, 4 Sep 2024 10:05:15 -0700 Subject: [PATCH 5/6] Renamed stripes to moving stripes --- .../java/com/example/jetlagged/JetLaggedScreen.kt | 4 ++-- .../jetlagged/backgrounds/StripesShaderBackground.kt | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/JetLaggedScreen.kt b/JetLagged/app/src/main/java/com/example/jetlagged/JetLaggedScreen.kt index 6bc5382021..8495fa7eb6 100644 --- a/JetLagged/app/src/main/java/com/example/jetlagged/JetLaggedScreen.kt +++ b/JetLagged/app/src/main/java/com/example/jetlagged/JetLaggedScreen.kt @@ -40,7 +40,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel -import com.example.jetlagged.backgrounds.stripesBackground +import com.example.jetlagged.backgrounds.movingStripesBackground import com.example.jetlagged.data.JetLaggedHomeScreenViewModel import com.example.jetlagged.heartrate.HeartRateCard import com.example.jetlagged.sleep.JetLaggedHeader @@ -63,7 +63,7 @@ fun JetLaggedScreen( .verticalScroll(rememberScrollState()) .background(Color.White) ) { - Column(modifier = Modifier.stripesBackground()) { + Column(modifier = Modifier.movingStripesBackground()) { JetLaggedHeader( modifier = Modifier.fillMaxWidth(), onDrawerClicked = onDrawerClicked diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt index ac3b6983f6..be13d3c8e1 100644 --- a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt +++ b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt @@ -31,15 +31,15 @@ import com.example.jetlagged.ui.theme.Yellow import kotlinx.coroutines.launch import org.intellij.lang.annotations.Language -private data object StripesBackgroundElement : ModifierNodeElement() { +private data object MovingStripesBackgroundElement : ModifierNodeElement() { @RequiresApi(Build.VERSION_CODES.TIRAMISU) - override fun create() = StripesBackgroundNode() - override fun update(node: StripesBackgroundNode) { + override fun create() = MovingStripesBackgroundNode() + override fun update(node: MovingStripesBackgroundNode) { } } @RequiresApi(Build.VERSION_CODES.TIRAMISU) -private class StripesBackgroundNode : DrawModifierNode, Modifier.Node() { +private class MovingStripesBackgroundNode : DrawModifierNode, Modifier.Node() { private val shader = RuntimeShader(SHADER) private val shaderBrush = ShaderBrush(shader) @@ -72,9 +72,9 @@ private class StripesBackgroundNode : DrawModifierNode, Modifier.Node() { } } -fun Modifier.stripesBackground(): Modifier = +fun Modifier.movingStripesBackground(): Modifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - this.then(StripesBackgroundElement) + this.then(MovingStripesBackgroundElement) } else { this.then(Modifier.simpleGradient()) } From 05bd1b68c3a5135a9318af472be6d5b1d1f25842 Mon Sep 17 00:00:00 2001 From: "Ian G. Clifton" Date: Wed, 4 Sep 2024 10:13:24 -0700 Subject: [PATCH 6/6] Breaking up lines and giving the formatter cookies, yum yum --- .../example/jetlagged/backgrounds/StripesShaderBackground.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt index be13d3c8e1..94e93e61a0 100644 --- a/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt +++ b/JetLagged/app/src/main/java/com/example/jetlagged/backgrounds/StripesShaderBackground.kt @@ -31,7 +31,8 @@ import com.example.jetlagged.ui.theme.Yellow import kotlinx.coroutines.launch import org.intellij.lang.annotations.Language -private data object MovingStripesBackgroundElement : ModifierNodeElement() { +private data object MovingStripesBackgroundElement : + ModifierNodeElement() { @RequiresApi(Build.VERSION_CODES.TIRAMISU) override fun create() = MovingStripesBackgroundNode() override fun update(node: MovingStripesBackgroundNode) {