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

Commit 657aa093 authored by Yein Jo's avatar Yein Jo Committed by Android (Google) Code Review
Browse files

Merge changes from topic "polish-docking-anim" into tm-qpr-dev

* changes:
  Update uniform params for the tablet docking animation.
  Support multiple sizes at specific progress t in RippleShader.
  Expose blur, Draw a ring outside of the given size in RippleShader.
parents 16ac8f21 6a731c2c
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -68,7 +68,7 @@ class RippleAnimation(private val config: RippleAnimationConfig) {
    private fun applyConfigToShader() {
        with(rippleShader) {
            setCenter(config.centerX, config.centerY)
            setMaxSize(config.maxWidth, config.maxHeight)
            rippleSize.setMaxSize(config.maxWidth, config.maxHeight)
            pixelDensity = config.pixelDensity
            color = ColorUtils.setAlphaComponent(config.color, config.opacity)
            sparkleStrength = config.sparkleStrength
+124 −21
Original line number Diff line number Diff line
@@ -15,9 +15,10 @@
 */
package com.android.systemui.surfaceeffects.ripple

import android.graphics.PointF
import android.graphics.RuntimeShader
import android.util.Log
import android.util.MathUtils
import androidx.annotation.VisibleForTesting
import com.android.systemui.animation.Interpolators
import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
@@ -44,6 +45,8 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
    }
    // language=AGSL
    companion object {
        private val TAG = RippleShader::class.simpleName

        // Default fade in/ out values. The value range is [0,1].
        const val DEFAULT_FADE_IN_START = 0f
        const val DEFAULT_FADE_OUT_END = 1f
@@ -99,7 +102,7 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
            vec4 main(vec2 p) {
                float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
                    in_thickness), in_blur);
                float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
                float inside = soften(sdRoundedBox(p-in_center, in_size * 1.25, in_cornerRadius),
                    in_blur);
                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
                    * (1.-sparkleRing) * in_fadeSparkle;
@@ -184,12 +187,17 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
        setFloatUniform("in_center", x, y)
    }

    /** Max width of the ripple. */
    private var maxSize: PointF = PointF()
    fun setMaxSize(width: Float, height: Float) {
        maxSize.x = width
        maxSize.y = height
    }
    /**
     * Blur multipliers for the ripple.
     *
     * <p>It interpolates from [blurStart] to [blurEnd] based on the [progress]. Increase number to
     * add more blur.
     */
    var blurStart: Float = 1.25f
    var blurEnd: Float = 0.5f

    /** Size of the ripple. */
    val rippleSize = RippleSize()

    /**
     * Linear progress of the ripple. Float value between [0, 1].
@@ -209,15 +217,19 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
    /** Progress with Standard easing curve applied. */
    private var progress: Float = 0.0f
        set(value) {
            currentWidth = maxSize.x * value
            currentHeight = maxSize.y * value
            setFloatUniform("in_size", currentWidth, currentHeight)
            field = value

            setFloatUniform("in_thickness", maxSize.y * value * 0.5f)
            // radius should not exceed width and height values.
            setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * value)
            rippleSize.update(value)

            setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
            setFloatUniform("in_size", rippleSize.currentWidth, rippleSize.currentHeight)
            setFloatUniform("in_thickness", rippleSize.currentHeight * 0.5f)
            // Corner radius is always max of the min between the current width and height.
            setFloatUniform(
                "in_cornerRadius",
                Math.min(rippleSize.currentWidth, rippleSize.currentHeight)
            )

            setFloatUniform("in_blur", MathUtils.lerp(blurStart, blurEnd, value))
        }

    /** Play time since the start of the effect. */
@@ -264,12 +276,6 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
            setFloatUniform("in_pixelDensity", value)
        }

    var currentWidth: Float = 0f
        private set

    var currentHeight: Float = 0f
        private set

    /** Parameters that are used to fade in/ out of the sparkle ring. */
    val sparkleRingFadeParams =
        FadeParams(
@@ -342,4 +348,101 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
        /** The endpoint of the fade out, given that the animation goes from 0 to 1. */
        var fadeOutEnd: Float = DEFAULT_FADE_OUT_END,
    )

    /**
     * Desired size of the ripple at a point t in [progress].
     *
     * <p>Note that [progress] is curved and normalized. Below is an example usage:
     * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 0.2f, width= 500f, height=
     * 700f), SizeAtProgress(t= 1f, width= 100f, height= 300f)
     *
     * <p>For simple ripple effects, you will want to use [setMaxSize] as it is translated into:
     * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 1f, width= maxWidth, height=
     * maxHeight)
     */
    data class SizeAtProgress(
        /** Time t in [0,1] progress range. */
        var t: Float,
        /** Target width size of the ripple at time [t]. */
        var width: Float,
        /** Target height size of the ripple at time [t]. */
        var height: Float
    )

    /** Updates and stores the ripple size. */
    inner class RippleSize {
        @VisibleForTesting var sizes = mutableListOf<SizeAtProgress>()
        @VisibleForTesting var currentSizeIndex = 0
        @VisibleForTesting val initialSize = SizeAtProgress(0f, 0f, 0f)

        var currentWidth: Float = 0f
            private set
        var currentHeight: Float = 0f
            private set

        /**
         * Sets the max size of the ripple.
         *
         * <p>Use this if the ripple shape simply changes linearly.
         */
        fun setMaxSize(width: Float, height: Float) {
            setSizeAtProgresses(initialSize, SizeAtProgress(1f, width, height))
        }

        /**
         * Sets the list of [sizes].
         *
         * <p>Note that setting this clears the existing sizes.
         */
        fun setSizeAtProgresses(vararg sizes: SizeAtProgress) {
            // Reset everything.
            this.sizes.clear()
            currentSizeIndex = 0

            this.sizes.addAll(sizes)
            this.sizes.sortBy { it.t }
        }

        /**
         * Updates the current ripple size based on the progress.
         *
         * <p>Should be called when progress updates.
         */
        fun update(progress: Float) {
            val targetIndex = updateTargetIndex(progress)
            val prevIndex = Math.max(targetIndex - 1, 0)

            val targetSize = sizes[targetIndex]
            val prevSize = sizes[prevIndex]

            val subProgress = subProgress(prevSize.t, targetSize.t, progress)

            currentWidth = targetSize.width * subProgress + prevSize.width
            currentHeight = targetSize.height * subProgress + prevSize.height
        }

        private fun updateTargetIndex(progress: Float): Int {
            if (sizes.isEmpty()) {
                // It could be empty on init.
                if (progress > 0f) {
                    Log.e(
                        TAG,
                        "Did you forget to set the ripple size? Use [setMaxSize] or " +
                            "[setSizeAtProgresses] before playing the animation."
                    )
                }
                // If there's no size is set, we set everything to 0.
                setSizeAtProgresses(initialSize)
            }

            var candidate = sizes[currentSizeIndex]

            while (progress > candidate.t) {
                currentSizeIndex = Math.min(currentSizeIndex + 1, sizes.size - 1)
                candidate = sizes[currentSizeIndex]
            }

            return currentSizeIndex
        }
    }
}
+110 −10
Original line number Diff line number Diff line
@@ -45,12 +45,8 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a

    var duration: Long = 1750

    private var maxWidth: Float = 0.0f
    private var maxHeight: Float = 0.0f
    fun setMaxSize(maxWidth: Float, maxHeight: Float) {
        this.maxWidth = maxWidth
        this.maxHeight = maxHeight
        rippleShader.setMaxSize(maxWidth, maxHeight)
        rippleShader.rippleSize.setMaxSize(maxWidth, maxHeight)
    }

    private var centerX: Float = 0.0f
@@ -84,6 +80,106 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
        ripplePaint.shader = rippleShader
    }

    /**
     * Sets the fade parameters for the base ring.
     *
     * <p>Base ring indicates a blurred ring below the sparkle ring. See
     * [RippleShader.baseRingFadeParams].
     */
    @JvmOverloads
    fun setBaseRingFadeParams(
        fadeInStart: Float = rippleShader.baseRingFadeParams.fadeInStart,
        fadeInEnd: Float = rippleShader.baseRingFadeParams.fadeInEnd,
        fadeOutStart: Float = rippleShader.baseRingFadeParams.fadeOutStart,
        fadeOutEnd: Float = rippleShader.baseRingFadeParams.fadeOutEnd
    ) {
        setFadeParams(
            rippleShader.baseRingFadeParams,
            fadeInStart,
            fadeInEnd,
            fadeOutStart,
            fadeOutEnd
        )
    }

    /**
     * Sets the fade parameters for the sparkle ring.
     *
     * <p>Sparkle ring refers to the ring that's drawn on top of the base ring. See
     * [RippleShader.sparkleRingFadeParams].
     */
    @JvmOverloads
    fun setSparkleRingFadeParams(
        fadeInStart: Float = rippleShader.sparkleRingFadeParams.fadeInStart,
        fadeInEnd: Float = rippleShader.sparkleRingFadeParams.fadeInEnd,
        fadeOutStart: Float = rippleShader.sparkleRingFadeParams.fadeOutStart,
        fadeOutEnd: Float = rippleShader.sparkleRingFadeParams.fadeOutEnd
    ) {
        setFadeParams(
            rippleShader.sparkleRingFadeParams,
            fadeInStart,
            fadeInEnd,
            fadeOutStart,
            fadeOutEnd
        )
    }

    /**
     * Sets the fade parameters for the center fill.
     *
     * <p>One common use case is set all the params to 1, which completely removes the center fill.
     * See [RippleShader.centerFillFadeParams].
     */
    @JvmOverloads
    fun setCenterFillFadeParams(
        fadeInStart: Float = rippleShader.centerFillFadeParams.fadeInStart,
        fadeInEnd: Float = rippleShader.centerFillFadeParams.fadeInEnd,
        fadeOutStart: Float = rippleShader.centerFillFadeParams.fadeOutStart,
        fadeOutEnd: Float = rippleShader.centerFillFadeParams.fadeOutEnd
    ) {
        setFadeParams(
            rippleShader.centerFillFadeParams,
            fadeInStart,
            fadeInEnd,
            fadeOutStart,
            fadeOutEnd
        )
    }

    private fun setFadeParams(
        fadeParams: RippleShader.FadeParams,
        fadeInStart: Float,
        fadeInEnd: Float,
        fadeOutStart: Float,
        fadeOutEnd: Float
    ) {
        with(fadeParams) {
            this.fadeInStart = fadeInStart
            this.fadeInEnd = fadeInEnd
            this.fadeOutStart = fadeOutStart
            this.fadeOutEnd = fadeOutEnd
        }
    }

    /**
     * Sets blur multiplier at start and end of the progress.
     *
     * <p>It interpolates between [start] and [end]. No need to set it if using default blur.
     */
    fun setBlur(start: Float, end: Float) {
        rippleShader.blurStart = start
        rippleShader.blurEnd = end
    }

    /**
     * Sets the list of [RippleShader.SizeAtProgress].
     *
     * <p>Note that this clears the list before it sets with the new data.
     */
    fun setSizeAtProgresses(vararg targetSizes: RippleShader.SizeAtProgress) {
        rippleShader.rippleSize.setSizeAtProgresses(*targetSizes)
    }

    @JvmOverloads
    fun startRipple(onAnimationEnd: Runnable? = null) {
        if (animator.isRunning) {
@@ -133,13 +229,13 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
        }
        // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
        // active effect area. Values here should be kept in sync with the animation implementation
        // in the ripple shader. (Twice bigger)
        // in the ripple shader.
        if (rippleShape == RippleShape.CIRCLE) {
            val maskRadius = rippleShader.currentWidth
            val maskRadius = rippleShader.rippleSize.currentWidth
            canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
        } else {
            val maskWidth = rippleShader.currentWidth * 2
            val maskHeight = rippleShader.currentHeight * 2
        } else if (rippleShape == RippleShape.ELLIPSE) {
            val maskWidth = rippleShader.rippleSize.currentWidth * 2
            val maskHeight = rippleShader.rippleSize.currentHeight * 2
            canvas.drawRect(
                /* left= */ centerX - maskWidth,
                /* top= */ centerY - maskHeight,
@@ -147,6 +243,10 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
                /* bottom= */ centerY + maskHeight,
                ripplePaint
            )
        } else { // RippleShape.RoundedBox
            // No masking for the rounded box, as it has more blur which requires larger bounds.
            // Masking creates sharp bounds even when the masking is 4 times bigger.
            canvas.drawPaint(ripplePaint)
        }
    }
}
+9 −6
Original line number Diff line number Diff line
@@ -50,9 +50,9 @@ class SdfShaderLibrary {

            float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
                float borderThickness) {
                float outerRoundBox = sdRoundedBox(p, size, cornerRadius);
                float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness),
                    cornerRadius - borderThickness);
                float outerRoundBox = sdRoundedBox(p, size + vec2(borderThickness),
                    cornerRadius + borderThickness);
                float innerRoundBox = sdRoundedBox(p, size, cornerRadius);
                return subtract(outerRoundBox, innerRoundBox);
            }
        """
@@ -69,10 +69,13 @@ class SdfShaderLibrary {

            vec2 u = wh*p, v = wh*wh;

            float U1 = u.y/2.0;  float U5 = 4.0*U1;
            float U2 = v.y-v.x;  float U6 = 6.0*U1;
            float U3 = u.x-U2;   float U7 = 3.0*U3;
            float U1 = u.y/2.0;
            float U2 = v.y-v.x;
            float U3 = u.x-U2;
            float U4 = u.x+U2;
            float U5 = 4.0*U1;
            float U6 = 6.0*U1;
            float U7 = 3.0*U3;

            float t = 0.5;
            for (int i = 0; i < 3; i ++) {
+2 −2
Original line number Diff line number Diff line
@@ -75,7 +75,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
        }
    private var radius: Float = 0f
        set(value) {
            rippleShader.setMaxSize(value * 2f, value * 2f)
            rippleShader.rippleSize.setMaxSize(value * 2f, value * 2f)
            field = value
        }
    private var origin: Point = Point()
@@ -364,7 +364,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at

        if (drawRipple) {
            canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(),
                    rippleShader.currentWidth, ripplePaint)
                    rippleShader.rippleSize.currentWidth, ripplePaint)
        }
    }
}
Loading