Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fbab15d5 authored by Lucas Dupin's avatar Lucas Dupin
Browse files

New glow spec

Combination of a linear gradient, clipped around a radial gradient, and
distorted with simplex noise.

Fixes: 416168362
Flag: com.android.systemui.enable_underlay
Test: atest AmbientCueContainerScreenshotTest
Test: visual
Change-Id: I456e65780bd7aea747be9851a7f17b14155b6c03
parent 12c60df3
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -49,7 +49,11 @@ 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
+59 −30
Original line number Diff line number Diff line
@@ -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
@@ -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) }
        }
    )
}
@@ -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
}
+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()
}