Loading packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt 0 → 100644 +111 −0 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 } } packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt 0 → 100644 +65 −0 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 } } packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt 0 → 100644 +144 −0 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) } } packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt 0 → 100644 +50 −0 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 } } packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt 0 → 100644 +91 −0 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() } } Loading
packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt 0 → 100644 +111 −0 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 } }
packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt 0 → 100644 +65 −0 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 } }
packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt 0 → 100644 +144 −0 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) } }
packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt 0 → 100644 +50 −0 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 } }
packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt 0 → 100644 +91 −0 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() } }