Loading packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/ActionList.kt +11 −4 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package com.android.systemui.ambientcue.ui.compose import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut Loading @@ -40,14 +42,19 @@ fun ActionList(actions: List<ActionViewModel>, visible: Boolean, modifier: Modif horizontalAlignment = Alignment.CenterHorizontally, ) { actions.fastForEachIndexed { index, action -> val delay = 50 * (actions.size - index) val delay = 64 * (actions.size - index) AnimatedVisibility( visible = visible, enter = slideInVertically(tween(450, delayMillis = delay)) { with(density) { 15.dp.roundToPx() } slideInVertically( spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow, ) ) { with(density) { 15.dp.roundToPx() * (actions.size - index) } } + fadeIn(tween(450, delayMillis = delay)), exit = fadeOut(tween(250)), exit = fadeOut(tween(350, delayMillis = delay)), ) { Chip(action) } Loading packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/AmbientCueContainer.kt +6 −2 Original line number Diff line number Diff line Loading @@ -49,10 +49,14 @@ fun AmbientCueContainer( viewModel.collapse() } ) { BackgroundGlow(visible, Modifier.align(Alignment.BottomCenter)) BackgroundGlow( visible = visible, expanded = expanded, modifier = Modifier.align(Alignment.BottomCenter), ) NavBarPill( actions = actions, navBarWidth = 90.dp, // TODO: b/414507396 - Replace with the width of the navbar navBarWidth = 110.dp, // TODO: b/414507396 - Replace with the width of the navbar visible = visible, expanded = expanded, modifier = Modifier.align(Alignment.BottomCenter), Loading packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/BackgroundGlow.kt +59 −30 Original line number Diff line number Diff line Loading @@ -16,7 +16,14 @@ package com.android.systemui.ambientcue.ui.compose import android.graphics.RuntimeShader import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size Loading @@ -25,48 +32,62 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Brush import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.core.graphics.ColorUtils import com.android.systemui.ambientcue.ui.shader.BackgroundGlowShader @Composable fun BackgroundGlow(visible: Boolean, modifier: Modifier) { val alpha by animateFloatAsState(if (visible) 1f else 0f, animationSpec = tween(750)) val blurScale = 1.3f fun BackgroundGlow(visible: Boolean, expanded: Boolean, modifier: Modifier) { val density = LocalDensity.current val turbulenceDisplacementPx = with(density) { Defaults.TURBULENCE_DISPLACEMENT_DP.dp.toPx() } val gradientRadiusPx = with(density) { Defaults.GRADIENT_RADIUS.dp.toPx() } val primaryBoosted = Color(boostChroma(MaterialTheme.colorScheme.primary.toArgb())) val primaryFixedBoosted = Color(boostChroma(MaterialTheme.colorScheme.primary.toArgb())) val tertiaryBoosted = Color(boostChroma(MaterialTheme.colorScheme.tertiaryContainer.toArgb())) val alpha by animateFloatAsState(if (visible) 1f else 0f, animationSpec = tween(750)) val verticalOffset by animateDpAsState(if (expanded) 0.dp else Defaults.COLLAPSED_TRANSLATION_DP.dp, tween(350)) val verticalOffsetPx = with(density) { verticalOffset.toPx() } val gradient1Brush = Brush.radialGradient( listOf(primaryFixedBoosted.copy(alpha = 0.3f), primaryFixedBoosted.copy(alpha = 0f)) ) val gradient2Brush = Brush.radialGradient( listOf(primaryBoosted.copy(alpha = 0.4f), primaryBoosted.copy(alpha = 0f)) ) val gradient3Brush = Brush.radialGradient( listOf(tertiaryBoosted.copy(alpha = 0.3f), tertiaryBoosted.copy(alpha = 0f)) // Infinite animation responsible for the "vapor" effect distorting the radial gradient val infiniteTransition = rememberInfiniteTransition(label = "backgroundGlow") val turbulencePhase by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 10f, animationSpec = infiniteRepeatable( animation = tween(Defaults.ONE_MINUTE_MS, easing = LinearEasing), repeatMode = RepeatMode.Reverse, ), label = "turbulencePhase", ) // The glow is made of 3 radial gradients. // All gradients are in the same box to make it simpler to move them around val color1 = Color(boostChroma(MaterialTheme.colorScheme.secondaryContainer.toArgb())) val color2 = Color(boostChroma(MaterialTheme.colorScheme.primary.toArgb())) val color3 = Color(boostChroma(MaterialTheme.colorScheme.tertiary.toArgb())) val shader = RuntimeShader(BackgroundGlowShader.FRAG_SHADER) val shaderBrush = ShaderBrush(shader) Box( modifier.size(372.dp, 68.dp).alpha(alpha).drawBehind { scale(2.12f * blurScale, 1f) { translate(0f, size.height * 0.8f) { drawCircle(gradient1Brush) } } scale(4.59f * blurScale, 1f) { translate(0f, size.height * 0.45f) { drawOval(gradient2Brush) } modifier.size(400.dp, 200.dp).alpha(alpha).drawWithCache { onDrawWithContent { shader.setFloatUniform("alpha", alpha) shader.setFloatUniform("resolution", size.width, size.height) shader.setColorUniform("color1", color1.toArgb()) shader.setColorUniform("color2", color2.toArgb()) shader.setColorUniform("color3", color3.toArgb()) shader.setFloatUniform("origin", size.width / 2, size.height + verticalOffsetPx) shader.setFloatUniform("radius", gradientRadiusPx) shader.setFloatUniform("turbulenceAmount", turbulenceDisplacementPx) shader.setFloatUniform("turbulencePhase", turbulencePhase) shader.setFloatUniform("turbulenceSize", Defaults.TURBULENCE_SIZE) drawRect(shaderBrush) } scale(2.41f * blurScale, 1f) { drawOval(gradient3Brush) } } ) } Loading @@ -80,3 +101,11 @@ private fun boostChroma(color: Int): Int { } return ColorUtils.M3HCTToColor(outColor[0], 120f, outColor[2]) } private object Defaults { const val COLLAPSED_TRANSLATION_DP = 110 const val TURBULENCE_SIZE = 4.7f const val TURBULENCE_DISPLACEMENT_DP = 30 const val GRADIENT_RADIUS = 200 const val ONE_MINUTE_MS = 60 * 1000 } packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/shader/BackgroundGlowShader.kt 0 → 100644 +58 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 * * http://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.android.systemui.ambientcue.ui.shader import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary import org.intellij.lang.annotations.Language object BackgroundGlowShader { @Language("AGSL") val FRAG_SHADER = ShaderUtilLibrary.SHADER_LIB + """ uniform float2 resolution; uniform half2 origin; uniform half radius; uniform half alpha; uniform half turbulencePhase; uniform half turbulenceAmount; uniform half turbulenceSize; layout(color) uniform half4 color1; layout(color) uniform half4 color2; layout(color) uniform half4 color3; half4 main(in float2 fragCoord) { float2 uv = fragCoord / resolution; half2 aspectRatio = half2(1.0, resolution.y / resolution.x); // Turbulence that distorts the circle shape vec3 noiseP = vec3(uv * aspectRatio, turbulencePhase) * turbulenceSize; float turbulence = (simplex3d(noiseP) * 0.5 + 0.5); float2 displacedCoord = fragCoord + float2(0.0, turbulence * turbulenceAmount); // Linear gradient, clipped to a radial shape half4 gradientLeft = mix(color1, color2, uv.x * 4. - 1); half4 gradientRight = mix(color2, color3, uv.x * 4 - 2.); half4 linearGradient = mix(gradientLeft, gradientRight, uv.x); half radialGradient = 1.0 - saturate(length(origin - displacedCoord) / radius); half4 combinedGradients = linearGradient * radialGradient; return combinedGradients; } """ .trimIndent() } Loading
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/ActionList.kt +11 −4 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package com.android.systemui.ambientcue.ui.compose import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut Loading @@ -40,14 +42,19 @@ fun ActionList(actions: List<ActionViewModel>, visible: Boolean, modifier: Modif horizontalAlignment = Alignment.CenterHorizontally, ) { actions.fastForEachIndexed { index, action -> val delay = 50 * (actions.size - index) val delay = 64 * (actions.size - index) AnimatedVisibility( visible = visible, enter = slideInVertically(tween(450, delayMillis = delay)) { with(density) { 15.dp.roundToPx() } slideInVertically( spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow, ) ) { with(density) { 15.dp.roundToPx() * (actions.size - index) } } + fadeIn(tween(450, delayMillis = delay)), exit = fadeOut(tween(250)), exit = fadeOut(tween(350, delayMillis = delay)), ) { Chip(action) } Loading
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/AmbientCueContainer.kt +6 −2 Original line number Diff line number Diff line Loading @@ -49,10 +49,14 @@ fun AmbientCueContainer( viewModel.collapse() } ) { BackgroundGlow(visible, Modifier.align(Alignment.BottomCenter)) BackgroundGlow( visible = visible, expanded = expanded, modifier = Modifier.align(Alignment.BottomCenter), ) NavBarPill( actions = actions, navBarWidth = 90.dp, // TODO: b/414507396 - Replace with the width of the navbar navBarWidth = 110.dp, // TODO: b/414507396 - Replace with the width of the navbar visible = visible, expanded = expanded, modifier = Modifier.align(Alignment.BottomCenter), Loading
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/BackgroundGlow.kt +59 −30 Original line number Diff line number Diff line Loading @@ -16,7 +16,14 @@ package com.android.systemui.ambientcue.ui.compose import android.graphics.RuntimeShader import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size Loading @@ -25,48 +32,62 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Brush import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.core.graphics.ColorUtils import com.android.systemui.ambientcue.ui.shader.BackgroundGlowShader @Composable fun BackgroundGlow(visible: Boolean, modifier: Modifier) { val alpha by animateFloatAsState(if (visible) 1f else 0f, animationSpec = tween(750)) val blurScale = 1.3f fun BackgroundGlow(visible: Boolean, expanded: Boolean, modifier: Modifier) { val density = LocalDensity.current val turbulenceDisplacementPx = with(density) { Defaults.TURBULENCE_DISPLACEMENT_DP.dp.toPx() } val gradientRadiusPx = with(density) { Defaults.GRADIENT_RADIUS.dp.toPx() } val primaryBoosted = Color(boostChroma(MaterialTheme.colorScheme.primary.toArgb())) val primaryFixedBoosted = Color(boostChroma(MaterialTheme.colorScheme.primary.toArgb())) val tertiaryBoosted = Color(boostChroma(MaterialTheme.colorScheme.tertiaryContainer.toArgb())) val alpha by animateFloatAsState(if (visible) 1f else 0f, animationSpec = tween(750)) val verticalOffset by animateDpAsState(if (expanded) 0.dp else Defaults.COLLAPSED_TRANSLATION_DP.dp, tween(350)) val verticalOffsetPx = with(density) { verticalOffset.toPx() } val gradient1Brush = Brush.radialGradient( listOf(primaryFixedBoosted.copy(alpha = 0.3f), primaryFixedBoosted.copy(alpha = 0f)) ) val gradient2Brush = Brush.radialGradient( listOf(primaryBoosted.copy(alpha = 0.4f), primaryBoosted.copy(alpha = 0f)) ) val gradient3Brush = Brush.radialGradient( listOf(tertiaryBoosted.copy(alpha = 0.3f), tertiaryBoosted.copy(alpha = 0f)) // Infinite animation responsible for the "vapor" effect distorting the radial gradient val infiniteTransition = rememberInfiniteTransition(label = "backgroundGlow") val turbulencePhase by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 10f, animationSpec = infiniteRepeatable( animation = tween(Defaults.ONE_MINUTE_MS, easing = LinearEasing), repeatMode = RepeatMode.Reverse, ), label = "turbulencePhase", ) // The glow is made of 3 radial gradients. // All gradients are in the same box to make it simpler to move them around val color1 = Color(boostChroma(MaterialTheme.colorScheme.secondaryContainer.toArgb())) val color2 = Color(boostChroma(MaterialTheme.colorScheme.primary.toArgb())) val color3 = Color(boostChroma(MaterialTheme.colorScheme.tertiary.toArgb())) val shader = RuntimeShader(BackgroundGlowShader.FRAG_SHADER) val shaderBrush = ShaderBrush(shader) Box( modifier.size(372.dp, 68.dp).alpha(alpha).drawBehind { scale(2.12f * blurScale, 1f) { translate(0f, size.height * 0.8f) { drawCircle(gradient1Brush) } } scale(4.59f * blurScale, 1f) { translate(0f, size.height * 0.45f) { drawOval(gradient2Brush) } modifier.size(400.dp, 200.dp).alpha(alpha).drawWithCache { onDrawWithContent { shader.setFloatUniform("alpha", alpha) shader.setFloatUniform("resolution", size.width, size.height) shader.setColorUniform("color1", color1.toArgb()) shader.setColorUniform("color2", color2.toArgb()) shader.setColorUniform("color3", color3.toArgb()) shader.setFloatUniform("origin", size.width / 2, size.height + verticalOffsetPx) shader.setFloatUniform("radius", gradientRadiusPx) shader.setFloatUniform("turbulenceAmount", turbulenceDisplacementPx) shader.setFloatUniform("turbulencePhase", turbulencePhase) shader.setFloatUniform("turbulenceSize", Defaults.TURBULENCE_SIZE) drawRect(shaderBrush) } scale(2.41f * blurScale, 1f) { drawOval(gradient3Brush) } } ) } Loading @@ -80,3 +101,11 @@ private fun boostChroma(color: Int): Int { } return ColorUtils.M3HCTToColor(outColor[0], 120f, outColor[2]) } private object Defaults { const val COLLAPSED_TRANSLATION_DP = 110 const val TURBULENCE_SIZE = 4.7f const val TURBULENCE_DISPLACEMENT_DP = 30 const val GRADIENT_RADIUS = 200 const val ONE_MINUTE_MS = 60 * 1000 }
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/shader/BackgroundGlowShader.kt 0 → 100644 +58 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 * * http://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.android.systemui.ambientcue.ui.shader import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary import org.intellij.lang.annotations.Language object BackgroundGlowShader { @Language("AGSL") val FRAG_SHADER = ShaderUtilLibrary.SHADER_LIB + """ uniform float2 resolution; uniform half2 origin; uniform half radius; uniform half alpha; uniform half turbulencePhase; uniform half turbulenceAmount; uniform half turbulenceSize; layout(color) uniform half4 color1; layout(color) uniform half4 color2; layout(color) uniform half4 color3; half4 main(in float2 fragCoord) { float2 uv = fragCoord / resolution; half2 aspectRatio = half2(1.0, resolution.y / resolution.x); // Turbulence that distorts the circle shape vec3 noiseP = vec3(uv * aspectRatio, turbulencePhase) * turbulenceSize; float turbulence = (simplex3d(noiseP) * 0.5 + 0.5); float2 displacedCoord = fragCoord + float2(0.0, turbulence * turbulenceAmount); // Linear gradient, clipped to a radial shape half4 gradientLeft = mix(color1, color2, uv.x * 4. - 1); half4 gradientRight = mix(color2, color3, uv.x * 4 - 2.); half4 linearGradient = mix(gradientLeft, gradientRight, uv.x); half radialGradient = 1.0 - saturate(length(origin - displacedCoord) / radius); half4 combinedGradients = linearGradient * radialGradient; return combinedGradients; } """ .trimIndent() }