diff options
11 files changed, 342 insertions, 141 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index fef7383634f0..1c574808e0e9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -75,12 +75,12 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } private var radius: Float = 0f set(value) { - rippleShader.radius = value + rippleShader.setMaxSize(value * 2f, value * 2f) field = value } private var origin: PointF = PointF() set(value) { - rippleShader.origin = value + rippleShader.setCenter(value.x, value.y) field = value } diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt index 8292e52b1ffd..da675de2b9ec 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt @@ -19,7 +19,6 @@ package com.android.systemui.charging import android.content.Context import android.content.res.Configuration import android.graphics.PixelFormat -import android.graphics.PointF import android.os.SystemProperties import android.util.DisplayMetrics import android.view.View @@ -85,7 +84,7 @@ class WiredChargingRippleController @Inject constructor( private var debounceLevel = 0 @VisibleForTesting - var rippleView: RippleView = RippleView(context, attrs = null) + var rippleView: RippleView = RippleView(context, attrs = null).also { it.setupShader() } init { pluggedIn = batteryController.isPluggedIn @@ -177,20 +176,25 @@ class WiredChargingRippleController @Inject constructor( context.display.getRealMetrics(displayMetrics) val width = displayMetrics.widthPixels val height = displayMetrics.heightPixels - rippleView.radius = Integer.max(width, height).toFloat() - rippleView.origin = when (RotationUtils.getExactRotation(context)) { + val maxDiameter = Integer.max(width, height) * 2f + rippleView.setMaxSize(maxDiameter, maxDiameter) + when (RotationUtils.getExactRotation(context)) { RotationUtils.ROTATION_LANDSCAPE -> { - PointF(width * normalizedPortPosY, height * (1 - normalizedPortPosX)) + rippleView.setCenter( + width * normalizedPortPosY, height * (1 - normalizedPortPosX)) } RotationUtils.ROTATION_UPSIDE_DOWN -> { - PointF(width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY)) + rippleView.setCenter( + width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY)) } RotationUtils.ROTATION_SEASCAPE -> { - PointF(width * (1 - normalizedPortPosY), height * normalizedPortPosX) + rippleView.setCenter( + width * (1 - normalizedPortPosY), height * normalizedPortPosX) } else -> { // ROTATION_NONE - PointF(width * normalizedPortPosX, height * normalizedPortPosY) + rippleView.setCenter( + width * normalizedPortPosX, height * normalizedPortPosY) } } } diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index f6368ee19e8f..65400c22ebd7 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -21,7 +21,6 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Color; -import android.graphics.PointF; import android.util.AttributeSet; import android.util.TypedValue; import android.view.ContextThemeWrapper; @@ -34,6 +33,7 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.ripple.RippleShader; import com.android.systemui.ripple.RippleView; import java.text.NumberFormat; @@ -138,6 +138,8 @@ public class WirelessChargingLayout extends FrameLayout { animatorSetScrim.start(); mRippleView = findViewById(R.id.wireless_charging_ripple); + // TODO: Make rounded box shape if the device is tablet. + mRippleView.setupShader(RippleShader.RippleShape.CIRCLE); OnAttachStateChangeListener listener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View view) { @@ -230,11 +232,11 @@ public class WirelessChargingLayout extends FrameLayout { if (mRippleView != null) { int width = getMeasuredWidth(); int height = getMeasuredHeight(); - mRippleView.setColor( - Utils.getColorAttr(mRippleView.getContext(), - android.R.attr.colorAccent).getDefaultColor()); - mRippleView.setOrigin(new PointF(width / 2, height / 2)); - mRippleView.setRadius(Math.max(width, height) * 0.5f); + mRippleView.setCenter(width * 0.5f, height * 0.5f); + float maxSize = Math.max(width, height); + mRippleView.setMaxSize(maxSize, maxSize); + mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(), + android.R.attr.colorAccent).getDefaultColor()); } super.onLayout(changed, left, top, right, bottom); diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 99a5b8b2d450..5dc65ff194b9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -19,7 +19,6 @@ package com.android.systemui.media.taptotransfer.receiver import android.annotation.SuppressLint import android.app.StatusBarManager import android.content.Context -import android.graphics.PointF import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.media.MediaRoute2Info @@ -201,10 +200,10 @@ class MediaTttChipControllerReceiver @Inject constructor( val height = windowBounds.height() val width = windowBounds.width() - rippleView.radius = height / 5f + val maxDiameter = height / 2.5f + rippleView.setMaxSize(maxDiameter, maxDiameter) // Center the ripple on the bottom of the screen in the middle. - rippleView.origin = PointF(/* x= */ width / 2f, /* y= */ height.toFloat()) - + rippleView.setCenter(width * 0.5f, height.toFloat()) val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent) val colorWithAlpha = ColorUtils.setAlphaComponent(color, 70) rippleView.setColor(colorWithAlpha) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt index fed546bf52c2..8fd4440fe7a5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt @@ -23,10 +23,9 @@ import com.android.systemui.ripple.RippleView /** * An expanding ripple effect for the media tap-to-transfer receiver chip. */ -class ReceiverChipRippleView( - context: Context?, attrs: AttributeSet? -) : RippleView(context, attrs) { +class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) { init { + setupShader() setRippleFill(true) duration = 3000L } diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt index 93a2efc4e96d..f4b94ede3bd9 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt @@ -20,92 +20,83 @@ import android.graphics.RuntimeShader import android.util.MathUtils /** - * Shader class that renders an expanding charging ripple effect. A charging ripple contains - * three elements: - * 1. an expanding filled circle that appears in the beginning and quickly fades away + * Shader class that renders an expanding ripple effect. The ripple contains three elements: + * + * 1. an expanding filled [RippleShape] that appears in the beginning and quickly fades away * 2. an expanding ring that appears throughout the effect * 3. an expanding ring-shaped area that reveals noise over #2. * + * The ripple shader will be default to the circle shape if not specified. + * * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java. */ -class RippleShader internal constructor() : RuntimeShader(SHADER) { +class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) : + RuntimeShader(buildShader(rippleShape)) { + + /** Shapes that the [RippleShader] supports. */ + enum class RippleShape { + CIRCLE, + ROUNDED_BOX + } + companion object { - private const val SHADER_UNIFORMS = """uniform vec2 in_origin; + private const val SHADER_UNIFORMS = """uniform vec2 in_center; + uniform vec2 in_size; uniform float in_progress; - uniform float in_maxRadius; + uniform float in_cornerRadius; + uniform float in_thickness; uniform float in_time; uniform float in_distort_radial; uniform float in_distort_xy; - uniform float in_radius; uniform float in_fadeSparkle; - uniform float in_fadeCircle; + uniform float in_fadeFill; uniform float in_fadeRing; uniform float in_blur; uniform float in_pixelDensity; layout(color) uniform vec4 in_color; uniform float in_sparkle_strength;""" - private const val SHADER_LIB = """float triangleNoise(vec2 n) { - n = fract(n * vec2(5.3987, 5.4421)); - n += dot(n.yx, n.xy + vec2(21.5351, 14.3137)); - float xy = n.x * n.y; - return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0; - } - const float PI = 3.1415926535897932384626; - - float threshold(float v, float l, float h) { - return step(l, v) * (1.0 - step(h, v)); - } - - float sparkles(vec2 uv, float t) { - float n = triangleNoise(uv); - float s = 0.0; - for (float i = 0; i < 4; i += 1) { - float l = i * 0.01; - float h = l + 0.1; - float o = smoothstep(n - l, h, n); - o *= abs(sin(PI * o * (t + 0.55 * i))); - s += o; - } - return s; - } - - float softCircle(vec2 uv, vec2 xy, float radius, float blur) { - float blurHalf = blur * 0.5; - float d = distance(uv, xy); - return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius); - } - - float softRing(vec2 uv, vec2 xy, float radius, float blur) { - float thickness_half = radius * 0.25; - float circle_outer = softCircle(uv, xy, radius + thickness_half, blur); - float circle_inner = softCircle(uv, xy, radius - thickness_half, blur); - return circle_outer - circle_inner; - } - - vec2 distort(vec2 p, vec2 origin, float time, - float distort_amount_radial, float distort_amount_xy) { - float2 distance = origin - p; - float angle = atan(distance.y, distance.x); - return p + vec2(sin(angle * 8 + time * 0.003 + 1.641), - cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial - + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123), - cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy; - }""" - private const val SHADER_MAIN = """vec4 main(vec2 p) { - vec2 p_distorted = distort(p, in_origin, in_time, in_distort_radial, - in_distort_xy); - - // Draw shapes - float sparkleRing = softRing(p_distorted, in_origin, in_radius, in_blur); - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) - * sparkleRing * in_fadeSparkle; - float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur); - float rippleAlpha = max(circle * in_fadeCircle, - softRing(p_distorted, in_origin, in_radius, in_blur) * in_fadeRing) * 0.45; - vec4 ripple = in_color * rippleAlpha; - return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); - }""" - private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN + + private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) { + // distortion only applies to circle + vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy); + float radius = in_size.x * 0.5; + + float sparkleRing = softRing(p_distorted, in_center, radius, in_blur); + float inside = softCircle(p_distorted, in_center, radius * 1.2, in_blur); + float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) + * (1.-sparkleRing) * in_fadeSparkle; + + float rippleInsideAlpha = (1.-inside) * in_fadeFill; + float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; + float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45; + vec4 ripple = in_color * rippleAlpha; + return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); + } + """ + + private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) { + float sparkleRing = softRoundedBoxRing(p, in_center, in_size, in_cornerRadius, + in_thickness, in_blur); + float inside = softRoundedBox(p, in_center, in_size * 1.2, in_cornerRadius, in_blur); + float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) + * (1.-sparkleRing) * in_fadeSparkle; + + float rippleInsideAlpha = (1.-inside) * in_fadeFill; + float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; + float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45; + vec4 ripple = in_color * rippleAlpha; + return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); + } + """ + + private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB + + SdfShaderLibrary.CIRCLE_SDF + SHADER_CIRCLE_MAIN + private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS + + RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.ROUNDED_BOX_SDF + + SHADER_ROUNDED_BOX_MAIN + + private fun buildShader(rippleShape: RippleShape): String = + if (rippleShape == RippleShape.CIRCLE) CIRCLE_SHADER else ROUNDED_BOX_SHADER private fun subProgress(start: Float, end: Float, progress: Float): Float { val min = Math.min(start, end) @@ -116,22 +107,18 @@ class RippleShader internal constructor() : RuntimeShader(SHADER) { } /** - * Maximum radius of the ripple. + * Sets the center position of the ripple. */ - var radius: Float = 0.0f - set(value) { - field = value - setFloatUniform("in_maxRadius", value) - } + fun setCenter(x: Float, y: Float) { + setFloatUniform("in_center", x, y) + } - /** - * Origin coordinate of the ripple. - */ - var origin: PointF = PointF() - set(value) { - field = value - setFloatUniform("in_origin", value.x, value.y) - } + /** Max width of the ripple. */ + private var maxSize: PointF = PointF() + fun setMaxSize(width: Float, height: Float) { + maxSize.x = width + maxSize.y = height + } /** * Progress of the ripple. Float value between [0, 1]. @@ -140,20 +127,27 @@ class RippleShader internal constructor() : RuntimeShader(SHADER) { set(value) { field = value setFloatUniform("in_progress", value) - setFloatUniform("in_radius", - (1 - (1 - value) * (1 - value) * (1 - value))* radius) + val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value) + + setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg, + /* height= */ maxSize.y * curvedProg) + setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f) + // radius should not exceed width and height values. + setFloatUniform("in_cornerRadius", + Math.min(maxSize.x, maxSize.y) * curvedProg) + setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value)) val fadeIn = subProgress(0f, 0.1f, value) val fadeOutNoise = subProgress(0.4f, 1f, value) var fadeOutRipple = 0f - var fadeCircle = 0f + var fadeFill = 0f if (!rippleFill) { - fadeCircle = subProgress(0f, 0.2f, value) + fadeFill = subProgress(0f, 0.6f, value) fadeOutRipple = subProgress(0.3f, 1f, value) } setFloatUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise)) - setFloatUniform("in_fadeCircle", 1 - fadeCircle) + setFloatUniform("in_fadeFill", 1 - fadeFill) setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple)) } @@ -169,7 +163,7 @@ class RippleShader internal constructor() : RuntimeShader(SHADER) { /** * A hex value representing the ripple color, in the format of ARGB */ - var color: Int = 0xffffff.toInt() + var color: Int = 0xffffff set(value) { field = value setColorUniform("in_color", value) diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt new file mode 100644 index 000000000000..2dbc0ba2d8f5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 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.ripple + +/** A common utility functions that are used for computing [RippleShader]. */ +class RippleShaderUtilLibrary { + companion object { + const val SHADER_LIB = """float triangleNoise(vec2 n) { + n = fract(n * vec2(5.3987, 5.4421)); + n += dot(n.yx, n.xy + vec2(21.5351, 14.3137)); + float xy = n.x * n.y; + return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0; + } + const float PI = 3.1415926535897932384626; + + float sparkles(vec2 uv, float t) { + float n = triangleNoise(uv); + float s = 0.0; + for (float i = 0; i < 4; i += 1) { + float l = i * 0.01; + float h = l + 0.1; + float o = smoothstep(n - l, h, n); + o *= abs(sin(PI * o * (t + 0.55 * i))); + s += o; + } + return s; + } + + vec2 distort(vec2 p, float time, float distort_amount_radial, + float distort_amount_xy) { + float angle = atan(p.y, p.x); + return p + vec2(sin(angle * 8 + time * 0.003 + 1.641), + cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial + + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123), + cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy; + }""" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt index fc52464ecf85..83d9f2da1db1 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt @@ -23,39 +23,42 @@ import android.content.Context import android.content.res.Configuration import android.graphics.Canvas import android.graphics.Paint -import android.graphics.PointF import android.util.AttributeSet import android.view.View +import com.android.systemui.ripple.RippleShader.RippleShape private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f +private const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt() /** - * A generic expanding ripple effect. To trigger the ripple expansion, set [radius] and [origin], - * then call [startRipple]. + * A generic expanding ripple effect. + * + * Set up the shader with a desired [RippleShape] using [setupShader], [setMaxSize] and [setCenter], + * then call [startRipple] to trigger the ripple expansion. */ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - private val rippleShader = RippleShader() - private val defaultColor: Int = 0xffffffff.toInt() + + private lateinit var rippleShader: RippleShader + private lateinit var rippleShape: RippleShape private val ripplePaint = Paint() var rippleInProgress: Boolean = false - var radius: Float = 0.0f - set(value) { - rippleShader.radius = value - field = value - } - var origin: PointF = PointF() - set(value) { - rippleShader.origin = value - field = value - } var duration: Long = 1750 - init { - rippleShader.color = defaultColor - rippleShader.progress = 0f - rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH - ripplePaint.shader = rippleShader + 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) + } + + private var centerX: Float = 0.0f + private var centerY: Float = 0.0f + fun setCenter(x: Float, y: Float) { + this.centerX = x + this.centerY = y + rippleShader.setCenter(x, y) } override fun onConfigurationChanged(newConfig: Configuration?) { @@ -68,6 +71,18 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a super.onAttachedToWindow() } + /** Initializes the shader. Must be called before [startRipple]. */ + fun setupShader(rippleShape: RippleShape = RippleShape.CIRCLE) { + this.rippleShape = rippleShape + rippleShader = RippleShader(rippleShape) + + rippleShader.color = RIPPLE_DEFAULT_COLOR + rippleShader.progress = 0f + rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH + + ripplePaint.shader = rippleShader + } + @JvmOverloads fun startRipple(onAnimationEnd: Runnable? = null) { if (rippleInProgress) { @@ -113,11 +128,24 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a // if it's unsupported. return } - // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover - // the active effect area. Values here should be kept in sync with the - // animation implementation in the ripple shader. - val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * - (1 - rippleShader.progress)) * radius * 2 - canvas.drawCircle(origin.x, origin.y, maskRadius, ripplePaint) + // 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. + if (rippleShape == RippleShape.CIRCLE) { + val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * + (1 - rippleShader.progress)) * maxWidth + canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint) + } else { + val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * + (1 - rippleShader.progress)) * maxWidth * 2 + val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * + (1 - rippleShader.progress)) * maxHeight * 2 + canvas.drawRect( + /* left= */ centerX - maskWidth, + /* top= */ centerY - maskHeight, + /* right= */ centerX + maskWidth, + /* bottom= */ centerY + maskHeight, + ripplePaint) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt new file mode 100644 index 000000000000..4360105375cc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 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.ripple + +/** Library class that contains 2D signed distance functions. */ +class SdfShaderLibrary { + companion object { + const val CIRCLE_SDF = """ + float sdCircle(vec2 p, float r) { + return (length(p)-r) / r; + } + + float softCircle(vec2 p, vec2 origin, float radius, float blur) { + float d = sdCircle(p-origin, radius); + float blurHalf = blur * 0.5; + return smoothstep(-blurHalf, blurHalf, d); + } + + float softRing(vec2 p, vec2 origin, float radius, float blur) { + float thicknessHalf = radius * 0.25; + + float outerCircle = sdCircle(p-origin, radius + thicknessHalf); + float innerCircle = sdCircle(p-origin, radius); + + float d = max(outerCircle, -innerCircle); + float blurHalf = blur * 0.5; + + return smoothstep(-blurHalf, blurHalf, d); + } + """ + + const val ROUNDED_BOX_SDF = """ + float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) { + size *= 0.5; + cornerRadius *= 0.5; + vec2 d = abs(p)-size+cornerRadius; + + float outside = length(max(d, 0.0)); + float inside = min(max(d.x, d.y), 0.0); + + return (outside+inside-cornerRadius)/size.y; + } + + float softRoundedBox(vec2 p, vec2 origin, vec2 size, float cornerRadius, float blur) { + float d = sdRoundedBox(p-origin, size, cornerRadius); + float blurHalf = blur * 0.5; + return smoothstep(-blurHalf, blurHalf, d); + } + + float softRoundedBoxRing(vec2 p, vec2 origin, vec2 size, float cornerRadius, + float borderThickness, float blur) { + + float outerRoundBox = sdRoundedBox(p-origin, size, cornerRadius); + float innerRoundBox = sdRoundedBox(p-origin, size - vec2(borderThickness), + cornerRadius - borderThickness); + + float d = max(outerRoundBox, -innerRoundBox); + float blurHalf = blur * 0.5; + + return smoothstep(-blurHalf, blurHalf, d); + } + """ + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt index 6978490a1252..3ac28c8d0d37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt @@ -35,12 +35,12 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.any import org.mockito.Mockito.eq -import org.mockito.Mockito.reset import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -63,6 +63,7 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { controller = WiredChargingRippleController( commandRegistry, batteryController, configurationController, featureFlags, context, windowManager, systemClock, uiEventLogger) + rippleView.setupShader() controller.rippleView = rippleView // Replace the real ripple view with a mock instance controller.registerCallbacks() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt new file mode 100644 index 000000000000..f247c651cbca --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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.ripple + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class RippleViewTest : SysuiTestCase() { + @Mock + private lateinit var rippleView: RippleView + + @Before + fun setup() { + rippleView = RippleView(context, null) + } + + @Test + fun testSetupShader_compilesCircle() { + rippleView.setupShader(RippleShader.RippleShape.CIRCLE) + } + + @Test + fun testSetupShader_compilesRoundedBox() { + rippleView.setupShader(RippleShader.RippleShape.ROUNDED_BOX) + } +} |