Replace the hard gloss streak with soft overlapping color blobs (violet top-left, blue center, orange/coral right and bottom) that drift slightly for a subtle shimmer, matching the GhostCloud sponsor.png reference.
167 lines
5.7 KiB
Java
167 lines
5.7 KiB
Java
package tw.nekomimi.nekogram.helpers;
|
|
|
|
import android.graphics.Canvas;
|
|
import android.graphics.ColorFilter;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Path;
|
|
import android.graphics.PixelFormat;
|
|
import android.graphics.RadialGradient;
|
|
import android.graphics.Rect;
|
|
import android.graphics.Shader;
|
|
import android.graphics.drawable.Drawable;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import org.telegram.messenger.AndroidUtilities;
|
|
|
|
/**
|
|
* A soft, airbrushed 3D heart badge matching the GhostCloud "sponsor.png":
|
|
* violet in the upper-left, blue through the centre, warm orange/coral on the
|
|
* right and bottom, all blended smoothly with a gentle glossy highlight. The
|
|
* colour blobs drift slightly so the badge subtly shimmers.
|
|
*
|
|
* It self-invalidates each frame, so when attached to a view via
|
|
* {@code setRightDrawable(...)} / {@code setRightDrawable2(...)} the host keeps
|
|
* repainting automatically (the SimpleTextView drawable callback chain handles
|
|
* invalidation).
|
|
*/
|
|
public class ShimmerHeartDrawable extends Drawable {
|
|
|
|
private final Paint basePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
private final Paint blobPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
private final Paint highlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
private final Path heart = new Path();
|
|
|
|
private int lastWidth = -1;
|
|
private int lastHeight = -1;
|
|
|
|
private static final long CYCLE_MS = 5200L;
|
|
private final int size;
|
|
|
|
public ShimmerHeartDrawable() {
|
|
this(AndroidUtilities.dp(18));
|
|
}
|
|
|
|
public ShimmerHeartDrawable(int sizePx) {
|
|
this.size = sizePx;
|
|
basePaint.setStyle(Paint.Style.FILL);
|
|
blobPaint.setStyle(Paint.Style.FILL);
|
|
highlightPaint.setStyle(Paint.Style.FILL);
|
|
}
|
|
|
|
private void buildHeart(Rect b) {
|
|
heart.reset();
|
|
float w = b.width();
|
|
float h = b.height();
|
|
float l = b.left;
|
|
float t = b.top;
|
|
float cx = l + w / 2f;
|
|
heart.moveTo(cx, t + h * 0.30f);
|
|
heart.cubicTo(l + w * 0.40f, t + h * 0.05f, l + w * 0.02f, t + h * 0.18f, l + w * 0.10f, t + h * 0.45f);
|
|
heart.cubicTo(l + w * 0.17f, t + h * 0.66f, l + w * 0.40f, t + h * 0.80f, cx, t + h * 0.97f);
|
|
heart.cubicTo(l + w * 0.60f, t + h * 0.80f, l + w * 0.83f, t + h * 0.66f, l + w * 0.90f, t + h * 0.45f);
|
|
heart.cubicTo(l + w * 0.98f, t + h * 0.18f, l + w * 0.60f, t + h * 0.05f, cx, t + h * 0.30f);
|
|
heart.close();
|
|
}
|
|
|
|
/** A soft circular blob of one colour fading to transparent. */
|
|
private void drawBlob(Canvas canvas, float cx, float cy, float r, int color) {
|
|
blobPaint.setShader(new RadialGradient(
|
|
cx, cy, Math.max(1f, r),
|
|
new int[]{color, color & 0x00FFFFFF},
|
|
new float[]{0f, 1f},
|
|
Shader.TileMode.CLAMP));
|
|
canvas.drawRect(getBounds(), blobPaint);
|
|
}
|
|
|
|
@Override
|
|
protected void onBoundsChange(@NonNull Rect bounds) {
|
|
super.onBoundsChange(bounds);
|
|
buildHeart(bounds);
|
|
lastWidth = bounds.width();
|
|
lastHeight = bounds.height();
|
|
}
|
|
|
|
@Override
|
|
public void draw(@NonNull Canvas canvas) {
|
|
Rect b = getBounds();
|
|
if (b.width() == 0 || b.height() == 0) {
|
|
return;
|
|
}
|
|
if (lastWidth != b.width() || lastHeight != b.height()) {
|
|
buildHeart(b);
|
|
lastWidth = b.width();
|
|
lastHeight = b.height();
|
|
}
|
|
|
|
float w = b.width();
|
|
float h = b.height();
|
|
float l = b.left;
|
|
float t = b.top;
|
|
|
|
// Subtle drift so the colours softly shimmer.
|
|
float phase = (System.currentTimeMillis() % CYCLE_MS) / (float) CYCLE_MS;
|
|
double a = phase * 2 * Math.PI;
|
|
float dx = (float) Math.cos(a) * w * 0.05f;
|
|
float dy = (float) Math.sin(a) * h * 0.05f;
|
|
|
|
int save = canvas.save();
|
|
canvas.clipPath(heart);
|
|
|
|
// Light lavender base so blends stay airy.
|
|
basePaint.setShader(null);
|
|
basePaint.setColor(0xFFB9A8F0);
|
|
canvas.drawRect(b, basePaint);
|
|
|
|
// Soft overlapping colour blobs (airbrushed look).
|
|
// Violet — upper-left.
|
|
drawBlob(canvas, l + w * (0.30f) + dx, t + h * (0.28f) + dy, w * 0.62f, 0xFF7A2FE0);
|
|
// Blue — centre-left.
|
|
drawBlob(canvas, l + w * (0.40f) - dx, t + h * (0.55f) + dy, w * 0.55f, 0xFF4F6BFF);
|
|
// Cyan/blue glow — centre.
|
|
drawBlob(canvas, l + w * (0.52f) + dy, t + h * (0.45f) - dx, w * 0.42f, 0xCC59B7FF);
|
|
// Warm orange — right.
|
|
drawBlob(canvas, l + w * (0.82f) - dx, t + h * (0.40f) - dy, w * 0.60f, 0xFFFF9A3D);
|
|
// Coral/pink — bottom-right.
|
|
drawBlob(canvas, l + w * (0.70f) + dx, t + h * (0.80f) + dy, w * 0.55f, 0xFFFF7E5A);
|
|
// Deep violet — bottom-left corner for contrast.
|
|
drawBlob(canvas, l + w * (0.22f) + dx, t + h * (0.82f) - dy, w * 0.45f, 0xCC6A2BC8);
|
|
|
|
// Gentle glossy highlight, upper-centre.
|
|
highlightPaint.setShader(new RadialGradient(
|
|
l + w * 0.45f + dx, t + h * 0.30f + dy, w * 0.40f,
|
|
new int[]{0x80FFFFFF, 0x00FFFFFF},
|
|
new float[]{0f, 1f},
|
|
Shader.TileMode.CLAMP));
|
|
canvas.drawRect(b, highlightPaint);
|
|
|
|
canvas.restoreToCount(save);
|
|
|
|
invalidateSelf();
|
|
}
|
|
|
|
@Override
|
|
public int getIntrinsicWidth() {
|
|
return size;
|
|
}
|
|
|
|
@Override
|
|
public int getIntrinsicHeight() {
|
|
return size;
|
|
}
|
|
|
|
@Override
|
|
public void setAlpha(int alpha) {
|
|
basePaint.setAlpha(alpha);
|
|
}
|
|
|
|
@Override
|
|
public void setColorFilter(ColorFilter colorFilter) {
|
|
basePaint.setColorFilter(colorFilter);
|
|
}
|
|
|
|
@Override
|
|
public int getOpacity() {
|
|
return PixelFormat.TRANSLUCENT;
|
|
}
|
|
}
|