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

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

Revert "Add RippleRevealEffect"

This reverts commit ab7c0fee.

Change-Id: Icc2f30399a9a1256f4ec9b963056b013c52a0080
parent ab7c0fee
Loading
Loading
Loading
Loading
+0 −111
Original line number Diff line number Diff line
/*
 * Copyright (C) 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
 *
 *      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.surfaceeffects.revealeffect

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.graphics.RenderEffect
import androidx.core.graphics.ColorUtils
import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
import com.android.systemui.surfaceeffects.utils.MathUtils
import kotlin.math.max
import kotlin.math.min

/** Creates a reveal effect with a circular ripple sparkles on top. */
class RippleRevealEffect(
    private val config: RippleRevealEffectConfig,
    private val renderEffectCallback: RenderEffectDrawCallback,
    private val stateChangedCallback: AnimationStateChangedCallback? = null
) {
    private val rippleRevealShader = RippleRevealShader().apply { applyConfig(config) }
    private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)

    fun play() {
        if (animator.isRunning) {
            return
        }

        animator.duration = config.duration.toLong()
        animator.addUpdateListener { updateListener ->
            val playTime = updateListener.currentPlayTime.toFloat()
            rippleRevealShader.setTime(playTime * TIME_SCALE_FACTOR)

            // Compute radius.
            val progress = updateListener.animatedValue as Float
            val innerRad = MathUtils.lerp(config.innerRadiusStart, config.innerRadiusEnd, progress)
            val outerRad = MathUtils.lerp(config.outerRadiusStart, config.outerRadiusEnd, progress)
            rippleRevealShader.setInnerRadius(innerRad)
            rippleRevealShader.setOuterRadius(outerRad)

            // Compute alphas.
            val innerAlphaProgress =
                MathUtils.constrainedMap(
                    1f,
                    0f,
                    config.innerFadeOutStart,
                    config.duration,
                    playTime
                )
            val outerAlphaProgress =
                MathUtils.constrainedMap(
                    1f,
                    0f,
                    config.outerFadeOutStart,
                    config.duration,
                    playTime
                )
            val innerAlpha = MathUtils.lerp(0f, 255f, innerAlphaProgress)
            val outerAlpha = MathUtils.lerp(0f, 255f, outerAlphaProgress)

            val innerColor = ColorUtils.setAlphaComponent(config.innerColor, innerAlpha.toInt())
            val outerColor = ColorUtils.setAlphaComponent(config.outerColor, outerAlpha.toInt())
            rippleRevealShader.setInnerColor(innerColor)
            rippleRevealShader.setOuterColor(outerColor)

            // Pass in progresses since those functions take in normalized alpha values.
            rippleRevealShader.setBackgroundAlpha(max(innerAlphaProgress, outerAlphaProgress))
            rippleRevealShader.setSparkleAlpha(min(innerAlphaProgress, outerAlphaProgress))

            // Trigger draw callback.
            renderEffectCallback.onDraw(
                RenderEffect.createRuntimeShaderEffect(
                    rippleRevealShader,
                    RippleRevealShader.BACKGROUND_UNIFORM
                )
            )
        }
        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    stateChangedCallback?.onAnimationEnd()
                }
            }
        )
        animator.start()
        stateChangedCallback?.onAnimationStart()
    }

    interface AnimationStateChangedCallback {
        fun onAnimationStart()
        fun onAnimationEnd()
    }

    private companion object {
        private const val TIME_SCALE_FACTOR = 0.00175f
    }
}
+0 −65
Original line number Diff line number Diff line
/*
 * Copyright (C) 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
 *
 *      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.surfaceeffects.revealeffect

import android.graphics.Color

/** Defines parameters needed for [RippleRevealEffect]. */
data class RippleRevealEffectConfig(
    /** Total duration of the animation. */
    val duration: Float = 0f,
    /** Timestamp of when the inner mask starts fade out. (Linear fadeout) */
    val innerFadeOutStart: Float = 0f,
    /** Timestamp of when the outer mask starts fade out. (Linear fadeout) */
    val outerFadeOutStart: Float = 0f,
    /** Center x position of the effect. */
    val centerX: Float = 0f,
    /** Center y position of the effect. */
    val centerY: Float = 0f,
    /** Start radius of the inner circle. */
    val innerRadiusStart: Float = 0f,
    /** End radius of the inner circle. */
    val innerRadiusEnd: Float = 0f,
    /** Start radius of the outer circle. */
    val outerRadiusStart: Float = 0f,
    /** End radius of the outer circle. */
    val outerRadiusEnd: Float = 0f,
    /**
     * Pixel density of the display. Do not pass a random value. The value must come from
     * [context.resources.displayMetrics.density].
     */
    val pixelDensity: Float = 1f,
    /**
     * The amount the circle masks should be softened. Higher value will make the edge of the circle
     * mask soft.
     */
    val blurAmount: Float = 0f,
    /** Color of the inner circle mask. */
    val innerColor: Int = Color.WHITE,
    /** Color of the outer circle mask. */
    val outerColor: Int = Color.WHITE,
    /** Multiplier to make the sparkles visible. */
    val sparkleStrength: Float = SPARKLE_STRENGTH,
    /** Size of the sparkle. Expected range [0, 1]. */
    val sparkleScale: Float = SPARKLE_SCALE
) {
    /** Default parameters. */
    companion object {
        const val SPARKLE_STRENGTH: Float = 0.3f
        const val SPARKLE_SCALE: Float = 0.8f
    }
}
+0 −144
Original line number Diff line number Diff line
/*
 * Copyright (C) 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
 *
 *      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.surfaceeffects.revealeffect

import android.graphics.RuntimeShader
import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary

/** Circular reveal effect with sparkles. */
class RippleRevealShader : RuntimeShader(SHADER) {
    // language=AGSL
    companion object {
        const val BACKGROUND_UNIFORM = "in_dst"
        private const val MAIN =
            """
            uniform shader ${BACKGROUND_UNIFORM};
            uniform half in_dstAlpha;
            uniform half in_time;
            uniform vec2 in_center;
            uniform half in_innerRadius;
            uniform half in_outerRadius;
            uniform half in_sparkleStrength;
            uniform half in_blur;
            uniform half in_pixelDensity;
            uniform half in_sparkleScale;
            uniform half in_sparkleAlpha;
            layout(color) uniform vec4 in_innerColor;
            layout(color) uniform vec4 in_outerColor;

            vec4 main(vec2 p) {
                half innerMask = soften(sdCircle(p - in_center, in_innerRadius), in_blur);
                half outerMask = soften(sdCircle(p - in_center, in_outerRadius), in_blur);

                // Flip it since we are interested in the circle.
                innerMask = 1.-innerMask;
                outerMask = 1.-outerMask;

                // Color two circles using the mask.
                vec4 inColor = vec4(in_innerColor.rgb, 1.) * in_innerColor.a;
                vec4 outColor = vec4(in_outerColor.rgb, 1.) * in_outerColor.a;
                vec4 blend = mix(inColor, outColor, innerMask);

                vec4 dst = vec4(in_dst.eval(p).rgb, 1.);
                dst *= in_dstAlpha;

                blend *= blend.a;
                // Do normal blend with the background.
                blend = blend + dst * (1. - blend.a);

                half sparkle =
                    sparkles(p - mod(p, in_pixelDensity * in_sparkleScale), in_time);
                // Add sparkles using additive blending.
                blend += sparkle * in_sparkleStrength * in_sparkleAlpha;

                // Mask everything at the end.
                blend *= outerMask;

                return blend;
            }
        """

        private const val SHADER =
            ShaderUtilLibrary.SHADER_LIB +
                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
                SdfShaderLibrary.CIRCLE_SDF +
                MAIN
    }

    fun applyConfig(config: RippleRevealEffectConfig) {
        setCenter(config.centerX, config.centerY)
        setInnerRadius(config.innerRadiusStart)
        setOuterRadius(config.outerRadiusStart)
        setBlurAmount(config.blurAmount)
        setPixelDensity(config.pixelDensity)
        setSparkleScale(config.sparkleScale)
        setSparkleStrength(config.sparkleStrength)
        setInnerColor(config.innerColor)
        setOuterColor(config.outerColor)
    }

    fun setTime(time: Float) {
        setFloatUniform("in_time", time)
    }

    fun setCenter(centerX: Float, centerY: Float) {
        setFloatUniform("in_center", centerX, centerY)
    }

    fun setInnerRadius(radius: Float) {
        setFloatUniform("in_innerRadius", radius)
    }

    fun setOuterRadius(radius: Float) {
        setFloatUniform("in_outerRadius", radius)
    }

    fun setBlurAmount(blurAmount: Float) {
        setFloatUniform("in_blur", blurAmount)
    }

    fun setPixelDensity(density: Float) {
        setFloatUniform("in_pixelDensity", density)
    }

    fun setSparkleScale(scale: Float) {
        setFloatUniform("in_sparkleScale", scale)
    }

    fun setSparkleStrength(strength: Float) {
        setFloatUniform("in_sparkleStrength", strength)
    }

    fun setInnerColor(color: Int) {
        setColorUniform("in_innerColor", color)
    }

    fun setOuterColor(color: Int) {
        setColorUniform("in_outerColor", color)
    }

    /** Sets the background alpha. Range [0,1]. */
    fun setBackgroundAlpha(alpha: Float) {
        setFloatUniform("in_dstAlpha", alpha)
    }

    /** Sets the sparkle alpha. Range [0,1]. */
    fun setSparkleAlpha(alpha: Float) {
        setFloatUniform("in_sparkleAlpha", alpha)
    }
}
+0 −50
Original line number Diff line number Diff line
/*
 * Copyright (C) 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
 *
 *      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.surfaceeffects.utils

/** Copied from android.utils.MathUtils */
object MathUtils {
    fun constrainedMap(
        rangeMin: Float,
        rangeMax: Float,
        valueMin: Float,
        valueMax: Float,
        value: Float
    ): Float {
        return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value))
    }

    fun lerp(start: Float, stop: Float, amount: Float): Float {
        return start + (stop - start) * amount
    }

    fun lerpInv(a: Float, b: Float, value: Float): Float {
        return if (a != b) (value - a) / (b - a) else 0.0f
    }

    fun saturate(value: Float): Float {
        return constrain(value, 0.0f, 1.0f)
    }

    fun lerpInvSat(a: Float, b: Float, value: Float): Float {
        return saturate(lerpInv(a, b, value))
    }

    fun constrain(amount: Float, low: Float, high: Float): Float {
        return if (amount < low) low else if (amount > high) high else amount
    }
}
+0 −91
Original line number Diff line number Diff line
/*
 * Copyright (C) 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
 *
 *      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.surfaceeffects.revealeffect

import android.graphics.RenderEffect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.model.SysUiStateTest
import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class RippleRevealEffectTest : SysUiStateTest() {

    @get:Rule val animatorTestRule = AnimatorTestRule(this)

    @Test
    fun play_triggersDrawCallback() {
        var effectFromCallback: RenderEffect? = null
        val revealEffectConfig = RippleRevealEffectConfig(duration = 1000f)
        val drawCallback =
            object : RenderEffectDrawCallback {
                override fun onDraw(renderEffect: RenderEffect) {
                    effectFromCallback = renderEffect
                }
            }
        val revealEffect = RippleRevealEffect(revealEffectConfig, drawCallback)
        assertThat(effectFromCallback).isNull()

        revealEffect.play()

        animatorTestRule.advanceTimeBy(500L)

        assertThat(effectFromCallback).isNotNull()
    }

    @Test
    fun play_triggersStateChangedCallback() {
        val revealEffectConfig = RippleRevealEffectConfig(duration = 1000f)
        val drawCallback =
            object : RenderEffectDrawCallback {
                override fun onDraw(renderEffect: RenderEffect) {}
            }
        var animationStartedCalled = false
        var animationEndedCalled = false
        val stateChangedCallback =
            object : RippleRevealEffect.AnimationStateChangedCallback {
                override fun onAnimationStart() {
                    animationStartedCalled = true
                }

                override fun onAnimationEnd() {
                    animationEndedCalled = true
                }
            }
        val revealEffect =
            RippleRevealEffect(revealEffectConfig, drawCallback, stateChangedCallback)

        assertThat(animationStartedCalled).isFalse()
        assertThat(animationEndedCalled).isFalse()

        revealEffect.play()

        assertThat(animationStartedCalled).isTrue()

        animatorTestRule.advanceTimeBy(revealEffectConfig.duration.toLong())

        assertThat(animationEndedCalled).isTrue()
    }
}