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

Commit 0d7e0b24 authored by Lucas Dupin's avatar Lucas Dupin
Browse files

Magic action button animation

Animated turbulence in z-space with rotating linear gradient and
triangle noise phase.

Bug: 383567383
Test: atest SmartActionScreenshotTest
Flag: com.android.systemui.notification_magic_actions_treatment
Change-Id: I03b03ce2c8cdaa91ca99c4e48d0e86fd6b886b11
parent aec8597a
Loading
Loading
Loading
Loading
+79 −45
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.statusbar.notification.row

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.BlendMode
import android.graphics.Canvas
@@ -38,8 +39,8 @@ import androidx.compose.ui.viewinterop.AndroidView
import com.android.internal.graphics.ColorUtils
import com.android.systemui.res.R
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
import kotlin.math.max
import kotlin.math.roundToInt
import com.android.wm.shell.shared.animation.Interpolators
import kotlin.math.min

/**
 * A background style for smarter-smart-actions. The style is composed by a simplex3d noise,
@@ -48,7 +49,7 @@ import kotlin.math.roundToInt
class MagicActionBackgroundDrawable(
    context: Context,
    primaryContainer: Int? = null,
    private val seed: Float = 0f,
    seed: Float = 0f,
) : Drawable() {

    private val pixelDensity = context.resources.displayMetrics.density
@@ -60,17 +61,15 @@ class MagicActionBackgroundDrawable(
            .toFloat()
    private val buttonShape = Path()
    private val paddingVertical =
        context.resources
            .getDimensionPixelSize(R.dimen.smart_reply_button_padding_vertical)
            .toFloat()
        context.resources.getDimensionPixelSize(R.dimen.smart_action_button_icon_padding).toFloat()

    /** The color of the button background. */
    private val mainColor =
        primaryContainer
            ?: context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)

    /** Slightly dimmed down version of [mainColor] used on the simplex noise. */
    private val dimColor: Int
    /** Slightly brighter version of [mainColor] used on the simplex noise. */
    private val effectColor: Int
        get() {
            val labColor = arrayOf(0.0, 0.0, 0.0).toDoubleArray()
            ColorUtils.colorToLAB(mainColor, labColor)
@@ -78,56 +77,97 @@ class MagicActionBackgroundDrawable(
            return ColorUtils.CAMToColor(
                camColor.hue,
                camColor.chroma,
                max(0f, (labColor[0] - 20).toFloat()),
                min(100f, (labColor[0] + 10).toFloat()),
            )
        }

    private val bgShader = MagicActionBackgroundShader()
    private val bgPaint = Paint()
    private val outlinePaint = Paint()
    private val gradientAnimator =
        ValueAnimator.ofFloat(0f, 1f).apply {
            duration = 2500
            interpolator = Interpolators.LINEAR
            addUpdateListener { invalidateSelf() }
        }
    private val turbulenceAnimator =
        ValueAnimator.ofFloat(seed, seed + TURBULENCE_MOVEMENT).apply {
            duration = ANIMATION_DURATION
            interpolator = Interpolators.LINEAR
            addUpdateListener { invalidateSelf() }
            start()
        }
    private val effectFadeAnimation =
        ValueAnimator.ofFloat(0f, 1f).apply {
            duration = 1000
            startDelay = ANIMATION_DURATION - 1000L
            interpolator = Interpolators.STANDARD_DECELERATE
            addUpdateListener { invalidateSelf() }
        }

    init {
        bgShader.setColorUniform("in_color", mainColor)
        bgShader.setColorUniform("in_dimColor", dimColor)
        bgShader.setColorUniform("in_effectColor", effectColor)
        bgPaint.shader = bgShader
        outlinePaint.style = Paint.Style.STROKE
        // Stroke is doubled in width and then clipped, to avoid anti-aliasing artifacts at the edge
        // of the rectangle.
        outlinePaint.strokeWidth = outlineStrokeWidth * 2
        outlinePaint.blendMode = BlendMode.SCREEN
        outlinePaint.alpha = (255 * 0.32f).roundToInt()
        outlinePaint.alpha = OUTLINE_ALPHA

        animate()
    }

    private fun animate() {
        turbulenceAnimator.start()
        gradientAnimator.start()
        effectFadeAnimation.start()
    }

    override fun draw(canvas: Canvas) {
        updateShaders()

        // We clip instead of drawing 2 rounded rects, otherwise there will be artifacts where
        // around the button background and the outline.
        canvas.save()
        canvas.clipPath(buttonShape)
        canvas.drawPath(buttonShape, bgPaint)
        canvas.drawPath(buttonShape, outlinePaint)
        canvas.restore()
    }

        canvas.drawRect(bounds, bgPaint)
        canvas.drawRoundRect(
            bounds.left.toFloat(),
            bounds.top + paddingVertical,
            bounds.right.toFloat(),
            bounds.bottom - paddingVertical,
            cornerRadius,
            cornerRadius,
            outlinePaint,
    private fun updateShaders() {
        val effectAlpha = 1f - effectFadeAnimation.animatedValue as Float
        val turbulenceZ = turbulenceAnimator.animatedValue as Float
        bgShader.setFloatUniform("in_sparkleMove", turbulenceZ * 1000)
        bgShader.setFloatUniform("in_noiseMove", 0f, 0f, turbulenceZ)
        bgShader.setFloatUniform("in_turbulenceAlpha", effectAlpha)
        bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA * effectAlpha)
        val gradientOffset = gradientAnimator.animatedValue as Float * bounds.width()
        val outlineGradient =
            LinearGradient(
                gradientOffset + bounds.left.toFloat(),
                0f,
                gradientOffset + bounds.right.toFloat(),
                0f,
                mainColor,
                ColorUtils.setAlphaComponent(mainColor, 0),
                Shader.TileMode.MIRROR,
            )
        outlinePaint.shader = outlineGradient
    }

    override fun onBoundsChange(bounds: Rect) {
        super.onBoundsChange(bounds)

        val width = bounds.width().toFloat()
        val height = bounds.height() - paddingVertical * 2
        val height = bounds.height().toFloat()
        if (width == 0f || height == 0f) return

        bgShader.setFloatUniform("in_gridNum", NOISE_SIZE)
        bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA)
        bgShader.setFloatUniform("in_noiseMove", 0f, 0f, 0f)
        bgShader.setFloatUniform("in_size", width, height)
        bgShader.setFloatUniform("in_aspectRatio", width / height)
        bgShader.setFloatUniform("in_time", seed)
        bgShader.setFloatUniform("in_pixelDensity", pixelDensity)

        buttonShape.reset()
@@ -140,18 +180,6 @@ class MagicActionBackgroundDrawable(
            cornerRadius,
            Path.Direction.CW,
        )

        val outlineGradient =
            LinearGradient(
                bounds.left.toFloat(),
                0f,
                bounds.right.toFloat(),
                0f,
                mainColor,
                ColorUtils.setAlphaComponent(mainColor, 0),
                Shader.TileMode.CLAMP,
            )
        outlinePaint.shader = outlineGradient
    }

    override fun setAlpha(alpha: Int) {
@@ -168,10 +196,15 @@ class MagicActionBackgroundDrawable(

    companion object {
        /** Smoothness of the turbulence. Larger numbers yield more detail. */
        private const val NOISE_SIZE = 0.7f

        private const val NOISE_SIZE = 0.57f
        /** Strength of the sparkles overlaid on the turbulence. */
        private const val SPARKLE_ALPHA = 0.15f
        /** Alpha (0..255) of the button outline */
        private const val OUTLINE_ALPHA = 82
        /** Turbulence grid size */
        private const val TURBULENCE_MOVEMENT = 4.3f
        /** Total animation duration in millis */
        private const val ANIMATION_DURATION = 5000L
    }
}

@@ -183,24 +216,25 @@ private class MagicActionBackgroundShader : RuntimeShader(SHADER) {
            """
            uniform float in_gridNum;
            uniform vec3 in_noiseMove;
            uniform half in_sparkleMove;
            uniform vec2 in_size;
            uniform float in_aspectRatio;
            uniform half in_time;
            uniform half in_pixelDensity;
            uniform float in_turbulenceAlpha;
            uniform float in_spkarkleAlpha;
            layout(color) uniform vec4 in_color;
            layout(color) uniform vec4 in_dimColor;
            layout(color) uniform vec4 in_effectColor;
        """
        private const val MAIN_SHADER =
            """vec4 main(vec2 p) {
            vec2 uv = p / in_size.xy;
            uv.x *= in_aspectRatio;
            vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
            half luma = 1.0 - getLuminosity(half3(simplex3d(noiseP)));
            half4 turbulenceColor = mix(in_color, in_dimColor, luma);
            float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time);
            half luma = getLuminosity(half3(simplex3d(noiseP)));
            half4 turbulenceColor = mix(in_color, in_effectColor, luma * in_turbulenceAlpha);
            float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_sparkleMove);
            sparkle = min(sparkle * in_spkarkleAlpha, in_spkarkleAlpha);
            return turbulenceColor + half4(half3(sparkle), 1.0);
            return saturate(turbulenceColor + half4(sparkle));
        }
        """
        private const val SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER