1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.charging
18 
19 import android.graphics.Color
20 import android.graphics.PointF
21 import android.graphics.RuntimeShader
22 import android.util.MathUtils
23 
24 /**
25  * Shader class that renders a distorted ripple for the UDFPS dwell effect.
26  * Adjustable shader parameters:
27  *   - progress
28  *   - origin
29  *   - color
30  *   - time
31  *   - maxRadius
32  *   - distortionStrength.
33  * See per field documentation for more details.
34  *
35  * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
36  */
37 class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) {
38     companion object {
39         private const val SHADER_UNIFORMS = """uniform vec2 in_origin;
40                 uniform float in_time;
41                 uniform float in_radius;
42                 uniform float in_blur;
43                 uniform vec4 in_color;
44                 uniform float in_phase1;
45                 uniform float in_phase2;
46                 uniform float in_distortion_strength;"""
47         private const val SHADER_LIB = """
48                 float softCircle(vec2 uv, vec2 xy, float radius, float blur) {
49                   float blurHalf = blur * 0.5;
50                   float d = distance(uv, xy);
51                   return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);
52                 }
53 
54                 float softRing(vec2 uv, vec2 xy, float radius, float blur) {
55                   float thickness_half = radius * 0.25;
56                   float circle_outer = softCircle(uv, xy, radius + thickness_half, blur);
57                   float circle_inner = softCircle(uv, xy, radius - thickness_half, blur);
58                   return circle_outer - circle_inner;
59                 }
60 
61                 vec2 distort(vec2 p, float time, float distort_amount_xy, float frequency) {
62                     return p + vec2(sin(p.y * frequency + in_phase1),
63                                     cos(p.x * frequency * -1.23 + in_phase2)) * distort_amount_xy;
64                 }
65 
66                 vec4 ripple(vec2 p, float distort_xy, float frequency) {
67                     vec2 p_distorted = distort(p, in_time, distort_xy, frequency);
68                     float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur);
69                     float rippleAlpha = max(circle,
70                         softRing(p_distorted, in_origin, in_radius, in_blur)) * 0.25;
71                     return in_color * rippleAlpha;
72                 }
73                 """
74         private const val SHADER_MAIN = """vec4 main(vec2 p) {
75                     vec4 color1 = ripple(p,
76                         34 * in_distortion_strength, // distort_xy
77                         0.012 // frequency
78                     );
79                     vec4 color2 = ripple(p,
80                         49 * in_distortion_strength, // distort_xy
81                         0.018 // frequency
82                     );
83                     // Alpha blend between two layers.
84                     return vec4(color1.xyz + color2.xyz
85                         * (1 - color1.w), color1.w + color2.w * (1-color1.w));
86                 }"""
87         private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN
88     }
89 
90     /**
91      * Maximum radius of the ripple.
92      */
93     var maxRadius: Float = 0.0f
94 
95     /**
96      * Origin coordinate of the ripple.
97      */
98     var origin: PointF = PointF()
99         set(value) {
100             field = value
101             setUniform("in_origin", floatArrayOf(value.x, value.y))
102         }
103 
104     /**
105      * Progress of the ripple. Float value between [0, 1].
106      */
107     var progress: Float = 0.0f
108         set(value) {
109             field = value
110             setUniform("in_radius",
111                     (1 - (1 - value) * (1 - value) * (1 - value))* maxRadius)
112             setUniform("in_blur", MathUtils.lerp(1f, 0.7f, value))
113         }
114 
115     /**
116      * Distortion strength between [0, 1], with 0 being no distortion and 1 being full distortion.
117      */
118     var distortionStrength: Float = 0.0f
119         set(value) {
120             field = value
121             setUniform("in_distortion_strength", value)
122         }
123 
124     /**
125      * Play time since the start of the effect in seconds.
126      */
127     var time: Float = 0.0f
128         set(value) {
129             field = value * 0.001f
130             setUniform("in_time", field)
131             setUniform("in_phase1", field * 3f + 0.367f)
132             setUniform("in_phase2", field * 7.2f * 1.531f)
133         }
134 
135     /**
136      * A hex value representing the ripple color, in the format of ARGB
137      */
138     var color: Int = 0xffffff.toInt()
139         set(value) {
140             field = value
141             val color = Color.valueOf(value)
142             setUniform("in_color", floatArrayOf(color.red(),
143                     color.green(), color.blue(), color.alpha()))
144         }
145 }
146