Merge "Update ripple spec" into sc-dev
This commit is contained in:
commit
a331c3390f
@ -27,8 +27,9 @@ import android.graphics.Paint;
|
||||
import android.graphics.RecordingCanvas;
|
||||
import android.graphics.animation.RenderNodeAnimator;
|
||||
import android.util.ArraySet;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.view.animation.PathInterpolator;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -36,32 +37,41 @@ import java.util.function.Consumer;
|
||||
* @hide
|
||||
*/
|
||||
public final class RippleAnimationSession {
|
||||
private static final int ENTER_ANIM_DURATION = 350;
|
||||
private static final int EXIT_ANIM_OFFSET = ENTER_ANIM_DURATION;
|
||||
private static final int EXIT_ANIM_DURATION = 350;
|
||||
private static final String TAG = "RippleAnimationSession";
|
||||
private static final int ENTER_ANIM_DURATION = 300;
|
||||
private static final int SLIDE_ANIM_DURATION = 450;
|
||||
private static final int EXIT_ANIM_DURATION = 300;
|
||||
private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
|
||||
// Matches R.interpolator.fast_out_slow_in but as we have no context we can't just import that
|
||||
private static final TimeInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
|
||||
|
||||
private static final TimeInterpolator PATH_INTERPOLATOR =
|
||||
new PathInterpolator(.2f, 0, 0, 1f);
|
||||
private Consumer<RippleAnimationSession> mOnSessionEnd;
|
||||
private AnimationProperties<Float, Paint> mProperties;
|
||||
private final AnimationProperties<Float, Paint> mProperties;
|
||||
private AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> mCanvasProperties;
|
||||
private Runnable mOnUpdate;
|
||||
private long mStartTime;
|
||||
private boolean mForceSoftware;
|
||||
private ArraySet<Animator> mActiveAnimations = new ArraySet(3);
|
||||
private final float mWidth, mHeight;
|
||||
private final ValueAnimator mSparkle = ValueAnimator.ofFloat(0, 1);
|
||||
private final ArraySet<Animator> mActiveAnimations = new ArraySet<>(3);
|
||||
|
||||
RippleAnimationSession(@NonNull AnimationProperties<Float, Paint> properties,
|
||||
boolean forceSoftware) {
|
||||
boolean forceSoftware, float width, float height) {
|
||||
mProperties = properties;
|
||||
mForceSoftware = forceSoftware;
|
||||
}
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
|
||||
void end() {
|
||||
for (Animator anim: mActiveAnimations) {
|
||||
if (anim != null) anim.end();
|
||||
}
|
||||
mActiveAnimations.clear();
|
||||
mSparkle.addUpdateListener(anim -> {
|
||||
final long now = AnimationUtils.currentAnimationTimeMillis();
|
||||
final long elapsed = now - mStartTime - ENTER_ANIM_DURATION;
|
||||
final float phase = (float) elapsed / 1000f;
|
||||
mProperties.getShader().setSecondsOffset(phase);
|
||||
notifyUpdate();
|
||||
});
|
||||
mSparkle.setDuration(ENTER_ANIM_DURATION);
|
||||
mSparkle.setStartDelay(ENTER_ANIM_DURATION);
|
||||
mSparkle.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
mSparkle.setRepeatCount(ValueAnimator.INFINITE);
|
||||
}
|
||||
|
||||
@NonNull RippleAnimationSession enter(Canvas canvas) {
|
||||
@ -70,17 +80,19 @@ public final class RippleAnimationSession {
|
||||
} else {
|
||||
enterSoftware();
|
||||
}
|
||||
mStartTime = System.nanoTime();
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull RippleAnimationSession exit(Canvas canvas) {
|
||||
mSparkle.end();
|
||||
if (isHwAccelerated(canvas)) exitHardware((RecordingCanvas) canvas);
|
||||
else exitSoftware();
|
||||
return this;
|
||||
}
|
||||
|
||||
private void onAnimationEnd(Animator anim) {
|
||||
notifyUpdate();
|
||||
mActiveAnimations.remove(anim);
|
||||
}
|
||||
|
||||
@ -92,7 +104,6 @@ public final class RippleAnimationSession {
|
||||
|
||||
RippleAnimationSession setOnAnimationUpdated(@Nullable Runnable run) {
|
||||
mOnUpdate = run;
|
||||
mProperties.setOnChange(mOnUpdate);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -122,14 +133,12 @@ public final class RippleAnimationSession {
|
||||
}
|
||||
|
||||
private long computeDelay() {
|
||||
long currentTime = System.nanoTime();
|
||||
long timePassed = (currentTime - mStartTime) / 1_000_000;
|
||||
long difference = EXIT_ANIM_OFFSET;
|
||||
return Math.max(difference - timePassed, 0);
|
||||
final long timePassed = AnimationUtils.currentAnimationTimeMillis() - mStartTime;
|
||||
return Math.max((long) SLIDE_ANIM_DURATION - timePassed, 0);
|
||||
}
|
||||
|
||||
private void notifyUpdate() {
|
||||
Runnable onUpdate = mOnUpdate;
|
||||
if (onUpdate != null) onUpdate.run();
|
||||
if (mOnUpdate != null) mOnUpdate.run();
|
||||
}
|
||||
|
||||
RippleAnimationSession setForceSoftwareAnimation(boolean forceSw) {
|
||||
@ -153,7 +162,7 @@ public final class RippleAnimationSession {
|
||||
}
|
||||
});
|
||||
exit.setTarget(canvas);
|
||||
exit.setInterpolator(DECELERATE_INTERPOLATOR);
|
||||
exit.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
|
||||
long delay = computeDelay();
|
||||
exit.setStartDelay(delay);
|
||||
@ -161,36 +170,67 @@ public final class RippleAnimationSession {
|
||||
mActiveAnimations.add(exit);
|
||||
}
|
||||
|
||||
private void enterHardware(RecordingCanvas can) {
|
||||
private void enterHardware(RecordingCanvas canvas) {
|
||||
AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>>
|
||||
props = getCanvasProperties();
|
||||
RenderNodeAnimator expand =
|
||||
new RenderNodeAnimator(props.getProgress(), .5f);
|
||||
expand.setTarget(can);
|
||||
expand.setDuration(ENTER_ANIM_DURATION);
|
||||
expand.addListener(new AnimatorListener(this));
|
||||
RenderNodeAnimator slideX =
|
||||
new RenderNodeAnimator(props.getX(), mWidth / 2);
|
||||
RenderNodeAnimator slideY =
|
||||
new RenderNodeAnimator(props.getY(), mHeight / 2);
|
||||
expand.setTarget(canvas);
|
||||
slideX.setTarget(canvas);
|
||||
slideY.setTarget(canvas);
|
||||
startAnimation(expand, slideX, slideY);
|
||||
}
|
||||
|
||||
private void startAnimation(Animator expand,
|
||||
Animator slideX, Animator slideY) {
|
||||
expand.setDuration(SLIDE_ANIM_DURATION);
|
||||
slideX.setDuration(SLIDE_ANIM_DURATION);
|
||||
slideY.setDuration(SLIDE_ANIM_DURATION);
|
||||
slideX.addListener(new AnimatorListener(this));
|
||||
expand.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
slideX.setInterpolator(PATH_INTERPOLATOR);
|
||||
slideY.setInterpolator(PATH_INTERPOLATOR);
|
||||
expand.start();
|
||||
slideX.start();
|
||||
slideY.start();
|
||||
if (!mSparkle.isRunning()) {
|
||||
mSparkle.start();
|
||||
mActiveAnimations.add(mSparkle);
|
||||
}
|
||||
mActiveAnimations.add(expand);
|
||||
mActiveAnimations.add(slideX);
|
||||
mActiveAnimations.add(slideY);
|
||||
}
|
||||
|
||||
private void enterSoftware() {
|
||||
ValueAnimator expand = ValueAnimator.ofFloat(0f, 0.5f);
|
||||
ValueAnimator slideX = ValueAnimator.ofFloat(
|
||||
mProperties.getX(), mWidth / 2);
|
||||
ValueAnimator slideY = ValueAnimator.ofFloat(
|
||||
mProperties.getY(), mHeight / 2);
|
||||
expand.addUpdateListener(updatedAnimation -> {
|
||||
notifyUpdate();
|
||||
mProperties.getShader().setProgress((Float) expand.getAnimatedValue());
|
||||
});
|
||||
expand.addListener(new AnimatorListener(this));
|
||||
expand.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
expand.start();
|
||||
mActiveAnimations.add(expand);
|
||||
slideX.addUpdateListener(anim -> {
|
||||
float x = (float) slideX.getAnimatedValue();
|
||||
float y = (float) slideY.getAnimatedValue();
|
||||
mProperties.setOrigin(x, y);
|
||||
mProperties.getShader().setOrigin(x, y);
|
||||
});
|
||||
startAnimation(expand, slideX, slideY);
|
||||
}
|
||||
|
||||
@NonNull AnimationProperties<Float, Paint> getProperties() {
|
||||
return mProperties;
|
||||
}
|
||||
|
||||
@NonNull AnimationProperties getCanvasProperties() {
|
||||
@NonNull
|
||||
AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> getCanvasProperties() {
|
||||
if (mCanvasProperties == null) {
|
||||
mCanvasProperties = new AnimationProperties<>(
|
||||
CanvasProperty.createFloat(mProperties.getX()),
|
||||
@ -209,6 +249,7 @@ public final class RippleAnimationSession {
|
||||
AnimatorListener(RippleAnimationSession session) {
|
||||
mSession = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
|
||||
@ -231,21 +272,12 @@ public final class RippleAnimationSession {
|
||||
}
|
||||
|
||||
static class AnimationProperties<FloatType, PaintType> {
|
||||
private final FloatType mY;
|
||||
private FloatType mProgress;
|
||||
private FloatType mMaxRadius;
|
||||
private final FloatType mProgress;
|
||||
private final FloatType mMaxRadius;
|
||||
private final PaintType mPaint;
|
||||
private final FloatType mX;
|
||||
private final RippleShader mShader;
|
||||
private Runnable mOnChange;
|
||||
|
||||
private void onChange() {
|
||||
if (mOnChange != null) mOnChange.run();
|
||||
}
|
||||
|
||||
private void setOnChange(Runnable onChange) {
|
||||
mOnChange = onChange;
|
||||
}
|
||||
private FloatType mX;
|
||||
private FloatType mY;
|
||||
|
||||
AnimationProperties(FloatType x, FloatType y, FloatType maxRadius,
|
||||
PaintType paint, FloatType progress, RippleShader shader) {
|
||||
@ -261,6 +293,11 @@ public final class RippleAnimationSession {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
void setOrigin(FloatType x, FloatType y) {
|
||||
mX = x;
|
||||
mY = y;
|
||||
}
|
||||
|
||||
FloatType getX() {
|
||||
return mX;
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ import android.graphics.RecordingCanvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Shader;
|
||||
import android.os.Build;
|
||||
import android.os.SystemProperties;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
@ -159,6 +160,9 @@ public class RippleDrawable extends LayerDrawable {
|
||||
/** The maximum number of ripples supported. */
|
||||
private static final int MAX_RIPPLES = 10;
|
||||
private static final LinearInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
|
||||
/** Temporary flag for teamfood. **/
|
||||
private static final boolean FORCE_PATTERNED_STYLE =
|
||||
SystemProperties.getBoolean("persist.material.patternedripple", false);
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
|
||||
@ -361,7 +365,9 @@ public class RippleDrawable extends LayerDrawable {
|
||||
}
|
||||
} else {
|
||||
if (focused || hovered) {
|
||||
enterPatternedBackgroundAnimation(focused, hovered);
|
||||
if (!pressed) {
|
||||
enterPatternedBackgroundAnimation(focused, hovered);
|
||||
}
|
||||
} else {
|
||||
exitPatternedBackgroundAnimation();
|
||||
}
|
||||
@ -571,7 +577,10 @@ public class RippleDrawable extends LayerDrawable {
|
||||
mState.mMaxRadius = a.getDimensionPixelSize(
|
||||
R.styleable.RippleDrawable_radius, mState.mMaxRadius);
|
||||
|
||||
mState.mRippleStyle = a.getInteger(R.styleable.RippleDrawable_rippleStyle, STYLE_SOLID);
|
||||
if (!FORCE_PATTERNED_STYLE) {
|
||||
mState.mRippleStyle = a.getInteger(R.styleable.RippleDrawable_rippleStyle,
|
||||
mState.mRippleStyle);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
|
||||
@ -812,21 +821,25 @@ public class RippleDrawable extends LayerDrawable {
|
||||
}
|
||||
|
||||
private void drawPatterned(@NonNull Canvas canvas) {
|
||||
final Rect bounds = getBounds();
|
||||
final Rect bounds = getDirtyBounds();
|
||||
final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
|
||||
boolean useCanvasProps = shouldUseCanvasProps(canvas);
|
||||
boolean changedHotspotBounds = !bounds.equals(mHotspotBounds);
|
||||
if (isBounded()) {
|
||||
canvas.clipRect(mHotspotBounds);
|
||||
canvas.clipRect(bounds);
|
||||
}
|
||||
float x, y;
|
||||
float x, y, w, h;
|
||||
if (changedHotspotBounds) {
|
||||
x = mHotspotBounds.exactCenterX();
|
||||
y = mHotspotBounds.exactCenterY();
|
||||
w = mHotspotBounds.width();
|
||||
h = mHotspotBounds.height();
|
||||
useCanvasProps = false;
|
||||
} else {
|
||||
x = mPendingX;
|
||||
y = mPendingY;
|
||||
w = bounds.width();
|
||||
h = bounds.height();
|
||||
}
|
||||
boolean shouldAnimate = mRippleActive;
|
||||
boolean shouldExit = mExitingAnimation;
|
||||
@ -837,9 +850,9 @@ public class RippleDrawable extends LayerDrawable {
|
||||
drawPatternedBackground(canvas);
|
||||
if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) {
|
||||
RippleAnimationSession.AnimationProperties<Float, Paint> properties =
|
||||
createAnimationProperties(x, y);
|
||||
mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps)
|
||||
.setOnAnimationUpdated(useCanvasProps ? null : () -> invalidateSelf(false))
|
||||
createAnimationProperties(x, y, w, h);
|
||||
mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps, w, h)
|
||||
.setOnAnimationUpdated(() -> invalidateSelf(false))
|
||||
.setOnSessionEnd(session -> {
|
||||
mRunningAnimations.remove(session);
|
||||
})
|
||||
@ -864,20 +877,8 @@ public class RippleDrawable extends LayerDrawable {
|
||||
} else {
|
||||
RippleAnimationSession.AnimationProperties<Float, Paint> p =
|
||||
s.getProperties();
|
||||
float posX, posY;
|
||||
if (changedHotspotBounds) {
|
||||
posX = x;
|
||||
posY = y;
|
||||
if (p.getPaint().getShader() instanceof RippleShader) {
|
||||
RippleShader shader = (RippleShader) p.getPaint().getShader();
|
||||
shader.setOrigin(posX, posY);
|
||||
}
|
||||
} else {
|
||||
posX = p.getX();
|
||||
posY = p.getY();
|
||||
}
|
||||
float radius = p.getMaxRadius();
|
||||
canvas.drawCircle(posX, posY, radius, p.getPaint());
|
||||
canvas.drawCircle(p.getX(), p.getY(), radius, p.getPaint());
|
||||
}
|
||||
}
|
||||
canvas.restoreToCount(saveCount);
|
||||
@ -905,14 +906,13 @@ public class RippleDrawable extends LayerDrawable {
|
||||
|
||||
private float computeRadius() {
|
||||
Rect b = getDirtyBounds();
|
||||
float gap = 0;
|
||||
float radius = (float) Math.sqrt(b.width() * b.width() + b.height() * b.height()) / 2 + gap;
|
||||
float radius = (float) Math.sqrt(b.width() * b.width() + b.height() * b.height()) / 2;
|
||||
return radius;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private RippleAnimationSession.AnimationProperties<Float, Paint> createAnimationProperties(
|
||||
float x, float y) {
|
||||
float x, float y, float w, float h) {
|
||||
Paint p = new Paint(mRipplePaint);
|
||||
float radius = mState.mMaxRadius;
|
||||
RippleAnimationSession.AnimationProperties<Float, Paint> properties;
|
||||
@ -920,19 +920,19 @@ public class RippleDrawable extends LayerDrawable {
|
||||
int color = mMaskColorFilter == null
|
||||
? mState.mColor.getColorForState(getState(), Color.BLACK)
|
||||
: mMaskColorFilter.getColor();
|
||||
color = color | 0xFF000000;
|
||||
shader.setColor(color);
|
||||
shader.setOrigin(x, y);
|
||||
shader.setResolution(w, h);
|
||||
shader.setSecondsOffset(0);
|
||||
shader.setRadius(radius);
|
||||
shader.setProgress(.0f);
|
||||
properties = new RippleAnimationSession.AnimationProperties<>(
|
||||
x, y, radius, p, 0f,
|
||||
shader);
|
||||
if (mMaskShader == null) {
|
||||
shader.setHasMask(false);
|
||||
shader.setShader(null);
|
||||
} else {
|
||||
shader.setShader(mMaskShader);
|
||||
shader.setHasMask(true);
|
||||
}
|
||||
p.setShader(shader);
|
||||
p.setColorFilter(null);
|
||||
@ -1160,7 +1160,7 @@ public class RippleDrawable extends LayerDrawable {
|
||||
// The ripple timing depends on the paint's alpha value, so we need
|
||||
// to push just the alpha channel into the paint and let the filter
|
||||
// handle the full-alpha color.
|
||||
int maskColor = color | 0xFF000000;
|
||||
int maskColor = mState.mRippleStyle == STYLE_PATTERNED ? color : color | 0xFF000000;
|
||||
if (mMaskColorFilter.getColor() != maskColor) {
|
||||
mMaskColorFilter = new PorterDuffColorFilter(maskColor, mMaskColorFilter.getMode());
|
||||
}
|
||||
@ -1276,7 +1276,7 @@ public class RippleDrawable extends LayerDrawable {
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA);
|
||||
int mMaxRadius = RADIUS_AUTO;
|
||||
int mRippleStyle = STYLE_SOLID;
|
||||
int mRippleStyle = FORCE_PATTERNED_STYLE ? STYLE_PATTERNED : STYLE_SOLID;
|
||||
|
||||
public RippleState(LayerState orig, RippleDrawable owner, Resources res) {
|
||||
super(orig, owner, res);
|
||||
|
@ -17,58 +17,153 @@
|
||||
package android.graphics.drawable;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.NonNull;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.RuntimeShader;
|
||||
import android.graphics.Shader;
|
||||
|
||||
final class RippleShader extends RuntimeShader {
|
||||
private static final String SHADER = "uniform float2 in_origin;\n"
|
||||
+ "uniform float in_maxRadius;\n"
|
||||
private static final String SHADER_UNIFORMS = "uniform vec2 in_origin;\n"
|
||||
+ "uniform float in_progress;\n"
|
||||
+ "uniform float in_maxRadius;\n"
|
||||
+ "uniform vec2 in_resolution;\n"
|
||||
+ "uniform float in_hasMask;\n"
|
||||
+ "uniform float4 in_color;\n"
|
||||
+ "uniform shader in_shader;\n"
|
||||
+ "float dist2(float2 p0, float2 pf) { return sqrt((pf.x - p0.x) * (pf.x - p0.x) + "
|
||||
+ "(pf.y - p0.y) * (pf.y - p0.y)); }\n"
|
||||
+ "float mod2(float a, float b) { return a - (b * floor(a / b)); }\n"
|
||||
+ "float rand(float2 src) { return fract(sin(dot(src.xy, float2(12.9898, 78.233))) * "
|
||||
+ "43758.5453123); }\n"
|
||||
+ "float4 main(float2 p)\n"
|
||||
+ "{\n"
|
||||
+ " float fraction = in_progress;\n"
|
||||
+ " float2 fragCoord = p;//sk_FragCoord.xy;\n"
|
||||
+ " float maxDist = in_maxRadius;\n"
|
||||
+ " float fragDist = dist2(in_origin, fragCoord.xy);\n"
|
||||
+ " float circleRadius = maxDist * fraction;\n"
|
||||
+ " float colorVal = (fragDist - circleRadius) / maxDist;\n"
|
||||
+ " float d = fragDist < circleRadius \n"
|
||||
+ " ? 1. - abs(colorVal * 3. * smoothstep(0., 1., fraction)) \n"
|
||||
+ " : 1. - abs(colorVal * 5.);\n"
|
||||
+ " d = smoothstep(0., 1., d);\n"
|
||||
+ " float divider = 2.;\n"
|
||||
+ " float x = floor(fragCoord.x / divider);\n"
|
||||
+ " float y = floor(fragCoord.y / divider);\n"
|
||||
+ " float density = .95;\n"
|
||||
+ " d = rand(float2(x, y)) > density ? d : d * .2;\n"
|
||||
+ " d = d * rand(float2(fraction, x * y));\n"
|
||||
+ " float alpha = 1. - pow(fraction, 2.);\n"
|
||||
+ " if (in_hasMask != 0.) {return sample(in_shader).a * in_color * d * alpha;}\n"
|
||||
+ " return in_color * d * alpha;\n"
|
||||
+ "uniform float in_secondsOffset;\n"
|
||||
+ "uniform vec4 in_color;\n"
|
||||
+ "uniform shader in_shader;\n";
|
||||
private static final String SHADER_LIB =
|
||||
"float triangleNoise(vec2 n) {\n"
|
||||
+ " n = fract(n * vec2(5.3987, 5.4421));\n"
|
||||
+ " n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));\n"
|
||||
+ " float xy = n.x * n.y;\n"
|
||||
+ " return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;\n"
|
||||
+ "}"
|
||||
+ "const float PI = 3.1415926535897932384626;\n"
|
||||
+ "\n"
|
||||
+ "float threshold(float v, float l, float h) {\n"
|
||||
+ " return step(l, v) * (1.0 - step(h, v));\n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "float sparkles(vec2 uv, float t) {\n"
|
||||
+ " float n = triangleNoise(uv);\n"
|
||||
+ " float s = 0.0;\n"
|
||||
+ " for (float i = 0; i < 4; i += 1) {\n"
|
||||
+ " float l = i * 0.25;\n"
|
||||
+ " float h = l + 0.025;\n"
|
||||
+ " float o = abs(sin(0.1 * PI * (t + i)));\n"
|
||||
+ " s += threshold(n + o, l, h);\n"
|
||||
+ " }\n"
|
||||
+ " return saturate(s);\n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "float softCircle(vec2 uv, vec2 xy, float radius, float blur) {\n"
|
||||
+ " float blurHalf = blur * 0.5;\n"
|
||||
+ " float d = distance(uv, xy);\n"
|
||||
+ " return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);\n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "float softRing(vec2 uv, vec2 xy, float radius, float blur) {\n"
|
||||
+ " float thickness = 0.4;\n"
|
||||
+ " float circle_outer = softCircle(uv, xy, radius + thickness * 0.5, blur);\n"
|
||||
+ " float circle_inner = softCircle(uv, xy, radius - thickness * 0.5, blur);\n"
|
||||
+ " return circle_outer - circle_inner;\n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "struct Viewport {\n"
|
||||
+ " float aspect;\n"
|
||||
+ " vec2 uv;\n"
|
||||
+ " vec2 resolution_pixels;\n"
|
||||
+ "};\n"
|
||||
+ "\n"
|
||||
+ "Viewport getViewport(vec2 frag_coord, vec2 resolution_pixels) {\n"
|
||||
+ " Viewport v;\n"
|
||||
+ " v.aspect = resolution_pixels.y / resolution_pixels.x;\n"
|
||||
+ " v.uv = frag_coord / resolution_pixels;\n"
|
||||
+ " v.uv.y = (1.0 - v.uv.y) * v.aspect;\n"
|
||||
+ " v.resolution_pixels = resolution_pixels;\n"
|
||||
+ " return v;\n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "vec2 getTouch(vec2 touch_position_pixels, Viewport viewport) {\n"
|
||||
+ " vec2 touch = touch_position_pixels / viewport.resolution_pixels;\n"
|
||||
+ " touch.y *= viewport.aspect;\n"
|
||||
+ " return touch;\n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "struct Wave {\n"
|
||||
+ " float ring;\n"
|
||||
+ " float circle;\n"
|
||||
+ "};\n"
|
||||
+ "\n"
|
||||
+ "Wave getWave(Viewport viewport, vec2 touch, float progress) {\n"
|
||||
+ " float fade = pow((clamp(progress, 0.8, 1.0)), 8.);\n"
|
||||
+ " Wave w;\n"
|
||||
+ " w.ring = max(softRing(viewport.uv, touch, progress, 0.45) - fade, 0.);\n"
|
||||
+ " w.circle = softCircle(viewport.uv, touch, 2.0 * progress, 0.2) - progress;\n"
|
||||
+ " return w;\n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "vec4 getRipple(vec4 color, float loudness, float sparkle, Wave wave) {\n"
|
||||
+ " float alpha = wave.ring * sparkle * loudness\n"
|
||||
+ " + wave.circle * color.a;\n"
|
||||
+ " return vec4(color.rgb, saturate(alpha));\n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "float getRingMask(vec2 frag, vec2 center, float r, float progress) {\n"
|
||||
+ " float dist = distance(frag, center);\n"
|
||||
+ " float expansion = r * .6;\n"
|
||||
+ " r = r * min(1.,progress);\n"
|
||||
+ " float minD = max(r - expansion, 0.);\n"
|
||||
+ " float maxD = r + expansion;\n"
|
||||
+ " if (dist > maxD || dist < minD) return .0;\n"
|
||||
+ " return min(maxD - dist, dist - minD) / expansion; \n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "float subProgress(float start, float end, float progress) {\n"
|
||||
+ " float sub = clamp(progress, start, end);\n"
|
||||
+ " return (sub - start) / (end - start); \n"
|
||||
+ "}\n";
|
||||
private static final String SHADER_MAIN = "vec4 main(vec2 p) {\n"
|
||||
+ " float fadeIn = subProgress(0., 0.175, in_progress);\n"
|
||||
+ " float fadeOutNoise = subProgress(0.375, 1., in_progress);\n"
|
||||
+ " float fadeOutRipple = subProgress(0.375, 0.75, in_progress);\n"
|
||||
+ " Viewport vp = getViewport(p, in_resolution);\n"
|
||||
+ " vec2 touch = getTouch(in_origin, vp);\n"
|
||||
+ " Wave w = getWave(vp, touch, in_progress * 0.25);\n"
|
||||
+ " float ring = getRingMask(p, in_origin, in_maxRadius, fadeIn);\n"
|
||||
+ " float alpha = min(fadeIn, 1. - fadeOutNoise);\n"
|
||||
+ " float sparkle = sparkles(p, in_progress * 0.25 + in_secondsOffset)\n"
|
||||
+ " * ring * alpha;\n"
|
||||
+ " vec4 r = getRipple(in_color, 1., sparkle, w);\n"
|
||||
+ " float fade = min(fadeIn, 1.-fadeOutRipple);\n"
|
||||
+ " vec4 circle = vec4(in_color.rgb, softCircle(p, in_origin, in_maxRadius "
|
||||
+ " * fadeIn, 0.2) * fade * in_color.a);\n"
|
||||
+ " float mask = in_hasMask == 1. ? sample(in_shader).a > 0. ? 1. : 0. : 1.;\n"
|
||||
+ " return mix(circle, vec4(1.), sparkle * mask);\n"
|
||||
+ "}";
|
||||
private static final String SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN;
|
||||
|
||||
RippleShader() {
|
||||
super(SHADER, false);
|
||||
}
|
||||
|
||||
public void setShader(@NonNull Shader s) {
|
||||
setInputShader("in_shader", s);
|
||||
public void setShader(Shader shader) {
|
||||
if (shader != null) {
|
||||
setInputShader("in_shader", shader);
|
||||
}
|
||||
setUniform("in_hasMask", shader == null ? 0 : 1);
|
||||
}
|
||||
|
||||
public void setRadius(float radius) {
|
||||
setUniform("in_maxRadius", radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Continuous offset used as noise phase.
|
||||
*/
|
||||
public void setSecondsOffset(float t) {
|
||||
setUniform("in_secondsOffset", t);
|
||||
}
|
||||
|
||||
public void setOrigin(float x, float y) {
|
||||
setUniform("in_origin", new float[] {x, y});
|
||||
}
|
||||
@ -77,13 +172,16 @@ final class RippleShader extends RuntimeShader {
|
||||
setUniform("in_progress", progress);
|
||||
}
|
||||
|
||||
public void setHasMask(boolean hasMask) {
|
||||
setUniform("in_hasMask", hasMask ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Color of the circle that's under the sparkles. Sparkles will always be white.
|
||||
*/
|
||||
public void setColor(@ColorInt int colorIn) {
|
||||
Color color = Color.valueOf(colorIn);
|
||||
this.setUniform("in_color", new float[] {color.red(),
|
||||
color.green(), color.blue(), color.alpha()});
|
||||
}
|
||||
|
||||
public void setResolution(float w, float h) {
|
||||
setUniform("in_resolution", w, h);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user