mirror of https://github.com/NekoX-Dev/NekoX.git
440 lines
15 KiB
Java
440 lines
15 KiB
Java
package org.telegram.ui.Components;
|
|
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.ColorFilter;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Rect;
|
|
import android.graphics.drawable.BitmapDrawable;
|
|
import android.graphics.drawable.GradientDrawable;
|
|
import android.view.View;
|
|
|
|
import androidx.annotation.Nullable;
|
|
import androidx.collection.ArrayMap;
|
|
|
|
import org.telegram.messenger.AndroidUtilities;
|
|
import org.telegram.messenger.ApplicationLoader;
|
|
import org.telegram.messenger.Utilities;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public class BackgroundGradientDrawable extends GradientDrawable {
|
|
|
|
public static final float DEFAULT_COMPRESS_RATIO = 0.5f;
|
|
|
|
public interface Disposable {
|
|
void dispose();
|
|
}
|
|
|
|
public interface Listener {
|
|
void onSizeReady(int width, int height);
|
|
void onAllSizesReady();
|
|
}
|
|
|
|
public static class ListenerAdapter implements Listener {
|
|
@Override
|
|
public void onSizeReady(int width, int height) {
|
|
}
|
|
@Override
|
|
public void onAllSizesReady() {
|
|
}
|
|
}
|
|
|
|
public static class Sizes {
|
|
|
|
public enum Orientation {
|
|
PORTRAIT, LANDSCAPE, BOTH
|
|
}
|
|
|
|
private final IntSize[] arr;
|
|
|
|
private Sizes(int width, int height, int... additionalSizes) {
|
|
arr = new IntSize[1 + additionalSizes.length / 2];
|
|
arr[0] = new IntSize(width, height);
|
|
for (int i = 0; i < additionalSizes.length / 2; i++) {
|
|
arr[i + 1] = new IntSize(additionalSizes[i * 2], additionalSizes[i * 2 + 1]);
|
|
}
|
|
}
|
|
|
|
/** @param additionalSizes [width1, height1, width2, height2, ...] */
|
|
public static Sizes of(int width, int height, int... additionalSizes) {
|
|
return new Sizes(width, height, additionalSizes);
|
|
}
|
|
|
|
public static Sizes ofDeviceScreen() {
|
|
return ofDeviceScreen(DEFAULT_COMPRESS_RATIO);
|
|
}
|
|
|
|
public static Sizes ofDeviceScreen(float compressRatio) {
|
|
return ofDeviceScreen(compressRatio, Orientation.BOTH);
|
|
}
|
|
|
|
public static Sizes ofDeviceScreen(Orientation orientation) {
|
|
return ofDeviceScreen(DEFAULT_COMPRESS_RATIO, orientation);
|
|
}
|
|
|
|
public static Sizes ofDeviceScreen(float compressRatio, Orientation orientation) {
|
|
final int width = (int) (AndroidUtilities.displaySize.x * compressRatio);
|
|
final int height = (int) (AndroidUtilities.displaySize.y * compressRatio);
|
|
|
|
if (width == height) {
|
|
return of(width, height);
|
|
}
|
|
|
|
if (orientation == Orientation.BOTH) {
|
|
// displaySize is orientation-dependent, so we will firstly dither a gradient
|
|
// for the current orientation and only then for another one
|
|
return of(width, height, height, width);
|
|
}
|
|
|
|
//noinspection SuspiciousNameCombination
|
|
return (orientation == Orientation.PORTRAIT) == (width < height) ? of(width, height) : of(height, width);
|
|
}
|
|
}
|
|
|
|
private final int[] colors;
|
|
private final ArrayMap<IntSize, Bitmap> bitmaps = new ArrayMap<>();
|
|
private final ArrayMap<IntSize, Boolean> isForExactBounds = new ArrayMap<>();
|
|
private final ArrayMap<View, Disposable> disposables = new ArrayMap<>();
|
|
private final List<Runnable[]> ditheringRunnables = new ArrayList<>();
|
|
private final Paint bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
|
|
private boolean disposed = false;
|
|
|
|
public BackgroundGradientDrawable(Orientation orientation, int[] colors) {
|
|
super(orientation, colors);
|
|
setDither(true);
|
|
this.colors = colors;
|
|
bitmapPaint.setDither(true);
|
|
}
|
|
|
|
@Override
|
|
public void draw(Canvas canvas) {
|
|
if (disposed) {
|
|
super.draw(canvas);
|
|
return;
|
|
}
|
|
|
|
final Rect bounds = getBounds();
|
|
final Bitmap bitmap = findBestBitmapForSize(bounds.width(), bounds.height());
|
|
|
|
if (bitmap != null) {
|
|
canvas.drawBitmap(bitmap, null, bounds, bitmapPaint);
|
|
} else {
|
|
super.draw(canvas);
|
|
}
|
|
}
|
|
|
|
public Disposable drawExactBoundsSize(Canvas canvas, View ownerView) {
|
|
return drawExactBoundsSize(canvas, ownerView, DEFAULT_COMPRESS_RATIO);
|
|
}
|
|
|
|
public Disposable drawExactBoundsSize(Canvas canvas, View ownerView, float compressRatio) {
|
|
if (disposed) {
|
|
super.draw(canvas);
|
|
return null;
|
|
}
|
|
|
|
final Rect bounds = getBounds();
|
|
final int width = (int) (bounds.width() * compressRatio);
|
|
final int height = (int) (bounds.height() * compressRatio);
|
|
|
|
for (int i = 0, count = bitmaps.size(); i < count; i++) {
|
|
final IntSize size = bitmaps.keyAt(i);
|
|
if (size.width == width && size.height == height) {
|
|
final Bitmap bitmap = bitmaps.valueAt(i);
|
|
|
|
if (bitmap != null) { // ready
|
|
canvas.drawBitmap(bitmap, null, bounds, bitmapPaint);
|
|
} else { // processing
|
|
super.draw(canvas);
|
|
}
|
|
|
|
return disposables.get(ownerView);
|
|
}
|
|
}
|
|
|
|
final Disposable oldDisposable = disposables.remove(ownerView);
|
|
if (oldDisposable != null) {
|
|
oldDisposable.dispose();
|
|
}
|
|
|
|
final IntSize size = new IntSize(width, height);
|
|
bitmaps.put(size, null);
|
|
isForExactBounds.put(size, true);
|
|
final Disposable delegate = startDitheringInternal(new IntSize[]{size}, new ListenerAdapter() {
|
|
@Override
|
|
public void onAllSizesReady() {
|
|
ownerView.invalidate();
|
|
}
|
|
}, 0);
|
|
final Disposable disposable = disposables.put(ownerView, () -> {
|
|
disposables.remove(ownerView);
|
|
delegate.dispose();
|
|
});
|
|
super.draw(canvas);
|
|
return disposable;
|
|
}
|
|
|
|
@Override
|
|
public void setAlpha(int alpha) {
|
|
super.setAlpha(alpha);
|
|
bitmapPaint.setAlpha(alpha);
|
|
}
|
|
|
|
@Override
|
|
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
|
super.setColorFilter(colorFilter);
|
|
bitmapPaint.setColorFilter(colorFilter);
|
|
}
|
|
|
|
@Nullable
|
|
public int[] getColorsList() {
|
|
return colors;
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
try {
|
|
dispose();
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
public Disposable startDithering(Sizes sizes, Listener listener) {
|
|
return startDithering(sizes, listener, 0);
|
|
}
|
|
|
|
public Disposable startDithering(Sizes sizes, Listener listener, long delay) {
|
|
if (disposed) {
|
|
return null;
|
|
}
|
|
|
|
final List<IntSize> sizesList = new ArrayList<>(sizes.arr.length);
|
|
|
|
for (int i = 0; i < sizes.arr.length; i++) {
|
|
final IntSize size = sizes.arr[i];
|
|
if (!bitmaps.containsKey(size)) {
|
|
bitmaps.put(size, null);
|
|
sizesList.add(size);
|
|
}
|
|
}
|
|
|
|
if (sizesList.isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
return startDitheringInternal(sizesList.toArray(new IntSize[0]), listener, delay);
|
|
}
|
|
|
|
private Disposable startDitheringInternal(IntSize[] sizesArr, Listener listener, long delay) {
|
|
if (sizesArr.length == 0) {
|
|
return null;
|
|
}
|
|
|
|
final Listener[] listenerReference = new Listener[]{listener};
|
|
final Runnable[] runnables = new Runnable[sizesArr.length];
|
|
ditheringRunnables.add(runnables);
|
|
|
|
for (int i = 0; i < sizesArr.length; i++) {
|
|
final IntSize size = sizesArr[i];
|
|
if (size.width == 0 || size.height == 0) {
|
|
continue;
|
|
}
|
|
final int index = i;
|
|
Utilities.globalQueue.postRunnable(runnables[i] = () -> {
|
|
Bitmap gradientBitmap = null;
|
|
try {
|
|
gradientBitmap = createDitheredGradientBitmap(getOrientation(), colors, size.width, size.height);
|
|
} finally {
|
|
final Bitmap bitmap = gradientBitmap;
|
|
AndroidUtilities.runOnUIThread(() -> {
|
|
if (!ditheringRunnables.contains(runnables)) {
|
|
if (bitmap != null) {
|
|
bitmap.recycle();
|
|
}
|
|
return;
|
|
}
|
|
if (bitmap != null) {
|
|
bitmaps.put(size, bitmap);
|
|
} else {
|
|
bitmaps.remove(size);
|
|
isForExactBounds.remove(size);
|
|
}
|
|
runnables[index] = null;
|
|
boolean hasNotNull = false;
|
|
if (runnables.length > 1) {
|
|
for (int j = 0; j < runnables.length; j++) {
|
|
if (runnables[j] != null) {
|
|
hasNotNull = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!hasNotNull) {
|
|
ditheringRunnables.remove(runnables);
|
|
}
|
|
if (listenerReference[0] != null) {
|
|
listenerReference[0].onSizeReady(size.width, size.height);
|
|
if (!hasNotNull) {
|
|
listenerReference[0].onAllSizesReady();
|
|
listenerReference[0] = null;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}, delay);
|
|
}
|
|
|
|
return () -> {
|
|
listenerReference[0] = null;
|
|
if (ditheringRunnables.contains(runnables)) {
|
|
Utilities.globalQueue.cancelRunnables(runnables);
|
|
ditheringRunnables.remove(runnables);
|
|
}
|
|
for (int i = 0; i < sizesArr.length; i++) {
|
|
final IntSize size = sizesArr[i];
|
|
final Bitmap bitmap = bitmaps.remove(size);
|
|
isForExactBounds.remove(size);
|
|
if (bitmap != null) {
|
|
bitmap.recycle();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
public void dispose() {
|
|
if (!disposed) {
|
|
for (int i = ditheringRunnables.size() - 1; i >= 0; i--) {
|
|
Utilities.globalQueue.cancelRunnables(ditheringRunnables.remove(i));
|
|
}
|
|
for (int i = bitmaps.size() - 1; i >= 0; i--) {
|
|
final Bitmap bitmap = bitmaps.removeAt(i);
|
|
if (bitmap != null) {
|
|
bitmap.recycle();
|
|
}
|
|
}
|
|
isForExactBounds.clear();
|
|
disposables.clear();
|
|
disposed = true;
|
|
}
|
|
}
|
|
|
|
private Bitmap findBestBitmapForSize(int width, int height) {
|
|
Bitmap bestBitmap = null;
|
|
float minDist = Float.MAX_VALUE;
|
|
for (int i = 0, count = bitmaps.size(); i < count; i++) {
|
|
final IntSize size = bitmaps.keyAt(i);
|
|
final float dist = (float) Math.sqrt(Math.pow(width - size.width, 2) + Math.pow(height - size.height, 2));
|
|
if (dist < minDist) {
|
|
final Bitmap bitmap = bitmaps.valueAt(i);
|
|
if (bitmap != null) {
|
|
final Boolean forExactBounds = isForExactBounds.get(size);
|
|
if (forExactBounds == null || !forExactBounds) {
|
|
bestBitmap = bitmap;
|
|
minDist = dist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bestBitmap;
|
|
}
|
|
|
|
public static Rect getGradientPoints(Orientation orientation, int width, int height) {
|
|
final Rect outRect = new Rect();
|
|
switch (orientation) {
|
|
case TOP_BOTTOM:
|
|
outRect.left = width / 2;
|
|
outRect.top = 0;
|
|
outRect.right = outRect.left;
|
|
outRect.bottom = height;
|
|
break;
|
|
case TR_BL:
|
|
outRect.left = width;
|
|
outRect.top = 0;
|
|
outRect.right = 0;
|
|
outRect.bottom = height;
|
|
break;
|
|
case RIGHT_LEFT:
|
|
outRect.left = width;
|
|
outRect.top = height / 2;
|
|
outRect.right = 0;
|
|
outRect.bottom = outRect.top;
|
|
break;
|
|
case BR_TL:
|
|
outRect.left = width;
|
|
outRect.top = height;
|
|
outRect.right = 0;
|
|
outRect.bottom = 0;
|
|
break;
|
|
case BOTTOM_TOP:
|
|
outRect.left = width / 2;
|
|
outRect.top = height;
|
|
outRect.right = outRect.left;
|
|
outRect.bottom = 0;
|
|
break;
|
|
case BL_TR:
|
|
outRect.left = 0;
|
|
outRect.top = height;
|
|
outRect.right = width;
|
|
outRect.bottom = 0;
|
|
break;
|
|
case LEFT_RIGHT:
|
|
outRect.left = 0;
|
|
outRect.top = height / 2;
|
|
outRect.right = width;
|
|
outRect.bottom = outRect.top;
|
|
break;
|
|
default: // TL_BR
|
|
outRect.left = 0;
|
|
outRect.top = 0;
|
|
outRect.right = width;
|
|
outRect.bottom = height;
|
|
break;
|
|
}
|
|
return outRect;
|
|
}
|
|
|
|
public static Rect getGradientPoints(int gradientAngle, int width, int height) {
|
|
return getGradientPoints(getGradientOrientation(gradientAngle), width, height);
|
|
}
|
|
|
|
public static Orientation getGradientOrientation(int gradientAngle) {
|
|
switch (gradientAngle) {
|
|
case 0:
|
|
return Orientation.BOTTOM_TOP;
|
|
case 90:
|
|
return Orientation.LEFT_RIGHT;
|
|
case 135:
|
|
return Orientation.TL_BR;
|
|
case 180:
|
|
return Orientation.TOP_BOTTOM;
|
|
case 225:
|
|
return Orientation.TR_BL;
|
|
case 270:
|
|
return Orientation.RIGHT_LEFT;
|
|
case 315:
|
|
return Orientation.BR_TL;
|
|
default: // 45
|
|
return Orientation.BL_TR;
|
|
}
|
|
}
|
|
|
|
public static BitmapDrawable createDitheredGradientBitmapDrawable(int angle, int[] colors, int width, int height) {
|
|
return createDitheredGradientBitmapDrawable(getGradientOrientation(angle), colors, width, height);
|
|
}
|
|
|
|
public static BitmapDrawable createDitheredGradientBitmapDrawable(Orientation orientation, int[] colors, int width, int height) {
|
|
return new BitmapDrawable(ApplicationLoader.applicationContext.getResources(), createDitheredGradientBitmap(orientation, colors, width, height));
|
|
}
|
|
|
|
private static Bitmap createDitheredGradientBitmap(Orientation orientation, int[] colors, int width, int height) {
|
|
final Rect r = getGradientPoints(orientation, width, height);
|
|
final Bitmap ditheredGradientBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
Utilities.drawDitheredGradient(ditheredGradientBitmap, colors, r.left, r.top, r.right, r.bottom);
|
|
return ditheredGradientBitmap;
|
|
}
|
|
}
|