Add ellipse shape to the ripple.
Bug: b/203800342 Test: Manual, RippleViewTest Change-Id: I29fe4d05a868305814a980ef2bde53a608df0ce2
This commit is contained in:
@@ -25,6 +25,7 @@ import com.android.systemui.ripple.RippleView
|
||||
*/
|
||||
class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
|
||||
init {
|
||||
// TODO: use RippleShape#ELLIPSE when calling setupShader.
|
||||
setupShader()
|
||||
setRippleFill(true)
|
||||
duration = 3000L
|
||||
|
||||
@@ -36,7 +36,8 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
||||
/** Shapes that the [RippleShader] supports. */
|
||||
enum class RippleShape {
|
||||
CIRCLE,
|
||||
ROUNDED_BOX
|
||||
ROUNDED_BOX,
|
||||
ELLIPSE
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -57,12 +58,10 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
||||
uniform float in_sparkle_strength;"""
|
||||
|
||||
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 sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
|
||||
float inside = soften(sdCircle(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;
|
||||
|
||||
@@ -75,9 +74,26 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
||||
"""
|
||||
|
||||
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 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),
|
||||
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_ELLIPSE_MAIN = """vec4 main(vec2 p) {
|
||||
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
|
||||
|
||||
float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
|
||||
float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur);
|
||||
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
|
||||
* (1.-sparkleRing) * in_fadeSparkle;
|
||||
|
||||
@@ -90,13 +106,21 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
||||
"""
|
||||
|
||||
private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
|
||||
SdfShaderLibrary.CIRCLE_SDF + SHADER_CIRCLE_MAIN
|
||||
SdfShaderLibrary.SHADER_SDF_OPERATION_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
|
||||
RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
|
||||
SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
|
||||
private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
|
||||
SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
|
||||
SHADER_ELLIPSE_MAIN
|
||||
|
||||
private fun buildShader(rippleShape: RippleShape): String =
|
||||
if (rippleShape == RippleShape.CIRCLE) CIRCLE_SHADER else ROUNDED_BOX_SHADER
|
||||
when (rippleShape) {
|
||||
RippleShape.CIRCLE -> CIRCLE_SHADER
|
||||
RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
|
||||
RippleShape.ELLIPSE -> ELLIPSE_SHADER
|
||||
}
|
||||
|
||||
private fun subProgress(start: Float, end: Float, progress: Float): Float {
|
||||
val min = Math.min(start, end)
|
||||
|
||||
@@ -18,7 +18,8 @@ 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) {
|
||||
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;
|
||||
@@ -27,25 +28,25 @@ class RippleShaderUtilLibrary {
|
||||
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;
|
||||
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),
|
||||
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;
|
||||
}"""
|
||||
}"""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,22 +23,13 @@ class SdfShaderLibrary {
|
||||
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 circleRing(vec2 p, float radius) {
|
||||
float thicknessHalf = radius * 0.25;
|
||||
|
||||
float outerCircle = sdCircle(p-origin, radius + thicknessHalf);
|
||||
float innerCircle = sdCircle(p-origin, radius);
|
||||
float outerCircle = sdCircle(p, radius + thicknessHalf);
|
||||
float innerCircle = sdCircle(p, radius);
|
||||
|
||||
float d = max(outerCircle, -innerCircle);
|
||||
float blurHalf = blur * 0.5;
|
||||
|
||||
return smoothstep(-blurHalf, blurHalf, d);
|
||||
return subtract(outerCircle, innerCircle);
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -54,23 +45,66 @@ class SdfShaderLibrary {
|
||||
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 roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
|
||||
float borderThickness) {
|
||||
float outerRoundBox = sdRoundedBox(p, size, cornerRadius);
|
||||
float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness),
|
||||
cornerRadius - borderThickness);
|
||||
return subtract(outerRoundBox, innerRoundBox);
|
||||
}
|
||||
"""
|
||||
|
||||
// Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
|
||||
// This is more expensive than the regular circle SDF, recommend to use the circle SDF if
|
||||
// possible.
|
||||
const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
|
||||
wh *= 0.5;
|
||||
|
||||
// symmetry
|
||||
(wh.x > wh.y) ? wh = wh.yx, p = abs(p.yx) : p = abs(p);
|
||||
|
||||
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 U4 = u.x+U2;
|
||||
|
||||
float t = 0.5;
|
||||
for (int i = 0; i < 3; i ++) {
|
||||
float F1 = t*(t*t*(U1*t+U3)+U4)-U1;
|
||||
float F2 = t*t*(U5*t+U7)+U4;
|
||||
float F3 = t*(U6*t+U7);
|
||||
|
||||
t += (F1*F2)/(F1*F3-F2*F2);
|
||||
}
|
||||
|
||||
t = clamp(t, 0.0, 1.0);
|
||||
|
||||
float d = distance(p, wh*vec2(1.0-t*t,2.0*t)/(t*t+1.0));
|
||||
d /= wh.y;
|
||||
|
||||
return (dot(p/wh,p/wh)>1.0) ? d : -d;
|
||||
}
|
||||
|
||||
float ellipseRing(vec2 p, vec2 wh) {
|
||||
vec2 thicknessHalf = wh * 0.25;
|
||||
|
||||
float outerEllipse = sdEllipse(p, wh + thicknessHalf);
|
||||
float innerEllipse = sdEllipse(p, wh);
|
||||
|
||||
return subtract(outerEllipse, innerEllipse);
|
||||
}
|
||||
"""
|
||||
|
||||
const val SHADER_SDF_OPERATION_LIB = """
|
||||
float soften(float d, float blur) {
|
||||
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);
|
||||
float subtract(float outer, float inner) {
|
||||
return max(outer, -inner);
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
@@ -43,4 +43,9 @@ class RippleViewTest : SysuiTestCase() {
|
||||
fun testSetupShader_compilesRoundedBox() {
|
||||
rippleView.setupShader(RippleShader.RippleShape.ROUNDED_BOX)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetupShader_compilesEllipse() {
|
||||
rippleView.setupShader(RippleShader.RippleShape.ELLIPSE)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user